1
0
mirror of synced 2026-07-08 20:30:04 +00:00

Compare commits

...

111 Commits

Author SHA1 Message Date
github-actions[bot] b220d9aa87 Release 6.1.4 2023-09-18 15:21:13 +00:00
Rob Winch 7627c2df46 Merge branch '6.0.x' into 6.1.x
Automate spring-security.xsd

Closes gh-13825
2023-09-14 23:43:27 -05:00
Rob Winch 342735043d Merge branch '5.8.x' into 6.0.x
Automate spring-security.xsd

Closes gh-13824
2023-09-14 23:42:31 -05:00
Rob Winch 779541b340 Merge branch '5.7.x' into 5.8.x
Automate spring-security.xsd

Closes gh-13823
2023-09-14 23:37:53 -05:00
Rob Winch 5b293d2116 Automate spring-security.xsd
Closes gh-13819
2023-09-14 16:01:50 -05:00
Marcus Da Coregio 7fcf44f8d9 Merge branch '6.0.x' into 6.1.x
Closes gh-13820
2023-09-14 21:25:48 +01:00
Marcus Da Coregio 18e88366d2 Resolve The matchingRequestParameterName From The Query String
Prior to this commit, the ServletRequest#getParameter method was used in order to verify if the matchingRequestParameterName was present in the request. That method has some side effects like interfering in the execution of the ServletRequest#getInputStream and ServletRequest#getReader method when the request is an HTTP POST (if those methods are invoked after getParameter, or vice-versa, the content won't be available). This commit makes that we only use the query string to check for the parameter, avoiding draining the request's input stream.

Closes gh-13731
2023-09-14 21:25:25 +01:00
Marcus Da Coregio 59a9aa3268 Merge branch '6.0.x' into 6.1.x
Closes gh-13814
2023-09-14 14:49:29 +01:00
Marcus Da Coregio aeafcc1377 Defer MethodSecurityExpressionHandler Resolution
When using Spring Security ACL and compiling to Native, in order to create the '*AuthorizationMethodInterceptor' Proxy beans during build time, Spring tries to resolve the DataSource bean since the DataSource can be a dependency of some AclService implementations, and fails because some required data source properties are not available during build time.

This commit defers the initialization of the MethodSecurityExpressionHandler to the runtime.

Closes gh-12653
2023-09-14 14:48:24 +01:00
Marcus Da Coregio b4ce77c028 Merge branch '6.0.x' into 6.1.x 2023-09-13 14:23:28 +01:00
Marcus Da Coregio 48babb7efa Merge branch '5.8.x' into 6.0.x 2023-09-13 14:23:01 +01:00
Eric Haag f026e29771 Add dependency on rncToXsd task from nohttp
This addresses a deprecation warning causing build caching to be
disabled for the checkstyleNohttp task. With this change, we tell
Gradle that the rncToXsd task in the spring-security-config project
produces output that should be considered when running the
checkstyleNohttp task. This clears up ambiguities when computing the
task graph.
2023-09-13 13:58:44 +01:00
Eric Haag 620e6e0c34 Add rncToXsd task to resources set
This addresses a deprecation warning causing build caching to be
disabled for some tasks. With this change, we tell Gradle that the
rncToXsd task produces output that should be considered a resource.
This clears up ambiguities when computing the task graph.
2023-09-13 13:58:42 +01:00
Eric Haag 4ebfa2c804 Use lazy API to configure rncToXsd task
This avoids configuring the task eagerly.
2023-09-13 13:58:05 +01:00
Steve Riesenberg 461bf9a09c Merge branch '6.0.x' into 6.1.x
Closes gh-13806
2023-09-12 18:48:28 -05:00
Steve Riesenberg f03224fe7f Merge branch '5.8.x' into 6.0.x
Closes gh-13805
2023-09-12 18:48:13 -05:00
Steve Riesenberg 3feb809b35 Fix Saml2AuthenticationExceptionMixin on JDK 17
Closes gh-13804
2023-09-12 18:39:29 -05:00
Steve Riesenberg 74dc3fd7b1 Merge branch '6.0.x' into 6.1.x
Closes gh-13799 in 6.1.x
Closes gh-13801
2023-09-12 17:02:48 -05:00
Steve Riesenberg 771d9cd8b6 Merge branch '5.8.x' into 6.0.x
Closes gh-13799
2023-09-12 17:00:47 -05:00
Steve Riesenberg a580856bb2 Update jacoco tool version to 0.8.9
Closes gh-13798
2023-09-12 17:00:12 -05:00
Steve Riesenberg 9b7a110704 Fix OAuth2AuthenticationExceptionMixinTests on JDK 17
Closes gh-11893
2023-09-12 16:51:47 -05:00
Marcus Da Coregio b80a1de9fa Merge branch '6.0.x' into 6.1.x
Closes gh-13796
2023-09-12 16:22:04 +01:00
Marcus Da Coregio db37bdfe94 Merge branch '5.8.x' into 6.0.x
Closes gh-13795
2023-09-12 16:21:48 +01:00
Marcus Da Coregio ce012a4661 CookieRequestCache Should Preserve Request Locale
Closes gh-13792
2023-09-12 16:21:27 +01:00
Marcus Da Coregio b64d5395c5 Merge branch '6.0.x' into 6.1.x
Closes gh-13759
2023-08-31 10:16:07 -03:00
Marcus Da Coregio 629540f9d8 Merge branch '5.8.x' into 6.0.x
Closes gh-13758
2023-08-31 10:12:59 -03:00
Marcus Da Coregio 96d1763fc4 WWW-Authenticate header should not be added twice
Closes gh-13737
2023-08-31 10:07:10 -03:00
username1103 14b328e3ed Fix incorrect documentation 2023-08-30 14:15:55 -03:00
Steve Riesenberg 3cff9ff04a Merge branch '6.0.x' into 6.1.x 2023-08-29 10:11:00 -05:00
Steve Riesenberg 98f9306a7c Merge branch '5.8.x' into 6.0.x 2023-08-29 10:10:29 -05:00
Josh Cummings a4d8c62ad7 withHttpOnlyCookie defaults to false
Closes gh-13659
2023-08-28 16:58:28 -06:00
github-actions[bot] bbf2dd7091 Next development version 2023-08-21 18:19:39 +00:00
github-actions[bot] 741bdcff62 Release 6.1.3 2023-08-21 17:42:14 +00:00
github-actions[bot] 8efbbb3eb5 Next development version 2023-08-21 16:09:41 +00:00
github-actions[bot] 681681cbb8 Next development version 2023-08-21 15:54:03 +00:00
github-actions[bot] 612909ac4f Release 5.8.6 2023-08-21 15:21:12 +00:00
github-actions[bot] 54f7eacae6 Release 6.0.6 2023-08-21 15:20:33 +00:00
Josh Cummings bcfa4adc44 Add MvcRequestMatcher Reference
Closes gh-13726
2023-08-20 23:32:14 -06:00
Josh Cummings 0df1884372 Merge branch '6.0.x' into 6.1.x
Closes gh-13722
2023-08-20 23:10:00 -06:00
Josh Cummings 5fb6f5768c Merge branch '5.8.x' into 6.0.x
Closes gh-13666 in 6.0.x
Closes gh-13721
2023-08-20 23:07:36 -06:00
Josh Cummings 28f98b3351 Improve Error Message
Closes gh-13667
2023-08-20 22:53:57 -06:00
Josh Cummings ed96e2cddf Ignore Unmappable Servlets
Closes gh-13666
2023-08-20 22:53:55 -06:00
Steve Riesenberg 037c7cbf2d Merge branch '6.0.x' into 6.1.x 2023-08-18 16:13:51 -05:00
Steve Riesenberg fd2b2b7974 Update spring-ldap-core to 3.0.5
Closes gh-13678
2023-08-18 16:13:18 -05:00
Steve Riesenberg 7d4e5bf6b7 Update org.springframework.data to 2022.0.9
Closes gh-13677
2023-08-18 16:13:18 -05:00
Steve Riesenberg 509c6c2952 Update org.aspectj to 1.9.20
Closes gh-13676
2023-08-18 16:13:18 -05:00
Steve Riesenberg b83377bc77 Update io.projectreactor to 2022.0.10
Closes gh-13674
2023-08-18 16:13:17 -05:00
Steve Riesenberg e5bef7ad3b Update mockk to 1.13.7
Closes gh-13673
2023-08-18 16:13:17 -05:00
Steve Riesenberg 9ddb94f887 Update micrometer-observation to 1.10.10
Closes gh-13672
2023-08-18 16:13:17 -05:00
Steve Riesenberg b38af6d048 Update logback-classic to 1.4.11
Closes gh-13669
2023-08-18 16:13:16 -05:00
Steve Riesenberg 97f6ca6f82 Merge branch '5.8.x' into 6.0.x 2023-08-18 16:08:57 -05:00
Steve Riesenberg ecc20ceffd Update spring-ldap-core to 3.0.5
Closes gh-13714
2023-08-18 16:01:18 -05:00
Steve Riesenberg e147b22130 Update org.springframework.data to 2022.0.9
Closes gh-13713
2023-08-18 16:01:15 -05:00
Steve Riesenberg d2cddb7e2d Update org.aspectj to 1.9.20
Closes gh-13712
2023-08-18 16:01:12 -05:00
Steve Riesenberg 67e1832922 Update io.projectreactor to 2022.0.10
Closes gh-13710
2023-08-18 16:01:07 -05:00
Steve Riesenberg 0426d27698 Update mockk to 1.13.7
Closes gh-13709
2023-08-18 16:01:03 -05:00
Steve Riesenberg 7afadfc0ef Update micrometer-observation to 1.10.10
Closes gh-13708
2023-08-18 16:01:00 -05:00
Steve Riesenberg 5f2a8c46d7 Update logback-classic to 1.4.11
Closes gh-13707
2023-08-18 16:00:57 -05:00
Steve Riesenberg 7200f76ac1 Update org.springframework.data to 2021.2.15
Closes gh-13705
2023-08-18 15:58:36 -05:00
Steve Riesenberg 094b71b329 Update org.aspectj to 1.9.20
Closes gh-13704
2023-08-18 15:58:33 -05:00
Steve Riesenberg 491f0f647c Update io.projectreactor to 2020.0.35
Closes gh-13702
2023-08-18 15:58:27 -05:00
Josh Cummings 321deb30cc Resolve EntityID Map Key
Closes gh-13700
2023-08-18 14:36:27 -06:00
Josh Cummings d2d1f19133 Merge branch '6.0.x' into 6.1.x
Closes gh-13655
2023-08-16 17:54:37 -06:00
Josh Cummings ca0140c586 saml2Login Honors AuthenticationProvider bean
Closes gh-13654
2023-08-16 17:54:14 -06:00
Marcus Da Coregio 35d879191c Merge branch '6.0.x' into 6.1.x 2023-08-09 09:56:05 -03:00
Marcus Da Coregio 17e9fec6eb Merge branch '5.8.x' into 6.0.x 2023-08-09 09:55:39 -03:00
Mario Petrovski 1db8734101 Closes #11450 Add Java beans configuration for Remmember Me Docs 2023-08-09 09:54:53 -03:00
Josh Cummings 67bdbf6c56 Merge branch '6.0.x' into 6.1.x
Closes gh-13637
2023-08-08 18:18:36 -06:00
bigfang b5f42b509b Fix Typo in Getting Started Docs
Closes gh-13605
2023-08-08 18:17:51 -06:00
galmegiz 8368c234a5 Add Missing Return Statement in Snippet
Closes gh-13596
Closes gh-13595
2023-08-08 18:11:53 -06:00
Josh Cummings 3edbdc6e87 Merge branch '6.0.x' into 6.1.x
Closes gh-13632
2023-08-08 17:46:04 -06:00
Seongguk Jeong 90936537dc Update links in adocs
Spring Security 6.0 requires Spring 6.0 as a minimum and Spring 6.0 requires a minimum of Tomcat 10/Jetty 11

Closes gh-13565
2023-08-08 17:45:07 -06:00
Josh Cummings 36d0ca340a Merge branch '6.0.x' into 6.1.x 2023-08-08 17:26:47 -06:00
Guillaume Husta a8fcfaa428 Doc : typo in Custom DSLs section
Method 'configure' was renamed 'filterChain'
2023-08-08 17:26:25 -06:00
Josh Cummings 8d4a024809 Update Copyright
PR gh-13472
2023-08-07 16:00:56 -06:00
Seongguk Jeong bcd4dcc15c Refactor equals method
Using the accessor method for fields instead of directly access
2023-08-07 16:00:18 -06:00
Seongguk Jeong 8df8d4022e Fix documentation typo
changed "user name" to "username"
2023-08-07 16:00:18 -06:00
Seongguk Jeong de1357cbd1 Refactor equals method
To use the accessor method for username instead of directly accessing the attribute.
2023-08-07 16:00:18 -06:00
Seongguk Jeong ea19f82b8a Using pattern matching for instanceof 2023-08-07 16:00:18 -06:00
Seongguk Jeong cd6f33c03e Using putIfAbsent instead of put 2023-08-07 16:00:18 -06:00
Josh Cummings 9a50ca74b4 Merge branch '6.0.x' into 6.1.x 2023-08-07 15:31:39 -06:00
Josh Cummings c20100dab7 Merge branch '5.8.x' into 6.0.x 2023-08-07 15:31:20 -06:00
Kawin Naipongprasit 4b44a2d924 Adapeter to Adapter 2023-08-07 15:25:47 -06:00
Josh Cummings b7efa15591 Merge branch '6.0.x' into 6.1.x 2023-08-07 14:48:37 -06:00
Josh Cummings 1f27b18398 Merge branch '5.8.x' into 6.0.x 2023-08-07 14:48:12 -06:00
Daniel Shuy e8b9a35494 Fix Bearer Token RestTemplate Support example 2023-08-07 14:47:37 -06:00
Rob Winch a6c3817a45 Merge branch '6.0.x' into 6.1.x
Use includeGroupByRegex

This makes the include more robust

Issue gh-13582
2023-08-07 10:28:18 -05:00
Rob Winch c4b773b150 Merge branch '5.8.x' into 6.0.x
Use includeGroupByRegex

This makes the include more robust

Issue gh-13582
2023-08-07 10:27:32 -05:00
Rob Winch 82e5f62079 Use includeGroupByRegex
This makes the include more robust

Issue gh-13582
2023-08-07 10:26:57 -05:00
Rob Winch d4d715d8e1 Merge branch '6.0.x' into 6.1.x
Dependencies are resolved from appropriate repositories

Closes gh-13623
2023-08-07 09:54:27 -05:00
Rob Winch 4257a97504 Merge branch '5.8.x' into 6.0.x
Dependencies are resolved from appropriate repositories

Closes gh-13622
2023-08-07 09:51:55 -05:00
Eric Haag 30bc2634d7 Optimize configuration of project repositories
This change applies repository content filtering to configured
repositories, reducing the time spent during dependency resolution.

This fixes an issue where requests for 'org.opensaml',
'net.shibboleth.utilities' and 'net.minidev' dependencies were being
made in the Spring releases repositories, resulting in many failed
requests during dependency resolution and increased resolution times.

Closes gh-13582
2023-08-07 09:51:42 -05:00
Marcus Da Coregio 1416b0649e Merge branch '6.0.x' into 6.1.x
Closes gh-13590
2023-07-27 11:25:19 -03:00
Marcus Da Coregio 461d6edd85 Merge branch '5.8.x' into 6.0.x
Closes gh-13589
2023-07-27 11:23:58 -03:00
Marcus Da Coregio 13ca7ac4d4 Referrer-Policy is added by default in Reactive applications
Closes gh-13561
2023-07-27 11:22:21 -03:00
Josh Cummings 442d3fb99d Merge branch '6.0.x' into 6.1.x
Closes gh-13580
2023-07-24 11:31:52 -06:00
Josh Cummings ee13216882 Merge branch '5.8.x' into 6.0.x
Closes gh-13579
2023-07-24 11:31:29 -06:00
Josh Cummings c4f061c63d Do Not Re-register Method Security Advisors
Closes gh-13572
2023-07-24 11:24:03 -06:00
Marcus Da Coregio 441655b6a6 Merge branch '6.0.x' into 6.1.x 2023-07-18 09:52:31 -03:00
Marcus Da Coregio 0d2315cf3a Merge branch '5.8.x' into 6.0.x 2023-07-18 09:52:08 -03:00
Marcus Da Coregio 2c6d053ceb Merge branch '5.7.x' into 5.8.x 2023-07-18 09:51:45 -03:00
Marcus Da Coregio ff4745a461 Merge branch '5.6.x' into 5.7.x 2023-07-18 09:51:19 -03:00
github-actions[bot] e37f6e5e63 Next development version 2023-07-17 22:01:38 +00:00
github-actions[bot] 76b5d17c83 Next development version 2023-07-17 21:56:07 +00:00
github-actions[bot] 38a7ffcc9b Next development version 2023-07-17 21:52:14 +00:00
github-actions[bot] 6f55fc60bb Next development version 2023-07-17 21:46:35 +00:00
github-actions[bot] d0491c07e9 Next development version 2023-07-17 21:42:28 +00:00
github-actions[bot] 486c5118d2 Release 5.7.10 2023-07-17 21:05:15 +00:00
github-actions[bot] d02ab50577 Release 5.8.5 2023-07-17 21:04:33 +00:00
github-actions[bot] 17d8293be1 Release 5.6.12 2023-07-17 21:00:49 +00:00
github-actions[bot] 0686b461af Release 6.0.5 2023-07-17 20:43:30 +00:00
52 changed files with 749 additions and 155 deletions
+2 -1
View File
@@ -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) {
+1 -1
View File
@@ -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'
}
}
}
@@ -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;
}
}
}
@@ -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/");
}
@@ -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();
}
}
+21 -7
View File
@@ -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
@@ -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;
@@ -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);
}
}
}
@@ -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
@@ -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);
}
/**
@@ -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
@@ -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 {
@@ -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);
@@ -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
@@ -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;
}
@@ -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;
@@ -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;
}
@@ -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
View File
@@ -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 -7
View File
@@ -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
View File
@@ -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
@@ -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) {
@@ -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,
@@ -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,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();
}
}
@@ -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;
}
@@ -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());
}
@@ -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;
}
@@ -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;
@@ -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) {
@@ -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) {
@@ -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\"");
}
}
@@ -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));
@@ -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());
}
@@ -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;