1
0
mirror of synced 2026-07-09 21:00:02 +00:00

Compare commits

..

30 Commits

Author SHA1 Message Date
Joe Grandja c40f65f5a2 Release 5.7.2 2022-06-20 12:17:25 -04:00
Joe Grandja bca43af9bb Update org.opensaml:opensaml-core4 to 4.1.1
Closes gh-11410
2022-06-20 12:08:07 -04:00
Joe Grandja d9b8882fa8 Update spring-ldap-core to 2.4.1
Closes gh-11409
2022-06-20 11:52:48 -04:00
Joe Grandja 7358c65a8c Update org.springframework.data to 2021.2.1
Closes gh-11408
2022-06-20 11:52:44 -04:00
Joe Grandja e02d5f2dd7 Update org.springframework to 5.3.21
Closes gh-11407
2022-06-20 11:52:41 -04:00
Joe Grandja 91a965c6db Update org.jetbrains.kotlinx to 1.6.3
Closes gh-11406
2022-06-20 11:52:37 -04:00
Joe Grandja 0e88064942 Update hibernate-entitymanager to 5.6.9.Final
Closes gh-11405
2022-06-20 11:52:35 -04:00
Joe Grandja 641b9ef83b Update io.projectreactor to 2020.0.20
Closes gh-11403
2022-06-20 11:52:30 -04:00
Joe Grandja 6f43d234dc Update aspectj-plugin to 6.4.3.1
Closes gh-11402
2022-06-20 11:52:27 -04:00
Joe Grandja d7819ea4da Update jackson-bom to 2.13.3
Closes gh-11399
2022-06-20 11:52:17 -04:00
Joe Grandja 37ee70ae86 Add dependency update exclusion for spring-javaformat-checkstyle 2022-06-20 11:16:37 -04:00
Joe Grandja 8ea37360ac Add dependency exclusion rules 2022-06-20 10:03:29 -04:00
Rob Winch 29db051f7a Cache SecurityContextRepository.loadContext(HttpServletRequest) Result
Closes gh-11390
2022-06-17 14:52:35 -05:00
Josh Cummings f035c30edb Encode postLogoutRedirectUri query params
Closes gh-11379
2022-06-16 16:12:13 -06:00
Josh Cummings d22277ce36 Add missing KeyInfo
Closes gh-11354
2022-06-09 13:16:50 -06:00
Josh Cummings bd60a0f8c9 Add OpenSamlSigningUtilsTests
Issue gh-11354
2022-06-09 13:16:49 -06:00
Zhivko Delchev d882bfcf2b Reverse content type check
When MultipartFormData is enabled currently the CsrfWebFilter compares
the content-type header against MULTIPART_FORM_DATA MediaType which
leads to NullPointerExecption when there is no content-type header.
This commit reverse the check to compare the MULTIPART_FORM_DATA
MediaType against the content-type which contains null check and avoids
the exception.

closes gh-11204
Closes gh-11205
2022-06-06 15:47:14 -05:00
Rob Winch 6c3f53ac0a Fix typo in BasicLookupStrategy Javadoc
Issue gh-11336
2022-06-06 14:09:24 -05:00
shirohoo b274431c07 Fix typo in BasicLookupStrategy Javadoc
Closes gh-11336
2022-06-06 13:55:43 -05:00
Rob Winch 3d5e5ff556 Enable BackportBot on 5.7.x 2022-06-06 13:54:36 -05:00
sKai.fun a3e996a66b Fix title render issue of Digest Authentication document
Closes gh-11272
2022-06-01 17:33:41 -05:00
André Luis Gomes 0c31cb21dc Update opaque-token.adoc
Fixing yaml sample in Servlet and Reactive pages
2022-06-01 08:50:56 -03:00
Claudio Consolmagno c39d39b35f Use 'md:' prefix in EntityDescriptor XML
Create the EntityDescriptor object with
EntityDescriptor.DEFAULT_ELEMENT_NAME instead of
EntityDescriptor.ELEMENT_QNAME. That ensures the EntityDescriptor tag
is marshalled to xml with the 'md:' prefix, consistent with all other
metadata tags.

Closes #11283
2022-05-31 17:08:51 -06:00
Josh Cummings 292585080a Correct access(String) reference
Closes gh-11280
2022-05-27 14:59:06 -06:00
Josh Cummings 8690accd57 Improve ContextConfiguration Docs
Point to updated Spring Reference

Issue gh-10934
2022-05-27 12:57:57 -06:00
Josh Cummings e3c15260e7 Polish ExtendWith Docs
Use spring-framework-reference-url placeholder

Issue gh-10934
2022-05-27 12:57:57 -06:00
nor-ek 9625382b22 Update JUnit 5 annotations in documentation
- replace Before with BeforeEach
- replace RunWith with ExtendWith

Closes gh-10934
2022-05-27 12:57:56 -06:00
Evgeniy Cheban 48ef3f4719 Some Security Expressions cause NPE when used within Query annotation
Added trustResolver, roleHierarchy, permissionEvaluator, defaultRolePrefix
fields to SecurityEvaluationContextExtension.

Closes gh-11196
Closes gh-11289
2022-05-26 17:43:50 -05:00
Juny Tse d0da160007 Use Base64 encoder with no CRLF in output for SAML 2.0 messages
Closes gh-11262
2022-05-25 12:02:13 -06:00
Rob Winch 4caf53e96d Next Development Version 2022-05-18 10:06:25 -05:00
35 changed files with 427 additions and 76 deletions
+26
View File
@@ -0,0 +1,26 @@
name: Backport Bot
on:
issues:
types: [labeled]
pull_request:
types: [labeled]
push:
branches:
- '*.x'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '17'
- name: Download BackportBot
run: wget https://github.com/spring-io/backport-bot/releases/download/latest/backport-bot-0.0.1-SNAPSHOT.jar
- name: Backport
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_EVENT: ${{ toJSON(github.event) }}
run: java -jar backport-bot-0.0.1-SNAPSHOT.jar --github.accessToken="$GITHUB_TOKEN" --github.event_name "$GITHUB_EVENT_NAME" --github.event "$GITHUB_EVENT"
@@ -74,8 +74,8 @@ import org.springframework.util.Assert;
* one in <tt>lookupObjectIdentities</tt>. These are built from the same select and "order
* by" clause, using a different where clause in each case. In order to use custom schema
* or column names, each of these SQL clauses can be customized, but they must be
* consistent with each other and with the expected result set generated by the the
* default values.
* consistent with each other and with the expected result set generated by the default
* values.
*
* @author Ben Alex
*/
+16 -1
View File
@@ -2,7 +2,7 @@ buildscript {
dependencies {
classpath "io.spring.javaformat:spring-javaformat-gradle-plugin:$springJavaformatVersion"
classpath 'io.spring.nohttp:nohttp-gradle:0.0.10'
classpath "io.freefair.gradle:aspectj-plugin:6.4.3"
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"
}
@@ -70,6 +70,9 @@ updateDependenciesSettings {
})
dependencyExcludes {
majorVersionBump()
minorVersionBump()
releaseCandidatesVersions()
milestoneVersions()
alphaBetaVersions()
snapshotVersions()
addRule { components ->
@@ -99,6 +102,18 @@ updateDependenciesSettings {
selection.reject("org.opensaml maintains two different versions, so it must be updated manually");
}
}
components.withModule("io.spring.javaformat:spring-javaformat-gradle-plugin") { selection ->
ModuleComponentIdentifier candidate = selection.getCandidate();
if (!candidate.getVersion().equals(selection.getCurrentVersion())) {
selection.reject("spring-javaformat-gradle-plugin updates break checkstyle");
}
}
components.withModule("io.spring.javaformat:spring-javaformat-checkstyle") { selection ->
ModuleComponentIdentifier candidate = selection.getCandidate();
if (!candidate.getVersion().equals(selection.getCurrentVersion())) {
selection.reject("spring-javaformat-checkstyle updates break checkstyle");
}
}
}
}
}
@@ -307,7 +307,7 @@ public class Saml2LoginConfigurerTests {
public void authenticateWithInvalidDeflatedSAMLResponseThenFailureHandlerUses() throws Exception {
this.spring.register(CustomAuthenticationFailureHandler.class).autowire();
byte[] invalidDeflated = "invalid".getBytes();
String encoded = Saml2Utils.samlEncodeNotRfc2045(invalidDeflated);
String encoded = Saml2Utils.samlEncode(invalidDeflated);
MockHttpServletRequestBuilder request = get("/login/saml2/sso/registration-id").queryParam("SAMLResponse",
encoded);
this.mvc.perform(request);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,13 @@
package org.springframework.security.data.repository.query;
import org.springframework.data.spel.spi.EvaluationContextExtension;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.access.expression.DenyAllPermissionEvaluator;
import org.springframework.security.access.expression.SecurityExpressionRoot;
import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
@@ -77,12 +83,21 @@ import org.springframework.security.core.context.SecurityContextHolder;
* it.
*
* @author Rob Winch
* @author Evgeniy Cheban
* @since 4.0
*/
public class SecurityEvaluationContextExtension implements EvaluationContextExtension {
private Authentication authentication;
private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
private RoleHierarchy roleHierarchy = new NullRoleHierarchy();
private PermissionEvaluator permissionEvaluator = new DenyAllPermissionEvaluator();
private String defaultRolePrefix = "ROLE_";
/**
* Creates a new instance that uses the current {@link Authentication} found on the
* {@link org.springframework.security.core.context.SecurityContextHolder}.
@@ -106,8 +121,13 @@ public class SecurityEvaluationContextExtension implements EvaluationContextExte
@Override
public SecurityExpressionRoot getRootObject() {
Authentication authentication = getAuthentication();
return new SecurityExpressionRoot(authentication) {
SecurityExpressionRoot root = new SecurityExpressionRoot(authentication) {
};
root.setTrustResolver(this.trustResolver);
root.setRoleHierarchy(this.roleHierarchy);
root.setPermissionEvaluator(this.permissionEvaluator);
root.setDefaultRolePrefix(this.defaultRolePrefix);
return root;
}
private Authentication getAuthentication() {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,7 +20,10 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.security.access.expression.DenyAllPermissionEvaluator;
import org.springframework.security.access.expression.SecurityExpressionRoot;
import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
@@ -69,6 +72,17 @@ public class SecurityEvaluationContextExtensionTests {
assertThat(getRoot().getAuthentication()).isSameAs(explicit);
}
@Test
public void getRootObjectWhenAdditionalFieldsNotSetThenVerifyDefaults() {
TestingAuthenticationToken explicit = new TestingAuthenticationToken("explicit", "password", "ROLE_EXPLICIT");
this.securityExtension = new SecurityEvaluationContextExtension(explicit);
SecurityExpressionRoot root = getRoot();
assertThat(root).extracting("trustResolver").isInstanceOf(AuthenticationTrustResolverImpl.class);
assertThat(root).extracting("roleHierarchy").isInstanceOf(NullRoleHierarchy.class);
assertThat(root).extracting("permissionEvaluator").isInstanceOf(DenyAllPermissionEvaluator.class);
assertThat(root).extracting("defaultRolePrefix").isEqualTo("ROLE_");
}
private SecurityExpressionRoot getRoot() {
return this.securityExtension.getRootObject();
}
+6 -6
View File
@@ -8,13 +8,13 @@ javaPlatform {
dependencies {
api platform("org.springframework:spring-framework-bom:$springFrameworkVersion")
api platform("io.projectreactor:reactor-bom:2020.0.19")
api platform("io.projectreactor:reactor-bom:2020.0.20")
api platform("io.rsocket:rsocket-bom:1.1.2")
api platform("org.junit:junit-bom:5.8.2")
api platform("org.springframework.data:spring-data-bom:2021.2.0")
api platform("org.springframework.data:spring-data-bom:2021.2.1")
api platform("org.jetbrains.kotlin:kotlin-bom:$kotlinVersion")
api platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.6.1")
api platform("com.fasterxml.jackson:jackson-bom:2.13.2.20220328")
api platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.6.3")
api platform("com.fasterxml.jackson:jackson-bom:2.13.3")
constraints {
api "ch.qos.logback:logback-classic:1.2.11"
api "com.google.inject:guice:3.0"
@@ -54,7 +54,7 @@ dependencies {
api "org.eclipse.jetty:jetty-servlet:9.4.46.v20220331"
api "org.eclipse.persistence:javax.persistence:2.2.1"
api "org.hamcrest:hamcrest:2.2"
api "org.hibernate:hibernate-entitymanager:5.6.8.Final"
api "org.hibernate:hibernate-entitymanager:5.6.9.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"
@@ -71,7 +71,7 @@ dependencies {
api "org.skyscreamer:jsonassert:1.5.0"
api "org.slf4j:log4j-over-slf4j:1.7.36"
api "org.slf4j:slf4j-api:1.7.36"
api "org.springframework.ldap:spring-ldap-core:2.4.0"
api "org.springframework.ldap:spring-ldap-core:2.4.1"
api "org.synchronoss.cloud:nio-multipart-parser:1.1.0"
}
}
+1 -1
View File
@@ -1,2 +1,2 @@
name: ROOT
version: '5.7.1'
version: '5.7.2'
@@ -23,13 +23,14 @@ To specify where the introspection endpoint is, simply do:
[source,yaml]
----
security:
oauth2:
resourceserver:
opaque-token:
introspection-uri: https://idp.example.com/introspect
client-id: client
client-secret: secret
spring:
security:
oauth2:
resourceserver:
opaque-token:
introspection-uri: https://idp.example.com/introspect
client-id: client
client-secret: secret
----
Where `https://idp.example.com/introspect` is the introspection endpoint hosted by your authorization server and `client-id` and `client-secret` are the credentials needed to hit that endpoint.
@@ -8,7 +8,7 @@ Here is a minimal sample of what we can do:
.Java
[source,java,role="primary"]
----
@RunWith(SpringRunner.class)
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = HelloWebfluxMethodApplication.class)
public class HelloWorldMessageServiceTests {
@Autowired
@@ -42,7 +42,7 @@ public class HelloWorldMessageServiceTests {
.Kotlin
[source,kotlin,role="secondary"]
----
@RunWith(SpringRunner::class)
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = [HelloWebfluxMethodApplication::class])
class HelloWorldMessageServiceTests {
@Autowired
@@ -4,7 +4,7 @@ The basic setup looks like this:
[source,java]
----
@RunWith(SpringRunner.class)
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = HelloWebfluxMethodApplication.class)
public class HelloWebfluxMethodApplicationTests {
@Autowired
@@ -12,7 +12,7 @@ public class HelloWebfluxMethodApplicationTests {
WebTestClient rest;
@Before
@BeforeEach
public void setup() {
this.rest = WebTestClient
.bindToApplicationContext(this.context)
@@ -1,4 +1,4 @@
**[[**servlet-authentication-digest]]
[[servlet-authentication-digest]]
= Digest Authentication
This section provides details on how Spring Security provides support for https://tools.ietf.org/html/rfc2617[Digest Authentication] which is provided `DigestAuthenticationFilter`.
@@ -69,7 +69,11 @@ SecurityFilterChain web(HttpSecurity http) throws Exception {
.authorizeHttpRequests(authorize -> authorize // <1>
.mvcMatchers("/resources/**", "/signup", "/about").permitAll() // <2>
.mvcMatchers("/admin/**").hasRole("ADMIN") // <3>
.mvcMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')") // <4>
.mvcMatchers("/db/**").access((authentication, request) ->
Optional.of(hasRole("ADMIN").check(authentication, request))
.filter((decision) -> !decision.isGranted())
.orElseGet(() -> hasRole("DBA").check(authentication, request));
) // <4>
.anyRequest().denyAll() // <5>
);
@@ -24,13 +24,14 @@ To specify where the introspection endpoint is, simply do:
[source,yaml]
----
security:
oauth2:
resourceserver:
opaque-token:
introspection-uri: https://idp.example.com/introspect
client-id: client
client-secret: secret
spring:
security:
oauth2:
resourceserver:
opaque-token:
introspection-uri: https://idp.example.com/introspect
client-id: client
client-secret: secret
----
Where `https://idp.example.com/introspect` is the introspection endpoint hosted by your authorization server and `client-id` and `client-secret` are the credentials needed to hit that endpoint.
@@ -49,7 +49,7 @@ Before we can use Spring Security Test support, we must perform some setup. An e
.Java
[source,java,role="primary"]
----
@RunWith(SpringJUnit4ClassRunner.class) // <1>
@ExtendWith(SpringExtension.class) // <1>
@ContextConfiguration // <2>
public class WithMockUserTests {
----
@@ -57,16 +57,15 @@ public class WithMockUserTests {
.Kotlin
[source,kotlin,role="secondary"]
----
@RunWith(SpringJUnit4ClassRunner::class)
@ExtendWith(SpringExtension.class)
@ContextConfiguration
class WithMockUserTests {
----
====
This is a basic example of how to setup Spring Security Test. The highlights are:
<1> `@RunWith` instructs the spring-test module that it should create an `ApplicationContext`. This is no different than using the existing Spring Test support. For additional information, refer to the https://docs.spring.io/spring-framework/docs/4.0.x/spring-framework-reference/htmlsingle/#integration-testing-annotations-standard[Spring Reference]
<2> `@ContextConfiguration` instructs the spring-test the configuration to use to create the `ApplicationContext`. Since no configuration is specified, the default configuration locations will be tried. This is no different than using the existing Spring Test support. For additional information, refer to the https://docs.spring.io/spring-framework/docs/4.0.x/spring-framework-reference/htmlsingle/#testcontext-ctx-management[Spring Reference]
<1> `@ExtendWith` instructs the spring-test module that it should create an `ApplicationContext`. For additional information, refer to the {spring-framework-reference-url}testing.html#testcontext-junit-jupiter-extension[Spring reference].
<2> `@ContextConfiguration` instructs the spring-test the configuration to use to create the `ApplicationContext`. Since no configuration is specified, the default configuration locations will be tried. This is no different than using the existing Spring Test support. For additional information, refer to the {spring-framework-reference-url}testing.html#spring-testing-annotation-contextconfiguration[Spring Reference]
NOTE: Spring Security hooks into Spring Test support using the `WithSecurityContextTestExecutionListener` which will ensure our tests are ran with the correct user.
It does this by populating the `SecurityContextHolder` prior to running our tests.
@@ -225,7 +224,7 @@ For example, the following would run every test with a user with the username "a
.Java
[source,java,role="primary"]
----
@RunWith(SpringJUnit4ClassRunner.class)
@ExtendWith(SpringExtension.class)
@ContextConfiguration
@WithMockUser(username="admin",roles={"USER","ADMIN"})
public class WithMockUserTests {
@@ -234,7 +233,7 @@ public class WithMockUserTests {
.Kotlin
[source,kotlin,role="secondary"]
----
@RunWith(SpringJUnit4ClassRunner::class)
@ExtendWith(SpringExtension.class)
@ContextConfiguration
@WithMockUser(username="admin",roles=["USER","ADMIN"])
class WithMockUserTests {
@@ -304,7 +303,7 @@ For example, the following will run withMockUser1 and withMockUser2 using <<test
.Java
[source,java,role="primary"]
----
@RunWith(SpringJUnit4ClassRunner.class)
@ExtendWith(SpringExtension.class)
@WithMockUser
public class WithUserClassLevelAuthenticationTests {
@@ -327,7 +326,7 @@ public class WithUserClassLevelAuthenticationTests {
.Kotlin
[source,kotlin,role="secondary"]
----
@RunWith(SpringJUnit4ClassRunner::class)
@ExtendWith(SpringExtension.class)
@WithMockUser
class WithUserClassLevelAuthenticationTests {
@Test
@@ -15,7 +15,7 @@ NOTE: Spring Security's testing support requires spring-test-4.1.3.RELEASE or gr
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*;
@RunWith(SpringJUnit4ClassRunner.class)
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = SecurityConfig.class)
@WebAppConfiguration
public class CsrfShowcaseTests {
@@ -25,7 +25,7 @@ public class CsrfShowcaseTests {
private MockMvc mvc;
@Before
@BeforeEach
public void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
@@ -39,7 +39,7 @@ public class CsrfShowcaseTests {
.Kotlin
[source,kotlin,role="secondary"]
----
@RunWith(SpringJUnit4ClassRunner::class)
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = [SecurityConfig::class])
@WebAppConfiguration
class CsrfShowcaseTests {
@@ -49,7 +49,7 @@ class CsrfShowcaseTests {
private var mvc: MockMvc? = null
@Before
@BeforeEach
fun setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
+2 -2
View File
@@ -1,9 +1,9 @@
aspectjVersion=1.9.9.1
springJavaformatVersion=0.0.31
springBootVersion=2.4.2
springFrameworkVersion=5.3.20
springFrameworkVersion=5.3.21
openSamlVersion=3.4.6
version=5.7.1
version=5.7.2
kotlinVersion=1.6.21
samplesBranch=5.7.x
org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError
@@ -85,13 +85,13 @@ public class OidcClientInitiatedServerLogoutSuccessHandler implements ServerLogo
return Mono.empty();
}
String idToken = idToken(authentication);
URI postLogoutRedirectUri = postLogoutRedirectUri(exchange.getExchange().getRequest());
String postLogoutRedirectUri = postLogoutRedirectUri(exchange.getExchange().getRequest());
return Mono.just(endpointUri(endSessionEndpoint, idToken, postLogoutRedirectUri));
})
.switchIfEmpty(
this.serverLogoutSuccessHandler.onLogoutSuccess(exchange, authentication).then(Mono.empty())
)
.flatMap((endpointUri) -> this.redirectStrategy.sendRedirect(exchange.getExchange(), endpointUri));
.flatMap((endpointUri) -> this.redirectStrategy.sendRedirect(exchange.getExchange(), URI.create(endpointUri)));
// @formatter:on
}
@@ -106,20 +106,20 @@ public class OidcClientInitiatedServerLogoutSuccessHandler implements ServerLogo
return null;
}
private URI endpointUri(URI endSessionEndpoint, String idToken, URI postLogoutRedirectUri) {
private String endpointUri(URI endSessionEndpoint, String idToken, String postLogoutRedirectUri) {
UriComponentsBuilder builder = UriComponentsBuilder.fromUri(endSessionEndpoint);
builder.queryParam("id_token_hint", idToken);
if (postLogoutRedirectUri != null) {
builder.queryParam("post_logout_redirect_uri", postLogoutRedirectUri);
}
return builder.encode(StandardCharsets.UTF_8).build().toUri();
return builder.encode(StandardCharsets.UTF_8).build().toUriString();
}
private String idToken(Authentication authentication) {
return ((OidcUser) authentication.getPrincipal()).getIdToken().getTokenValue();
}
private URI postLogoutRedirectUri(ServerHttpRequest request) {
private String postLogoutRedirectUri(ServerHttpRequest request) {
if (this.postLogoutRedirectUri == null) {
return null;
}
@@ -131,7 +131,7 @@ public class OidcClientInitiatedServerLogoutSuccessHandler implements ServerLogo
.build();
return UriComponentsBuilder.fromUriString(this.postLogoutRedirectUri)
.buildAndExpand(Collections.singletonMap("baseUrl", uriComponents.toUriString()))
.toUri();
.toUriString();
// @formatter:on
}
@@ -150,6 +150,19 @@ public class OidcClientInitiatedServerLogoutSuccessHandlerTests {
"https://endpoint?" + "id_token_hint=id-token&" + "post_logout_redirect_uri=https://rp.example.org");
}
// gh-11379
@Test
public void logoutWhenUsingPostLogoutRedirectUriWithQueryParametersThenBuildsItForRedirect() {
OAuth2AuthenticationToken token = new OAuth2AuthenticationToken(TestOidcUsers.create(),
AuthorityUtils.NO_AUTHORITIES, this.registration.getRegistrationId());
given(this.exchange.getPrincipal()).willReturn(Mono.just(token));
this.handler.setPostLogoutRedirectUri("https://rp.example.org/context?forwardUrl=secured%3Fparam%3Dtrue");
WebFilterExchange f = new WebFilterExchange(this.exchange, this.chain);
this.handler.onLogoutSuccess(f, token).block();
assertThat(redirectedUrl(this.exchange)).isEqualTo("https://endpoint?id_token_hint=id-token&"
+ "post_logout_redirect_uri=https://rp.example.org/context?forwardUrl%3Dsecured%253Fparam%253Dtrue");
}
@Test
public void setPostLogoutRedirectUriWhenGivenNullThenThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.handler.setPostLogoutRedirectUri((URI) null));
@@ -46,9 +46,9 @@ dependencies {
api "org.opensaml:opensaml-core"
api "org.opensaml:opensaml-saml-api"
api "org.opensaml:opensaml-saml-impl"
opensaml4MainImplementation "org.opensaml:opensaml-core:4.1.0"
opensaml4MainImplementation "org.opensaml:opensaml-saml-api:4.1.0"
opensaml4MainImplementation "org.opensaml:opensaml-saml-impl:4.1.0"
opensaml4MainImplementation "org.opensaml:opensaml-core:4.1.1"
opensaml4MainImplementation "org.opensaml:opensaml-saml-api:4.1.1"
opensaml4MainImplementation "org.opensaml:opensaml-saml-impl:4.1.1"
provided 'jakarta.servlet:jakarta.servlet-api'
@@ -36,7 +36,7 @@ final class Saml2Utils {
}
static String samlEncode(byte[] b) {
return Base64.getMimeEncoder().encodeToString(b);
return Base64.getEncoder().encodeToString(b);
}
static byte[] samlDecode(String s) {
@@ -40,7 +40,7 @@ final class Saml2Utils {
}
static String samlEncode(byte[] b) {
return Base64.getMimeEncoder().encodeToString(b);
return Base64.getEncoder().encodeToString(b);
}
static byte[] samlDecode(String s) {
@@ -75,7 +75,7 @@ public final class OpenSamlMetadataResolver implements Saml2MetadataResolver {
@Override
public String resolve(RelyingPartyRegistration relyingPartyRegistration) {
EntityDescriptor entityDescriptor = build(EntityDescriptor.ELEMENT_QNAME);
EntityDescriptor entityDescriptor = build(EntityDescriptor.DEFAULT_ELEMENT_NAME);
entityDescriptor.setEntityID(relyingPartyRegistration.getEntityId());
SPSSODescriptor spSsoDescriptor = buildSpSsoDescriptor(relyingPartyRegistration);
entityDescriptor.getRoleDescriptors(SPSSODescriptor.DEFAULT_ELEMENT_NAME).add(spSsoDescriptor);
@@ -42,6 +42,9 @@ import org.opensaml.xmlsec.SignatureSigningParametersResolver;
import org.opensaml.xmlsec.criterion.SignatureSigningConfigurationCriterion;
import org.opensaml.xmlsec.crypto.XMLSigningUtil;
import org.opensaml.xmlsec.impl.BasicSignatureSigningConfiguration;
import org.opensaml.xmlsec.keyinfo.KeyInfoGeneratorManager;
import org.opensaml.xmlsec.keyinfo.NamedKeyInfoGeneratorManager;
import org.opensaml.xmlsec.keyinfo.impl.X509KeyInfoGeneratorFactory;
import org.opensaml.xmlsec.signature.SignableXMLObject;
import org.opensaml.xmlsec.signature.support.SignatureConstants;
import org.opensaml.xmlsec.signature.support.SignatureSupport;
@@ -102,6 +105,7 @@ final class OpenSamlSigningUtils {
signingConfiguration.setSignatureAlgorithms(algorithms);
signingConfiguration.setSignatureReferenceDigestMethods(digests);
signingConfiguration.setSignatureCanonicalizationAlgorithm(canonicalization);
signingConfiguration.setKeyInfoGeneratorManager(buildSignatureKeyInfoGeneratorManager());
criteria.add(new SignatureSigningConfigurationCriterion(signingConfiguration));
try {
SignatureSigningParameters parameters = resolver.resolveSingle(criteria);
@@ -113,6 +117,22 @@ final class OpenSamlSigningUtils {
}
}
private static NamedKeyInfoGeneratorManager buildSignatureKeyInfoGeneratorManager() {
final NamedKeyInfoGeneratorManager namedManager = new NamedKeyInfoGeneratorManager();
namedManager.setUseDefaultManager(true);
final KeyInfoGeneratorManager defaultManager = namedManager.getDefaultManager();
// Generator for X509Credentials
final X509KeyInfoGeneratorFactory x509Factory = new X509KeyInfoGeneratorFactory();
x509Factory.setEmitEntityCertificate(true);
x509Factory.setEmitEntityCertificateChain(true);
defaultManager.registerFactory(x509Factory);
return namedManager;
}
private static List<Credential> resolveSigningCredentials(RelyingPartyRegistration relyingPartyRegistration) {
List<Credential> credentials = new ArrayList<>();
for (Saml2X509Credential x509Credential : relyingPartyRegistration.getSigningX509Credentials()) {
@@ -40,7 +40,7 @@ final class Saml2Utils {
}
static String samlEncode(byte[] b) {
return Base64.getMimeEncoder().encodeToString(b);
return Base64.getEncoder().encodeToString(b);
}
static byte[] samlDecode(String s) {
@@ -40,7 +40,7 @@ final class Saml2Utils {
}
static String samlEncode(byte[] b) {
return Base64.getMimeEncoder().encodeToString(b);
return Base64.getEncoder().encodeToString(b);
}
static byte[] samlDecode(String s) {
@@ -32,13 +32,8 @@ public final class Saml2Utils {
private Saml2Utils() {
}
@Deprecated
public static String samlEncodeNotRfc2045(byte[] b) {
return Base64.getEncoder().encodeToString(b);
}
public static String samlEncode(byte[] b) {
return Base64.getMimeEncoder().encodeToString(b);
return Base64.getEncoder().encodeToString(b);
}
public static byte[] samlDecode(String s) {
@@ -36,7 +36,7 @@ public class OpenSamlMetadataResolverTests {
.assertionConsumerServiceBinding(Saml2MessageBinding.REDIRECT).build();
OpenSamlMetadataResolver openSamlMetadataResolver = new OpenSamlMetadataResolver();
String metadata = openSamlMetadataResolver.resolve(relyingPartyRegistration);
assertThat(metadata).contains("<EntityDescriptor").contains("entityID=\"rp-entity-id\"")
assertThat(metadata).contains("<md:EntityDescriptor").contains("entityID=\"rp-entity-id\"")
.contains("<md:KeyDescriptor use=\"signing\">").contains("<md:KeyDescriptor use=\"encryption\">")
.contains("<ds:X509Certificate>MIICgTCCAeoCCQCuVzyqFgMSyDANBgkqhkiG9w0BAQsFADCBhDELMAkGA1UEBh")
.contains("Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\"")
@@ -52,7 +52,7 @@ public class OpenSamlMetadataResolverTests {
.build();
OpenSamlMetadataResolver openSamlMetadataResolver = new OpenSamlMetadataResolver();
String metadata = openSamlMetadataResolver.resolve(relyingPartyRegistration);
assertThat(metadata).contains("<EntityDescriptor").contains("entityID=\"rp-entity-id\"")
assertThat(metadata).contains("<md:EntityDescriptor").contains("entityID=\"rp-entity-id\"")
.doesNotContain("<md:KeyDescriptor use=\"signing\">")
.doesNotContain("<md:KeyDescriptor use=\"encryption\">")
.contains("Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"")
@@ -86,7 +86,7 @@ public class OpenSamlMetadataResolverTests {
openSamlMetadataResolver.setEntityDescriptorCustomizer(
(parameters) -> parameters.getEntityDescriptor().setEntityID("overriddenEntityId"));
String metadata = openSamlMetadataResolver.resolve(relyingPartyRegistration);
assertThat(metadata).contains("<EntityDescriptor").contains("entityID=\"overriddenEntityId\"");
assertThat(metadata).contains("<md:EntityDescriptor").contains("entityID=\"overriddenEntityId\"");
}
}
@@ -64,7 +64,7 @@ public class Saml2AuthenticationTokenConverterTests {
.willReturn(this.relyingPartyRegistration);
MockHttpServletRequest request = new MockHttpServletRequest();
request.setParameter(Saml2ParameterNames.SAML_RESPONSE,
Saml2Utils.samlEncodeNotRfc2045("response".getBytes(StandardCharsets.UTF_8)));
Saml2Utils.samlEncode("response".getBytes(StandardCharsets.UTF_8)));
Saml2AuthenticationToken token = converter.convert(request);
assertThat(token.getSaml2Response()).isEqualTo("response");
assertThat(token.getRelyingPartyRegistration().getRegistrationId())
@@ -115,7 +115,7 @@ public class Saml2AuthenticationTokenConverterTests {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("GET");
byte[] deflated = Saml2Utils.samlDeflate("response");
String encoded = Saml2Utils.samlEncodeNotRfc2045(deflated);
String encoded = Saml2Utils.samlEncode(deflated);
request.setParameter(Saml2ParameterNames.SAML_RESPONSE, encoded);
Saml2AuthenticationToken token = converter.convert(request);
assertThat(token.getSaml2Response()).isEqualTo("response");
@@ -0,0 +1,89 @@
/*
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.web.authentication;
import java.util.UUID;
import javax.xml.namespace.QName;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.opensaml.core.xml.XMLObject;
import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
import org.opensaml.saml.common.SAMLVersion;
import org.opensaml.saml.saml2.core.Issuer;
import org.opensaml.saml.saml2.core.Response;
import org.opensaml.xmlsec.signature.Signature;
import org.springframework.security.saml2.core.OpenSamlInitializationService;
import org.springframework.security.saml2.core.TestSaml2X509Credentials;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test open SAML signatures
*/
public class OpenSamlSigningUtilsTests {
static {
OpenSamlInitializationService.initialize();
}
private RelyingPartyRegistration registration;
@BeforeEach
public void setup() {
this.registration = RelyingPartyRegistration.withRegistrationId("saml-idp")
.entityId("https://some.idp.example.com/entity-id").signingX509Credentials((c) -> {
c.add(TestSaml2X509Credentials.relyingPartySigningCredential());
c.add(TestSaml2X509Credentials.assertingPartySigningCredential());
}).assertingPartyDetails((c) -> c.entityId("https://some.idp.example.com/entity-id")
.singleSignOnServiceLocation("https://some.idp.example.com/service-location"))
.build();
}
@Test
public void whenSigningAnObjectThenKeyInfoIsPartOfTheSignature() {
Response response = response("destination", "issuer");
OpenSamlSigningUtils.sign(response, this.registration);
Signature signature = response.getSignature();
assertThat(signature).isNotNull();
assertThat(signature.getKeyInfo()).isNotNull();
}
Response response(String destination, String issuerEntityId) {
Response response = build(Response.DEFAULT_ELEMENT_NAME);
response.setID("R" + UUID.randomUUID());
response.setVersion(SAMLVersion.VERSION_20);
response.setID("_" + UUID.randomUUID());
response.setDestination(destination);
response.setIssuer(issuer(issuerEntityId));
return response;
}
Issuer issuer(String entityId) {
Issuer issuer = build(Issuer.DEFAULT_ELEMENT_NAME);
issuer.setValue(entityId);
return issuer;
}
<T extends XMLObject> T build(QName qName) {
return (T) XMLObjectProviderRegistrySupport.getBuilderFactory().getBuilder(qName).buildObject(qName);
}
}
@@ -0,0 +1,89 @@
/*
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.web.authentication.logout;
import java.util.UUID;
import javax.xml.namespace.QName;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.opensaml.core.xml.XMLObject;
import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
import org.opensaml.saml.common.SAMLVersion;
import org.opensaml.saml.saml2.core.Issuer;
import org.opensaml.saml.saml2.core.Response;
import org.opensaml.xmlsec.signature.Signature;
import org.springframework.security.saml2.core.OpenSamlInitializationService;
import org.springframework.security.saml2.core.TestSaml2X509Credentials;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test open SAML signatures
*/
public class OpenSamlSigningUtilsTests {
static {
OpenSamlInitializationService.initialize();
}
private RelyingPartyRegistration registration;
@BeforeEach
public void setup() {
this.registration = RelyingPartyRegistration.withRegistrationId("saml-idp")
.entityId("https://some.idp.example.com/entity-id").signingX509Credentials((c) -> {
c.add(TestSaml2X509Credentials.relyingPartySigningCredential());
c.add(TestSaml2X509Credentials.assertingPartySigningCredential());
}).assertingPartyDetails((c) -> c.entityId("https://some.idp.example.com/entity-id")
.singleSignOnServiceLocation("https://some.idp.example.com/service-location"))
.build();
}
@Test
public void whenSigningAnObjectThenKeyInfoIsPartOfTheSignature() {
Response response = response("destination", "issuer");
OpenSamlSigningUtils.sign(response, this.registration);
Signature signature = response.getSignature();
assertThat(signature).isNotNull();
assertThat(signature.getKeyInfo()).isNotNull();
}
Response response(String destination, String issuerEntityId) {
Response response = build(Response.DEFAULT_ELEMENT_NAME);
response.setID("R" + UUID.randomUUID());
response.setVersion(SAMLVersion.VERSION_20);
response.setID("_" + UUID.randomUUID());
response.setDestination(destination);
response.setIssuer(issuer(issuerEntityId));
return response;
}
Issuer issuer(String entityId) {
Issuer issuer = build(Issuer.DEFAULT_ELEMENT_NAME);
issuer.setValue(entityId);
return issuer;
}
<T extends XMLObject> T build(QName qName) {
return (T) XMLObjectProviderRegistrySupport.getBuilderFactory().getBuilder(qName).buildObject(qName);
}
}
@@ -22,6 +22,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.util.function.SingletonSupplier;
/**
* Strategy used for persisting a {@link SecurityContext} between requests.
@@ -76,7 +77,7 @@ public interface SecurityContextRepository {
* @since 5.7
*/
default Supplier<SecurityContext> loadContext(HttpServletRequest request) {
return () -> loadContext(new HttpRequestResponseHolder(request, null));
return SingletonSupplier.of(() -> loadContext(new HttpRequestResponseHolder(request, null)));
}
/**
@@ -151,7 +151,7 @@ public class CsrfWebFilter implements WebFilter {
ServerHttpRequest request = exchange.getRequest();
HttpHeaders headers = request.getHeaders();
MediaType contentType = headers.getContentType();
if (!contentType.includes(MediaType.MULTIPART_FORM_DATA)) {
if (!MediaType.MULTIPART_FORM_DATA.isCompatibleWith(contentType)) {
return Mono.empty();
}
return exchange.getMultipartData().map((d) -> d.getFirst(expected.getParameterName())).cast(FormFieldPart.class)
@@ -0,0 +1,53 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.web.context;
import java.util.function.Supplier;
import javax.servlet.http.HttpServletRequest;
import org.junit.jupiter.api.Test;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextImpl;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
/**
* @author Rob Winch
*/
class SecurityContextRepositoryTests {
SecurityContextRepository repository = spy(SecurityContextRepository.class);
@Test
void loadContextHttpRequestResponseHolderWhenInvokeSupplierTwiceThenOnlyInvokesLoadContextOnce() {
given(this.repository.loadContext(any(HttpRequestResponseHolder.class))).willReturn(new SecurityContextImpl());
Supplier<SecurityContext> deferredContext = this.repository.loadContext(mock(HttpServletRequest.class));
verify(this.repository).loadContext(any(HttpServletRequest.class));
deferredContext.get();
verify(this.repository).loadContext(any(HttpRequestResponseHolder.class));
deferredContext.get();
verifyNoMoreInteractions(this.repository);
}
}
@@ -189,6 +189,17 @@ public class CsrfWebFilterTests {
.expectStatus().is2xxSuccessful();
}
@Test
public void filterWhenPostAndMultipartFormDataEnabledAndNoBodyProvided() {
this.csrfFilter.setCsrfTokenRepository(this.repository);
this.csrfFilter.setTokenFromMultipartDataEnabled(true);
given(this.repository.loadToken(any())).willReturn(Mono.just(this.token));
given(this.repository.generateToken(any())).willReturn(Mono.just(this.token));
WebTestClient client = WebTestClient.bindToController(new OkController()).webFilter(this.csrfFilter).build();
client.post().uri("/").header(this.token.getHeaderName(), this.token.getToken()).exchange().expectStatus()
.is2xxSuccessful();
}
@Test
public void filterWhenFormDataAndEnabledThenGranted() {
this.csrfFilter.setCsrfTokenRepository(this.repository);