1
0
mirror of synced 2026-07-14 23:25:10 +00:00

Compare commits

...

29 Commits

Author SHA1 Message Date
github-actions[bot] faadadf71a Release 5.7.7 2023-02-21 14:41:42 +00:00
Josh Cummings 2fcf48a11b Update org.springframework.data to 2021.2.8
Closes gh-12738
2023-02-17 15:53:49 -07:00
Josh Cummings 0398d86f39 Update org.springframework to 5.3.25
Closes gh-12737
2023-02-17 15:53:40 -07:00
Josh Cummings c9112dfaab Update hibernate-entitymanager to 5.6.15.Final
Closes gh-12736
2023-02-17 15:53:32 -07:00
Josh Cummings 4156766fe0 Update org.aspectj to 1.9.19
Closes gh-12735
2023-02-17 15:53:24 -07:00
Josh Cummings f125785328 Update io.spring.nohttp to 0.0.11
Closes gh-12734
2023-02-17 15:53:16 -07:00
Josh Cummings 4643c0d999 Update blockhound to 1.0.7.RELEASE
Closes gh-12733
2023-02-17 15:53:08 -07:00
Josh Cummings d2d82b5e94 Update io.projectreactor to 2020.0.28
Closes gh-12732
2023-02-17 15:52:54 -07:00
Josh Cummings d4d03ea146 Update jackson-bom to 2.13.5
Closes gh-12731
2023-02-17 15:52:32 -07:00
Josh Cummings 2db4430dcd Preserve OpenSamlAssertingPartyDetails Instance
Closes gh-12667
2023-02-17 10:02:17 -07:00
Leonid Rozenblyum 000b4bc495 Fix NPE in HttpSecurity#addFilterBefore, HttpSecurity#addFilterAfter
Before the fix, these methods would throw a NPE in case when the filter class passed as the second parameter, is not registered yet.

In particular, this exception can occur when mixing standard and custom DSL to register filters.

The fix doesn't change the situation that standard DSL for registration of filters cannot refer to filters that are registered via custom DSL even though those calls were done earlier.

It just provides more user-friendly error handling for this and most likely other scenarios of calls of HttpSecurity#addFilterBefore, HttpSecurity#addFilterAfter.

The error handling is implemented similarly to HttpSecurity#addFilter.

Closes gh-12637
2023-02-16 14:54:44 -07:00
jongwooo aad3e61f4d chore: Use cache in continuous-integration-workflow.yml
Signed-off-by: jongwooo <jongwooo.han@gmail.com>
2023-02-01 14:26:25 -07:00
Dan Allen 51d5fb9c03 upgrade the Gradle Antora Plugin to 1.0.0 2023-01-30 16:04:40 -06:00
Rob Winch 7db357d36a Use io.spring.antora.generate-antora-yml 2023-01-17 17:05:55 -06:00
Dan Allen f5bc6ce665 fix unclosed block in docs 2023-01-17 15:02:30 -06:00
Dan Allen 4cbb057b97 enable fetch option in Antora unless Gradle is running in offline mode 2023-01-17 14:42:54 -03:00
Marcus Da Coregio c0f7cecc6d Merge branch '5.6.x' into 5.7.x 2023-01-17 09:46:41 -03:00
Dan Allen 22ffa833ca upgrade Antora plugin and configure playbook provider to support local build 2023-01-17 09:46:24 -03:00
Josh Cummings f41b77a4db Fix Diagram to Say SecurityContextHolderFilter
Closes gh-11800
2023-01-11 12:47:07 -07:00
Josh Cummings 2028507bf8 Fix Typo in Sample
Closes gh-11095
2023-01-10 10:38:28 -07:00
Marcus Da Coregio ffdb397830 Save the SecurityContext when switching user
Closes gh-12504
2023-01-10 09:27:56 -03:00
Jon Kjennbakken 225dc593a8 Polish NimbusJwtDecoderTests
- Add missing mock

Closes gh-12238
2023-01-06 12:53:36 -07:00
Josh Cummings 3cfaf0d11d Avoid LinkedMultiValueMap in Serializable Object
Closes gh-11785
2022-12-23 15:54:00 -07:00
Eleftheria Stein-Kousathana fbfa13bd47 Fix OAuth 2.0 testing docs 2022-12-19 16:52:25 -03:00
Marcus Da Coregio 0c6b427132 Merge branch '5.6.x' into 5.7.x 2022-12-19 15:03:50 -03:00
github-actions[bot] 0f7d17bae6 Next development version 2022-12-19 15:59:53 +00:00
github-actions[bot] c11248b589 Next development version 2022-12-19 15:57:16 +00:00
github-actions[bot] 972075677f Release 5.6.10 2022-12-19 15:23:12 +00:00
github-actions[bot] 11614edcfe Release 5.7.6 2022-12-19 15:22:32 +00:00
28 changed files with 372 additions and 137 deletions
@@ -55,6 +55,7 @@ jobs:
with:
java-version: '11'
distribution: 'adopt'
cache: 'gradle'
- name: Set up Gradle
uses: gradle/gradle-build-action@v2
- name: Set up gradle user name
+1 -1
View File
@@ -3,7 +3,7 @@ import io.spring.gradle.IncludeRepoTask
buildscript {
dependencies {
classpath "io.spring.javaformat:spring-javaformat-gradle-plugin:$springJavaformatVersion"
classpath 'io.spring.nohttp:nohttp-gradle:0.0.10'
classpath 'io.spring.nohttp:nohttp-gradle:0.0.11'
classpath "io.freefair.gradle:aspectj-plugin:6.4.3.1"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
classpath "com.netflix.nebula:nebula-project-plugin:8.2.0"
+1 -1
View File
@@ -93,7 +93,7 @@ dependencies {
implementation 'com.github.ben-manes:gradle-versions-plugin:0.38.0'
implementation 'com.github.spullara.mustache.java:compiler:0.9.4'
implementation 'io.spring.javaformat:spring-javaformat-gradle-plugin:0.0.15'
implementation 'io.spring.nohttp:nohttp-gradle:0.0.10'
implementation 'io.spring.nohttp:nohttp-gradle:0.0.11'
implementation 'net.sourceforge.htmlunit:htmlunit:2.37.0'
implementation 'org.hidetake:gradle-ssh-plugin:2.10.1'
implementation 'org.jfrog.buildinfo:build-info-extractor-gradle:4.29.0'
@@ -36,7 +36,7 @@ class CheckstylePlugin implements Plugin<Project> {
if (checkstyleDir.exists() && checkstyleDir.directory) {
project.getPluginManager().apply('checkstyle')
project.dependencies.add('checkstyle', 'io.spring.javaformat:spring-javaformat-checkstyle:0.0.15')
project.dependencies.add('checkstyle', 'io.spring.nohttp:nohttp-checkstyle:0.0.10')
project.dependencies.add('checkstyle', 'io.spring.nohttp:nohttp-checkstyle:0.0.11')
project.checkstyle {
configDirectory = checkstyleDir
@@ -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.
@@ -3254,7 +3254,12 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
}
private HttpSecurity addFilterAtOffsetOf(Filter filter, int offset, Class<? extends Filter> registeredFilter) {
int order = this.filterOrders.getOrder(registeredFilter) + offset;
Integer registeredFilterOrder = this.filterOrders.getOrder(registeredFilter);
if (registeredFilterOrder == null) {
throw new IllegalArgumentException(
"The Filter class " + registeredFilter.getName() + " does not have a registered order");
}
int order = registeredFilterOrder + offset;
this.filters.add(new OrderedFilter(filter, order));
this.filterOrders.put(filter.getClass(), order);
return this;
@@ -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.
@@ -30,6 +30,7 @@ import org.assertj.core.api.ListAssert;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@@ -45,12 +46,29 @@ import org.springframework.security.web.context.request.async.WebAsyncManagerInt
import org.springframework.security.web.header.HeaderWriterFilter;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@ExtendWith(SpringTestContextExtension.class)
public class HttpSecurityAddFilterTest {
public final SpringTestContext spring = new SpringTestContext(this);
@Test
public void addFilterAfterFilterNotRegisteredYetThenThrowIllegalArgument() {
assertThatExceptionOfType(UnsatisfiedDependencyException.class)
.isThrownBy(
() -> this.spring.register(MyOtherFilterAfterMyFilterNotRegisteredYetConfig.class).autowire())
.havingRootCause().isInstanceOf(IllegalArgumentException.class);
}
@Test
public void addFilterBeforeFilterNotRegisteredYetThenThrowIllegalArgument() {
assertThatExceptionOfType(UnsatisfiedDependencyException.class)
.isThrownBy(
() -> this.spring.register(MyOtherFilterBeforeMyFilterNotRegisteredYetConfig.class).autowire())
.havingRootCause().isInstanceOf(IllegalArgumentException.class);
}
@Test
public void addFilterAfterWhenSameFilterDifferentPlacesThenOrderCorrect() {
this.spring.register(MyFilterMultipleAfterConfig.class).autowire();
@@ -209,6 +227,34 @@ public class HttpSecurityAddFilterTest {
}
@EnableWebSecurity
static class MyOtherFilterAfterMyFilterNotRegisteredYetConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.addFilterAfter(new MyOtherFilter(), MyFilter.class);
// @formatter:on
return http.build();
}
}
@EnableWebSecurity
static class MyOtherFilterBeforeMyFilterNotRegisteredYetConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.addFilterBefore(new MyOtherFilter(), MyFilter.class);
// @formatter:on
return http.build();
}
}
@EnableWebSecurity
static class MyOtherFilterRelativeToMyFilterBeforeConfig {
+5 -5
View File
@@ -8,13 +8,13 @@ javaPlatform {
dependencies {
api platform("org.springframework:spring-framework-bom:$springFrameworkVersion")
api platform("io.projectreactor:reactor-bom:2020.0.26")
api platform("io.projectreactor:reactor-bom:2020.0.28")
api platform("io.rsocket:rsocket-bom:1.1.3")
api platform("org.junit:junit-bom:5.8.2")
api platform("org.springframework.data:spring-data-bom:2021.2.6")
api platform("org.springframework.data:spring-data-bom:2021.2.8")
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.13.4.20221013")
api platform("com.fasterxml.jackson:jackson-bom:2.13.5")
constraints {
api "ch.qos.logback:logback-classic:1.2.11"
api "com.google.inject:guice:3.0"
@@ -26,7 +26,7 @@ dependencies {
api "commons-codec:commons-codec:1.15"
api "commons-collections:commons-collections:3.2.2"
api "io.mockk:mockk:1.12.8"
api "io.projectreactor.tools:blockhound:1.0.6.RELEASE"
api "io.projectreactor.tools:blockhound:1.0.7.RELEASE"
api "jakarta.inject:jakarta.inject-api:1.0.5"
api "jakarta.annotation:jakarta.annotation-api:1.3.5"
api "jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api:1.2.7"
@@ -54,7 +54,7 @@ dependencies {
api "org.eclipse.jetty:jetty-servlet:9.4.50.v20221201"
api "org.eclipse.persistence:javax.persistence:2.2.1"
api "org.hamcrest:hamcrest:2.2"
api "org.hibernate:hibernate-entitymanager:5.6.14.Final"
api "org.hibernate:hibernate-entitymanager:5.6.15.Final"
api "org.hsqldb:hsqldb:2.6.1"
api "org.jasig.cas.client:cas-client-core:3.6.4"
api "org.mockito:mockito-core:3.12.4"
+1 -2
View File
@@ -1,2 +1 @@
/package-lock.json
/node_modules/
/*-antora-playbook.yml
+8 -2
View File
@@ -6,7 +6,13 @@ nav:
ext:
collector:
run:
command: gradlew -q -PbuildSrc.skipTests=true "-Dorg.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError" :spring-security-docs:generateAntora
command: gradlew -q -PbuildSrc.skipTests=true "-Dorg.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError" :spring-security-docs:generateAntoraYml
local: true
scan:
dir: ./build/generateAntora
dir: ./build/generated-antora-resources
asciidoc:
attributes:
icondir: icons
gh-old-samples-url: 'https://github.com/spring-projects/spring-security/tree/5.4.x/samples'
gh-samples-url: "https://github.com/spring-projects/spring-security-samples/tree/{gh-tag}"
gh-url: "https://github.com/spring-projects/spring-security/tree/{gh-tag}"
-23
View File
@@ -1,23 +0,0 @@
# The purpose of this Antora playbook is to generate a preview of the docs in the current branch.
antora:
extensions:
- '@antora/collector-extension'
site:
title: Spring Security
url: https://docs.spring.io/spring-security/reference
content:
sources:
- url: ./..
branches: HEAD
start_path: docs
worktrees: true
asciidoc:
attributes:
page-pagination: ''
hide-uri-scheme: '@'
urls:
latest_version_segment: ''
ui:
bundle:
url: https://github.com/spring-io/antora-ui-spring/releases/download/latest/ui-bundle.zip
snapshot: true
Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 89 KiB

@@ -547,7 +547,7 @@ class MethodSecurityConfig {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public Advisor customAuthorize(AuthorizationManager<MethodInvocationResult> rules) {
AnnotationMethodMatcher pattern = new AnnotationMethodMatcher(MySecurityAnnotation.class);
AnnotationMatchingPointcut pattern = new AnnotationMatchingPointcut(MySecurityAnnotation.class);
AuthorizationManagerAfterMethodInterceptor interceptor = new AuthorizationManagerAfterMethodInterceptor(pattern, rules);
interceptor.setOrder(AuthorizationInterceptorsOrder.POST_AUTHORIZE_ADVISOR_ORDER.getOrder() + 1);
return interceptor;
@@ -563,7 +563,7 @@ class MethodSecurityConfig {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
fun customAuthorize(rules : AuthorizationManager<MethodInvocationResult>) : Advisor {
val pattern = AnnotationMethodMatcher(MySecurityAnnotation::class.java);
val pattern = AnnotationMatchingPointcut(MySecurityAnnotation::class.java);
val interceptor = AuthorizationManagerAfterMethodInterceptor(pattern, rules);
interceptor.setOrder(AuthorizationInterceptorsOrder.POST_AUTHORIZE_ADVISOR_ORDER.getOrder() + 1);
return interceptor;
@@ -1,5 +1,36 @@
[[servlet-saml2login-metadata]]
= Producing `<saml2:SPSSODescriptor>` Metadata
= Saml 2.0 Metadata
Spring Security can <<parsing-asserting-party-metadata,parse asserting party metadata>> to produce an `AssertingPartyDetails` instance as well as <<publishing-relying-party-metadata,publish relying party metadata>> from a `RelyingPartyRegistration` instance.
[[parsing-asserting-party-metadata]]
== Parsing `<saml2:IDPSSODescriptor>` metadata
You can parse an asserting party's metadata xref:servlet/saml2/login/overview.adoc#servlet-saml2login-relyingpartyregistrationrepository[using `RelyingPartyRegistrations`].
When using the OpenSAML vendor support, the resulting `AssertingPartyDetails` will be of type `OpenSamlAssertingPartyDetails`.
This means you'll be able to do get the underlying OpenSAML XMLObject by doing the following:
====
.Java
[source,java,role="primary"]
----
OpenSamlAssertingPartyDetails details = (OpenSamlAssertingPartyDetails)
registration.getAssertingPartyDetails();
EntityDescriptor openSamlEntityDescriptor = details.getEntityDescriptor();
----
.Kotlin
[source,kotlin,role="secondary"]
----
val details: OpenSamlAssertingPartyDetails =
registration.getAssertingPartyDetails() as OpenSamlAssertingPartyDetails;
val openSamlEntityDescriptor: EntityDescriptor = details.getEntityDescriptor();
----
====
[[publishing-relying-party-metadata]]
== Producing `<saml2:SPSSODescriptor>` Metadata
You can publish a metadata endpoint by adding the `Saml2MetadataFilter` to the filter chain, as you'll see below:
@@ -61,6 +61,7 @@ public class WithMockUserTests {
@ContextConfiguration
class WithMockUserTests {
----
====
This is a basic example of how to setup Spring Security Test. The highlights are:
@@ -296,7 +296,7 @@ fun foo(@AuthenticationPrincipal oauth2User: OAuth2User): String? {
----
====
In that case, we can tell Spring Security to include a default `OAuth2User` using the `oauth2User` xref:servlet/test/mockmvc/request-post-processors.adoc[`RequestPostProcessor`], like so:
In that case, we can tell Spring Security to include a default `OAuth2User` using the `oauth2Login` xref:servlet/test/mockmvc/request-post-processors.adoc[`RequestPostProcessor`], like so:
====
.Java
+37 -69
View File
@@ -1,78 +1,25 @@
plugins {
id 'org.antora' version '1.0.0-alpha.3'
id 'org.antora' version '1.0.0'
id 'io.spring.antora.generate-antora-yml' version '0.0.1'
}
apply plugin: 'io.spring.convention.docs'
apply plugin: 'java'
antora {
version = '3.2.0-alpha.2'
playbook = file('local-antora-playbook.yml')
options = ['--clean', '--stacktrace']
environment = [
'ALGOLIA_API_KEY': '82c7ead946afbac3cf98c32446154691',
'ALGOLIA_APP_ID': '244V8V9FGG',
'ALGOLIA_INDEX_NAME': 'security-docs'
]
dependencies = [
'@antora/collector-extension': '1.0.0-alpha.3'
]
playbook = 'cached-antora-playbook.yml'
playbookProvider {
repository = 'spring-projects/spring-security'
branch = 'docs-build'
path = 'lib/antora/templates/per-branch-antora-playbook.yml'
checkLocalBranch = true
}
options = [clean: true, fetch: !project.gradle.startParameter.offline, stacktrace: true]
}
tasks.register('generateAntora') {
group = 'Documentation'
description = 'Generates the antora.yml for dynamic properties'
doLast {
def docsTag = snapshotBuild ? 'current' : project.version
def ghTag = snapshotBuild ? 'main' : project.version
def ghUrl = "https://github.com/spring-projects/spring-security/tree/$ghTag"
def ghOldSamplesUrl = 'https://github.com/spring-projects/spring-security/tree/5.4.x/samples'
def ghSamplesUrl = "https://github.com/spring-projects/spring-security-samples/tree/$samplesBranch"
def securityDocsUrl = "https://docs.spring.io/spring-security/site/docs/$docsTag"
def securityApiUrl = "$securityDocsUrl/api/"
def securityReferenceUrl = "$securityDocsUrl/reference/html5/"
def springFrameworkApiUrl = "https://docs.spring.io/spring-framework/docs/$springFrameworkVersion/javadoc-api/"
def springFrameworkReferenceUrl = "https://docs.spring.io/spring-framework/docs/$springFrameworkVersion/reference/html/"
def ymlVersions = resolvedVersions(project.configurations.testRuntimeClasspath).call()
.collect(v -> " ${v.getKey()}: ${v.getValue()}")
.join('\n')
def outputFile = layout.buildDirectory.file('generateAntora/antora.yml').get().asFile
mkdir(outputFile.getParentFile())
def mainVersion = project.version
def prerelease = null
def versionComponents = mainVersion.split(/(?=-)/)
if (versionComponents.length > 1) {
if (versionComponents[1] == '-SNAPSHOT') {
mainVersion = versionComponents[0]
prerelease = "'-SNAPSHOT'"
} else {
prerelease = 'true'
}
}
def antoraYmlText = file('antora.yml').text
layout.buildDirectory.file('.antora.yml').get().asFile.text = antoraYmlText
antoraYmlText = antoraYmlText.lines().collect { l ->
if (l.startsWith('version: ')) {
return prerelease == null ? "version: '${mainVersion}'" : "version: '${mainVersion}'\nprerelease: ${prerelease}"
}
if (l.startsWith('title: ')) return "title: ${project.parent.description}"
return l == 'ext:' || l.getAt(0) == ' ' ? null : l
}.findAll(Objects::nonNull).join('\n')
outputFile.text = """$antoraYmlText
asciidoc:
attributes:
icondir: icons
gh-old-samples-url: $ghOldSamplesUrl
gh-samples-url: $ghSamplesUrl
gh-url: $ghUrl
security-api-url: $securityApiUrl
security-reference-url: $securityReferenceUrl
spring-framework-api-url: $springFrameworkApiUrl
spring-framework-reference-url: $springFrameworkReferenceUrl
spring-security-version: ${project.version}
${ymlVersions}
"""
}
tasks.named("generateAntoraYml") {
asciidocAttributes = project.provider( { generateAttributes() } )
asciidocAttributes.putAll(providers.provider( { resolvedVersions(project.configurations.testRuntimeClasspath) }))
}
dependencies {
@@ -82,12 +29,33 @@ dependencies {
testImplementation 'org.springframework:spring-core'
}
def generateAttributes() {
def docsTag = snapshotBuild ? 'current' : project.version
def ghTag = snapshotBuild ? 'main' : project.version
def ghUrl = "https://github.com/spring-projects/spring-security/tree/$ghTag"
def ghOldSamplesUrl = 'https://github.com/spring-projects/spring-security/tree/5.4.x/samples'
def ghSamplesUrl = "https://github.com/spring-projects/spring-security-samples/tree/$samplesBranch"
def securityDocsUrl = "https://docs.spring.io/spring-security/site/docs/$docsTag"
def securityApiUrl = "$securityDocsUrl/api/"
def securityReferenceUrl = "$securityDocsUrl/reference/html5/"
def springFrameworkApiUrl = "https://docs.spring.io/spring-framework/docs/$springFrameworkVersion/javadoc-api/"
def springFrameworkReferenceUrl = "https://docs.spring.io/spring-framework/docs/$springFrameworkVersion/reference/html/"
return ['gh-old-samples-url': ghOldSamplesUrl.toString(),
'gh-samples-url': ghSamplesUrl.toString(),
'gh-url': ghUrl.toString(),
'security-api-url': securityApiUrl.toString(),
'security-reference-url': securityReferenceUrl.toString(),
'spring-framework-api-url': springFrameworkApiUrl.toString(),
'spring-framework-reference-url': springFrameworkReferenceUrl.toString(),
'spring-security-version': project.version]
+ resolvedVersions(project.configurations.testRuntimeClasspath)
}
def resolvedVersions(Configuration configuration) {
return {
configuration.resolvedConfiguration
return configuration.resolvedConfiguration
.resolvedArtifacts
.collectEntries { [(it.name + '-version'): it.moduleVersion.id.version] }
}
}
repositories {
+3 -3
View File
@@ -1,9 +1,9 @@
aspectjVersion=1.9.9.1
aspectjVersion=1.9.19
springJavaformatVersion=0.0.31
springBootVersion=2.4.2
springFrameworkVersion=5.3.24
springFrameworkVersion=5.3.25
openSamlVersion=3.4.6
version=5.7.6-SNAPSHOT
version=5.7.7
kotlinVersion=1.6.21
samplesBranch=5.7.x
org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError
@@ -60,6 +60,7 @@ import org.mockito.ArgumentCaptor;
import org.springframework.cache.Cache;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.cache.support.SimpleValueWrapper;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
@@ -700,6 +701,7 @@ public class NimbusJwtDecoderTests {
RestOperations restOperations = mock(RestOperations.class);
Cache cache = mock(Cache.class);
given(cache.get(eq(JWK_SET_URI), eq(String.class))).willReturn(JWK_SET);
given(cache.get(eq(JWK_SET_URI))).willReturn(new SimpleValueWrapper(JWK_SET));
given(restOperations.exchange(any(RequestEntity.class), eq(String.class)))
.willReturn(new ResponseEntity<>(NEW_KID_JWK_SET, HttpStatus.OK));
@@ -0,0 +1,35 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.saml2.provider.service.registration;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
class OpenSamlMetadataRelyingPartyRegistrationConverter {
private final OpenSamlMetadataAssertingPartyDetailsConverter converter = new OpenSamlMetadataAssertingPartyDetailsConverter();
Collection<RelyingPartyRegistration.Builder> convert(InputStream source) {
Collection<RelyingPartyRegistration.Builder> builders = new ArrayList<>();
for (RelyingPartyRegistration.AssertingPartyDetails.Builder builder : this.converter.convert(source)) {
builders.add(new RelyingPartyRegistration.Builder(builder));
}
return builders;
}
}
@@ -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.
@@ -89,8 +89,7 @@ public class OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter
@Override
public RelyingPartyRegistration.Builder read(Class<? extends RelyingPartyRegistration.Builder> clazz,
HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
return RelyingPartyRegistration
.withAssertingPartyDetails(this.converter.convert(inputMessage.getBody()).iterator().next().build());
return new RelyingPartyRegistration.Builder(this.converter.convert(inputMessage.getBody()).iterator().next());
}
@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.
@@ -30,6 +30,7 @@ import java.util.function.Function;
import org.opensaml.xmlsec.signature.support.SignatureConstants;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.saml2.core.Saml2X509Credential;
import org.springframework.util.Assert;
@@ -970,6 +971,14 @@ public final class RelyingPartyRegistration {
private AssertingPartyDetails.Builder assertingPartyDetailsBuilder = new AssertingPartyDetails.Builder();
private Builder() {
}
private Builder(AssertingPartyDetails.Builder assertingPartyDetailsBuilder) {
this.assertingPartyDetailsBuilder = assertingPartyDetailsBuilder;
}
/**
* Set the asserting party's <a href=
* "https://www.oasis-open.org/committees/download.php/51890/SAML%20MD%20simplified%20overview.pdf#2.9%20EntityDescriptor">EntityID</a>.
@@ -1032,7 +1041,7 @@ public final class RelyingPartyRegistration {
public static final class Builder {
private String registrationId;
private Converter<ProviderDetails, String> registrationId = ProviderDetails::getEntityId;
private String entityId = "{baseUrl}/saml2/service-provider-metadata/{registrationId}";
@@ -1052,12 +1061,17 @@ public final class RelyingPartyRegistration {
private String nameIdFormat = null;
private ProviderDetails.Builder providerDetails = new ProviderDetails.Builder();
private ProviderDetails.Builder providerDetails;
private Collection<org.springframework.security.saml2.credentials.Saml2X509Credential> credentials = new LinkedHashSet<>();
private Builder(String registrationId) {
this.registrationId = registrationId;
this.registrationId = (party) -> registrationId;
this.providerDetails = new ProviderDetails.Builder();
}
Builder(AssertingPartyDetails.Builder builder) {
this.providerDetails = new ProviderDetails.Builder(builder);
}
/**
@@ -1066,7 +1080,7 @@ public final class RelyingPartyRegistration {
* @return this object
*/
public Builder registrationId(String id) {
this.registrationId = id;
this.registrationId = (party) -> id;
return this;
}
@@ -1363,11 +1377,12 @@ public final class RelyingPartyRegistration {
if (this.singleLogoutServiceResponseLocation == null) {
this.singleLogoutServiceResponseLocation = this.singleLogoutServiceLocation;
}
return new RelyingPartyRegistration(this.registrationId, this.entityId,
this.assertionConsumerServiceLocation, this.assertionConsumerServiceBinding,
this.singleLogoutServiceLocation, this.singleLogoutServiceResponseLocation,
this.singleLogoutServiceBinding, this.providerDetails.build(), this.nameIdFormat, this.credentials,
this.decryptionX509Credentials, this.signingX509Credentials);
ProviderDetails party = this.providerDetails.build();
String registrationId = this.registrationId.convert(party);
return new RelyingPartyRegistration(registrationId, this.entityId, this.assertionConsumerServiceLocation,
this.assertionConsumerServiceBinding, this.singleLogoutServiceLocation,
this.singleLogoutServiceResponseLocation, this.singleLogoutServiceBinding, party, this.nameIdFormat,
this.credentials, this.decryptionX509Credentials, this.signingX509Credentials);
}
}
@@ -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.
@@ -18,13 +18,11 @@ package org.springframework.security.saml2.provider.service.registration;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.ResourceLoader;
import org.springframework.security.saml2.Saml2Exception;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration.AssertingPartyDetails;
/**
* A utility class for constructing instances of {@link RelyingPartyRegistration}
@@ -36,7 +34,7 @@ import org.springframework.security.saml2.provider.service.registration.RelyingP
*/
public final class RelyingPartyRegistrations {
private static final OpenSamlMetadataAssertingPartyDetailsConverter assertingPartyMetadataConverter = new OpenSamlMetadataAssertingPartyDetailsConverter();
private static final OpenSamlMetadataRelyingPartyRegistrationConverter relyingPartyRegistrationConverter = new OpenSamlMetadataRelyingPartyRegistrationConverter();
private static final ResourceLoader resourceLoader = new DefaultResourceLoader();
@@ -215,11 +213,7 @@ public final class RelyingPartyRegistrations {
* @since 5.7
*/
public static Collection<RelyingPartyRegistration.Builder> collectionFromMetadata(InputStream source) {
Collection<RelyingPartyRegistration.Builder> builders = new ArrayList<>();
for (AssertingPartyDetails.Builder builder : assertingPartyMetadataConverter.convert(source)) {
builders.add(RelyingPartyRegistration.withAssertingPartyDetails(builder.build()));
}
return builders;
return relyingPartyRegistrationConverter.convert(source);
}
}
@@ -23,6 +23,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
@@ -659,7 +660,7 @@ public final class OpenSaml4AuthenticationProvider implements AuthenticationProv
attributeMap.addAll(attribute.getName(), attributeValues);
}
}
return attributeMap;
return new LinkedHashMap<>(attributeMap); // gh-11785
}
private static List<String> getSessionIndexes(Assertion assertion) {
@@ -32,6 +32,7 @@ import java.util.function.Consumer;
import javax.xml.namespace.QName;
import com.fasterxml.jackson.databind.ObjectMapper;
import net.shibboleth.utilities.java.support.xml.SerializeSupport;
import org.junit.jupiter.api.Test;
import org.opensaml.core.xml.XMLObject;
@@ -68,6 +69,7 @@ import org.w3c.dom.Element;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.core.Authentication;
import org.springframework.security.jackson2.SecurityJackson2Modules;
import org.springframework.security.saml2.Saml2Exception;
import org.springframework.security.saml2.core.Saml2Error;
import org.springframework.security.saml2.core.Saml2ErrorCodes;
@@ -349,6 +351,23 @@ public class OpenSaml4AuthenticationProviderTests {
assertThat(principal.getSessionIndexes()).contains("session-index");
}
// gh-11785
@Test
public void deserializeWhenAssertionContainsAttributesThenWorks() throws Exception {
ObjectMapper mapper = new ObjectMapper();
ClassLoader loader = getClass().getClassLoader();
mapper.registerModules(SecurityJackson2Modules.getModules(loader));
Response response = response();
Assertion assertion = assertion();
List<AttributeStatement> attributes = TestOpenSamlObjects.attributeStatements();
assertion.getAttributeStatements().addAll(attributes);
response.getAssertions().add(signed(assertion));
Saml2AuthenticationToken token = token(response, verifying(registration()));
Authentication authentication = this.provider.authenticate(token);
String result = mapper.writeValueAsString(authentication);
mapper.readValue(result, Authentication.class);
}
@Test
public void authenticateWhenAssertionContainsCustomAttributesThenItSucceeds() {
Response response = response();
@@ -0,0 +1,57 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.saml2.provider.service.registration;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.stream.Collectors;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.ClassPathResource;
import static org.assertj.core.api.Assertions.assertThat;
public class OpenSamlMetadataRelyingPartyRegistrationConverterTests {
private OpenSamlMetadataRelyingPartyRegistrationConverter converter = new OpenSamlMetadataRelyingPartyRegistrationConverter();
private String metadata;
@BeforeEach
public void setup() throws Exception {
ClassPathResource resource = new ClassPathResource("test-metadata.xml");
try (BufferedReader reader = new BufferedReader(new InputStreamReader(resource.getInputStream()))) {
this.metadata = reader.lines().collect(Collectors.joining());
}
}
// gh-12667
@Test
public void convertWhenDefaultsThenAssertingPartyInstanceOfOpenSaml() throws Exception {
try (InputStream source = new ByteArrayInputStream(this.metadata.getBytes(StandardCharsets.UTF_8))) {
this.converter.convert(source)
.forEach((registration) -> assertThat(registration.build().getAssertingPartyDetails())
.isInstanceOf(OpenSamlAssertingPartyDetails.class));
}
}
}
@@ -58,6 +58,8 @@ import org.springframework.security.web.authentication.AuthenticationSuccessHand
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.security.web.context.RequestAttributeSecurityContextRepository;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.security.web.util.UrlUtils;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
@@ -142,6 +144,8 @@ public class SwitchUserFilter extends GenericFilterBean implements ApplicationEv
private AuthenticationFailureHandler failureHandler;
private SecurityContextRepository securityContextRepository = new RequestAttributeSecurityContextRepository();
@Override
public void afterPropertiesSet() {
Assert.notNull(this.userDetailsService, "userDetailsService must be specified");
@@ -179,6 +183,7 @@ public class SwitchUserFilter extends GenericFilterBean implements ApplicationEv
context.setAuthentication(targetUser);
SecurityContextHolder.setContext(context);
this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", targetUser));
this.securityContextRepository.saveContext(context, request, response);
// redirect to target url
this.successHandler.onAuthenticationSuccess(request, response, targetUser);
}
@@ -196,6 +201,7 @@ public class SwitchUserFilter extends GenericFilterBean implements ApplicationEv
context.setAuthentication(originalUser);
SecurityContextHolder.setContext(context);
this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", originalUser));
this.securityContextRepository.saveContext(context, request, response);
// redirect to target url
this.successHandler.onAuthenticationSuccess(request, response, originalUser);
return;
@@ -510,6 +516,19 @@ public class SwitchUserFilter extends GenericFilterBean implements ApplicationEv
this.switchAuthorityRole = switchAuthorityRole;
}
/**
* Sets the {@link SecurityContextRepository} to save the {@link SecurityContext} on
* switch user success. The default is
* {@link RequestAttributeSecurityContextRepository}.
* @param securityContextRepository the {@link SecurityContextRepository} to use.
* Cannot be null.
* @since 5.7.7
*/
public void setSecurityContextRepository(SecurityContextRepository securityContextRepository) {
Assert.notNull(securityContextRepository, "securityContextRepository cannot be null");
this.securityContextRepository = securityContextRepository;
}
private static RequestMatcher createMatcher(String pattern) {
return new AntPathRequestMatcher(pattern, "POST", true, new UrlPathHelper());
}
@@ -16,15 +16,18 @@
package org.springframework.security.web.authentication.switchuser;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.authentication.AccountExpiredException;
@@ -44,11 +47,15 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.util.FieldUtils;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.security.web.context.RequestAttributeSecurityContextRepository;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -483,6 +490,59 @@ public class SwitchUserFilterTests {
filter.setSwitchFailureUrl("/foo");
}
@Test
void filterWhenDefaultSecurityContextRepositoryThenRequestAttributeRepository() {
SwitchUserFilter switchUserFilter = new SwitchUserFilter();
assertThat(ReflectionTestUtils.getField(switchUserFilter, "securityContextRepository"))
.isInstanceOf(RequestAttributeSecurityContextRepository.class);
}
@Test
void doFilterWhenSwitchUserThenSaveSecurityContext() throws ServletException, IOException {
SecurityContextRepository securityContextRepository = mock(SecurityContextRepository.class);
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
MockFilterChain filterChain = new MockFilterChain();
request.setParameter(SwitchUserFilter.SPRING_SECURITY_SWITCH_USERNAME_KEY, "jacklord");
request.setRequestURI("/login/impersonate");
SwitchUserFilter filter = new SwitchUserFilter();
filter.setSecurityContextRepository(securityContextRepository);
filter.setUserDetailsService(new MockUserDetailsService());
filter.setTargetUrl("/target");
filter.afterPropertiesSet();
filter.doFilter(request, response, filterChain);
verify(securityContextRepository).saveContext(any(), any(), any());
}
@Test
void doFilterWhenExitUserThenSaveSecurityContext() throws ServletException, IOException {
UsernamePasswordAuthenticationToken source = UsernamePasswordAuthenticationToken.authenticated("dano",
"hawaii50", ROLES_12);
// set current user (Admin)
List<GrantedAuthority> adminAuths = new ArrayList<>(ROLES_12);
adminAuths.add(new SwitchUserGrantedAuthority("PREVIOUS_ADMINISTRATOR", source));
UsernamePasswordAuthenticationToken admin = UsernamePasswordAuthenticationToken.authenticated("jacklord",
"hawaii50", adminAuths);
SecurityContextHolder.getContext().setAuthentication(admin);
SecurityContextRepository securityContextRepository = mock(SecurityContextRepository.class);
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
MockFilterChain filterChain = new MockFilterChain();
request.setParameter(SwitchUserFilter.SPRING_SECURITY_SWITCH_USERNAME_KEY, "jacklord");
request.setRequestURI("/logout/impersonate");
SwitchUserFilter filter = new SwitchUserFilter();
filter.setSecurityContextRepository(securityContextRepository);
filter.setUserDetailsService(new MockUserDetailsService());
filter.setTargetUrl("/target");
filter.afterPropertiesSet();
filter.doFilter(request, response, filterChain);
verify(securityContextRepository).saveContext(any(), any(), any());
}
private class MockUserDetailsService implements UserDetailsService {
private String password = "hawaii50";