From 1be13b4d2bb800d35e534dbfd4b711a198a5590f Mon Sep 17 00:00:00 2001 From: kwondh5217 Date: Tue, 17 Dec 2024 14:40:44 +0900 Subject: [PATCH 001/132] Remove Deprecated Usages of RemoteJWKSet close gh-16251 --- .../security/oauth2/jwt/NimbusJwtDecoder.java | 73 +++++++++++++++---- 1 file changed, 58 insertions(+), 15 deletions(-) diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java index 2713ee96b2..934472922f 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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,6 +16,11 @@ package org.springframework.security.oauth2.jwt; +import com.nimbusds.jose.KeySourceException; +import com.nimbusds.jose.jwk.JWK; +import com.nimbusds.jose.jwk.JWKSelector; +import com.nimbusds.jose.jwk.source.JWKSetCacheRefreshEvaluator; +import com.nimbusds.jose.jwk.source.URLBasedJWKSetSource; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; @@ -26,6 +31,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Consumer; @@ -35,11 +41,8 @@ import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.JWSAlgorithm; -import com.nimbusds.jose.RemoteKeySourceException; import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.source.JWKSetCache; import com.nimbusds.jose.jwk.source.JWKSource; -import com.nimbusds.jose.jwk.source.RemoteJWKSet; import com.nimbusds.jose.proc.JWSKeySelector; import com.nimbusds.jose.proc.JWSVerificationKeySelector; import com.nimbusds.jose.proc.SecurityContext; @@ -80,6 +83,7 @@ * @author Josh Cummings * @author Joe Grandja * @author Mykyta Bezverkhyi + * @author Daeho Kwon * @since 5.2 */ public final class NimbusJwtDecoder implements JwtDecoder { @@ -165,7 +169,7 @@ private Jwt createJwt(String token, JWT parsedJwt) { .build(); // @formatter:on } - catch (RemoteKeySourceException ex) { + catch (KeySourceException ex) { this.logger.trace("Failed to retrieve JWK set", ex); if (ex.getCause() instanceof ParseException) { throw new JwtException(String.format(DECODING_ERROR_MESSAGE_TEMPLATE, "Malformed Jwk set"), ex); @@ -377,11 +381,12 @@ JWSKeySelector jwsKeySelector(JWKSource jwkSou } JWKSource jwkSource(ResourceRetriever jwkSetRetriever, String jwkSetUri) { - if (this.cache == null) { - return new RemoteJWKSet<>(toURL(jwkSetUri), jwkSetRetriever); + URLBasedJWKSetSource urlBasedJWKSetSource = new URLBasedJWKSetSource(toURL(jwkSetUri), jwkSetRetriever); + if(this.cache == null) { + return new SpringURLBasedJWKSource(urlBasedJWKSetSource); } - JWKSetCache jwkSetCache = new SpringJWKSetCache(jwkSetUri, this.cache); - return new RemoteJWKSet<>(toURL(jwkSetUri), jwkSetRetriever, jwkSetCache); + SpringJWKSetCache jwkSetCache = new SpringJWKSetCache(jwkSetUri, this.cache); + return new SpringURLBasedJWKSource<>(urlBasedJWKSetSource, jwkSetCache); } JWTProcessor processor() { @@ -414,7 +419,49 @@ private static URL toURL(String url) { } } - private static final class SpringJWKSetCache implements JWKSetCache { + private static final class SpringURLBasedJWKSource implements JWKSource { + + private final URLBasedJWKSetSource urlBasedJWKSetSource; + + private final SpringJWKSetCache jwkSetCache; + + private SpringURLBasedJWKSource(URLBasedJWKSetSource urlBasedJWKSetSource) { + this(urlBasedJWKSetSource, null); + } + + private SpringURLBasedJWKSource(URLBasedJWKSetSource urlBasedJWKSetSource, SpringJWKSetCache jwkSetCache) { + this.urlBasedJWKSetSource = urlBasedJWKSetSource; + this.jwkSetCache = jwkSetCache; + } + + @Override + public List get(JWKSelector jwkSelector, SecurityContext context) throws KeySourceException { + if (this.jwkSetCache != null) { + synchronized (this) { + JWKSet jwkSet = this.jwkSetCache.get(); + if (this.jwkSetCache.requiresRefresh() || jwkSet == null) { + jwkSet = fetchJWKSet(context); + this.jwkSetCache.put(jwkSet); + } + List jwks = jwkSelector.select(jwkSet); + if(!jwks.isEmpty()) { + return jwks; + } + jwkSet = fetchJWKSet(context); + this.jwkSetCache.put(jwkSet); + return jwkSelector.select(jwkSet); + } + } + return jwkSelector.select(fetchJWKSet(context)); + } + + private JWKSet fetchJWKSet(SecurityContext context) throws KeySourceException { + return this.urlBasedJWKSetSource.getJWKSet(JWKSetCacheRefreshEvaluator.noRefresh(), + System.currentTimeMillis(), context); + } + } + + private static final class SpringJWKSetCache { private final String jwkSetUri; @@ -440,20 +487,16 @@ private void updateJwkSetFromCache() { } } - // Note: Only called from inside a synchronized block in RemoteJWKSet. - @Override + // Note: Only called from inside a synchronized block in SpringURLBasedJWKSource. public void put(JWKSet jwkSet) { this.jwkSet = jwkSet; this.cache.put(this.jwkSetUri, jwkSet.toString(false)); } - @Override public JWKSet get() { return (!requiresRefresh()) ? this.jwkSet : null; - } - @Override public boolean requiresRefresh() { return this.cache.get(this.jwkSetUri) == null; } From bab5f350e0c2d3d741174f93f24fd6b7eb2ddd8a Mon Sep 17 00:00:00 2001 From: kwondh5217 Date: Wed, 18 Dec 2024 14:18:04 +0900 Subject: [PATCH 002/132] Remove Deprecated Usages of RemoteJWKSet close gh-16251 Signed-off-by: Daeho Kwon --- .../security/oauth2/jwt/NimbusJwtDecoder.java | 44 +++++++++++++++---- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java index 934472922f..d216aef02b 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java @@ -18,6 +18,7 @@ import com.nimbusds.jose.KeySourceException; import com.nimbusds.jose.jwk.JWK; +import com.nimbusds.jose.jwk.JWKMatcher; import com.nimbusds.jose.jwk.JWKSelector; import com.nimbusds.jose.jwk.source.JWKSetCacheRefreshEvaluator; import com.nimbusds.jose.jwk.source.URLBasedJWKSetSource; @@ -437,20 +438,32 @@ private SpringURLBasedJWKSource(URLBasedJWKSetSource urlBasedJWKSetSource, Sprin @Override public List get(JWKSelector jwkSelector, SecurityContext context) throws KeySourceException { if (this.jwkSetCache != null) { - synchronized (this) { - JWKSet jwkSet = this.jwkSetCache.get(); - if (this.jwkSetCache.requiresRefresh() || jwkSet == null) { + JWKSet jwkSet = this.jwkSetCache.get(); + if (this.jwkSetCache.requiresRefresh() || jwkSet == null) { + synchronized (this) { jwkSet = fetchJWKSet(context); this.jwkSetCache.put(jwkSet); } - List jwks = jwkSelector.select(jwkSet); - if(!jwks.isEmpty()) { - return jwks; - } + } + List matches = jwkSelector.select(jwkSet); + if(!matches.isEmpty()) { + return matches; + } + String soughtKeyID = getFirstSpecifiedKeyID(jwkSelector.getMatcher()); + if (soughtKeyID == null) { + return Collections.emptyList(); + } + if (jwkSet.getKeyByKeyId(soughtKeyID) != null) { + return Collections.emptyList(); + } + synchronized (this) { jwkSet = fetchJWKSet(context); this.jwkSetCache.put(jwkSet); - return jwkSelector.select(jwkSet); } + if(jwkSet == null) { + return Collections.emptyList(); + } + return jwkSelector.select(jwkSet); } return jwkSelector.select(fetchJWKSet(context)); } @@ -459,6 +472,21 @@ private JWKSet fetchJWKSet(SecurityContext context) throws KeySourceException { return this.urlBasedJWKSetSource.getJWKSet(JWKSetCacheRefreshEvaluator.noRefresh(), System.currentTimeMillis(), context); } + + private static String getFirstSpecifiedKeyID(JWKMatcher jwkMatcher) { + Set keyIDs = jwkMatcher.getKeyIDs(); + + if (keyIDs == null || keyIDs.isEmpty()) { + return null; + } + + for (String id: keyIDs) { + if (id != null) { + return id; + } + } + return null; + } } private static final class SpringJWKSetCache { From 662f7ddc3e72528cd4575272302af7d414116118 Mon Sep 17 00:00:00 2001 From: kwondh5217 Date: Wed, 18 Dec 2024 14:19:51 +0900 Subject: [PATCH 003/132] Remove Deprecated Usages of RemoteJWKSet close gh-16251 Signed-off-by: Daeho Kwon --- .../springframework/security/oauth2/jwt/NimbusJwtDecoder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java index d216aef02b..fed8d06731 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java @@ -473,7 +473,7 @@ private JWKSet fetchJWKSet(SecurityContext context) throws KeySourceException { System.currentTimeMillis(), context); } - private static String getFirstSpecifiedKeyID(JWKMatcher jwkMatcher) { + private String getFirstSpecifiedKeyID(JWKMatcher jwkMatcher) { Set keyIDs = jwkMatcher.getKeyIDs(); if (keyIDs == null || keyIDs.isEmpty()) { From 43a07a75a68b25a5b6dcff1a43b15295f1c7ed03 Mon Sep 17 00:00:00 2001 From: kwondh5217 Date: Wed, 18 Dec 2024 14:50:45 +0900 Subject: [PATCH 004/132] Remove Deprecated Usages of RemoteJWKSet close gh-16251 Signed-off-by: Daeho Kwon --- .../security/oauth2/jwt/NimbusJwtDecoder.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java index fed8d06731..e5a6368e92 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java @@ -441,7 +441,7 @@ public List get(JWKSelector jwkSelector, SecurityContext context) throws Ke JWKSet jwkSet = this.jwkSetCache.get(); if (this.jwkSetCache.requiresRefresh() || jwkSet == null) { synchronized (this) { - jwkSet = fetchJWKSet(context); + jwkSet = fetchJWKSet(); this.jwkSetCache.put(jwkSet); } } @@ -457,7 +457,7 @@ public List get(JWKSelector jwkSelector, SecurityContext context) throws Ke return Collections.emptyList(); } synchronized (this) { - jwkSet = fetchJWKSet(context); + jwkSet = fetchJWKSet(); this.jwkSetCache.put(jwkSet); } if(jwkSet == null) { @@ -465,12 +465,12 @@ public List get(JWKSelector jwkSelector, SecurityContext context) throws Ke } return jwkSelector.select(jwkSet); } - return jwkSelector.select(fetchJWKSet(context)); + return jwkSelector.select(fetchJWKSet()); } - private JWKSet fetchJWKSet(SecurityContext context) throws KeySourceException { + private JWKSet fetchJWKSet() throws KeySourceException { return this.urlBasedJWKSetSource.getJWKSet(JWKSetCacheRefreshEvaluator.noRefresh(), - System.currentTimeMillis(), context); + System.currentTimeMillis(), null); } private String getFirstSpecifiedKeyID(JWKMatcher jwkMatcher) { From bf4ed1455a67029418bdec93e5522bf98cb0853c Mon Sep 17 00:00:00 2001 From: kwondh5217 Date: Wed, 18 Dec 2024 14:59:46 +0900 Subject: [PATCH 005/132] Remove Deprecated Usages of RemoteJWKSet close gh-16251 Signed-off-by: Daeho Kwon --- .../security/oauth2/jwt/NimbusJwtDecoder.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java index e5a6368e92..936eb8b1dd 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java @@ -457,8 +457,12 @@ public List get(JWKSelector jwkSelector, SecurityContext context) throws Ke return Collections.emptyList(); } synchronized (this) { - jwkSet = fetchJWKSet(); - this.jwkSetCache.put(jwkSet); + if(jwkSet == this.jwkSetCache.get()) { + jwkSet = fetchJWKSet(); + this.jwkSetCache.put(jwkSet); + } else { + jwkSet = this.jwkSetCache.get(); + } } if(jwkSet == null) { return Collections.emptyList(); From 22949c0bc466242bdf32fe4ba10d74ed649a8562 Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Tue, 17 Dec 2024 09:36:29 -0700 Subject: [PATCH 006/132] Add Serializable Compatilibity to Saml 2.0 Exceptions Issue gh-16276 Signed-off-by: Daeho Kwon --- ...ingSecurityCoreVersionSerializableTests.java | 7 +++++++ ...ork.security.saml2.Saml2Exception.serialized | Bin 0 -> 16297 bytes ...tion.Saml2AuthenticationException.serialized | Bin 0 -> 16678 bytes .../security/saml2/Saml2Exception.java | 7 ++++++- .../Saml2AuthenticationException.java | 7 ++++++- 5 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.saml2.Saml2Exception.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException.serialized diff --git a/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java b/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java index cff442fffe..ecc30bd446 100644 --- a/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java +++ b/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java @@ -116,8 +116,11 @@ import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionAuthenticatedPrincipal; +import org.springframework.security.saml2.Saml2Exception; +import org.springframework.security.saml2.core.Saml2Error; import org.springframework.security.saml2.provider.service.authentication.DefaultSaml2AuthenticatedPrincipal; import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; import org.springframework.security.saml2.provider.service.authentication.Saml2PostAuthenticationRequest; import org.springframework.security.saml2.provider.service.authentication.Saml2RedirectAuthenticationRequest; import org.springframework.security.saml2.provider.service.authentication.TestSaml2Authentications; @@ -301,6 +304,10 @@ class SpringSecurityCoreVersionSerializableTests { (r) -> new LdapAuthority("USER", "username", Map.of("attribute", List.of("value1", "value2")))); // saml2-service-provider + generatorByClassName.put(Saml2AuthenticationException.class, + (r) -> new Saml2AuthenticationException(new Saml2Error("code", "descirption"), "message", + new IOException("fail"))); + generatorByClassName.put(Saml2Exception.class, (r) -> new Saml2Exception("message", new IOException("fail"))); generatorByClassName.put(DefaultSaml2AuthenticatedPrincipal.class, (r) -> TestSaml2Authentications.authentication().getPrincipal()); generatorByClassName.put(Saml2Authentication.class, diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.saml2.Saml2Exception.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.saml2.Saml2Exception.serialized new file mode 100644 index 0000000000000000000000000000000000000000..4fd752b76ff32c61363d4a88deaec496110dd86d GIT binary patch literal 16297 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 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..f771882b3de39663ba2cce10cb968e40abb8bba0 GIT binary patch literal 16678 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 W1l%!3`Fw_Qeg3-zl({a^*uMY}4DY@G literal 0 HcmV?d00001 diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/Saml2Exception.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/Saml2Exception.java index dc4e6bb770..3595dec00a 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/Saml2Exception.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/Saml2Exception.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2024 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,11 +16,16 @@ package org.springframework.security.saml2; +import java.io.Serial; + /** * @since 5.2 */ public class Saml2Exception extends RuntimeException { + @Serial + private static final long serialVersionUID = 6076252564189633016L; + public Saml2Exception(String message) { super(message); } diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationException.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationException.java index 6ee38c6d60..36075ba0df 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationException.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2024 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,6 +16,8 @@ package org.springframework.security.saml2.provider.service.authentication; +import java.io.Serial; + import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.saml2.core.Saml2Error; @@ -40,6 +42,9 @@ */ public class Saml2AuthenticationException extends AuthenticationException { + @Serial + private static final long serialVersionUID = -2996886630890949105L; + private final Saml2Error error; /** From 09c583a4e9908c8611adb4e85f4261533aff297c Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Tue, 17 Dec 2024 13:05:23 -0700 Subject: [PATCH 007/132] Add Serializable Compatibility to Web Authentication Exceptions Issue gh-16276 Signed-off-by: Daeho Kwon --- ...ingSecurityCoreVersionSerializableTests.java | 16 ++++++++++++++++ ...catedCredentialsNotFoundException.serialized | Bin 0 -> 16420 bytes ...n.rememberme.CookieTheftException.serialized | Bin 0 -> 10859 bytes ...rememberme.InvalidCookieException.serialized | Bin 0 -> 10861 bytes ...RememberMeAuthenticationException.serialized | Bin 0 -> 16357 bytes ...on.SessionAuthenticationException.serialized | Bin 0 -> 10753 bytes ...ication.www.NonceExpiredException.serialized | Bin 0 -> 16338 bytes ...thenticatedCredentialsNotFoundException.java | 7 ++++++- .../rememberme/CookieTheftException.java | 7 ++++++- .../rememberme/InvalidCookieException.java | 7 ++++++- .../RememberMeAuthenticationException.java | 7 ++++++- .../session/SessionAuthenticationException.java | 7 ++++++- .../www/NonceExpiredException.java | 5 +++++ 13 files changed, 51 insertions(+), 5 deletions(-) create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.web.authentication.preauth.PreAuthenticatedCredentialsNotFoundException.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.web.authentication.rememberme.CookieTheftException.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.web.authentication.rememberme.InvalidCookieException.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationException.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.web.authentication.session.SessionAuthenticationException.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.web.authentication.www.NonceExpiredException.serialized diff --git a/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java b/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java index ecc30bd446..5072da9f5c 100644 --- a/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java +++ b/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java @@ -128,6 +128,12 @@ import org.springframework.security.saml2.provider.service.authentication.TestSaml2RedirectAuthenticationRequests; import org.springframework.security.web.authentication.WebAuthenticationDetails; import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; +import org.springframework.security.web.authentication.preauth.PreAuthenticatedCredentialsNotFoundException; +import org.springframework.security.web.authentication.rememberme.CookieTheftException; +import org.springframework.security.web.authentication.rememberme.InvalidCookieException; +import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationException; +import org.springframework.security.web.authentication.session.SessionAuthenticationException; +import org.springframework.security.web.authentication.www.NonceExpiredException; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; @@ -328,6 +334,16 @@ class SpringSecurityCoreVersionSerializableTests { token.setDetails(details); return token; }); + generatorByClassName.put(PreAuthenticatedCredentialsNotFoundException.class, + (r) -> new PreAuthenticatedCredentialsNotFoundException("message", new IOException("fail"))); + generatorByClassName.put(CookieTheftException.class, (r) -> new CookieTheftException("message")); + generatorByClassName.put(InvalidCookieException.class, (r) -> new InvalidCookieException("message")); + generatorByClassName.put(RememberMeAuthenticationException.class, + (r) -> new RememberMeAuthenticationException("message", new IOException("fail"))); + generatorByClassName.put(SessionAuthenticationException.class, + (r) -> new SessionAuthenticationException("message")); + generatorByClassName.put(NonceExpiredException.class, + (r) -> new NonceExpiredException("message", new IOException("fail"))); } @ParameterizedTest diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.web.authentication.preauth.PreAuthenticatedCredentialsNotFoundException.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.web.authentication.preauth.PreAuthenticatedCredentialsNotFoundException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..6d7a94c295056eecba83fa32183d38fbf96d17f0 GIT binary patch literal 16420 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 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.web.authentication.rememberme.InvalidCookieException.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.web.authentication.rememberme.InvalidCookieException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..b4f3a5f6acc8e3b98177c470d27a6a4f79d5f1ea GIT binary patch literal 10861 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 RSScoVY9So1OMXX2{|6t~g*E^H literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.web.authentication.session.SessionAuthenticationException.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.web.authentication.session.SessionAuthenticationException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..5b627fb9c7a5b8ec33d00805b3af38c331b5982b GIT binary patch literal 10753 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 literal 0 HcmV?d00001 diff --git a/web/src/main/java/org/springframework/security/web/authentication/preauth/PreAuthenticatedCredentialsNotFoundException.java b/web/src/main/java/org/springframework/security/web/authentication/preauth/PreAuthenticatedCredentialsNotFoundException.java index d18e7c0cc2..57767d815c 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/preauth/PreAuthenticatedCredentialsNotFoundException.java +++ b/web/src/main/java/org/springframework/security/web/authentication/preauth/PreAuthenticatedCredentialsNotFoundException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2024 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,10 +16,15 @@ package org.springframework.security.web.authentication.preauth; +import java.io.Serial; + import org.springframework.security.core.AuthenticationException; public class PreAuthenticatedCredentialsNotFoundException extends AuthenticationException { + @Serial + private static final long serialVersionUID = 2026209817833032728L; + public PreAuthenticatedCredentialsNotFoundException(String msg) { super(msg); } diff --git a/web/src/main/java/org/springframework/security/web/authentication/rememberme/CookieTheftException.java b/web/src/main/java/org/springframework/security/web/authentication/rememberme/CookieTheftException.java index dabaedd895..3a477e0c50 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/rememberme/CookieTheftException.java +++ b/web/src/main/java/org/springframework/security/web/authentication/rememberme/CookieTheftException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2024 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,11 +16,16 @@ package org.springframework.security.web.authentication.rememberme; +import java.io.Serial; + /** * @author Luke Taylor */ public class CookieTheftException extends RememberMeAuthenticationException { + @Serial + private static final long serialVersionUID = -7215039140728554850L; + public CookieTheftException(String message) { super(message); } diff --git a/web/src/main/java/org/springframework/security/web/authentication/rememberme/InvalidCookieException.java b/web/src/main/java/org/springframework/security/web/authentication/rememberme/InvalidCookieException.java index 00668e06d8..d434bbc47b 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/rememberme/InvalidCookieException.java +++ b/web/src/main/java/org/springframework/security/web/authentication/rememberme/InvalidCookieException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2024 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,6 +16,8 @@ package org.springframework.security.web.authentication.rememberme; +import java.io.Serial; + /** * Exception thrown by a RememberMeServices implementation to indicate that a submitted * cookie is of an invalid format or has expired. @@ -24,6 +26,9 @@ */ public class InvalidCookieException extends RememberMeAuthenticationException { + @Serial + private static final long serialVersionUID = -7952247791921087125L; + public InvalidCookieException(String message) { super(message); } diff --git a/web/src/main/java/org/springframework/security/web/authentication/rememberme/RememberMeAuthenticationException.java b/web/src/main/java/org/springframework/security/web/authentication/rememberme/RememberMeAuthenticationException.java index dc727efa92..a1fc8c4ee8 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/rememberme/RememberMeAuthenticationException.java +++ b/web/src/main/java/org/springframework/security/web/authentication/rememberme/RememberMeAuthenticationException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2024 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,6 +16,8 @@ package org.springframework.security.web.authentication.rememberme; +import java.io.Serial; + import org.springframework.security.core.AuthenticationException; /** @@ -27,6 +29,9 @@ */ public class RememberMeAuthenticationException extends AuthenticationException { + @Serial + private static final long serialVersionUID = 7028526952590057426L; + /** * Constructs a {@code RememberMeAuthenticationException} with the specified message * and root cause. diff --git a/web/src/main/java/org/springframework/security/web/authentication/session/SessionAuthenticationException.java b/web/src/main/java/org/springframework/security/web/authentication/session/SessionAuthenticationException.java index db1650b3a9..6ec0835f75 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/session/SessionAuthenticationException.java +++ b/web/src/main/java/org/springframework/security/web/authentication/session/SessionAuthenticationException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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,6 +16,8 @@ package org.springframework.security.web.authentication.session; +import java.io.Serial; + import org.springframework.security.core.AuthenticationException; /** @@ -31,6 +33,9 @@ */ public class SessionAuthenticationException extends AuthenticationException { + @Serial + private static final long serialVersionUID = -2359914603911936474L; + public SessionAuthenticationException(String msg) { super(msg); } diff --git a/web/src/main/java/org/springframework/security/web/authentication/www/NonceExpiredException.java b/web/src/main/java/org/springframework/security/web/authentication/www/NonceExpiredException.java index 8ac38137d0..6628a9e27a 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/www/NonceExpiredException.java +++ b/web/src/main/java/org/springframework/security/web/authentication/www/NonceExpiredException.java @@ -16,6 +16,8 @@ package org.springframework.security.web.authentication.www; +import java.io.Serial; + import org.springframework.security.core.AuthenticationException; /** @@ -25,6 +27,9 @@ */ public class NonceExpiredException extends AuthenticationException { + @Serial + private static final long serialVersionUID = -3487244679050681257L; + /** * Constructs a NonceExpiredException with the specified message. * @param msg the detail message From a3170d115dd2d8a9d91a77f440dd1cbb858b7652 Mon Sep 17 00:00:00 2001 From: Michal Okosy Date: Sun, 10 Mar 2024 12:53:16 +0100 Subject: [PATCH 008/132] Use relative URLs in /login redirects Closes gh-7273 Signed-off-by: Daeho Kwon --- .../web/builders/NamespaceHttpTests.java | 6 ++--- .../DefaultLoginPageConfigurerTests.java | 2 +- .../ExceptionHandlingConfigurerTests.java | 5 ++-- .../configurers/FormLoginConfigurerTests.java | 8 +++--- .../NamespaceHttpFormLoginTests.java | 8 +++--- .../configurers/NamespaceRememberMeTests.java | 6 ++--- .../RememberMeConfigurerTests.java | 4 +-- .../RequestCacheConfigurerTests.java | 20 +++++++------- .../client/OAuth2LoginConfigurerTests.java | 14 +++++----- .../OAuth2ResourceServerConfigurerTests.java | 4 +-- .../saml2/Saml2LoginConfigurerTests.java | 7 ++--- .../security/config/http/CsrfConfigTests.java | 8 +++--- .../config/http/FormLoginConfigTests.java | 6 ++--- .../security/config/http/HttpConfigTests.java | 8 +++--- .../config/http/MiscHttpConfigTests.java | 6 ++--- .../OAuth2LoginBeanDefinitionParserTests.java | 14 +++++----- .../http/PlaceHolderAndELConfigTests.java | 6 ++--- .../Saml2LoginBeanDefinitionParserTests.java | 6 ++--- ...yContextHolderAwareRequestConfigTests.java | 16 ++++++------ .../web/ExceptionHandlingDslTests.kt | 8 +++--- .../annotation/web/FormLoginDslTests.kt | 4 +-- .../annotation/web/RememberMeDslTests.kt | 6 ++--- .../LoginUrlAuthenticationEntryPoint.java | 26 +++++++------------ ...LoginUrlAuthenticationEntryPointTests.java | 14 +++++++--- 24 files changed, 105 insertions(+), 107 deletions(-) diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/builders/NamespaceHttpTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/builders/NamespaceHttpTests.java index e24b8d8f10..7299e3ebc8 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/builders/NamespaceHttpTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/builders/NamespaceHttpTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2024 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. @@ -77,7 +77,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrlPattern; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** @@ -167,7 +167,7 @@ public void configureWhenAuthenticationEntryPointSetAndRequestUnauthorizedThenRe // @formatter:off this.mockMvc.perform(get("/")) .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrlPattern("**/entry-point")); + .andExpect(redirectedUrl("/entry-point")); // @formatter:on } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/DefaultLoginPageConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/DefaultLoginPageConfigurerTests.java index 6d683c4899..b519769bdb 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/DefaultLoginPageConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/DefaultLoginPageConfigurerTests.java @@ -82,7 +82,7 @@ public class DefaultLoginPageConfigurerTests { @Test public void getWhenFormLoginEnabledThenRedirectsToLoginPage() throws Exception { this.spring.register(DefaultLoginPageConfig.class).autowire(); - this.mvc.perform(get("/")).andExpect(redirectedUrl("http://localhost/login")); + this.mvc.perform(get("/")).andExpect(redirectedUrl("/login")); } @Test diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurerTests.java index d89526127e..cd57c7bd1b 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2024 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. @@ -213,8 +213,7 @@ public void getWhenCustomSecurityContextHolderStrategyThenUsed() throws Exceptio @Test public void getWhenUsingDefaultsAndUnauthenticatedThenRedirectsToLogin() throws Exception { this.spring.register(DefaultHttpConfig.class).autowire(); - this.mvc.perform(get("/").header(HttpHeaders.ACCEPT, "bogus/type")) - .andExpect(redirectedUrl("http://localhost/login")); + this.mvc.perform(get("/").header(HttpHeaders.ACCEPT, "bogus/type")).andExpect(redirectedUrl("/login")); } @Test diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.java index 49b8ed2a1a..663b67bcbf 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2024 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. @@ -162,7 +162,7 @@ public void requestProtectedWhenFormLoginConfiguredThenRedirectsToLogin() throws // @formatter:off this.mockMvc.perform(get("/private")) .andExpect(status().isFound()) - .andExpect(redirectedUrl("http://localhost/login")); + .andExpect(redirectedUrl("/login")); // @formatter:on } @@ -217,7 +217,7 @@ public void requestProtectedWhenFormLoginDefaultsInLambdaThenRedirectsToLogin() // @formatter:off this.mockMvc.perform(get("/private")) .andExpect(status().isFound()) - .andExpect(redirectedUrl("http://localhost/login")); + .andExpect(redirectedUrl("/login")); // @formatter:on } @@ -331,7 +331,7 @@ public void failureUrlWhenPermitAllAndFailureHandlerThenSecured() throws Excepti // @formatter:off this.mockMvc.perform(get("/login?error")) .andExpect(status().isFound()) - .andExpect(redirectedUrl("http://localhost/login")); + .andExpect(redirectedUrl("/login")); // @formatter:on } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpFormLoginTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpFormLoginTests.java index c1bd55dbdb..2d46951de1 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpFormLoginTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpFormLoginTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2024 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. @@ -65,7 +65,7 @@ public class NamespaceHttpFormLoginTests { @Test public void formLoginWhenDefaultConfigurationThenMatchesNamespace() throws Exception { this.spring.register(FormLoginConfig.class, UserDetailsServiceConfig.class).autowire(); - this.mvc.perform(get("/")).andExpect(redirectedUrl("http://localhost/login")); + this.mvc.perform(get("/")).andExpect(redirectedUrl("/login")); this.mvc.perform(post("/login").with(csrf())).andExpect(redirectedUrl("/login?error")); // @formatter:off MockHttpServletRequestBuilder loginRequest = post("/login") @@ -79,7 +79,7 @@ public void formLoginWhenDefaultConfigurationThenMatchesNamespace() throws Excep @Test public void formLoginWithCustomEndpointsThenBehaviorMatchesNamespace() throws Exception { this.spring.register(FormLoginCustomConfig.class, UserDetailsServiceConfig.class).autowire(); - this.mvc.perform(get("/")).andExpect(redirectedUrl("http://localhost/authentication/login")); + this.mvc.perform(get("/")).andExpect(redirectedUrl("/authentication/login")); this.mvc.perform(post("/authentication/login/process").with(csrf())) .andExpect(redirectedUrl("/authentication/login?failed")); // @formatter:off @@ -94,7 +94,7 @@ public void formLoginWithCustomEndpointsThenBehaviorMatchesNamespace() throws Ex @Test public void formLoginWithCustomHandlersThenBehaviorMatchesNamespace() throws Exception { this.spring.register(FormLoginCustomRefsConfig.class, UserDetailsServiceConfig.class).autowire(); - this.mvc.perform(get("/")).andExpect(redirectedUrl("http://localhost/login")); + this.mvc.perform(get("/")).andExpect(redirectedUrl("/login")); this.mvc.perform(post("/login").with(csrf())).andExpect(redirectedUrl("/custom/failure")); verifyBean(WebAuthenticationDetailsSource.class).buildDetails(any(HttpServletRequest.class)); // @formatter:off diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceRememberMeTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceRememberMeTests.java index c55f865db8..2c448484e5 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceRememberMeTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceRememberMeTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2024 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. @@ -104,7 +104,7 @@ public void rememberMeLoginWhenUsingDefaultsThenMatchesNamespace() throws Except .with(csrf()) .cookie(rememberMe); this.mvc.perform(authenticationClassRequest) - .andExpect(redirectedUrl("http://localhost/login")) + .andExpect(redirectedUrl("/login")) .andReturn(); // @formatter:on } @@ -150,7 +150,7 @@ public void rememberMeLoginWhenKeyDeclaredThenMatchesNamespace() throws Exceptio // @formatter:off this.mvc.perform(somewhereRequest) .andExpect(status().isFound()) - .andExpect(redirectedUrl("http://localhost/login")); + .andExpect(redirectedUrl("/login")); MockHttpServletRequestBuilder loginWithRememberme = post("/login").with(rememberMeLogin()); Cookie withKey = this.mvc.perform(loginWithRememberme) .andReturn() diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RememberMeConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RememberMeConfigurerTests.java index e3cb83f76f..9277158ba2 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RememberMeConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RememberMeConfigurerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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. @@ -240,7 +240,7 @@ public void getWhenRememberMeCookieAndLoggedOutThenRedirectsToLogin() throws Exc .with(csrf()) .cookie(expiredRememberMeCookie); // @formatter:on - this.mvc.perform(expiredRequest).andExpect(redirectedUrl("http://localhost/login")); + this.mvc.perform(expiredRequest).andExpect(redirectedUrl("/login")); } @Test diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurerTests.java index f22e55043d..09ada968b6 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2024 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. @@ -90,7 +90,7 @@ public void getWhenBookmarkedUrlIsFaviconIcoThenPostAuthenticationRedirectsToRoo this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire(); // @formatter:off MockHttpSession session = (MockHttpSession) this.mvc.perform(get("/favicon.ico")) - .andExpect(redirectedUrl("http://localhost/login")) + .andExpect(redirectedUrl("/login")) .andReturn() .getRequest() .getSession(); @@ -104,7 +104,7 @@ public void getWhenBookmarkedUrlIsFaviconPngThenPostAuthenticationRedirectsToRoo this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire(); // @formatter:off MockHttpSession session = (MockHttpSession) this.mvc.perform(get("/favicon.png")) - .andExpect(redirectedUrl("http://localhost/login")) + .andExpect(redirectedUrl("/login")) .andReturn() .getRequest() .getSession(); @@ -120,7 +120,7 @@ public void getWhenBookmarkedRequestIsApplicationJsonThenPostAuthenticationRedir MockHttpServletRequestBuilder request = get("/messages").header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON); // @formatter:off MockHttpSession session = (MockHttpSession) this.mvc.perform(request) - .andExpect(redirectedUrl("http://localhost/login")) + .andExpect(redirectedUrl("/login")) .andReturn() .getRequest() .getSession(); @@ -140,7 +140,7 @@ public void getWhenBookmarkedRequestIsXRequestedWithThenPostAuthenticationRedire .header("X-Requested-With", "XMLHttpRequest"); MockHttpSession session = (MockHttpSession) this.mvc .perform(xRequestedWith) - .andExpect(redirectedUrl("http://localhost/login")) + .andExpect(redirectedUrl("/login")) .andReturn() .getRequest() .getSession(); @@ -157,7 +157,7 @@ public void getWhenBookmarkedRequestIsTextEventStreamThenPostAuthenticationRedir MediaType.TEXT_EVENT_STREAM); // @formatter:off MockHttpSession session = (MockHttpSession) this.mvc.perform(request) - .andExpect(redirectedUrl("http://localhost/login")) + .andExpect(redirectedUrl("/login")) .andReturn() .getRequest() .getSession(); @@ -174,7 +174,7 @@ public void getWhenBookmarkedRequestIsAllMediaTypeThenPostAuthenticationRemember MockHttpServletRequestBuilder request = get("/messages").header(HttpHeaders.ACCEPT, MediaType.ALL); // @formatter:off MockHttpSession session = (MockHttpSession) this.mvc.perform(request) - .andExpect(redirectedUrl("http://localhost/login")) + .andExpect(redirectedUrl("/login")) .andReturn() .getRequest() .getSession(); @@ -188,7 +188,7 @@ public void getWhenBookmarkedRequestIsTextHtmlThenPostAuthenticationRemembers() MockHttpServletRequestBuilder request = get("/messages").header(HttpHeaders.ACCEPT, MediaType.TEXT_HTML); // @formatter:off MockHttpSession session = (MockHttpSession) this.mvc.perform(request) - .andExpect(redirectedUrl("http://localhost/login")) + .andExpect(redirectedUrl("/login")) .andReturn() .getRequest() .getSession(); @@ -203,7 +203,7 @@ public void getWhenBookmarkedRequestIsChromeThenPostAuthenticationRemembers() th MockHttpServletRequestBuilder request = get("/messages") .header(HttpHeaders.ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"); MockHttpSession session = (MockHttpSession) this.mvc.perform(request) - .andExpect(redirectedUrl("http://localhost/login")) + .andExpect(redirectedUrl("/login")) .andReturn() .getRequest() .getSession(); @@ -218,7 +218,7 @@ public void getWhenBookmarkedRequestIsRequestedWithAndroidThenPostAuthentication MockHttpServletRequestBuilder request = get("/messages") .header("X-Requested-With", "com.android"); MockHttpSession session = (MockHttpSession) this.mvc.perform(request) - .andExpect(redirectedUrl("http://localhost/login")) + .andExpect(redirectedUrl("/login")) .andReturn() .getRequest() .getSession(); diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurerTests.java index b56d047a5f..65a56cdb7a 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurerTests.java @@ -437,7 +437,7 @@ public void oauth2LoginWithOneClientConfiguredThenRedirectForAuthorization() thr this.request = new MockHttpServletRequest("GET", requestUri); this.request.setServletPath(requestUri); this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); - assertThat(this.response.getRedirectedUrl()).matches("http://localhost/oauth2/authorization/google"); + assertThat(this.response.getRedirectedUrl()).matches("/oauth2/authorization/google"); } // gh-6802 @@ -448,7 +448,7 @@ public void oauth2LoginWithOneClientConfiguredAndFormLoginThenRedirectDefaultLog this.request = new MockHttpServletRequest("GET", requestUri); this.request.setServletPath(requestUri); this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); - assertThat(this.response.getRedirectedUrl()).matches("http://localhost/login"); + assertThat(this.response.getRedirectedUrl()).matches("/login"); } // gh-5347 @@ -461,7 +461,7 @@ public void oauth2LoginWithOneClientConfiguredAndRequestFaviconNotAuthenticatedT this.request.setServletPath(requestUri); this.request.addHeader(HttpHeaders.ACCEPT, new MediaType("image", "*").toString()); this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); - assertThat(this.response.getRedirectedUrl()).matches("http://localhost/login"); + assertThat(this.response.getRedirectedUrl()).matches("/login"); } // gh-5347 @@ -472,7 +472,7 @@ public void oauth2LoginWithMultipleClientsConfiguredThenRedirectDefaultLoginPage this.request = new MockHttpServletRequest("GET", requestUri); this.request.setServletPath(requestUri); this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); - assertThat(this.response.getRedirectedUrl()).matches("http://localhost/login"); + assertThat(this.response.getRedirectedUrl()).matches("/login"); } // gh-6812 @@ -521,7 +521,7 @@ public void oauth2LoginWithOneAuthorizationCodeClientAndOtherClientsConfiguredTh this.request = new MockHttpServletRequest("GET", requestUri); this.request.setServletPath(requestUri); this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); - assertThat(this.response.getRedirectedUrl()).matches("http://localhost/oauth2/authorization/google"); + assertThat(this.response.getRedirectedUrl()).matches("/oauth2/authorization/google"); } @Test @@ -531,7 +531,7 @@ public void oauth2LoginWithCustomLoginPageThenRedirectCustomLoginPage() throws E this.request = new MockHttpServletRequest("GET", requestUri); this.request.setServletPath(requestUri); this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); - assertThat(this.response.getRedirectedUrl()).matches("http://localhost/custom-login"); + assertThat(this.response.getRedirectedUrl()).matches("/custom-login"); } @Test @@ -541,7 +541,7 @@ public void requestWhenOauth2LoginWithCustomLoginPageInLambdaThenRedirectCustomL this.request = new MockHttpServletRequest("GET", requestUri); this.request.setServletPath(requestUri); this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); - assertThat(this.response.getRedirectedUrl()).matches("http://localhost/custom-login"); + assertThat(this.response.getRedirectedUrl()).matches("/custom-login"); } @Test diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java index c247a6d7fe..2dad6916fe 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2024 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. @@ -1210,7 +1210,7 @@ public void requestWhenFormLoginAndResourceServerEntryPointsThenSessionCreatedBy MvcResult result = this.mvc.perform(get("/authenticated") .header("Accept", "text/html")) .andExpect(status().isFound()) - .andExpect(redirectedUrl("http://localhost/login")) + .andExpect(redirectedUrl("/login")) .andReturn(); // @formatter:on assertThat(result.getRequest().getSession(false)).isNotNull(); diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurerTests.java index 6d874a583d..3a91207290 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2024 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. @@ -412,10 +412,11 @@ public void getFaviconWhenDefaultConfigurationThenDoesNotSaveAuthnRequest() thro this.spring.register(Saml2LoginConfig.class).autowire(); this.mvc.perform(get("/favicon.ico").accept(MediaType.TEXT_HTML)) .andExpect(status().isFound()) - .andExpect(redirectedUrl("http://localhost/login")); + .andExpect(redirectedUrl("/login")); this.mvc.perform(get("/").accept(MediaType.TEXT_HTML)) .andExpect(status().isFound()) - .andExpect(header().string("Location", startsWith("http://localhost/saml2/authenticate"))); + .andExpect(header().string("Location", startsWith("/saml2/authenticate"))); + } @Test diff --git a/config/src/test/java/org/springframework/security/config/http/CsrfConfigTests.java b/config/src/test/java/org/springframework/security/config/http/CsrfConfigTests.java index 901945e73a..781cce82e9 100644 --- a/config/src/test/java/org/springframework/security/config/http/CsrfConfigTests.java +++ b/config/src/test/java/org/springframework/security/config/http/CsrfConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2024 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. @@ -342,7 +342,7 @@ public void postWhenHasCsrfTokenButSessionExpiresThenRequestIsCancelledAfterSucc this.spring.configLocations(this.xml("CsrfEnabled")).autowire(); // simulates a request that has no authentication (e.g. session time-out) MvcResult result = this.mvc.perform(post("/authenticated").with(csrf())) - .andExpect(redirectedUrl("http://localhost/login")) + .andExpect(redirectedUrl("/login")) .andReturn(); MockHttpSession session = (MockHttpSession) result.getRequest().getSession(); // if the request cache is consulted, then it will redirect back to /some-url, @@ -363,9 +363,7 @@ public void getWhenHasCsrfTokenButSessionExpiresThenRequestIsRememeberedAfterSuc throws Exception { this.spring.configLocations(this.xml("CsrfEnabled")).autowire(); // simulates a request that has no authentication (e.g. session time-out) - MvcResult result = this.mvc.perform(get("/authenticated")) - .andExpect(redirectedUrl("http://localhost/login")) - .andReturn(); + MvcResult result = this.mvc.perform(get("/authenticated")).andExpect(redirectedUrl("/login")).andReturn(); MockHttpSession session = (MockHttpSession) result.getRequest().getSession(); // if the request cache is consulted, then it will redirect back to /some-url, // which we do want diff --git a/config/src/test/java/org/springframework/security/config/http/FormLoginConfigTests.java b/config/src/test/java/org/springframework/security/config/http/FormLoginConfigTests.java index 52237273df..b054762aac 100644 --- a/config/src/test/java/org/springframework/security/config/http/FormLoginConfigTests.java +++ b/config/src/test/java/org/springframework/security/config/http/FormLoginConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2024 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. @@ -73,7 +73,7 @@ public void getProtectedPageWhenFormLoginConfiguredThenRedirectsToDefaultLoginPa this.spring.configLocations(this.xml("WithAntRequestMatcher")).autowire(); // @formatter:off this.mvc.perform(get("/")) - .andExpect(redirectedUrl("http://localhost/login")); + .andExpect(redirectedUrl("/login")); // @formatter:on } @@ -107,7 +107,7 @@ public void authenticateWhenConfiguredWithSpelThenRedirectsAccordingly() throws this.mvc.perform(invalidPassword) .andExpect(redirectedUrl(WebConfigUtilsTests.URL + "/failure")); this.mvc.perform(get("/")) - .andExpect(redirectedUrl("http://localhost" + WebConfigUtilsTests.URL + "/login")); + .andExpect(redirectedUrl(WebConfigUtilsTests.URL + "/login")); // @formatter:on } diff --git a/config/src/test/java/org/springframework/security/config/http/HttpConfigTests.java b/config/src/test/java/org/springframework/security/config/http/HttpConfigTests.java index 9a4e3b041e..62b4ab99dd 100644 --- a/config/src/test/java/org/springframework/security/config/http/HttpConfigTests.java +++ b/config/src/test/java/org/springframework/security/config/http/HttpConfigTests.java @@ -71,7 +71,7 @@ public void getWhenUsingMinimalConfigurationThenRedirectsToLogin() throws Except // @formatter:off this.mvc.perform(get("/")) .andExpect(status().isFound()) - .andExpect(redirectedUrl("http://localhost/login")); + .andExpect(redirectedUrl("/login")); // @formatter:on } @@ -81,7 +81,7 @@ public void getWhenUsingMinimalAuthorizationManagerThenRedirectsToLogin() throws // @formatter:off this.mvc.perform(get("/")) .andExpect(status().isFound()) - .andExpect(redirectedUrl("http://localhost/login")); + .andExpect(redirectedUrl("/login")); // @formatter:on } @@ -95,7 +95,7 @@ public void getWhenUsingAuthorizationManagerThenRedirectsToLogin() throws Except // @formatter:off this.mvc.perform(get("/")) .andExpect(status().isFound()) - .andExpect(redirectedUrl("http://localhost/login")); + .andExpect(redirectedUrl("/login")); // @formatter:on verify(authorizationManager).check(any(), any()); } @@ -109,7 +109,7 @@ public void getWhenUsingMinimalConfigurationThenPreventsSessionAsUrlParameter() proxy.doFilter(request, new EncodeUrlDenyingHttpServletResponseWrapper(response), (req, resp) -> { }); assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_MOVED_TEMPORARILY); - assertThat(response.getRedirectedUrl()).isEqualTo("http://localhost/login"); + assertThat(response.getRedirectedUrl()).isEqualTo("/login"); } @Test diff --git a/config/src/test/java/org/springframework/security/config/http/MiscHttpConfigTests.java b/config/src/test/java/org/springframework/security/config/http/MiscHttpConfigTests.java index 180bd2ec53..b08a3c868a 100644 --- a/config/src/test/java/org/springframework/security/config/http/MiscHttpConfigTests.java +++ b/config/src/test/java/org/springframework/security/config/http/MiscHttpConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2024 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. @@ -573,7 +573,7 @@ public void configureWhenUsingDisableUrlRewritingThenRedirectIsNotEncodedByRespo proxy.doFilter(request, new EncodeUrlDenyingHttpServletResponseWrapper(response), (req, resp) -> { }); assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_MOVED_TEMPORARILY); - assertThat(response.getRedirectedUrl()).isEqualTo("http://localhost/login"); + assertThat(response.getRedirectedUrl()).isEqualTo("/login"); } @Test @@ -802,7 +802,7 @@ public void authenticateWhenUsingPortMapperThenRedirectsAppropriately() throws E this.spring.configLocations(xml("PortsMappedRequiresHttps")).autowire(); // @formatter:off MockHttpSession session = (MockHttpSession) this.mvc.perform(get("https://localhost:9080/protected")) - .andExpect(redirectedUrl("https://localhost:9443/login")) + .andExpect(redirectedUrl("/login")) .andReturn() .getRequest() .getSession(false); diff --git a/config/src/test/java/org/springframework/security/config/http/OAuth2LoginBeanDefinitionParserTests.java b/config/src/test/java/org/springframework/security/config/http/OAuth2LoginBeanDefinitionParserTests.java index b632f834de..6c1e24dcee 100644 --- a/config/src/test/java/org/springframework/security/config/http/OAuth2LoginBeanDefinitionParserTests.java +++ b/config/src/test/java/org/springframework/security/config/http/OAuth2LoginBeanDefinitionParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2024 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. @@ -174,7 +174,7 @@ public void requestWhenSingleClientRegistrationThenAutoRedirect() throws Excepti // @formatter:off this.mvc.perform(get("/")) .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("http://localhost/oauth2/authorization/google-login")); + .andExpect(redirectedUrl("/oauth2/authorization/google-login")); // @formatter:on verify(this.requestCache).saveRequest(any(), any()); } @@ -187,7 +187,7 @@ public void requestWhenSingleClientRegistrationAndRequestFaviconNotAuthenticated // @formatter:off this.mvc.perform(get("/favicon.ico").accept(new MediaType("image", "*"))) .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("http://localhost/login")); + .andExpect(redirectedUrl("/login")); // @formatter:on } @@ -199,7 +199,7 @@ public void requestWhenSingleClientRegistrationAndRequestXHRNotAuthenticatedThen // @formatter:off this.mvc.perform(get("/").header("X-Requested-With", "XMLHttpRequest")) .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("http://localhost/login")); + .andExpect(redirectedUrl("/login")); // @formatter:on } @@ -411,7 +411,7 @@ public void requestWhenMultiClientRegistrationThenRedirectDefaultLoginPage() thr // @formatter:off this.mvc.perform(get("/")) .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("http://localhost/login")); + .andExpect(redirectedUrl("/login")); // @formatter:on } @@ -421,7 +421,7 @@ public void requestWhenCustomLoginPageThenRedirectCustomLoginPage() throws Excep // @formatter:off this.mvc.perform(get("/")) .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("http://localhost/custom-login")); + .andExpect(redirectedUrl("/custom-login")); // @formatter:on } @@ -433,7 +433,7 @@ public void requestWhenSingleClientRegistrationAndFormLoginConfiguredThenRedirec // @formatter:off this.mvc.perform(get("/")) .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("http://localhost/login")); + .andExpect(redirectedUrl("/login")); // @formatter:on } diff --git a/config/src/test/java/org/springframework/security/config/http/PlaceHolderAndELConfigTests.java b/config/src/test/java/org/springframework/security/config/http/PlaceHolderAndELConfigTests.java index ffe686efc1..0c1710f98b 100644 --- a/config/src/test/java/org/springframework/security/config/http/PlaceHolderAndELConfigTests.java +++ b/config/src/test/java/org/springframework/security/config/http/PlaceHolderAndELConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2024 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. @@ -73,7 +73,7 @@ public void loginWhenUsingPlaceholderThenInterceptUrlsAndFormLoginWorks() throws // login-page setting // @formatter:off this.mvc.perform(get("/secured")) - .andExpect(redirectedUrl("http://localhost/loginPage")); + .andExpect(redirectedUrl("/loginPage")); // login-processing-url setting // default-target-url setting this.mvc.perform(post("/loginPage").param("username", "user").param("password", "password")) @@ -98,7 +98,7 @@ public void loginWhenUsingSpELThenInterceptUrlsAndFormLoginWorks() throws Except // login-page setting // @formatter:off this.mvc.perform(get("/secured")) - .andExpect(redirectedUrl("http://localhost/loginPage")); + .andExpect(redirectedUrl("/loginPage")); // login-processing-url setting // default-target-url setting this.mvc.perform(post("/loginPage").param("username", "user").param("password", "password")) diff --git a/config/src/test/java/org/springframework/security/config/http/Saml2LoginBeanDefinitionParserTests.java b/config/src/test/java/org/springframework/security/config/http/Saml2LoginBeanDefinitionParserTests.java index 9eb168233a..fad02a9ae0 100644 --- a/config/src/test/java/org/springframework/security/config/http/Saml2LoginBeanDefinitionParserTests.java +++ b/config/src/test/java/org/springframework/security/config/http/Saml2LoginBeanDefinitionParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2024 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. @@ -161,7 +161,7 @@ public void requestWhenSingleRelyingPartyRegistrationThenAutoRedirect() throws E // @formatter:off this.mvc.perform(get("/")) .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("http://localhost/saml2/authenticate/one")); + .andExpect(redirectedUrl("/saml2/authenticate/one")); // @formatter:on verify(this.requestCache).saveRequest(any(), any()); } @@ -172,7 +172,7 @@ public void requestWhenMultiRelyingPartyRegistrationThenRedirectToLoginWithRelyi // @formatter:off this.mvc.perform(get("/")) .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("http://localhost/login")); + .andExpect(redirectedUrl("/login")); // @formatter:on } diff --git a/config/src/test/java/org/springframework/security/config/http/SecurityContextHolderAwareRequestConfigTests.java b/config/src/test/java/org/springframework/security/config/http/SecurityContextHolderAwareRequestConfigTests.java index 713a03b846..5a7d0a9038 100644 --- a/config/src/test/java/org/springframework/security/config/http/SecurityContextHolderAwareRequestConfigTests.java +++ b/config/src/test/java/org/springframework/security/config/http/SecurityContextHolderAwareRequestConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2024 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. @@ -78,7 +78,7 @@ public void servletAuthenticateWhenUsingDefaultConfigurationThenUsesSpringSecuri // @formatter:off this.mvc.perform(get("/authenticate")) .andExpect(status().isFound()) - .andExpect(redirectedUrl("http://localhost/login")); + .andExpect(redirectedUrl("/login")); // @formatter:on } @@ -114,7 +114,7 @@ public void servletAuthenticateWhenUsingFormLoginThenUsesSpringSecurity() throws // @formatter:off this.mvc.perform(get("/authenticate")) .andExpect(status().isFound()) - .andExpect(redirectedUrl("http://localhost/login")); + .andExpect(redirectedUrl("/login")); // @formatter:on } @@ -137,10 +137,10 @@ public void servletAuthenticateWhenUsingMultipleHttpConfigsThenUsesSpringSecurit // @formatter:off this.mvc.perform(get("/authenticate")) .andExpect(status().isFound()) - .andExpect(redirectedUrl("http://localhost/login")); + .andExpect(redirectedUrl("/login")); this.mvc.perform(get("/v2/authenticate")) .andExpect(status().isFound()) - .andExpect(redirectedUrl("http://localhost/login2")); + .andExpect(redirectedUrl("/login2")); // @formatter:on } @@ -177,10 +177,10 @@ public void servletLogoutWhenUsingMultipleHttpConfigsThenUsesSpringSecurity() th @Test public void servletLogoutWhenUsingCustomLogoutThenUsesSpringSecurity() throws Exception { this.spring.configLocations(this.xml("Logout")).autowire(); - this.mvc.perform(get("/authenticate")) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("http://localhost/signin")); // @formatter:off + this.mvc.perform(get("/authenticate")) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("/signin")); MvcResult result = this.mvc.perform(get("/good-login")) .andReturn(); // @formatter:on diff --git a/config/src/test/kotlin/org/springframework/security/config/annotation/web/ExceptionHandlingDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/annotation/web/ExceptionHandlingDslTests.kt index ed3e409cff..2aea44c6ff 100644 --- a/config/src/test/kotlin/org/springframework/security/config/annotation/web/ExceptionHandlingDslTests.kt +++ b/config/src/test/kotlin/org/springframework/security/config/annotation/web/ExceptionHandlingDslTests.kt @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2024 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. @@ -217,7 +217,7 @@ class ExceptionHandlingDslTests { this.mockMvc.get("/") .andExpect { status { isFound() } - redirectedUrl("http://localhost/custom-login") + redirectedUrl("/custom-login") } } @@ -246,13 +246,13 @@ class ExceptionHandlingDslTests { this.mockMvc.get("/secured1") .andExpect { status { isFound() } - redirectedUrl("http://localhost/custom-login1") + redirectedUrl("/custom-login1") } this.mockMvc.get("/secured2") .andExpect { status { isFound() } - redirectedUrl("http://localhost/custom-login2") + redirectedUrl("/custom-login2") } } diff --git a/config/src/test/kotlin/org/springframework/security/config/annotation/web/FormLoginDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/annotation/web/FormLoginDslTests.kt index 965c361b4a..5b00105247 100644 --- a/config/src/test/kotlin/org/springframework/security/config/annotation/web/FormLoginDslTests.kt +++ b/config/src/test/kotlin/org/springframework/security/config/annotation/web/FormLoginDslTests.kt @@ -143,7 +143,7 @@ class FormLoginDslTests { this.mockMvc.get("/") .andExpect { status { isFound() } - redirectedUrl("http://localhost/login") + redirectedUrl("/login") } } @@ -169,7 +169,7 @@ class FormLoginDslTests { this.mockMvc.get("/") .andExpect { status { isFound() } - redirectedUrl("http://localhost/log-in") + redirectedUrl("/log-in") } } diff --git a/config/src/test/kotlin/org/springframework/security/config/annotation/web/RememberMeDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/annotation/web/RememberMeDslTests.kt index b73b41f50d..a3a07cc010 100644 --- a/config/src/test/kotlin/org/springframework/security/config/annotation/web/RememberMeDslTests.kt +++ b/config/src/test/kotlin/org/springframework/security/config/annotation/web/RememberMeDslTests.kt @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2024 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. @@ -153,7 +153,7 @@ internal class RememberMeDslTests { cookie(expiredRememberMeCookie) }.andExpect { status { isFound() } - redirectedUrl("http://localhost/login") + redirectedUrl("/login") } } @@ -229,7 +229,7 @@ internal class RememberMeDslTests { cookie(withoutKeyRememberMeCookie) }.andExpect { status { isFound() } - redirectedUrl("http://localhost/login") + redirectedUrl("/login") } val keyMvcResult = mockMvc.post("/login") { loginRememberMeRequest() diff --git a/web/src/main/java/org/springframework/security/web/authentication/LoginUrlAuthenticationEntryPoint.java b/web/src/main/java/org/springframework/security/web/authentication/LoginUrlAuthenticationEntryPoint.java index 3bf2c6f0db..60cf4eef4c 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/LoginUrlAuthenticationEntryPoint.java +++ b/web/src/main/java/org/springframework/security/web/authentication/LoginUrlAuthenticationEntryPoint.java @@ -61,6 +61,7 @@ * @author colin sampaleanu * @author Omri Spector * @author Luke Taylor + * @author Michal Okosy * @since 3.0 */ public class LoginUrlAuthenticationEntryPoint implements AuthenticationEntryPoint, InitializingBean { @@ -143,29 +144,22 @@ public void commence(HttpServletRequest request, HttpServletResponse response, protected String buildRedirectUrlToLoginPage(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) { String loginForm = determineUrlToUseForThisRequest(request, response, authException); - if (UrlUtils.isAbsoluteUrl(loginForm)) { + if (UrlUtils.isAbsoluteUrl(loginForm) || !this.forceHttps || "https".equals(request.getScheme())) { return loginForm; } int serverPort = this.portResolver.getServerPort(request); - String scheme = request.getScheme(); + Integer httpsPort = this.portMapper.lookupHttpsPort(serverPort); + if (httpsPort == null) { + logger.warn(LogMessage.format("Unable to redirect to HTTPS as no port mapping found for HTTP port %s", + serverPort)); + return loginForm; + } RedirectUrlBuilder urlBuilder = new RedirectUrlBuilder(); - urlBuilder.setScheme(scheme); + urlBuilder.setScheme("https"); urlBuilder.setServerName(request.getServerName()); - urlBuilder.setPort(serverPort); + urlBuilder.setPort(httpsPort); urlBuilder.setContextPath(request.getContextPath()); urlBuilder.setPathInfo(loginForm); - if (this.forceHttps && "http".equals(scheme)) { - Integer httpsPort = this.portMapper.lookupHttpsPort(serverPort); - if (httpsPort != null) { - // Overwrite scheme and port in the redirect URL - urlBuilder.setScheme("https"); - urlBuilder.setPort(httpsPort); - } - else { - logger.warn(LogMessage.format("Unable to redirect to HTTPS as no port mapping found for HTTP port %s", - serverPort)); - } - } return urlBuilder.getUrl(); } diff --git a/web/src/test/java/org/springframework/security/web/authentication/LoginUrlAuthenticationEntryPointTests.java b/web/src/test/java/org/springframework/security/web/authentication/LoginUrlAuthenticationEntryPointTests.java index 77b49be1a1..ad699da8ea 100644 --- a/web/src/test/java/org/springframework/security/web/authentication/LoginUrlAuthenticationEntryPointTests.java +++ b/web/src/test/java/org/springframework/security/web/authentication/LoginUrlAuthenticationEntryPointTests.java @@ -129,12 +129,18 @@ public void testHttpsOperationFromOriginalHttpsUrl() throws Exception { ep.setPortResolver(new MockPortResolver(80, 443)); ep.afterPropertiesSet(); ep.commence(request, response, null); - assertThat(response.getRedirectedUrl()).isEqualTo("https://www.example.com/bigWebApp/hello"); + assertThat(response.getRedirectedUrl()).isEqualTo("/bigWebApp/hello"); request.setServerPort(8443); response = new MockHttpServletResponse(); ep.setPortResolver(new MockPortResolver(8080, 8443)); ep.commence(request, response, null); - assertThat(response.getRedirectedUrl()).isEqualTo("https://www.example.com:8443/bigWebApp/hello"); + assertThat(response.getRedirectedUrl()).isEqualTo("/bigWebApp/hello"); + // access to https via http port + request.setServerPort(8080); + response = new MockHttpServletResponse(); + ep.setPortResolver(new MockPortResolver(8080, 8443)); + ep.commence(request, response, null); + assertThat(response.getRedirectedUrl()).isEqualTo("/bigWebApp/hello"); } @Test @@ -152,7 +158,7 @@ public void testNormalOperation() throws Exception { request.setServerPort(80); MockHttpServletResponse response = new MockHttpServletResponse(); ep.commence(request, response, null); - assertThat(response.getRedirectedUrl()).isEqualTo("http://localhost/bigWebApp/hello"); + assertThat(response.getRedirectedUrl()).isEqualTo("/bigWebApp/hello"); } @Test @@ -172,7 +178,7 @@ public void testOperationWhenHttpsRequestsButHttpsPortUnknown() throws Exception ep.commence(request, response, null); // Response doesn't switch to HTTPS, as we didn't know HTTP port 8888 to HTTP port // mapping - assertThat(response.getRedirectedUrl()).isEqualTo("http://localhost:8888/bigWebApp/hello"); + assertThat(response.getRedirectedUrl()).isEqualTo("/bigWebApp/hello"); } @Test From 831ac130ead9ad6177b9830893ddd3574effda3b Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Tue, 17 Dec 2024 17:12:41 -0700 Subject: [PATCH 009/132] Add setFavorRelativeUris This places the new functionality behind a setting so that we can remain passive until we can change the setting in the next major release. Issue gh-7273 Signed-off-by: Daeho Kwon --- .../web/builders/NamespaceHttpTests.java | 6 +- .../DefaultLoginPageConfigurerTests.java | 4 +- .../ExceptionHandlingConfigurerTests.java | 5 +- .../configurers/FormLoginConfigurerTests.java | 8 +- .../NamespaceHttpFormLoginTests.java | 8 +- .../configurers/NamespaceRememberMeTests.java | 6 +- .../RememberMeConfigurerTests.java | 4 +- .../RequestCacheConfigurerTests.java | 20 ++-- .../client/OAuth2LoginConfigurerTests.java | 14 +-- .../OAuth2ResourceServerConfigurerTests.java | 4 +- .../saml2/Saml2LoginConfigurerTests.java | 7 +- .../security/config/http/CsrfConfigTests.java | 8 +- .../config/http/FormLoginConfigTests.java | 6 +- .../security/config/http/HttpConfigTests.java | 10 +- .../config/http/MiscHttpConfigTests.java | 6 +- .../OAuth2LoginBeanDefinitionParserTests.java | 14 +-- .../http/PlaceHolderAndELConfigTests.java | 6 +- .../Saml2LoginBeanDefinitionParserTests.java | 6 +- ...yContextHolderAwareRequestConfigTests.java | 16 +-- .../web/ExceptionHandlingDslTests.kt | 8 +- .../annotation/web/FormLoginDslTests.kt | 4 +- .../annotation/web/RememberMeDslTests.kt | 6 +- docs/modules/ROOT/pages/migration-7/web.adoc | 104 ++++++++++++++++++ .../LoginUrlAuthenticationEntryPoint.java | 46 +++++++- ...LoginUrlAuthenticationEntryPointTests.java | 60 +++++++++- 25 files changed, 288 insertions(+), 98 deletions(-) create mode 100644 docs/modules/ROOT/pages/migration-7/web.adoc diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/builders/NamespaceHttpTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/builders/NamespaceHttpTests.java index 7299e3ebc8..e24b8d8f10 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/builders/NamespaceHttpTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/builders/NamespaceHttpTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 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. @@ -77,7 +77,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrlPattern; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** @@ -167,7 +167,7 @@ public void configureWhenAuthenticationEntryPointSetAndRequestUnauthorizedThenRe // @formatter:off this.mockMvc.perform(get("/")) .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("/entry-point")); + .andExpect(redirectedUrlPattern("**/entry-point")); // @formatter:on } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/DefaultLoginPageConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/DefaultLoginPageConfigurerTests.java index b519769bdb..f4646fe6f5 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/DefaultLoginPageConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/DefaultLoginPageConfigurerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 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. @@ -82,7 +82,7 @@ public class DefaultLoginPageConfigurerTests { @Test public void getWhenFormLoginEnabledThenRedirectsToLoginPage() throws Exception { this.spring.register(DefaultLoginPageConfig.class).autowire(); - this.mvc.perform(get("/")).andExpect(redirectedUrl("/login")); + this.mvc.perform(get("/")).andExpect(redirectedUrl("http://localhost/login")); } @Test diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurerTests.java index cd57c7bd1b..d89526127e 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 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. @@ -213,7 +213,8 @@ public void getWhenCustomSecurityContextHolderStrategyThenUsed() throws Exceptio @Test public void getWhenUsingDefaultsAndUnauthenticatedThenRedirectsToLogin() throws Exception { this.spring.register(DefaultHttpConfig.class).autowire(); - this.mvc.perform(get("/").header(HttpHeaders.ACCEPT, "bogus/type")).andExpect(redirectedUrl("/login")); + this.mvc.perform(get("/").header(HttpHeaders.ACCEPT, "bogus/type")) + .andExpect(redirectedUrl("http://localhost/login")); } @Test diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.java index 663b67bcbf..49b8ed2a1a 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 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. @@ -162,7 +162,7 @@ public void requestProtectedWhenFormLoginConfiguredThenRedirectsToLogin() throws // @formatter:off this.mockMvc.perform(get("/private")) .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login")); + .andExpect(redirectedUrl("http://localhost/login")); // @formatter:on } @@ -217,7 +217,7 @@ public void requestProtectedWhenFormLoginDefaultsInLambdaThenRedirectsToLogin() // @formatter:off this.mockMvc.perform(get("/private")) .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login")); + .andExpect(redirectedUrl("http://localhost/login")); // @formatter:on } @@ -331,7 +331,7 @@ public void failureUrlWhenPermitAllAndFailureHandlerThenSecured() throws Excepti // @formatter:off this.mockMvc.perform(get("/login?error")) .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login")); + .andExpect(redirectedUrl("http://localhost/login")); // @formatter:on } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpFormLoginTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpFormLoginTests.java index 2d46951de1..c1bd55dbdb 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpFormLoginTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpFormLoginTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 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. @@ -65,7 +65,7 @@ public class NamespaceHttpFormLoginTests { @Test public void formLoginWhenDefaultConfigurationThenMatchesNamespace() throws Exception { this.spring.register(FormLoginConfig.class, UserDetailsServiceConfig.class).autowire(); - this.mvc.perform(get("/")).andExpect(redirectedUrl("/login")); + this.mvc.perform(get("/")).andExpect(redirectedUrl("http://localhost/login")); this.mvc.perform(post("/login").with(csrf())).andExpect(redirectedUrl("/login?error")); // @formatter:off MockHttpServletRequestBuilder loginRequest = post("/login") @@ -79,7 +79,7 @@ public void formLoginWhenDefaultConfigurationThenMatchesNamespace() throws Excep @Test public void formLoginWithCustomEndpointsThenBehaviorMatchesNamespace() throws Exception { this.spring.register(FormLoginCustomConfig.class, UserDetailsServiceConfig.class).autowire(); - this.mvc.perform(get("/")).andExpect(redirectedUrl("/authentication/login")); + this.mvc.perform(get("/")).andExpect(redirectedUrl("http://localhost/authentication/login")); this.mvc.perform(post("/authentication/login/process").with(csrf())) .andExpect(redirectedUrl("/authentication/login?failed")); // @formatter:off @@ -94,7 +94,7 @@ public void formLoginWithCustomEndpointsThenBehaviorMatchesNamespace() throws Ex @Test public void formLoginWithCustomHandlersThenBehaviorMatchesNamespace() throws Exception { this.spring.register(FormLoginCustomRefsConfig.class, UserDetailsServiceConfig.class).autowire(); - this.mvc.perform(get("/")).andExpect(redirectedUrl("/login")); + this.mvc.perform(get("/")).andExpect(redirectedUrl("http://localhost/login")); this.mvc.perform(post("/login").with(csrf())).andExpect(redirectedUrl("/custom/failure")); verifyBean(WebAuthenticationDetailsSource.class).buildDetails(any(HttpServletRequest.class)); // @formatter:off diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceRememberMeTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceRememberMeTests.java index 2c448484e5..c55f865db8 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceRememberMeTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceRememberMeTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 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. @@ -104,7 +104,7 @@ public void rememberMeLoginWhenUsingDefaultsThenMatchesNamespace() throws Except .with(csrf()) .cookie(rememberMe); this.mvc.perform(authenticationClassRequest) - .andExpect(redirectedUrl("/login")) + .andExpect(redirectedUrl("http://localhost/login")) .andReturn(); // @formatter:on } @@ -150,7 +150,7 @@ public void rememberMeLoginWhenKeyDeclaredThenMatchesNamespace() throws Exceptio // @formatter:off this.mvc.perform(somewhereRequest) .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login")); + .andExpect(redirectedUrl("http://localhost/login")); MockHttpServletRequestBuilder loginWithRememberme = post("/login").with(rememberMeLogin()); Cookie withKey = this.mvc.perform(loginWithRememberme) .andReturn() diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RememberMeConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RememberMeConfigurerTests.java index 9277158ba2..e3cb83f76f 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RememberMeConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RememberMeConfigurerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 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. @@ -240,7 +240,7 @@ public void getWhenRememberMeCookieAndLoggedOutThenRedirectsToLogin() throws Exc .with(csrf()) .cookie(expiredRememberMeCookie); // @formatter:on - this.mvc.perform(expiredRequest).andExpect(redirectedUrl("/login")); + this.mvc.perform(expiredRequest).andExpect(redirectedUrl("http://localhost/login")); } @Test diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurerTests.java index 09ada968b6..f22e55043d 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 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. @@ -90,7 +90,7 @@ public void getWhenBookmarkedUrlIsFaviconIcoThenPostAuthenticationRedirectsToRoo this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire(); // @formatter:off MockHttpSession session = (MockHttpSession) this.mvc.perform(get("/favicon.ico")) - .andExpect(redirectedUrl("/login")) + .andExpect(redirectedUrl("http://localhost/login")) .andReturn() .getRequest() .getSession(); @@ -104,7 +104,7 @@ public void getWhenBookmarkedUrlIsFaviconPngThenPostAuthenticationRedirectsToRoo this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire(); // @formatter:off MockHttpSession session = (MockHttpSession) this.mvc.perform(get("/favicon.png")) - .andExpect(redirectedUrl("/login")) + .andExpect(redirectedUrl("http://localhost/login")) .andReturn() .getRequest() .getSession(); @@ -120,7 +120,7 @@ public void getWhenBookmarkedRequestIsApplicationJsonThenPostAuthenticationRedir MockHttpServletRequestBuilder request = get("/messages").header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON); // @formatter:off MockHttpSession session = (MockHttpSession) this.mvc.perform(request) - .andExpect(redirectedUrl("/login")) + .andExpect(redirectedUrl("http://localhost/login")) .andReturn() .getRequest() .getSession(); @@ -140,7 +140,7 @@ public void getWhenBookmarkedRequestIsXRequestedWithThenPostAuthenticationRedire .header("X-Requested-With", "XMLHttpRequest"); MockHttpSession session = (MockHttpSession) this.mvc .perform(xRequestedWith) - .andExpect(redirectedUrl("/login")) + .andExpect(redirectedUrl("http://localhost/login")) .andReturn() .getRequest() .getSession(); @@ -157,7 +157,7 @@ public void getWhenBookmarkedRequestIsTextEventStreamThenPostAuthenticationRedir MediaType.TEXT_EVENT_STREAM); // @formatter:off MockHttpSession session = (MockHttpSession) this.mvc.perform(request) - .andExpect(redirectedUrl("/login")) + .andExpect(redirectedUrl("http://localhost/login")) .andReturn() .getRequest() .getSession(); @@ -174,7 +174,7 @@ public void getWhenBookmarkedRequestIsAllMediaTypeThenPostAuthenticationRemember MockHttpServletRequestBuilder request = get("/messages").header(HttpHeaders.ACCEPT, MediaType.ALL); // @formatter:off MockHttpSession session = (MockHttpSession) this.mvc.perform(request) - .andExpect(redirectedUrl("/login")) + .andExpect(redirectedUrl("http://localhost/login")) .andReturn() .getRequest() .getSession(); @@ -188,7 +188,7 @@ public void getWhenBookmarkedRequestIsTextHtmlThenPostAuthenticationRemembers() MockHttpServletRequestBuilder request = get("/messages").header(HttpHeaders.ACCEPT, MediaType.TEXT_HTML); // @formatter:off MockHttpSession session = (MockHttpSession) this.mvc.perform(request) - .andExpect(redirectedUrl("/login")) + .andExpect(redirectedUrl("http://localhost/login")) .andReturn() .getRequest() .getSession(); @@ -203,7 +203,7 @@ public void getWhenBookmarkedRequestIsChromeThenPostAuthenticationRemembers() th MockHttpServletRequestBuilder request = get("/messages") .header(HttpHeaders.ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"); MockHttpSession session = (MockHttpSession) this.mvc.perform(request) - .andExpect(redirectedUrl("/login")) + .andExpect(redirectedUrl("http://localhost/login")) .andReturn() .getRequest() .getSession(); @@ -218,7 +218,7 @@ public void getWhenBookmarkedRequestIsRequestedWithAndroidThenPostAuthentication MockHttpServletRequestBuilder request = get("/messages") .header("X-Requested-With", "com.android"); MockHttpSession session = (MockHttpSession) this.mvc.perform(request) - .andExpect(redirectedUrl("/login")) + .andExpect(redirectedUrl("http://localhost/login")) .andReturn() .getRequest() .getSession(); diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurerTests.java index 65a56cdb7a..b56d047a5f 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurerTests.java @@ -437,7 +437,7 @@ public void oauth2LoginWithOneClientConfiguredThenRedirectForAuthorization() thr this.request = new MockHttpServletRequest("GET", requestUri); this.request.setServletPath(requestUri); this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); - assertThat(this.response.getRedirectedUrl()).matches("/oauth2/authorization/google"); + assertThat(this.response.getRedirectedUrl()).matches("http://localhost/oauth2/authorization/google"); } // gh-6802 @@ -448,7 +448,7 @@ public void oauth2LoginWithOneClientConfiguredAndFormLoginThenRedirectDefaultLog this.request = new MockHttpServletRequest("GET", requestUri); this.request.setServletPath(requestUri); this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); - assertThat(this.response.getRedirectedUrl()).matches("/login"); + assertThat(this.response.getRedirectedUrl()).matches("http://localhost/login"); } // gh-5347 @@ -461,7 +461,7 @@ public void oauth2LoginWithOneClientConfiguredAndRequestFaviconNotAuthenticatedT this.request.setServletPath(requestUri); this.request.addHeader(HttpHeaders.ACCEPT, new MediaType("image", "*").toString()); this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); - assertThat(this.response.getRedirectedUrl()).matches("/login"); + assertThat(this.response.getRedirectedUrl()).matches("http://localhost/login"); } // gh-5347 @@ -472,7 +472,7 @@ public void oauth2LoginWithMultipleClientsConfiguredThenRedirectDefaultLoginPage this.request = new MockHttpServletRequest("GET", requestUri); this.request.setServletPath(requestUri); this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); - assertThat(this.response.getRedirectedUrl()).matches("/login"); + assertThat(this.response.getRedirectedUrl()).matches("http://localhost/login"); } // gh-6812 @@ -521,7 +521,7 @@ public void oauth2LoginWithOneAuthorizationCodeClientAndOtherClientsConfiguredTh this.request = new MockHttpServletRequest("GET", requestUri); this.request.setServletPath(requestUri); this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); - assertThat(this.response.getRedirectedUrl()).matches("/oauth2/authorization/google"); + assertThat(this.response.getRedirectedUrl()).matches("http://localhost/oauth2/authorization/google"); } @Test @@ -531,7 +531,7 @@ public void oauth2LoginWithCustomLoginPageThenRedirectCustomLoginPage() throws E this.request = new MockHttpServletRequest("GET", requestUri); this.request.setServletPath(requestUri); this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); - assertThat(this.response.getRedirectedUrl()).matches("/custom-login"); + assertThat(this.response.getRedirectedUrl()).matches("http://localhost/custom-login"); } @Test @@ -541,7 +541,7 @@ public void requestWhenOauth2LoginWithCustomLoginPageInLambdaThenRedirectCustomL this.request = new MockHttpServletRequest("GET", requestUri); this.request.setServletPath(requestUri); this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); - assertThat(this.response.getRedirectedUrl()).matches("/custom-login"); + assertThat(this.response.getRedirectedUrl()).matches("http://localhost/custom-login"); } @Test diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java index 2dad6916fe..c247a6d7fe 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 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. @@ -1210,7 +1210,7 @@ public void requestWhenFormLoginAndResourceServerEntryPointsThenSessionCreatedBy MvcResult result = this.mvc.perform(get("/authenticated") .header("Accept", "text/html")) .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login")) + .andExpect(redirectedUrl("http://localhost/login")) .andReturn(); // @formatter:on assertThat(result.getRequest().getSession(false)).isNotNull(); diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurerTests.java index 3a91207290..6d874a583d 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 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. @@ -412,11 +412,10 @@ public void getFaviconWhenDefaultConfigurationThenDoesNotSaveAuthnRequest() thro this.spring.register(Saml2LoginConfig.class).autowire(); this.mvc.perform(get("/favicon.ico").accept(MediaType.TEXT_HTML)) .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login")); + .andExpect(redirectedUrl("http://localhost/login")); this.mvc.perform(get("/").accept(MediaType.TEXT_HTML)) .andExpect(status().isFound()) - .andExpect(header().string("Location", startsWith("/saml2/authenticate"))); - + .andExpect(header().string("Location", startsWith("http://localhost/saml2/authenticate"))); } @Test diff --git a/config/src/test/java/org/springframework/security/config/http/CsrfConfigTests.java b/config/src/test/java/org/springframework/security/config/http/CsrfConfigTests.java index 781cce82e9..901945e73a 100644 --- a/config/src/test/java/org/springframework/security/config/http/CsrfConfigTests.java +++ b/config/src/test/java/org/springframework/security/config/http/CsrfConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 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. @@ -342,7 +342,7 @@ public void postWhenHasCsrfTokenButSessionExpiresThenRequestIsCancelledAfterSucc this.spring.configLocations(this.xml("CsrfEnabled")).autowire(); // simulates a request that has no authentication (e.g. session time-out) MvcResult result = this.mvc.perform(post("/authenticated").with(csrf())) - .andExpect(redirectedUrl("/login")) + .andExpect(redirectedUrl("http://localhost/login")) .andReturn(); MockHttpSession session = (MockHttpSession) result.getRequest().getSession(); // if the request cache is consulted, then it will redirect back to /some-url, @@ -363,7 +363,9 @@ public void getWhenHasCsrfTokenButSessionExpiresThenRequestIsRememeberedAfterSuc throws Exception { this.spring.configLocations(this.xml("CsrfEnabled")).autowire(); // simulates a request that has no authentication (e.g. session time-out) - MvcResult result = this.mvc.perform(get("/authenticated")).andExpect(redirectedUrl("/login")).andReturn(); + MvcResult result = this.mvc.perform(get("/authenticated")) + .andExpect(redirectedUrl("http://localhost/login")) + .andReturn(); MockHttpSession session = (MockHttpSession) result.getRequest().getSession(); // if the request cache is consulted, then it will redirect back to /some-url, // which we do want diff --git a/config/src/test/java/org/springframework/security/config/http/FormLoginConfigTests.java b/config/src/test/java/org/springframework/security/config/http/FormLoginConfigTests.java index b054762aac..52237273df 100644 --- a/config/src/test/java/org/springframework/security/config/http/FormLoginConfigTests.java +++ b/config/src/test/java/org/springframework/security/config/http/FormLoginConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -73,7 +73,7 @@ public void getProtectedPageWhenFormLoginConfiguredThenRedirectsToDefaultLoginPa this.spring.configLocations(this.xml("WithAntRequestMatcher")).autowire(); // @formatter:off this.mvc.perform(get("/")) - .andExpect(redirectedUrl("/login")); + .andExpect(redirectedUrl("http://localhost/login")); // @formatter:on } @@ -107,7 +107,7 @@ public void authenticateWhenConfiguredWithSpelThenRedirectsAccordingly() throws this.mvc.perform(invalidPassword) .andExpect(redirectedUrl(WebConfigUtilsTests.URL + "/failure")); this.mvc.perform(get("/")) - .andExpect(redirectedUrl(WebConfigUtilsTests.URL + "/login")); + .andExpect(redirectedUrl("http://localhost" + WebConfigUtilsTests.URL + "/login")); // @formatter:on } diff --git a/config/src/test/java/org/springframework/security/config/http/HttpConfigTests.java b/config/src/test/java/org/springframework/security/config/http/HttpConfigTests.java index 62b4ab99dd..c7f0590bc1 100644 --- a/config/src/test/java/org/springframework/security/config/http/HttpConfigTests.java +++ b/config/src/test/java/org/springframework/security/config/http/HttpConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 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. @@ -71,7 +71,7 @@ public void getWhenUsingMinimalConfigurationThenRedirectsToLogin() throws Except // @formatter:off this.mvc.perform(get("/")) .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login")); + .andExpect(redirectedUrl("http://localhost/login")); // @formatter:on } @@ -81,7 +81,7 @@ public void getWhenUsingMinimalAuthorizationManagerThenRedirectsToLogin() throws // @formatter:off this.mvc.perform(get("/")) .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login")); + .andExpect(redirectedUrl("http://localhost/login")); // @formatter:on } @@ -95,7 +95,7 @@ public void getWhenUsingAuthorizationManagerThenRedirectsToLogin() throws Except // @formatter:off this.mvc.perform(get("/")) .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login")); + .andExpect(redirectedUrl("http://localhost/login")); // @formatter:on verify(authorizationManager).check(any(), any()); } @@ -109,7 +109,7 @@ public void getWhenUsingMinimalConfigurationThenPreventsSessionAsUrlParameter() proxy.doFilter(request, new EncodeUrlDenyingHttpServletResponseWrapper(response), (req, resp) -> { }); assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_MOVED_TEMPORARILY); - assertThat(response.getRedirectedUrl()).isEqualTo("/login"); + assertThat(response.getRedirectedUrl()).isEqualTo("http://localhost/login"); } @Test diff --git a/config/src/test/java/org/springframework/security/config/http/MiscHttpConfigTests.java b/config/src/test/java/org/springframework/security/config/http/MiscHttpConfigTests.java index b08a3c868a..180bd2ec53 100644 --- a/config/src/test/java/org/springframework/security/config/http/MiscHttpConfigTests.java +++ b/config/src/test/java/org/springframework/security/config/http/MiscHttpConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 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. @@ -573,7 +573,7 @@ public void configureWhenUsingDisableUrlRewritingThenRedirectIsNotEncodedByRespo proxy.doFilter(request, new EncodeUrlDenyingHttpServletResponseWrapper(response), (req, resp) -> { }); assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_MOVED_TEMPORARILY); - assertThat(response.getRedirectedUrl()).isEqualTo("/login"); + assertThat(response.getRedirectedUrl()).isEqualTo("http://localhost/login"); } @Test @@ -802,7 +802,7 @@ public void authenticateWhenUsingPortMapperThenRedirectsAppropriately() throws E this.spring.configLocations(xml("PortsMappedRequiresHttps")).autowire(); // @formatter:off MockHttpSession session = (MockHttpSession) this.mvc.perform(get("https://localhost:9080/protected")) - .andExpect(redirectedUrl("/login")) + .andExpect(redirectedUrl("https://localhost:9443/login")) .andReturn() .getRequest() .getSession(false); diff --git a/config/src/test/java/org/springframework/security/config/http/OAuth2LoginBeanDefinitionParserTests.java b/config/src/test/java/org/springframework/security/config/http/OAuth2LoginBeanDefinitionParserTests.java index 6c1e24dcee..b632f834de 100644 --- a/config/src/test/java/org/springframework/security/config/http/OAuth2LoginBeanDefinitionParserTests.java +++ b/config/src/test/java/org/springframework/security/config/http/OAuth2LoginBeanDefinitionParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 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. @@ -174,7 +174,7 @@ public void requestWhenSingleClientRegistrationThenAutoRedirect() throws Excepti // @formatter:off this.mvc.perform(get("/")) .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("/oauth2/authorization/google-login")); + .andExpect(redirectedUrl("http://localhost/oauth2/authorization/google-login")); // @formatter:on verify(this.requestCache).saveRequest(any(), any()); } @@ -187,7 +187,7 @@ public void requestWhenSingleClientRegistrationAndRequestFaviconNotAuthenticated // @formatter:off this.mvc.perform(get("/favicon.ico").accept(new MediaType("image", "*"))) .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("/login")); + .andExpect(redirectedUrl("http://localhost/login")); // @formatter:on } @@ -199,7 +199,7 @@ public void requestWhenSingleClientRegistrationAndRequestXHRNotAuthenticatedThen // @formatter:off this.mvc.perform(get("/").header("X-Requested-With", "XMLHttpRequest")) .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("/login")); + .andExpect(redirectedUrl("http://localhost/login")); // @formatter:on } @@ -411,7 +411,7 @@ public void requestWhenMultiClientRegistrationThenRedirectDefaultLoginPage() thr // @formatter:off this.mvc.perform(get("/")) .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("/login")); + .andExpect(redirectedUrl("http://localhost/login")); // @formatter:on } @@ -421,7 +421,7 @@ public void requestWhenCustomLoginPageThenRedirectCustomLoginPage() throws Excep // @formatter:off this.mvc.perform(get("/")) .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("/custom-login")); + .andExpect(redirectedUrl("http://localhost/custom-login")); // @formatter:on } @@ -433,7 +433,7 @@ public void requestWhenSingleClientRegistrationAndFormLoginConfiguredThenRedirec // @formatter:off this.mvc.perform(get("/")) .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("/login")); + .andExpect(redirectedUrl("http://localhost/login")); // @formatter:on } diff --git a/config/src/test/java/org/springframework/security/config/http/PlaceHolderAndELConfigTests.java b/config/src/test/java/org/springframework/security/config/http/PlaceHolderAndELConfigTests.java index 0c1710f98b..ffe686efc1 100644 --- a/config/src/test/java/org/springframework/security/config/http/PlaceHolderAndELConfigTests.java +++ b/config/src/test/java/org/springframework/security/config/http/PlaceHolderAndELConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -73,7 +73,7 @@ public void loginWhenUsingPlaceholderThenInterceptUrlsAndFormLoginWorks() throws // login-page setting // @formatter:off this.mvc.perform(get("/secured")) - .andExpect(redirectedUrl("/loginPage")); + .andExpect(redirectedUrl("http://localhost/loginPage")); // login-processing-url setting // default-target-url setting this.mvc.perform(post("/loginPage").param("username", "user").param("password", "password")) @@ -98,7 +98,7 @@ public void loginWhenUsingSpELThenInterceptUrlsAndFormLoginWorks() throws Except // login-page setting // @formatter:off this.mvc.perform(get("/secured")) - .andExpect(redirectedUrl("/loginPage")); + .andExpect(redirectedUrl("http://localhost/loginPage")); // login-processing-url setting // default-target-url setting this.mvc.perform(post("/loginPage").param("username", "user").param("password", "password")) diff --git a/config/src/test/java/org/springframework/security/config/http/Saml2LoginBeanDefinitionParserTests.java b/config/src/test/java/org/springframework/security/config/http/Saml2LoginBeanDefinitionParserTests.java index fad02a9ae0..9eb168233a 100644 --- a/config/src/test/java/org/springframework/security/config/http/Saml2LoginBeanDefinitionParserTests.java +++ b/config/src/test/java/org/springframework/security/config/http/Saml2LoginBeanDefinitionParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 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. @@ -161,7 +161,7 @@ public void requestWhenSingleRelyingPartyRegistrationThenAutoRedirect() throws E // @formatter:off this.mvc.perform(get("/")) .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("/saml2/authenticate/one")); + .andExpect(redirectedUrl("http://localhost/saml2/authenticate/one")); // @formatter:on verify(this.requestCache).saveRequest(any(), any()); } @@ -172,7 +172,7 @@ public void requestWhenMultiRelyingPartyRegistrationThenRedirectToLoginWithRelyi // @formatter:off this.mvc.perform(get("/")) .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("/login")); + .andExpect(redirectedUrl("http://localhost/login")); // @formatter:on } diff --git a/config/src/test/java/org/springframework/security/config/http/SecurityContextHolderAwareRequestConfigTests.java b/config/src/test/java/org/springframework/security/config/http/SecurityContextHolderAwareRequestConfigTests.java index 5a7d0a9038..713a03b846 100644 --- a/config/src/test/java/org/springframework/security/config/http/SecurityContextHolderAwareRequestConfigTests.java +++ b/config/src/test/java/org/springframework/security/config/http/SecurityContextHolderAwareRequestConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -78,7 +78,7 @@ public void servletAuthenticateWhenUsingDefaultConfigurationThenUsesSpringSecuri // @formatter:off this.mvc.perform(get("/authenticate")) .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login")); + .andExpect(redirectedUrl("http://localhost/login")); // @formatter:on } @@ -114,7 +114,7 @@ public void servletAuthenticateWhenUsingFormLoginThenUsesSpringSecurity() throws // @formatter:off this.mvc.perform(get("/authenticate")) .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login")); + .andExpect(redirectedUrl("http://localhost/login")); // @formatter:on } @@ -137,10 +137,10 @@ public void servletAuthenticateWhenUsingMultipleHttpConfigsThenUsesSpringSecurit // @formatter:off this.mvc.perform(get("/authenticate")) .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login")); + .andExpect(redirectedUrl("http://localhost/login")); this.mvc.perform(get("/v2/authenticate")) .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login2")); + .andExpect(redirectedUrl("http://localhost/login2")); // @formatter:on } @@ -177,10 +177,10 @@ public void servletLogoutWhenUsingMultipleHttpConfigsThenUsesSpringSecurity() th @Test public void servletLogoutWhenUsingCustomLogoutThenUsesSpringSecurity() throws Exception { this.spring.configLocations(this.xml("Logout")).autowire(); - // @formatter:off this.mvc.perform(get("/authenticate")) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/signin")); + .andExpect(status().isFound()) + .andExpect(redirectedUrl("http://localhost/signin")); + // @formatter:off MvcResult result = this.mvc.perform(get("/good-login")) .andReturn(); // @formatter:on diff --git a/config/src/test/kotlin/org/springframework/security/config/annotation/web/ExceptionHandlingDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/annotation/web/ExceptionHandlingDslTests.kt index 2aea44c6ff..ed3e409cff 100644 --- a/config/src/test/kotlin/org/springframework/security/config/annotation/web/ExceptionHandlingDslTests.kt +++ b/config/src/test/kotlin/org/springframework/security/config/annotation/web/ExceptionHandlingDslTests.kt @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 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. @@ -217,7 +217,7 @@ class ExceptionHandlingDslTests { this.mockMvc.get("/") .andExpect { status { isFound() } - redirectedUrl("/custom-login") + redirectedUrl("http://localhost/custom-login") } } @@ -246,13 +246,13 @@ class ExceptionHandlingDslTests { this.mockMvc.get("/secured1") .andExpect { status { isFound() } - redirectedUrl("/custom-login1") + redirectedUrl("http://localhost/custom-login1") } this.mockMvc.get("/secured2") .andExpect { status { isFound() } - redirectedUrl("/custom-login2") + redirectedUrl("http://localhost/custom-login2") } } diff --git a/config/src/test/kotlin/org/springframework/security/config/annotation/web/FormLoginDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/annotation/web/FormLoginDslTests.kt index 5b00105247..965c361b4a 100644 --- a/config/src/test/kotlin/org/springframework/security/config/annotation/web/FormLoginDslTests.kt +++ b/config/src/test/kotlin/org/springframework/security/config/annotation/web/FormLoginDslTests.kt @@ -143,7 +143,7 @@ class FormLoginDslTests { this.mockMvc.get("/") .andExpect { status { isFound() } - redirectedUrl("/login") + redirectedUrl("http://localhost/login") } } @@ -169,7 +169,7 @@ class FormLoginDslTests { this.mockMvc.get("/") .andExpect { status { isFound() } - redirectedUrl("/log-in") + redirectedUrl("http://localhost/log-in") } } diff --git a/config/src/test/kotlin/org/springframework/security/config/annotation/web/RememberMeDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/annotation/web/RememberMeDslTests.kt index a3a07cc010..b73b41f50d 100644 --- a/config/src/test/kotlin/org/springframework/security/config/annotation/web/RememberMeDslTests.kt +++ b/config/src/test/kotlin/org/springframework/security/config/annotation/web/RememberMeDslTests.kt @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 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. @@ -153,7 +153,7 @@ internal class RememberMeDslTests { cookie(expiredRememberMeCookie) }.andExpect { status { isFound() } - redirectedUrl("/login") + redirectedUrl("http://localhost/login") } } @@ -229,7 +229,7 @@ internal class RememberMeDslTests { cookie(withoutKeyRememberMeCookie) }.andExpect { status { isFound() } - redirectedUrl("/login") + redirectedUrl("http://localhost/login") } val keyMvcResult = mockMvc.post("/login") { loginRememberMeRequest() diff --git a/docs/modules/ROOT/pages/migration-7/web.adoc b/docs/modules/ROOT/pages/migration-7/web.adoc new file mode 100644 index 0000000000..024d560449 --- /dev/null +++ b/docs/modules/ROOT/pages/migration-7/web.adoc @@ -0,0 +1,104 @@ += Web Migrations + +== Favor Relative URIs + +When redirecting to a login endpoint, Spring Security has favored absolute URIs in the past. +For example, if you set your login page like so: + +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +http + // ... + .formLogin((form) -> form.loginPage("/my-login")) + // ... +---- + +Kotlin:: ++ +[source,kotlin,role="secondary"] +---- +http { + formLogin { + loginPage = "/my-login" + } +} +---- + +Xml:: ++ +[source,kotlin,role="secondary"] +---- + + + +---- +====== + +then when redirecting to `/my-login` Spring Security would use a `Location:` like the following: + +[source] +---- +302 Found +// ... +Location: https://myapp.example.org/my-login +---- + +However, this is no longer necessary given that the RFC is was based on is now obsolete. + +In Spring Security 7, this is changed to use a relative URI like so: + +[source] +---- +302 Found +// ... +Location: /my-login +---- + +Most applications will not notice a difference. +However, in the event that this change causes problems, you can switch back to the Spring Security 6 behavior by setting the `favorRelativeUrls` value: + +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +LoginUrlAuthenticationEntryPoint entryPoint = new LoginUrlAuthenticationEntryPoint("/my-login"); +entryPoint.setFavorRelativeUris(false); +http + // ... + .exceptionHandling((exceptions) -> exceptions.authenticaitonEntryPoint(entryPoint)) + // ... +---- + +Kotlin:: ++ +[source,kotlin,role="secondary"] +---- +LoginUrlAuthenticationEntryPoint entryPoint = LoginUrlAuthenticationEntryPoint("/my-login") +entryPoint.setFavorRelativeUris(false) + +http { + exceptionHandling { + authenticationEntryPoint = entryPoint + } +} +---- + +Xml:: ++ +[source,kotlin,role="secondary"] +---- + + + + + + + +---- +====== diff --git a/web/src/main/java/org/springframework/security/web/authentication/LoginUrlAuthenticationEntryPoint.java b/web/src/main/java/org/springframework/security/web/authentication/LoginUrlAuthenticationEntryPoint.java index 60cf4eef4c..5e62d2ebeb 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/LoginUrlAuthenticationEntryPoint.java +++ b/web/src/main/java/org/springframework/security/web/authentication/LoginUrlAuthenticationEntryPoint.java @@ -78,6 +78,8 @@ public class LoginUrlAuthenticationEntryPoint implements AuthenticationEntryPoin private boolean useForward = false; + private boolean favorRelativeUris = false; + private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); /** @@ -144,23 +146,41 @@ public void commence(HttpServletRequest request, HttpServletResponse response, protected String buildRedirectUrlToLoginPage(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) { String loginForm = determineUrlToUseForThisRequest(request, response, authException); - if (UrlUtils.isAbsoluteUrl(loginForm) || !this.forceHttps || "https".equals(request.getScheme())) { + if (UrlUtils.isAbsoluteUrl(loginForm)) { return loginForm; } + if (requiresRewrite(request)) { + return httpsUri(request, loginForm); + } + return this.favorRelativeUris ? loginForm : absoluteUri(request, loginForm).getUrl(); + } + + private boolean requiresRewrite(HttpServletRequest request) { + return this.forceHttps && "http".equals(request.getScheme()); + } + + private String httpsUri(HttpServletRequest request, String path) { int serverPort = this.portResolver.getServerPort(request); Integer httpsPort = this.portMapper.lookupHttpsPort(serverPort); if (httpsPort == null) { logger.warn(LogMessage.format("Unable to redirect to HTTPS as no port mapping found for HTTP port %s", serverPort)); - return loginForm; + return this.favorRelativeUris ? path : absoluteUri(request, path).getUrl(); } + RedirectUrlBuilder builder = absoluteUri(request, path); + builder.setScheme("https"); + builder.setPort(httpsPort); + return builder.getUrl(); + } + + private RedirectUrlBuilder absoluteUri(HttpServletRequest request, String path) { RedirectUrlBuilder urlBuilder = new RedirectUrlBuilder(); - urlBuilder.setScheme("https"); + urlBuilder.setScheme(request.getScheme()); urlBuilder.setServerName(request.getServerName()); - urlBuilder.setPort(httpsPort); + urlBuilder.setPort(this.portResolver.getServerPort(request)); urlBuilder.setContextPath(request.getContextPath()); - urlBuilder.setPathInfo(loginForm); - return urlBuilder.getUrl(); + urlBuilder.setPathInfo(path); + return urlBuilder; } /** @@ -238,4 +258,18 @@ protected boolean isUseForward() { return this.useForward; } + /** + * Favor using relative URIs when formulating a redirect. + * + *

+ * Note that a relative redirect is not always possible. For example, when redirecting + * from {@code http} to {@code https}, the URL needs to be absolute. + *

+ * @param favorRelativeUris whether to favor relative URIs or not + * @since 6.5 + */ + public void setFavorRelativeUris(boolean favorRelativeUris) { + this.favorRelativeUris = favorRelativeUris; + } + } diff --git a/web/src/test/java/org/springframework/security/web/authentication/LoginUrlAuthenticationEntryPointTests.java b/web/src/test/java/org/springframework/security/web/authentication/LoginUrlAuthenticationEntryPointTests.java index ad699da8ea..91e2d93cdf 100644 --- a/web/src/test/java/org/springframework/security/web/authentication/LoginUrlAuthenticationEntryPointTests.java +++ b/web/src/test/java/org/springframework/security/web/authentication/LoginUrlAuthenticationEntryPointTests.java @@ -129,18 +129,18 @@ public void testHttpsOperationFromOriginalHttpsUrl() throws Exception { ep.setPortResolver(new MockPortResolver(80, 443)); ep.afterPropertiesSet(); ep.commence(request, response, null); - assertThat(response.getRedirectedUrl()).isEqualTo("/bigWebApp/hello"); + assertThat(response.getRedirectedUrl()).isEqualTo("https://www.example.com/bigWebApp/hello"); request.setServerPort(8443); response = new MockHttpServletResponse(); ep.setPortResolver(new MockPortResolver(8080, 8443)); ep.commence(request, response, null); - assertThat(response.getRedirectedUrl()).isEqualTo("/bigWebApp/hello"); + assertThat(response.getRedirectedUrl()).isEqualTo("https://www.example.com:8443/bigWebApp/hello"); // access to https via http port request.setServerPort(8080); response = new MockHttpServletResponse(); ep.setPortResolver(new MockPortResolver(8080, 8443)); ep.commence(request, response, null); - assertThat(response.getRedirectedUrl()).isEqualTo("/bigWebApp/hello"); + assertThat(response.getRedirectedUrl()).isEqualTo("https://www.example.com:8443/bigWebApp/hello"); } @Test @@ -158,7 +158,7 @@ public void testNormalOperation() throws Exception { request.setServerPort(80); MockHttpServletResponse response = new MockHttpServletResponse(); ep.commence(request, response, null); - assertThat(response.getRedirectedUrl()).isEqualTo("/bigWebApp/hello"); + assertThat(response.getRedirectedUrl()).isEqualTo("http://localhost/bigWebApp/hello"); } @Test @@ -178,7 +178,7 @@ public void testOperationWhenHttpsRequestsButHttpsPortUnknown() throws Exception ep.commence(request, response, null); // Response doesn't switch to HTTPS, as we didn't know HTTP port 8888 to HTTP port // mapping - assertThat(response.getRedirectedUrl()).isEqualTo("/bigWebApp/hello"); + assertThat(response.getRedirectedUrl()).isEqualTo("http://localhost:8888/bigWebApp/hello"); } @Test @@ -237,4 +237,54 @@ public void absoluteLoginFormUrlCantBeUsedWithForwarding() throws Exception { assertThatIllegalArgumentException().isThrownBy(ep::afterPropertiesSet); } + @Test + public void commenceWhenFavorRelativeUrisThenHttpsSchemeNotIncluded() throws Exception { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setRequestURI("/some_path"); + request.setScheme("https"); + request.setServerName("www.example.com"); + request.setContextPath("/bigWebApp"); + request.setServerPort(443); + MockHttpServletResponse response = new MockHttpServletResponse(); + LoginUrlAuthenticationEntryPoint ep = new LoginUrlAuthenticationEntryPoint("/hello"); + ep.setFavorRelativeUris(true); + ep.setPortMapper(new PortMapperImpl()); + ep.setForceHttps(true); + ep.setPortMapper(new PortMapperImpl()); + ep.setPortResolver(new MockPortResolver(80, 443)); + ep.afterPropertiesSet(); + ep.commence(request, response, null); + assertThat(response.getRedirectedUrl()).isEqualTo("/bigWebApp/hello"); + request.setServerPort(8443); + response = new MockHttpServletResponse(); + ep.setPortResolver(new MockPortResolver(8080, 8443)); + ep.commence(request, response, null); + assertThat(response.getRedirectedUrl()).isEqualTo("/bigWebApp/hello"); + // access to https via http port + request.setServerPort(8080); + response = new MockHttpServletResponse(); + ep.setPortResolver(new MockPortResolver(8080, 8443)); + ep.commence(request, response, null); + assertThat(response.getRedirectedUrl()).isEqualTo("/bigWebApp/hello"); + } + + @Test + public void commenceWhenFavorRelativeUrisThenHttpSchemeNotIncluded() throws Exception { + LoginUrlAuthenticationEntryPoint ep = new LoginUrlAuthenticationEntryPoint("/hello"); + ep.setFavorRelativeUris(true); + ep.setPortMapper(new PortMapperImpl()); + ep.setPortResolver(new MockPortResolver(80, 443)); + ep.afterPropertiesSet(); + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setRequestURI("/some_path"); + request.setContextPath("/bigWebApp"); + request.setScheme("http"); + request.setServerName("localhost"); + request.setContextPath("/bigWebApp"); + request.setServerPort(80); + MockHttpServletResponse response = new MockHttpServletResponse(); + ep.commence(request, response, null); + assertThat(response.getRedirectedUrl()).isEqualTo("/bigWebApp/hello"); + } + } From d683eae9149b09292b45f0cfd66c07257475d3aa Mon Sep 17 00:00:00 2001 From: ThomasKasene <6691406+ThomasKasene@users.noreply.github.com> Date: Wed, 13 Nov 2024 16:30:18 +0100 Subject: [PATCH 010/132] Added a constant for DPOP in OAuth2AccessToken.TokenType Issue gh-14915 Signed-off-by: Daeho Kwon --- .../springframework/security/oauth2/core/OAuth2AccessToken.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/OAuth2AccessToken.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/OAuth2AccessToken.java index d288503f13..66667e1374 100644 --- a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/OAuth2AccessToken.java +++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/OAuth2AccessToken.java @@ -107,6 +107,8 @@ public static final class TokenType implements Serializable { public static final TokenType BEARER = new TokenType("Bearer"); + public static final TokenType DPOP = new TokenType("DPoP"); + private final String value; private TokenType(String value) { From a858a9b0cafd351a26f257f7f7d1926ac2623ccd Mon Sep 17 00:00:00 2001 From: Steve Riesenberg <5248162+sjohnr@users.noreply.github.com> Date: Wed, 18 Dec 2024 16:48:25 -0600 Subject: [PATCH 011/132] Polish gh-16087 Signed-off-by: Daeho Kwon --- .../security/oauth2/core/OAuth2AccessToken.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/OAuth2AccessToken.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/OAuth2AccessToken.java index 66667e1374..88fda0e609 100644 --- a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/OAuth2AccessToken.java +++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/OAuth2AccessToken.java @@ -107,6 +107,9 @@ public static final class TokenType implements Serializable { public static final TokenType BEARER = new TokenType("Bearer"); + /** + * @since 6.5 + */ public static final TokenType DPOP = new TokenType("DPoP"); private final String value; From 975170e6b3cb81e43fac4cdc6b017ed5adc87934 Mon Sep 17 00:00:00 2001 From: Steve Riesenberg <5248162+sjohnr@users.noreply.github.com> Date: Wed, 18 Dec 2024 16:51:47 -0600 Subject: [PATCH 012/132] Make TokenType constructor public Closes gh-16086 Signed-off-by: Daeho Kwon --- .../security/oauth2/core/OAuth2AccessToken.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/OAuth2AccessToken.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/OAuth2AccessToken.java index 88fda0e609..ea1124d041 100644 --- a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/OAuth2AccessToken.java +++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/OAuth2AccessToken.java @@ -114,7 +114,12 @@ public static final class TokenType implements Serializable { private final String value; - private TokenType(String value) { + /** + * Constructs a {@code TokenType} using the provided value. + * @param value the value of the token type + * @since 6.5 + */ + public TokenType(String value) { Assert.hasText(value, "value cannot be empty"); this.value = value; } From 6ec991b5d351723df0aae96f984589219741d611 Mon Sep 17 00:00:00 2001 From: Claudenir Machado Date: Thu, 5 Dec 2024 12:50:38 +0000 Subject: [PATCH 013/132] Address SessionLimitStrategy Closes gh-16206 Signed-off-by: Daeho Kwon --- .../SessionManagementConfigurer.java | 23 ++- .../config/http/HttpConfigurationBuilder.java | 18 ++- .../security/config/spring-security-6.5.rnc | 3 + .../security/config/spring-security-6.5.xsd | 7 + .../SessionManagementConfigurerTests.java | 115 ++++++++++++++- .../config/http/HttpHeadersConfigTests.java | 137 +++++++++++++++++- ...anagementConcurrencyControlMaxSessions.xml | 38 +++++ ...gementConcurrencyControlMaxSessionsRef.xml | 41 ++++++ ...ncyControlWithInvalidMaxSessionsConfig.xml | 42 ++++++ .../servlet/appendix/namespace/http.adoc | 3 + ...tSessionControlAuthenticationStrategy.java | 22 ++- .../security/web/session/SessionLimit.java | 49 +++++++ ...ionControlAuthenticationStrategyTests.java | 85 ++++++++++- .../web/session/SessionLimitTests.java | 59 ++++++++ 14 files changed, 627 insertions(+), 15 deletions(-) create mode 100644 config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsSessionManagementConcurrencyControlMaxSessions.xml create mode 100644 config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsSessionManagementConcurrencyControlMaxSessionsRef.xml create mode 100644 config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsSessionManagementConcurrencyControlWithInvalidMaxSessionsConfig.xml create mode 100644 web/src/main/java/org/springframework/security/web/session/SessionLimit.java create mode 100644 web/src/test/java/org/springframework/security/web/session/SessionLimitTests.java diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java index fc4a2a3880..5ff5b00e72 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java @@ -59,6 +59,7 @@ import org.springframework.security.web.session.ForceEagerSessionCreationFilter; import org.springframework.security.web.session.InvalidSessionStrategy; import org.springframework.security.web.session.SessionInformationExpiredStrategy; +import org.springframework.security.web.session.SessionLimit; import org.springframework.security.web.session.SessionManagementFilter; import org.springframework.security.web.session.SimpleRedirectInvalidSessionStrategy; import org.springframework.security.web.session.SimpleRedirectSessionInformationExpiredStrategy; @@ -123,7 +124,7 @@ public final class SessionManagementConfigurer> private SessionRegistry sessionRegistry; - private Integer maximumSessions; + private SessionLimit sessionLimit; private String expiredUrl; @@ -329,7 +330,7 @@ public SessionManagementConfigurer sessionFixation( * @return the {@link SessionManagementConfigurer} for further customizations */ public ConcurrencyControlConfigurer maximumSessions(int maximumSessions) { - this.maximumSessions = maximumSessions; + this.sessionLimit = SessionLimit.of(maximumSessions); this.propertiesThatRequireImplicitAuthentication.add("maximumSessions = " + maximumSessions); return new ConcurrencyControlConfigurer(); } @@ -570,7 +571,7 @@ private SessionAuthenticationStrategy getSessionAuthenticationStrategy(H http) { SessionRegistry sessionRegistry = getSessionRegistry(http); ConcurrentSessionControlAuthenticationStrategy concurrentSessionControlStrategy = new ConcurrentSessionControlAuthenticationStrategy( sessionRegistry); - concurrentSessionControlStrategy.setMaximumSessions(this.maximumSessions); + concurrentSessionControlStrategy.setMaximumSessions(this.sessionLimit); concurrentSessionControlStrategy.setExceptionIfMaximumExceeded(this.maxSessionsPreventsLogin); concurrentSessionControlStrategy = postProcess(concurrentSessionControlStrategy); RegisterSessionAuthenticationStrategy registerSessionStrategy = new RegisterSessionAuthenticationStrategy( @@ -614,7 +615,7 @@ private void registerDelegateApplicationListener(H http, ApplicationListener * @return */ private boolean isConcurrentSessionControlEnabled() { - return this.maximumSessions != null; + return this.sessionLimit != null; } /** @@ -706,7 +707,19 @@ private ConcurrencyControlConfigurer() { * @return the {@link ConcurrencyControlConfigurer} for further customizations */ public ConcurrencyControlConfigurer maximumSessions(int maximumSessions) { - SessionManagementConfigurer.this.maximumSessions = maximumSessions; + SessionManagementConfigurer.this.sessionLimit = SessionLimit.of(maximumSessions); + return this; + } + + /** + * Determines the behaviour when a session limit is detected. + * @param sessionLimit the {@link SessionLimit} to check the maximum number of + * sessions for a user + * @return the {@link ConcurrencyControlConfigurer} for further customizations + * @since 6.5 + */ + public ConcurrencyControlConfigurer maximumSessions(SessionLimit sessionLimit) { + SessionManagementConfigurer.this.sessionLimit = sessionLimit; return this; } diff --git a/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java b/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java index 53635b5aa0..db915da867 100644 --- a/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java +++ b/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java @@ -122,6 +122,10 @@ class HttpConfigurationBuilder { private static final String ATT_SESSION_AUTH_STRATEGY_REF = "session-authentication-strategy-ref"; + private static final String ATT_MAX_SESSIONS_REF = "max-sessions-ref"; + + private static final String ATT_MAX_SESSIONS = "max-sessions"; + private static final String ATT_SESSION_AUTH_ERROR_URL = "session-authentication-error-url"; private static final String ATT_SECURITY_CONTEXT_HOLDER_STRATEGY = "security-context-holder-strategy-ref"; @@ -485,10 +489,16 @@ else if (StringUtils.hasText(sessionAuthStratRef)) { concurrentSessionStrategy.addConstructorArgValue(this.sessionRegistryRef); String maxSessions = this.pc.getReaderContext() .getEnvironment() - .resolvePlaceholders(sessionCtrlElt.getAttribute("max-sessions")); + .resolvePlaceholders(sessionCtrlElt.getAttribute(ATT_MAX_SESSIONS)); if (StringUtils.hasText(maxSessions)) { concurrentSessionStrategy.addPropertyValue("maximumSessions", maxSessions); } + String maxSessionsRef = this.pc.getReaderContext() + .getEnvironment() + .resolvePlaceholders(sessionCtrlElt.getAttribute(ATT_MAX_SESSIONS_REF)); + if (StringUtils.hasText(maxSessionsRef)) { + concurrentSessionStrategy.addPropertyReference("maximumSessions", maxSessionsRef); + } String exceptionIfMaximumExceeded = sessionCtrlElt.getAttribute("error-if-maximum-exceeded"); if (StringUtils.hasText(exceptionIfMaximumExceeded)) { concurrentSessionStrategy.addPropertyValue("exceptionIfMaximumExceeded", exceptionIfMaximumExceeded); @@ -591,6 +601,12 @@ private void createConcurrencyControlFilterAndSessionRegistry(Element element) { .error("Cannot use 'expired-url' attribute and 'expired-session-strategy-ref'" + " attribute together.", source); } + String maxSessions = element.getAttribute(ATT_MAX_SESSIONS); + String maxSessionsRef = element.getAttribute(ATT_MAX_SESSIONS_REF); + if (StringUtils.hasText(maxSessions) && StringUtils.hasText(maxSessionsRef)) { + this.pc.getReaderContext() + .error("Cannot use 'max-sessions' attribute and 'max-sessions-ref' attribute together.", source); + } if (StringUtils.hasText(expiryUrl)) { BeanDefinitionBuilder expiredSessionBldr = BeanDefinitionBuilder .rootBeanDefinition(SimpleRedirectSessionInformationExpiredStrategy.class); diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-6.5.rnc b/config/src/main/resources/org/springframework/security/config/spring-security-6.5.rnc index 9b2469aa87..9dcb730571 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-6.5.rnc +++ b/config/src/main/resources/org/springframework/security/config/spring-security-6.5.rnc @@ -934,6 +934,9 @@ concurrency-control = concurrency-control.attlist &= ## The maximum number of sessions a single authenticated user can have open at the same time. Defaults to "1". A negative value denotes unlimited sessions. attribute max-sessions {xsd:token}? +concurrency-control.attlist &= + ## Allows injection of the SessionLimit instance used by the ConcurrentSessionControlAuthenticationStrategy + attribute max-sessions-ref {xsd:token}? concurrency-control.attlist &= ## The URL a user will be redirected to if they attempt to use a session which has been "expired" because they have logged in again. attribute expired-url {xsd:token}? diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-6.5.xsd b/config/src/main/resources/org/springframework/security/config/spring-security-6.5.xsd index e46438d80d..03a00f3665 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-6.5.xsd +++ b/config/src/main/resources/org/springframework/security/config/spring-security-6.5.xsd @@ -2688,6 +2688,13 @@ + + + Allows injection of the SessionLimit instance used by the + ConcurrentSessionControlAuthenticationStrategy + + + The URL a user will be redirected to if they attempt to use a session which has been diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerTests.java index fbe52459a4..cc3011a719 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2024 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. @@ -64,6 +64,7 @@ import org.springframework.security.web.savedrequest.RequestCache; import org.springframework.security.web.session.ConcurrentSessionFilter; import org.springframework.security.web.session.HttpSessionDestroyedEvent; +import org.springframework.security.web.session.SessionLimit; import org.springframework.security.web.session.SessionManagementFilter; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; @@ -249,6 +250,82 @@ public void loginWhenUserLoggedInAndMaxSessionsOneInLambdaThenLoginPrevented() t // @formatter:on } + @Test + public void loginWhenAdminUserLoggedInAndSessionLimitIsConfiguredThenLoginSuccessfully() throws Exception { + this.spring.register(ConcurrencyControlWithSessionLimitConfig.class).autowire(); + // @formatter:off + MockHttpServletRequestBuilder requestBuilder = post("/login") + .with(csrf()) + .param("username", "admin") + .param("password", "password"); + HttpSession firstSession = this.mvc.perform(requestBuilder) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("/")) + .andReturn() + .getRequest() + .getSession(false); + assertThat(firstSession).isNotNull(); + HttpSession secondSession = this.mvc.perform(requestBuilder) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("/")) + .andReturn() + .getRequest() + .getSession(false); + assertThat(secondSession).isNotNull(); + // @formatter:on + assertThat(firstSession.getId()).isNotEqualTo(secondSession.getId()); + } + + @Test + public void loginWhenAdminUserLoggedInAndSessionLimitIsConfiguredThenLoginPrevented() throws Exception { + this.spring.register(ConcurrencyControlWithSessionLimitConfig.class).autowire(); + // @formatter:off + MockHttpServletRequestBuilder requestBuilder = post("/login") + .with(csrf()) + .param("username", "admin") + .param("password", "password"); + HttpSession firstSession = this.mvc.perform(requestBuilder) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("/")) + .andReturn() + .getRequest() + .getSession(false); + assertThat(firstSession).isNotNull(); + HttpSession secondSession = this.mvc.perform(requestBuilder) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("/")) + .andReturn() + .getRequest() + .getSession(false); + assertThat(secondSession).isNotNull(); + assertThat(firstSession.getId()).isNotEqualTo(secondSession.getId()); + this.mvc.perform(requestBuilder) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("/login?error")); + // @formatter:on + } + + @Test + public void loginWhenUserLoggedInAndSessionLimitIsConfiguredThenLoginPrevented() throws Exception { + this.spring.register(ConcurrencyControlWithSessionLimitConfig.class).autowire(); + // @formatter:off + MockHttpServletRequestBuilder requestBuilder = post("/login") + .with(csrf()) + .param("username", "user") + .param("password", "password"); + HttpSession firstSession = this.mvc.perform(requestBuilder) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("/")) + .andReturn() + .getRequest() + .getSession(false); + assertThat(firstSession).isNotNull(); + this.mvc.perform(requestBuilder) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("/login?error")); + // @formatter:on + } + @Test public void requestWhenSessionCreationPolicyStateLessInLambdaThenNoSessionCreated() throws Exception { this.spring.register(SessionCreationPolicyStateLessInLambdaConfig.class).autowire(); @@ -625,6 +702,42 @@ UserDetailsService userDetailsService() { } + @Configuration + @EnableWebSecurity + static class ConcurrencyControlWithSessionLimitConfig { + + @Bean + SecurityFilterChain filterChain(HttpSecurity http, SessionLimit sessionLimit) throws Exception { + // @formatter:off + http + .formLogin(withDefaults()) + .sessionManagement((sessionManagement) -> sessionManagement + .sessionConcurrency((sessionConcurrency) -> sessionConcurrency + .maximumSessions(sessionLimit) + .maxSessionsPreventsLogin(true) + ) + ); + // @formatter:on + return http.build(); + } + + @Bean + UserDetailsService userDetailsService() { + return new InMemoryUserDetailsManager(PasswordEncodedUser.admin(), PasswordEncodedUser.user()); + } + + @Bean + SessionLimit SessionLimit() { + return (authentication) -> { + if ("admin".equals(authentication.getName())) { + return 2; + } + return 1; + }; + } + + } + @Configuration @EnableWebSecurity static class SessionCreationPolicyStateLessInLambdaConfig { diff --git a/config/src/test/java/org/springframework/security/config/http/HttpHeadersConfigTests.java b/config/src/test/java/org/springframework/security/config/http/HttpHeadersConfigTests.java index c66933de16..6c89be179a 100644 --- a/config/src/test/java/org/springframework/security/config/http/HttpHeadersConfigTests.java +++ b/config/src/test/java/org/springframework/security/config/http/HttpHeadersConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2024 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. @@ -24,6 +24,7 @@ import java.util.Set; import com.google.common.collect.ImmutableMap; +import jakarta.servlet.http.HttpSession; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -33,14 +34,21 @@ import org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.session.SessionLimit; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultMatcher; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** @@ -49,6 +57,7 @@ * @author Josh Cummings * @author Rafiullah Hamedy * @author Marcus Da Coregio + * @author Claudenir Freitas */ @ExtendWith(SpringTestContextExtension.class) public class HttpHeadersConfigTests { @@ -782,6 +791,120 @@ public void requestWhenCrossOriginPoliciesRespondsCrossOriginPolicies() throws E // @formatter:on } + @Test + public void requestWhenSessionManagementConcurrencyControlMaxSessionIsOne() throws Exception { + System.setProperty("security.session-management.concurrency-control.max-sessions", "1"); + this.spring.configLocations(this.xml("DefaultsSessionManagementConcurrencyControlMaxSessions")).autowire(); + // @formatter:off + MockHttpServletRequestBuilder requestBuilder = post("/login") + .with(csrf()) + .param("username", "user") + .param("password", "password"); + HttpSession firstSession = this.mvc.perform(requestBuilder) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("/")) + .andReturn() + .getRequest() + .getSession(false); + // @formatter:on + assertThat(firstSession).isNotNull(); + // @formatter:off + this.mvc.perform(requestBuilder) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("/login?error")); + // @formatter:on + } + + @Test + public void requestWhenSessionManagementConcurrencyControlMaxSessionIsUnlimited() throws Exception { + System.setProperty("security.session-management.concurrency-control.max-sessions", "-1"); + this.spring.configLocations(this.xml("DefaultsSessionManagementConcurrencyControlMaxSessions")).autowire(); + // @formatter:off + MockHttpServletRequestBuilder requestBuilder = post("/login") + .with(csrf()) + .param("username", "user") + .param("password", "password"); + HttpSession firstSession = this.mvc.perform(requestBuilder) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("/")) + .andReturn() + .getRequest() + .getSession(false); + assertThat(firstSession).isNotNull(); + HttpSession secondSession = this.mvc.perform(requestBuilder) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("/")) + .andReturn() + .getRequest() + .getSession(false); + assertThat(secondSession).isNotNull(); + // @formatter:on + assertThat(firstSession.getId()).isNotEqualTo(secondSession.getId()); + } + + @Test + public void requestWhenSessionManagementConcurrencyControlMaxSessionRefIsOneForNonAdminUsers() throws Exception { + this.spring.configLocations(this.xml("DefaultsSessionManagementConcurrencyControlMaxSessionsRef")).autowire(); + // @formatter:off + MockHttpServletRequestBuilder requestBuilder = post("/login") + .with(csrf()) + .param("username", "user") + .param("password", "password"); + HttpSession firstSession = this.mvc.perform(requestBuilder) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("/")) + .andReturn() + .getRequest() + .getSession(false); + // @formatter:on + assertThat(firstSession).isNotNull(); + // @formatter:off + this.mvc.perform(requestBuilder) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("/login?error")); + // @formatter:on + } + + @Test + public void requestWhenSessionManagementConcurrencyControlMaxSessionRefIsTwoForAdminUsers() throws Exception { + this.spring.configLocations(this.xml("DefaultsSessionManagementConcurrencyControlMaxSessionsRef")).autowire(); + // @formatter:off + MockHttpServletRequestBuilder requestBuilder = post("/login") + .with(csrf()) + .param("username", "admin") + .param("password", "password"); + HttpSession firstSession = this.mvc.perform(requestBuilder) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("/")) + .andReturn() + .getRequest() + .getSession(false); + assertThat(firstSession).isNotNull(); + HttpSession secondSession = this.mvc.perform(requestBuilder) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("/")) + .andReturn() + .getRequest() + .getSession(false); + assertThat(secondSession).isNotNull(); + // @formatter:on + assertThat(firstSession.getId()).isNotEqualTo(secondSession.getId()); + // @formatter:off + this.mvc.perform(requestBuilder) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("/login?error")); + // @formatter:on + } + + @Test + public void requestWhenSessionManagementConcurrencyControlWithInvalidMaxSessionConfig() { + assertThatExceptionOfType(BeanDefinitionParsingException.class) + .isThrownBy(() -> this.spring + .configLocations(this.xml("DefaultsSessionManagementConcurrencyControlWithInvalidMaxSessionsConfig")) + .autowire()) + .withMessageContaining("Cannot use 'max-sessions' attribute and 'max-sessions-ref' attribute together."); + } + private static ResultMatcher includesDefaults() { return includes(defaultHeaders); } @@ -832,4 +955,16 @@ public String ok() { } + public static class CustomSessionLimit implements SessionLimit { + + @Override + public Integer apply(Authentication authentication) { + if ("admin".equals(authentication.getName())) { + return 2; + } + return 1; + } + + } + } diff --git a/config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsSessionManagementConcurrencyControlMaxSessions.xml b/config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsSessionManagementConcurrencyControlMaxSessions.xml new file mode 100644 index 0000000000..7e8c3d12a3 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsSessionManagementConcurrencyControlMaxSessions.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsSessionManagementConcurrencyControlMaxSessionsRef.xml b/config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsSessionManagementConcurrencyControlMaxSessionsRef.xml new file mode 100644 index 0000000000..98215ca86c --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsSessionManagementConcurrencyControlMaxSessionsRef.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsSessionManagementConcurrencyControlWithInvalidMaxSessionsConfig.xml b/config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsSessionManagementConcurrencyControlWithInvalidMaxSessionsConfig.xml new file mode 100644 index 0000000000..7bf56c9a3a --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsSessionManagementConcurrencyControlWithInvalidMaxSessionsConfig.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + diff --git a/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc b/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc index 59e48e0986..d49c2f12db 100644 --- a/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc +++ b/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc @@ -2168,6 +2168,9 @@ Allows injection of the ExpiredSessionStrategy instance used by the ConcurrentSe Maps to the `maximumSessions` property of `ConcurrentSessionControlAuthenticationStrategy`. Specify `-1` as the value to support unlimited sessions. +[[nsa-concurrency-control-max-sessions-ref]] +* **max-sessions-ref** +Allows injection of the SessionLimit instance used by the ConcurrentSessionControlAuthenticationStrategy [[nsa-concurrency-control-session-registry-alias]] * **session-registry-alias** diff --git a/web/src/main/java/org/springframework/security/web/authentication/session/ConcurrentSessionControlAuthenticationStrategy.java b/web/src/main/java/org/springframework/security/web/authentication/session/ConcurrentSessionControlAuthenticationStrategy.java index 8d528f5621..b8f3c9e307 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/session/ConcurrentSessionControlAuthenticationStrategy.java +++ b/web/src/main/java/org/springframework/security/web/authentication/session/ConcurrentSessionControlAuthenticationStrategy.java @@ -33,6 +33,7 @@ import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.session.ConcurrentSessionFilter; +import org.springframework.security.web.session.SessionLimit; import org.springframework.security.web.session.SessionManagementFilter; import org.springframework.util.Assert; @@ -76,7 +77,7 @@ public class ConcurrentSessionControlAuthenticationStrategy private boolean exceptionIfMaximumExceeded = false; - private int maximumSessions = 1; + private SessionLimit sessionLimit = SessionLimit.of(1); /** * @param sessionRegistry the session registry which should be updated when the @@ -130,7 +131,7 @@ public void onAuthentication(Authentication authentication, HttpServletRequest r * @return either -1 meaning unlimited, or a positive integer to limit (never zero) */ protected int getMaximumSessionsForThisUser(Authentication authentication) { - return this.maximumSessions; + return this.sessionLimit.apply(authentication); } /** @@ -172,15 +173,24 @@ public void setExceptionIfMaximumExceeded(boolean exceptionIfMaximumExceeded) { } /** - * Sets the maxSessions property. The default value is 1. Use -1 for + * Sets the sessionLimit property. The default value is 1. Use -1 for * unlimited sessions. * @param maximumSessions the maximum number of permitted sessions a user can have * open simultaneously. */ public void setMaximumSessions(int maximumSessions) { - Assert.isTrue(maximumSessions != 0, - "MaximumLogins must be either -1 to allow unlimited logins, or a positive integer to specify a maximum"); - this.maximumSessions = maximumSessions; + this.sessionLimit = SessionLimit.of(maximumSessions); + } + + /** + * Sets the sessionLimit property. The default value is 1. Use -1 for + * unlimited sessions. + * @param sessionLimit the session limit strategy + * @since 6.5 + */ + public void setMaximumSessions(SessionLimit sessionLimit) { + Assert.notNull(sessionLimit, "sessionLimit cannot be null"); + this.sessionLimit = sessionLimit; } /** diff --git a/web/src/main/java/org/springframework/security/web/session/SessionLimit.java b/web/src/main/java/org/springframework/security/web/session/SessionLimit.java new file mode 100644 index 0000000000..385c462137 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/session/SessionLimit.java @@ -0,0 +1,49 @@ +/* + * Copyright 2015-2024 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.session; + +import java.util.function.Function; + +import org.springframework.security.core.Authentication; +import org.springframework.util.Assert; + +/** + * Represents the maximum number of sessions allowed. Use {@link #UNLIMITED} to indicate + * that there is no limit. + * + * @author Claudenir Freitas + * @since 6.5 + */ +public interface SessionLimit extends Function { + + /** + * Represents unlimited sessions. + */ + SessionLimit UNLIMITED = (authentication) -> -1; + + /** + * Creates a {@link SessionLimit} that always returns the given value for any user + * @param maxSessions the maximum number of sessions allowed + * @return a {@link SessionLimit} instance that returns the given value. + */ + static SessionLimit of(int maxSessions) { + Assert.isTrue(maxSessions != 0, + "MaximumLogins must be either -1 to allow unlimited logins, or a positive integer to specify a maximum"); + return (authentication) -> maxSessions; + } + +} diff --git a/web/src/test/java/org/springframework/security/web/authentication/session/ConcurrentSessionControlAuthenticationStrategyTests.java b/web/src/test/java/org/springframework/security/web/authentication/session/ConcurrentSessionControlAuthenticationStrategyTests.java index ffe51cc2a0..26d4afe3ef 100644 --- a/web/src/test/java/org/springframework/security/web/authentication/session/ConcurrentSessionControlAuthenticationStrategyTests.java +++ b/web/src/test/java/org/springframework/security/web/authentication/session/ConcurrentSessionControlAuthenticationStrategyTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2024 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. @@ -34,6 +34,7 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.session.SessionInformation; import org.springframework.security.core.session.SessionRegistry; +import org.springframework.security.web.session.SessionLimit; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -41,9 +42,11 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verifyNoInteractions; /** * @author Rob Winch + * @author Claudenir Freitas * */ @ExtendWith(MockitoExtension.class) @@ -144,6 +147,86 @@ public void onAuthenticationWhenMaxSessionsExceededByTwoThenTwoSessionsExpired() assertThat(this.sessionInformation.isExpired()).isFalse(); } + @Test + public void setMaximumSessionsWithNullValue() { + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> this.strategy.setMaximumSessions(null)) + .withMessage("sessionLimit cannot be null"); + } + + @Test + public void noRegisteredSessionUsingSessionLimit() { + given(this.sessionRegistry.getAllSessions(any(), anyBoolean())).willReturn(Collections.emptyList()); + this.strategy.setMaximumSessions(SessionLimit.of(1)); + this.strategy.setExceptionIfMaximumExceeded(true); + this.strategy.onAuthentication(this.authentication, this.request, this.response); + // no exception + } + + @Test + public void maxSessionsSameSessionIdUsingSessionLimit() { + MockHttpSession session = new MockHttpSession(new MockServletContext(), this.sessionInformation.getSessionId()); + this.request.setSession(session); + given(this.sessionRegistry.getAllSessions(any(), anyBoolean())) + .willReturn(Collections.singletonList(this.sessionInformation)); + this.strategy.setMaximumSessions(SessionLimit.of(1)); + this.strategy.setExceptionIfMaximumExceeded(true); + this.strategy.onAuthentication(this.authentication, this.request, this.response); + // no exception + } + + @Test + public void maxSessionsWithExceptionUsingSessionLimit() { + given(this.sessionRegistry.getAllSessions(any(), anyBoolean())) + .willReturn(Collections.singletonList(this.sessionInformation)); + this.strategy.setMaximumSessions(SessionLimit.of(1)); + this.strategy.setExceptionIfMaximumExceeded(true); + assertThatExceptionOfType(SessionAuthenticationException.class) + .isThrownBy(() -> this.strategy.onAuthentication(this.authentication, this.request, this.response)); + } + + @Test + public void maxSessionsExpireExistingUserUsingSessionLimit() { + given(this.sessionRegistry.getAllSessions(any(), anyBoolean())) + .willReturn(Collections.singletonList(this.sessionInformation)); + this.strategy.setMaximumSessions(SessionLimit.of(1)); + this.strategy.onAuthentication(this.authentication, this.request, this.response); + assertThat(this.sessionInformation.isExpired()).isTrue(); + } + + @Test + public void maxSessionsExpireLeastRecentExistingUserUsingSessionLimit() { + SessionInformation moreRecentSessionInfo = new SessionInformation(this.authentication.getPrincipal(), "unique", + new Date(1374766999999L)); + given(this.sessionRegistry.getAllSessions(any(), anyBoolean())) + .willReturn(Arrays.asList(moreRecentSessionInfo, this.sessionInformation)); + this.strategy.setMaximumSessions(SessionLimit.of(2)); + this.strategy.onAuthentication(this.authentication, this.request, this.response); + assertThat(this.sessionInformation.isExpired()).isTrue(); + } + + @Test + public void onAuthenticationWhenMaxSessionsExceededByTwoThenTwoSessionsExpiredUsingSessionLimit() { + SessionInformation oldestSessionInfo = new SessionInformation(this.authentication.getPrincipal(), "unique1", + new Date(1374766134214L)); + SessionInformation secondOldestSessionInfo = new SessionInformation(this.authentication.getPrincipal(), + "unique2", new Date(1374766134215L)); + given(this.sessionRegistry.getAllSessions(any(), anyBoolean())) + .willReturn(Arrays.asList(oldestSessionInfo, secondOldestSessionInfo, this.sessionInformation)); + this.strategy.setMaximumSessions(SessionLimit.of(2)); + this.strategy.onAuthentication(this.authentication, this.request, this.response); + assertThat(oldestSessionInfo.isExpired()).isTrue(); + assertThat(secondOldestSessionInfo.isExpired()).isTrue(); + assertThat(this.sessionInformation.isExpired()).isFalse(); + } + + @Test + public void onAuthenticationWhenSessionLimitIsUnlimited() { + this.strategy.setMaximumSessions(SessionLimit.UNLIMITED); + this.strategy.onAuthentication(this.authentication, this.request, this.response); + verifyNoInteractions(this.sessionRegistry); + } + @Test public void setMessageSourceNull() { assertThatIllegalArgumentException().isThrownBy(() -> this.strategy.setMessageSource(null)); diff --git a/web/src/test/java/org/springframework/security/web/session/SessionLimitTests.java b/web/src/test/java/org/springframework/security/web/session/SessionLimitTests.java new file mode 100644 index 0000000000..01df1449d7 --- /dev/null +++ b/web/src/test/java/org/springframework/security/web/session/SessionLimitTests.java @@ -0,0 +1,59 @@ +/* + * Copyright 2002-2024 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.session; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.Mockito; + +import org.springframework.security.core.Authentication; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +/** + * @author Claudenir Freitas + * @since 6.5 + */ +class SessionLimitTests { + + private final Authentication authentication = Mockito.mock(Authentication.class); + + @Test + void testUnlimitedInstance() { + SessionLimit sessionLimit = SessionLimit.UNLIMITED; + int result = sessionLimit.apply(this.authentication); + assertThat(result).isEqualTo(-1); + } + + @ParameterizedTest + @ValueSource(ints = { -1, 1, 2, 3 }) + void testInstanceWithValidMaxSessions(int maxSessions) { + SessionLimit sessionLimit = SessionLimit.of(maxSessions); + int result = sessionLimit.apply(this.authentication); + assertThat(result).isEqualTo(maxSessions); + } + + @Test + void testInstanceWithInvalidMaxSessions() { + assertThatIllegalArgumentException().isThrownBy(() -> SessionLimit.of(0)) + .withMessage( + "MaximumLogins must be either -1 to allow unlimited logins, or a positive integer to specify a maximum"); + } + +} From eb46d8963955c746e990b1b32d0a9b838d244be7 Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Wed, 18 Dec 2024 16:43:19 -0700 Subject: [PATCH 014/132] Polish SessionLimit - Move to the web.authentication.session package since it is only needed by web.authentication.session elements and does not access any other web element itself. - Add Kotlin support - Add documentation Issue gh-16206 Signed-off-by: Daeho Kwon --- .../SessionManagementConfigurer.java | 2 +- .../web/session/SessionConcurrencyDsl.kt | 11 +++ .../SessionManagementConfigurerTests.java | 2 +- .../config/http/HttpHeadersConfigTests.java | 2 +- .../web/session/SessionConcurrencyDslTests.kt | 69 +++++++++++++++++-- .../authentication/session-management.adoc | 57 ++++++++++++++- ...tSessionControlAuthenticationStrategy.java | 1 - .../session/SessionLimit.java | 2 +- ...ionControlAuthenticationStrategyTests.java | 1 - .../session/SessionLimitTests.java | 2 +- 10 files changed, 137 insertions(+), 12 deletions(-) rename web/src/main/java/org/springframework/security/web/{ => authentication}/session/SessionLimit.java (96%) rename web/src/test/java/org/springframework/security/web/{ => authentication}/session/SessionLimitTests.java (96%) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java index 5ff5b00e72..fa601b9449 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java @@ -47,6 +47,7 @@ import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy; import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; import org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy; +import org.springframework.security.web.authentication.session.SessionLimit; import org.springframework.security.web.context.DelegatingSecurityContextRepository; import org.springframework.security.web.context.HttpSessionSecurityContextRepository; import org.springframework.security.web.context.NullSecurityContextRepository; @@ -59,7 +60,6 @@ import org.springframework.security.web.session.ForceEagerSessionCreationFilter; import org.springframework.security.web.session.InvalidSessionStrategy; import org.springframework.security.web.session.SessionInformationExpiredStrategy; -import org.springframework.security.web.session.SessionLimit; import org.springframework.security.web.session.SessionManagementFilter; import org.springframework.security.web.session.SimpleRedirectInvalidSessionStrategy; import org.springframework.security.web.session.SimpleRedirectSessionInformationExpiredStrategy; diff --git a/config/src/main/kotlin/org/springframework/security/config/annotation/web/session/SessionConcurrencyDsl.kt b/config/src/main/kotlin/org/springframework/security/config/annotation/web/session/SessionConcurrencyDsl.kt index 0d33c0702a..ce4bc54ca5 100644 --- a/config/src/main/kotlin/org/springframework/security/config/annotation/web/session/SessionConcurrencyDsl.kt +++ b/config/src/main/kotlin/org/springframework/security/config/annotation/web/session/SessionConcurrencyDsl.kt @@ -19,7 +19,9 @@ package org.springframework.security.config.annotation.web.session import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer import org.springframework.security.core.session.SessionRegistry +import org.springframework.security.web.authentication.session.SessionLimit import org.springframework.security.web.session.SessionInformationExpiredStrategy +import org.springframework.util.Assert /** * A Kotlin DSL to configure the behaviour of multiple sessions using idiomatic @@ -44,12 +46,21 @@ class SessionConcurrencyDsl { var expiredSessionStrategy: SessionInformationExpiredStrategy? = null var maxSessionsPreventsLogin: Boolean? = null var sessionRegistry: SessionRegistry? = null + private var sessionLimit: SessionLimit? = null + + fun maximumSessions(max: SessionLimit) { + this.sessionLimit = max + } internal fun get(): (SessionManagementConfigurer.ConcurrencyControlConfigurer) -> Unit { + Assert.isTrue(maximumSessions == null || sessionLimit == null, "You cannot specify maximumSessions as both an Int and a SessionLimit. Please use only one.") return { sessionConcurrencyControl -> maximumSessions?.also { sessionConcurrencyControl.maximumSessions(maximumSessions!!) } + sessionLimit?.also { + sessionConcurrencyControl.maximumSessions(sessionLimit!!) + } expiredUrl?.also { sessionConcurrencyControl.expiredUrl(expiredUrl) } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerTests.java index cc3011a719..bca300ec52 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerTests.java @@ -59,12 +59,12 @@ import org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy; import org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy; import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy; +import org.springframework.security.web.authentication.session.SessionLimit; import org.springframework.security.web.context.RequestAttributeSecurityContextRepository; import org.springframework.security.web.context.SecurityContextRepository; import org.springframework.security.web.savedrequest.RequestCache; import org.springframework.security.web.session.ConcurrentSessionFilter; import org.springframework.security.web.session.HttpSessionDestroyedEvent; -import org.springframework.security.web.session.SessionLimit; import org.springframework.security.web.session.SessionManagementFilter; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; diff --git a/config/src/test/java/org/springframework/security/config/http/HttpHeadersConfigTests.java b/config/src/test/java/org/springframework/security/config/http/HttpHeadersConfigTests.java index 6c89be179a..2c41d1a368 100644 --- a/config/src/test/java/org/springframework/security/config/http/HttpHeadersConfigTests.java +++ b/config/src/test/java/org/springframework/security/config/http/HttpHeadersConfigTests.java @@ -35,7 +35,7 @@ import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.Authentication; -import org.springframework.security.web.session.SessionLimit; +import org.springframework.security.web.authentication.session.SessionLimit; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultMatcher; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; diff --git a/config/src/test/kotlin/org/springframework/security/config/annotation/web/session/SessionConcurrencyDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/annotation/web/session/SessionConcurrencyDslTests.kt index 6437c54326..9117ae757a 100644 --- a/config/src/test/kotlin/org/springframework/security/config/annotation/web/session/SessionConcurrencyDslTests.kt +++ b/config/src/test/kotlin/org/springframework/security/config/annotation/web/session/SessionConcurrencyDslTests.kt @@ -18,18 +18,19 @@ package org.springframework.security.config.annotation.web.session import io.mockk.every import io.mockk.mockkObject -import java.util.Date import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.springframework.beans.factory.annotation.Autowired import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.mock.web.MockHttpSession +import org.springframework.security.authorization.AuthorityAuthorizationManager +import org.springframework.security.authorization.AuthorizationManager import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity +import org.springframework.security.config.annotation.web.invoke import org.springframework.security.config.test.SpringTestContext import org.springframework.security.config.test.SpringTestContextExtension -import org.springframework.security.config.annotation.web.invoke import org.springframework.security.core.session.SessionInformation import org.springframework.security.core.session.SessionRegistry import org.springframework.security.core.session.SessionRegistryImpl @@ -44,6 +45,7 @@ import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post import org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status +import java.util.* /** * Tests for [SessionConcurrencyDsl] @@ -173,16 +175,75 @@ class SessionConcurrencyDslTests { open fun sessionRegistry(): SessionRegistry = SESSION_REGISTRY } + @Test + fun `session concurrency when session limit then no more sessions allowed`() { + this.spring.register(MaximumSessionsFunctionConfig::class.java, UserDetailsConfig::class.java).autowire() + + this.mockMvc.perform(post("/login") + .with(csrf()) + .param("username", "user") + .param("password", "password")) + + this.mockMvc.perform(post("/login") + .with(csrf()) + .param("username", "user") + .param("password", "password")) + .andExpect(status().isFound) + .andExpect(redirectedUrl("/login?error")) + + this.mockMvc.perform(post("/login") + .with(csrf()) + .param("username", "admin") + .param("password", "password")) + .andExpect(status().isFound) + .andExpect(redirectedUrl("/")) + + this.mockMvc.perform(post("/login") + .with(csrf()) + .param("username", "admin") + .param("password", "password")) + .andExpect(status().isFound) + .andExpect(redirectedUrl("/")) + } + + @Configuration + @EnableWebSecurity + open class MaximumSessionsFunctionConfig { + + @Bean + open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + val isAdmin: AuthorizationManager = AuthorityAuthorizationManager.hasRole("ADMIN") + http { + sessionManagement { + sessionConcurrency { + maximumSessions { + authentication -> if (isAdmin.authorize({ authentication }, null)!!.isGranted) -1 else 1 + } + maxSessionsPreventsLogin = true + } + } + formLogin { } + } + return http.build() + } + + } + @Configuration open class UserDetailsConfig { @Bean open fun userDetailsService(): UserDetailsService { - val userDetails = User.withDefaultPasswordEncoder() + val user = User.withDefaultPasswordEncoder() .username("user") .password("password") .roles("USER") .build() - return InMemoryUserDetailsManager(userDetails) + val admin = User.withDefaultPasswordEncoder() + .username("admin") + .password("password") + .roles("ADMIN") + .build() + return InMemoryUserDetailsManager(user, admin) } } } diff --git a/docs/modules/ROOT/pages/servlet/authentication/session-management.adoc b/docs/modules/ROOT/pages/servlet/authentication/session-management.adoc index fe0821a31d..c6282e2351 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/session-management.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/session-management.adoc @@ -399,7 +399,62 @@ XML:: This will prevent a user from logging in multiple times - a second login will cause the first to be invalidated. -Using Spring Boot, you can test the above configuration scenario the following way: +You can also adjust this based on who the user is. +For example, administrators may be able to have more than one session: + +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +@Bean +public SecurityFilterChain filterChain(HttpSecurity http) { + AuthorizationManager isAdmin = AuthorityAuthorizationManager.hasRole("ADMIN"); + http + .sessionManagement(session -> session + .maximumSessions((authentication) -> isAdmin.authorize(() -> authentication, null).isGranted() ? -1 : 1) + ); + return http.build(); +} +---- + +Kotlin:: ++ +[source,kotlin,role="secondary"] +---- +@Bean +open fun filterChain(http: HttpSecurity): SecurityFilterChain { + val isAdmin: AuthorizationManager<*> = AuthorityAuthorizationManager.hasRole("ADMIN") + http { + sessionManagement { + sessionConcurrency { + maximumSessions { + authentication -> if (isAdmin.authorize({ authentication }, null)!!.isGranted) -1 else 1 + } + } + } + } + return http.build() +} +---- + +XML:: ++ +[source,xml,role="secondary"] +---- + +... + + + + + + +---- +====== + +Using Spring Boot, you can test the above configurations in the following way: [tabs] ====== diff --git a/web/src/main/java/org/springframework/security/web/authentication/session/ConcurrentSessionControlAuthenticationStrategy.java b/web/src/main/java/org/springframework/security/web/authentication/session/ConcurrentSessionControlAuthenticationStrategy.java index b8f3c9e307..51be7bd0ab 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/session/ConcurrentSessionControlAuthenticationStrategy.java +++ b/web/src/main/java/org/springframework/security/web/authentication/session/ConcurrentSessionControlAuthenticationStrategy.java @@ -33,7 +33,6 @@ import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.session.ConcurrentSessionFilter; -import org.springframework.security.web.session.SessionLimit; import org.springframework.security.web.session.SessionManagementFilter; import org.springframework.util.Assert; diff --git a/web/src/main/java/org/springframework/security/web/session/SessionLimit.java b/web/src/main/java/org/springframework/security/web/authentication/session/SessionLimit.java similarity index 96% rename from web/src/main/java/org/springframework/security/web/session/SessionLimit.java rename to web/src/main/java/org/springframework/security/web/authentication/session/SessionLimit.java index 385c462137..362f3a7f7d 100644 --- a/web/src/main/java/org/springframework/security/web/session/SessionLimit.java +++ b/web/src/main/java/org/springframework/security/web/authentication/session/SessionLimit.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.security.web.session; +package org.springframework.security.web.authentication.session; import java.util.function.Function; diff --git a/web/src/test/java/org/springframework/security/web/authentication/session/ConcurrentSessionControlAuthenticationStrategyTests.java b/web/src/test/java/org/springframework/security/web/authentication/session/ConcurrentSessionControlAuthenticationStrategyTests.java index 26d4afe3ef..aa1bed6d8f 100644 --- a/web/src/test/java/org/springframework/security/web/authentication/session/ConcurrentSessionControlAuthenticationStrategyTests.java +++ b/web/src/test/java/org/springframework/security/web/authentication/session/ConcurrentSessionControlAuthenticationStrategyTests.java @@ -34,7 +34,6 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.session.SessionInformation; import org.springframework.security.core.session.SessionRegistry; -import org.springframework.security.web.session.SessionLimit; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; diff --git a/web/src/test/java/org/springframework/security/web/session/SessionLimitTests.java b/web/src/test/java/org/springframework/security/web/authentication/session/SessionLimitTests.java similarity index 96% rename from web/src/test/java/org/springframework/security/web/session/SessionLimitTests.java rename to web/src/test/java/org/springframework/security/web/authentication/session/SessionLimitTests.java index 01df1449d7..134f9f6e7a 100644 --- a/web/src/test/java/org/springframework/security/web/session/SessionLimitTests.java +++ b/web/src/test/java/org/springframework/security/web/authentication/session/SessionLimitTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.security.web.session; +package org.springframework.security.web.authentication.session; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; From a7f749311ad0f27d47814f7c66ade4baf1b1d9ff Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Dec 2024 03:51:27 +0000 Subject: [PATCH 015/132] Bump org.hibernate.orm:hibernate-core from 6.6.3.Final to 6.6.4.Final Bumps [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm) from 6.6.3.Final to 6.6.4.Final. - [Release notes](https://github.com/hibernate/hibernate-orm/releases) - [Changelog](https://github.com/hibernate/hibernate-orm/blob/6.6.4/changelog.txt) - [Commits](https://github.com/hibernate/hibernate-orm/compare/6.6.3...6.6.4) --- updated-dependencies: - dependency-name: org.hibernate.orm:hibernate-core dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: Daeho Kwon --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e79f42a258..fc19a1a3a6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -70,7 +70,7 @@ org-bouncycastle-bcprov-jdk15on = { module = "org.bouncycastle:bcprov-jdk18on", org-eclipse-jetty-jetty-server = { module = "org.eclipse.jetty:jetty-server", version.ref = "org-eclipse-jetty" } org-eclipse-jetty-jetty-servlet = { module = "org.eclipse.jetty:jetty-servlet", version.ref = "org-eclipse-jetty" } org-hamcrest = "org.hamcrest:hamcrest:2.2" -org-hibernate-orm-hibernate-core = "org.hibernate.orm:hibernate-core:6.6.3.Final" +org-hibernate-orm-hibernate-core = "org.hibernate.orm:hibernate-core:6.6.4.Final" org-hsqldb = "org.hsqldb:hsqldb:2.7.4" org-jetbrains-kotlin-kotlin-bom = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "org-jetbrains-kotlin" } org-jetbrains-kotlin-kotlin-gradle-plugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25" From 5cbf1d49bae76dd8e0c08dc6bbe485dcdaaaebf3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Dec 2024 03:51:20 +0000 Subject: [PATCH 016/132] Bump ch.qos.logback:logback-classic from 1.5.12 to 1.5.13 Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.5.12 to 1.5.13. - [Commits](https://github.com/qos-ch/logback/commits) --- updated-dependencies: - dependency-name: ch.qos.logback:logback-classic dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: Daeho Kwon --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fc19a1a3a6..c45c80b445 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,7 +17,7 @@ org-opensaml5 = "5.1.2" org-springframework = "6.2.1" [libraries] -ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.5.12" +ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.5.13" com-fasterxml-jackson-jackson-bom = "com.fasterxml.jackson:jackson-bom:2.18.2" com-google-inject-guice = "com.google.inject:guice:3.0" com-netflix-nebula-nebula-project-plugin = "com.netflix.nebula:nebula-project-plugin:8.2.0" From cbcad6e54636e8b1cb1d58c45d76a7d0ccd8902a Mon Sep 17 00:00:00 2001 From: Steven Williams Date: Thu, 14 Nov 2024 16:39:12 -0500 Subject: [PATCH 017/132] Set Saml2RelyingPartyInitiatedLogoutSuccessHandler#logoutRequestRepository Closes gh-16093 Signed-off-by: Daeho Kwon --- .../security/config/http/Saml2LogoutBeanDefinitionParser.java | 1 + 1 file changed, 1 insertion(+) diff --git a/config/src/main/java/org/springframework/security/config/http/Saml2LogoutBeanDefinitionParser.java b/config/src/main/java/org/springframework/security/config/http/Saml2LogoutBeanDefinitionParser.java index 860ed9fc55..24566458e1 100644 --- a/config/src/main/java/org/springframework/security/config/http/Saml2LogoutBeanDefinitionParser.java +++ b/config/src/main/java/org/springframework/security/config/http/Saml2LogoutBeanDefinitionParser.java @@ -146,6 +146,7 @@ public BeanDefinition parse(Element element, ParserContext pc) { BeanMetadataElement saml2LogoutRequestSuccessHandler = BeanDefinitionBuilder .rootBeanDefinition(Saml2RelyingPartyInitiatedLogoutSuccessHandler.class) .addConstructorArgValue(logoutRequestResolver) + .addPropertyValue("logoutRequestRepository", logoutRequestRepository) .getBeanDefinition(); this.logoutFilter = BeanDefinitionBuilder.rootBeanDefinition(LogoutFilter.class) .addConstructorArgValue(saml2LogoutRequestSuccessHandler) From e1a4717ba72a3c01984c6aa4e0093f79c2ee6c1f Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Thu, 19 Dec 2024 08:49:07 -0700 Subject: [PATCH 018/132] Test Setting logoutRequestRepository Issue gh-16093 Signed-off-by: Daeho Kwon --- .../saml2/Saml2LogoutConfigurerTests.java | 1 + .../Saml2LogoutBeanDefinitionParserTests.java | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LogoutConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LogoutConfigurerTests.java index 3957d416da..e13bddf707 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LogoutConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LogoutConfigurerTests.java @@ -484,6 +484,7 @@ public void saml2LogoutResponseWhenCustomLogoutResponseHandlerThenUses() throws verify(getBean(Saml2LogoutResponseValidator.class)).validate(any()); } + // gh-11363 @Test public void saml2LogoutWhenCustomLogoutRequestRepositoryThenUses() throws Exception { this.spring.register(Saml2LogoutComponentsConfig.class).autowire(); diff --git a/config/src/test/java/org/springframework/security/config/http/Saml2LogoutBeanDefinitionParserTests.java b/config/src/test/java/org/springframework/security/config/http/Saml2LogoutBeanDefinitionParserTests.java index 152525d4a2..d51349440a 100644 --- a/config/src/test/java/org/springframework/security/config/http/Saml2LogoutBeanDefinitionParserTests.java +++ b/config/src/test/java/org/springframework/security/config/http/Saml2LogoutBeanDefinitionParserTests.java @@ -63,6 +63,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.containsString; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.verify; @@ -380,6 +381,22 @@ public void saml2LogoutResponseWhenCustomLogoutResponseHandlerThenUses() throws verify(getBean(Saml2LogoutResponseValidator.class)).validate(any()); } + // gh-11363 + @Test + public void saml2LogoutWhenCustomLogoutRequestRepositoryThenUses() throws Exception { + this.spring.configLocations(this.xml("CustomComponents")).autowire(); + RelyingPartyRegistration registration = this.repository.findByRegistrationId("get"); + Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration) + .samlRequest(this.rpLogoutRequest) + .id(this.rpLogoutRequestId) + .relayState(this.rpLogoutRequestRelayState) + .parameters((params) -> params.put("Signature", this.rpLogoutRequestSignature)) + .build(); + given(getBean(Saml2LogoutRequestResolver.class).resolve(any(), any())).willReturn(logoutRequest); + this.mvc.perform(post("/logout").with(authentication(this.saml2User)).with(csrf())); + verify(getBean(Saml2LogoutRequestRepository.class)).saveLogoutRequest(eq(logoutRequest), any(), any()); + } + private T getBean(Class clazz) { return this.spring.getContext().getBean(clazz); } From 29a9341714d4eade086630464cc2e845d65590f8 Mon Sep 17 00:00:00 2001 From: Max Batischev Date: Tue, 17 Dec 2024 14:00:29 +0300 Subject: [PATCH 019/132] Polish SecurityFilterChain Validation Issue gh-15982 Signed-off-by: Daeho Kwon --- .../http/DefaultFilterChainValidator.java | 36 +++++++----- .../DefaultFilterChainValidatorTests.java | 23 ++++++++ ...UnreachableFilterChainException.serialized | Bin 0 -> 759 bytes .../web/UnreachableFilterChainException.java | 55 ++++++++++++++++++ .../web/util/matcher/AndRequestMatcher.java | 20 ++++++- .../web/util/matcher/OrRequestMatcher.java | 20 ++++++- 6 files changed, 139 insertions(+), 15 deletions(-) create mode 100644 config/src/test/resources/serialized/6.5.x/org.springframework.security.web.UnreachableFilterChainException.serialized create mode 100644 web/src/main/java/org/springframework/security/web/UnreachableFilterChainException.java diff --git a/config/src/main/java/org/springframework/security/config/http/DefaultFilterChainValidator.java b/config/src/main/java/org/springframework/security/config/http/DefaultFilterChainValidator.java index ce7c50be58..74b6fbb7ca 100644 --- a/config/src/main/java/org/springframework/security/config/http/DefaultFilterChainValidator.java +++ b/config/src/main/java/org/springframework/security/config/http/DefaultFilterChainValidator.java @@ -39,6 +39,7 @@ import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.UnreachableFilterChainException; import org.springframework.security.web.access.ExceptionTranslationFilter; import org.springframework.security.web.access.intercept.AuthorizationFilter; import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; @@ -53,7 +54,6 @@ import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter; import org.springframework.security.web.session.SessionManagementFilter; import org.springframework.security.web.util.matcher.AnyRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; public class DefaultFilterChainValidator implements FilterChainProxy.FilterChainValidator { @@ -75,25 +75,35 @@ private void checkPathOrder(List filterChains) { // Check that the universal pattern is listed at the end, if at all Iterator chains = filterChains.iterator(); while (chains.hasNext()) { - RequestMatcher matcher = ((DefaultSecurityFilterChain) chains.next()).getRequestMatcher(); - if (AnyRequestMatcher.INSTANCE.equals(matcher) && chains.hasNext()) { - throw new IllegalArgumentException("A universal match pattern ('/**') is defined " - + " before other patterns in the filter chain, causing them to be ignored. Please check the " - + "ordering in your namespace or FilterChainProxy bean configuration"); + if (chains.next() instanceof DefaultSecurityFilterChain securityFilterChain) { + if (AnyRequestMatcher.INSTANCE.equals(securityFilterChain.getRequestMatcher()) && chains.hasNext()) { + throw new UnreachableFilterChainException("A universal match pattern ('/**') is defined " + + " before other patterns in the filter chain, causing them to be ignored. Please check the " + + "ordering in your namespace or FilterChainProxy bean configuration", + securityFilterChain, chains.next()); + } } } } private void checkForDuplicateMatchers(List chains) { - while (chains.size() > 1) { - DefaultSecurityFilterChain chain = (DefaultSecurityFilterChain) chains.remove(0); - for (SecurityFilterChain test : chains) { - if (chain.getRequestMatcher().equals(((DefaultSecurityFilterChain) test).getRequestMatcher())) { - throw new IllegalArgumentException("The FilterChainProxy contains two filter chains using the" - + " matcher " + chain.getRequestMatcher() + ". If you are using multiple namespace " - + "elements, you must use a 'pattern' attribute to define the request patterns to which they apply."); + DefaultSecurityFilterChain filterChain = null; + for (SecurityFilterChain chain : chains) { + if (filterChain != null) { + if (chain instanceof DefaultSecurityFilterChain defaultChain) { + if (defaultChain.getRequestMatcher().equals(filterChain.getRequestMatcher())) { + throw new UnreachableFilterChainException( + "The FilterChainProxy contains two filter chains using the" + " matcher " + + defaultChain.getRequestMatcher() + + ". If you are using multiple namespace " + + "elements, you must use a 'pattern' attribute to define the request patterns to which they apply.", + defaultChain, chain); + } } } + if (chain instanceof DefaultSecurityFilterChain defaultChain) { + filterChain = defaultChain; + } } } diff --git a/config/src/test/java/org/springframework/security/config/http/DefaultFilterChainValidatorTests.java b/config/src/test/java/org/springframework/security/config/http/DefaultFilterChainValidatorTests.java index a5b899db48..f120b9d058 100644 --- a/config/src/test/java/org/springframework/security/config/http/DefaultFilterChainValidatorTests.java +++ b/config/src/test/java/org/springframework/security/config/http/DefaultFilterChainValidatorTests.java @@ -16,7 +16,9 @@ package org.springframework.security.config.http; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; @@ -33,6 +35,8 @@ import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.DefaultSecurityFilterChain; import org.springframework.security.web.FilterChainProxy; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.UnreachableFilterChainException; import org.springframework.security.web.access.ExceptionTranslationFilter; import org.springframework.security.web.access.intercept.AuthorizationFilter; import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource; @@ -40,9 +44,11 @@ import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; import org.springframework.security.web.authentication.AnonymousAuthenticationFilter; import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.AnyRequestMatcher; import org.springframework.test.util.ReflectionTestUtils; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.willThrow; @@ -130,4 +136,21 @@ public void validateCustomMetadataSource() { verify(customMetaDataSource, atLeastOnce()).getAttributes(any()); } + @Test + void validateWhenSameRequestMatchersArePresentThenUnreachableFilterChainException() { + AnonymousAuthenticationFilter authenticationFilter = mock(AnonymousAuthenticationFilter.class); + ExceptionTranslationFilter exceptionTranslationFilter = mock(ExceptionTranslationFilter.class); + SecurityFilterChain chain1 = new DefaultSecurityFilterChain(AntPathRequestMatcher.antMatcher("/api"), + authenticationFilter, exceptionTranslationFilter, this.authorizationInterceptor); + SecurityFilterChain chain2 = new DefaultSecurityFilterChain(AntPathRequestMatcher.antMatcher("/api"), + authenticationFilter, exceptionTranslationFilter, this.authorizationInterceptor); + List chains = new ArrayList<>(); + chains.add(chain2); + chains.add(chain1); + FilterChainProxy proxy = new FilterChainProxy(chains); + + assertThatExceptionOfType(UnreachableFilterChainException.class) + .isThrownBy(() -> this.validator.validate(proxy)); + } + } diff --git a/config/src/test/resources/serialized/6.5.x/org.springframework.security.web.UnreachableFilterChainException.serialized b/config/src/test/resources/serialized/6.5.x/org.springframework.security.web.UnreachableFilterChainException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..418c3b8ece1a2ae8506494fb0533fd7574991846 GIT binary patch literal 759 zcmZvaziSjh6vtmy^TU%E(P$AxA%%j)Zli&4A*dmdz?#B~HYsFo_ubwkv%8ZwGu~M! zf>>DDTlg2)3Ko`$jg26dmX`hr0pHx^_U?=W)4cb2-}m$5ADA=*cT37bqe&K7i$so{ zlJ-J_i6d#BhaGH&j|)PPv_(_lhEx`5tu17+-c3+jSr*WHfeIjq;cRP2SXhc#Jo-mG z8i+?M9yN|LjVRY+xa|I$b01#9M3-Q3S3DD;5=9nnDutO)_b78Y7PhMD(_8c7MeE&{ z0DuD)h5?UUVP%dL_~Gf)gWB!azx&|DAy^ULkFKvS)UO_#Ahz37cKld=e!X*Zx%2tk zDL5U&WFnkF3zuTA75OToisasAQ?x>=hiHt*n7c5-HLTx5eFtjBiezt_M8d?ioiSwK zSXby?YKdEO5)^O{5s+5+#g@OOYaN@sasOPRDRA zMRxd>*S!DOI>R@FBUCL%+b+{FOPRVcW;X9xo*)@M$(|6`{)(&DmcKX5aq~QYzea`w AHUIzs literal 0 HcmV?d00001 diff --git a/web/src/main/java/org/springframework/security/web/UnreachableFilterChainException.java b/web/src/main/java/org/springframework/security/web/UnreachableFilterChainException.java new file mode 100644 index 0000000000..ff697bd07d --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/UnreachableFilterChainException.java @@ -0,0 +1,55 @@ +/* + * Copyright 2002-2024 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; + +import org.springframework.security.core.SpringSecurityCoreVersion; + +/** + * Thrown if {@link SecurityFilterChain securityFilterChain} is not valid. + * + * @author Max Batischev + * @since 6.5 + */ +public class UnreachableFilterChainException extends IllegalArgumentException { + + private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; + + private final SecurityFilterChain filterChain; + + private final SecurityFilterChain unreachableFilterChain; + + /** + * Constructs an UnreachableFilterChainException with the specified + * message. + * @param message the detail message + */ + public UnreachableFilterChainException(String message, SecurityFilterChain filterChain, + SecurityFilterChain unreachableFilterChain) { + super(message); + this.filterChain = filterChain; + this.unreachableFilterChain = unreachableFilterChain; + } + + public SecurityFilterChain getFilterChain() { + return this.filterChain; + } + + public SecurityFilterChain getUnreachableFilterChain() { + return this.unreachableFilterChain; + } + +} diff --git a/web/src/main/java/org/springframework/security/web/util/matcher/AndRequestMatcher.java b/web/src/main/java/org/springframework/security/web/util/matcher/AndRequestMatcher.java index b28b69bbba..b585db8ece 100644 --- a/web/src/main/java/org/springframework/security/web/util/matcher/AndRequestMatcher.java +++ b/web/src/main/java/org/springframework/security/web/util/matcher/AndRequestMatcher.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; @@ -90,6 +91,23 @@ public MatchResult matcher(HttpServletRequest request) { return MatchResult.match(variables); } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + AndRequestMatcher that = (AndRequestMatcher) o; + return Objects.equals(this.requestMatchers, that.requestMatchers); + } + + @Override + public int hashCode() { + return Objects.hash(this.requestMatchers); + } + @Override public String toString() { return "And " + this.requestMatchers; diff --git a/web/src/main/java/org/springframework/security/web/util/matcher/OrRequestMatcher.java b/web/src/main/java/org/springframework/security/web/util/matcher/OrRequestMatcher.java index e3add8edf3..53c0af8d92 100644 --- a/web/src/main/java/org/springframework/security/web/util/matcher/OrRequestMatcher.java +++ b/web/src/main/java/org/springframework/security/web/util/matcher/OrRequestMatcher.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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,6 +18,7 @@ import java.util.Arrays; import java.util.List; +import java.util.Objects; import jakarta.servlet.http.HttpServletRequest; @@ -81,6 +82,23 @@ public MatchResult matcher(HttpServletRequest request) { return MatchResult.notMatch(); } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + OrRequestMatcher that = (OrRequestMatcher) o; + return Objects.equals(this.requestMatchers, that.requestMatchers); + } + + @Override + public int hashCode() { + return Objects.hash(this.requestMatchers); + } + @Override public String toString() { return "Or " + this.requestMatchers; From 1a497a622a4ba60dbe61dbeec0a4993e71abcefa Mon Sep 17 00:00:00 2001 From: Max Batischev Date: Tue, 17 Dec 2024 14:00:47 +0300 Subject: [PATCH 020/132] Add Support Same Request Matchers Checking Closes gh-15982 Signed-off-by: Daeho Kwon --- .../WebSecurityFilterChainValidator.java | 29 +++++- .../WebSecurityFilterChainValidatorTests.java | 88 +++++++++++++++++++ .../WebSecurityConfigurationTests.java | 2 +- 3 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 config/src/test/java/org/springframework/security/config/annotation/web/builders/WebSecurityFilterChainValidatorTests.java diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/builders/WebSecurityFilterChainValidator.java b/config/src/main/java/org/springframework/security/config/annotation/web/builders/WebSecurityFilterChainValidator.java index cc11cdef40..5e069a446e 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/builders/WebSecurityFilterChainValidator.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/builders/WebSecurityFilterChainValidator.java @@ -21,11 +21,14 @@ import org.springframework.security.web.DefaultSecurityFilterChain; import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.UnreachableFilterChainException; import org.springframework.security.web.util.matcher.AnyRequestMatcher; /** * A filter chain validator for filter chains built by {@link WebSecurity} * + * @author Josh Cummings + * @author Max Batischev * @since 6.5 */ final class WebSecurityFilterChainValidator implements FilterChainProxy.FilterChainValidator { @@ -33,13 +36,18 @@ final class WebSecurityFilterChainValidator implements FilterChainProxy.FilterCh @Override public void validate(FilterChainProxy filterChainProxy) { List chains = filterChainProxy.getFilterChains(); + checkForAnyRequestRequestMatcher(chains); + checkForDuplicateMatchers(chains); + } + + private void checkForAnyRequestRequestMatcher(List chains) { DefaultSecurityFilterChain anyRequestFilterChain = null; for (SecurityFilterChain chain : chains) { if (anyRequestFilterChain != null) { String message = "A filter chain that matches any request [" + anyRequestFilterChain + "] has already been configured, which means that this filter chain [" + chain + "] will never get invoked. Please use `HttpSecurity#securityMatcher` to ensure that there is only one filter chain configured for 'any request' and that the 'any request' filter chain is published last."; - throw new IllegalArgumentException(message); + throw new UnreachableFilterChainException(message, anyRequestFilterChain, chain); } if (chain instanceof DefaultSecurityFilterChain defaultChain) { if (defaultChain.getRequestMatcher() instanceof AnyRequestMatcher) { @@ -49,4 +57,23 @@ public void validate(FilterChainProxy filterChainProxy) { } } + private void checkForDuplicateMatchers(List chains) { + DefaultSecurityFilterChain filterChain = null; + for (SecurityFilterChain chain : chains) { + if (filterChain != null) { + if (chain instanceof DefaultSecurityFilterChain defaultChain) { + if (defaultChain.getRequestMatcher().equals(filterChain.getRequestMatcher())) { + throw new UnreachableFilterChainException( + "The FilterChainProxy contains two filter chains using the" + " matcher " + + defaultChain.getRequestMatcher(), + filterChain, defaultChain); + } + } + } + if (chain instanceof DefaultSecurityFilterChain defaultChain) { + filterChain = defaultChain; + } + } + } + } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/builders/WebSecurityFilterChainValidatorTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/builders/WebSecurityFilterChainValidatorTests.java new file mode 100644 index 0000000000..edc0be90a6 --- /dev/null +++ b/config/src/test/java/org/springframework/security/config/annotation/web/builders/WebSecurityFilterChainValidatorTests.java @@ -0,0 +1,88 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.config.annotation.web.builders; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import org.springframework.security.web.DefaultSecurityFilterChain; +import org.springframework.security.web.FilterChainProxy; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.UnreachableFilterChainException; +import org.springframework.security.web.access.ExceptionTranslationFilter; +import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; +import org.springframework.security.web.authentication.AnonymousAuthenticationFilter; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.util.matcher.AnyRequestMatcher; + +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +/** + * Tests for {@link WebSecurityFilterChainValidator} + * + * @author Max Batischev + */ +@ExtendWith(MockitoExtension.class) +public class WebSecurityFilterChainValidatorTests { + + private final WebSecurityFilterChainValidator validator = new WebSecurityFilterChainValidator(); + + @Mock + private AnonymousAuthenticationFilter authenticationFilter; + + @Mock + private ExceptionTranslationFilter exceptionTranslationFilter; + + @Mock + private FilterSecurityInterceptor authorizationInterceptor; + + @Test + void validateWhenAnyRequestMatcherIsPresentThenUnreachableFilterChainException() { + SecurityFilterChain chain1 = new DefaultSecurityFilterChain(AntPathRequestMatcher.antMatcher("/api"), + this.authenticationFilter, this.exceptionTranslationFilter, this.authorizationInterceptor); + SecurityFilterChain chain2 = new DefaultSecurityFilterChain(AnyRequestMatcher.INSTANCE, + this.authenticationFilter, this.exceptionTranslationFilter, this.authorizationInterceptor); + List chains = new ArrayList<>(); + chains.add(chain2); + chains.add(chain1); + FilterChainProxy proxy = new FilterChainProxy(chains); + + assertThatExceptionOfType(UnreachableFilterChainException.class) + .isThrownBy(() -> this.validator.validate(proxy)); + } + + @Test + void validateWhenSameRequestMatchersArePresentThenUnreachableFilterChainException() { + SecurityFilterChain chain1 = new DefaultSecurityFilterChain(AntPathRequestMatcher.antMatcher("/api"), + this.authenticationFilter, this.exceptionTranslationFilter, this.authorizationInterceptor); + SecurityFilterChain chain2 = new DefaultSecurityFilterChain(AntPathRequestMatcher.antMatcher("/api"), + this.authenticationFilter, this.exceptionTranslationFilter, this.authorizationInterceptor); + List chains = new ArrayList<>(); + chains.add(chain2); + chains.add(chain1); + FilterChainProxy proxy = new FilterChainProxy(chains); + + assertThatExceptionOfType(UnreachableFilterChainException.class) + .isThrownBy(() -> this.validator.validate(proxy)); + } + +} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurationTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurationTests.java index 326e2bda10..f6a53bff45 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurationTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurationTests.java @@ -323,7 +323,7 @@ public void loadConfigWhenTwoSecurityFilterChainsPresentAndSecondWithAnyRequestT assertThatExceptionOfType(BeanCreationException.class) .isThrownBy(() -> this.spring.register(MultipleAnyRequestSecurityFilterChainConfig.class).autowire()) .havingRootCause() - .isExactlyInstanceOf(IllegalArgumentException.class); + .isInstanceOf(IllegalArgumentException.class); } private void assertAnotherUserPermission(WebInvocationPrivilegeEvaluator privilegeEvaluator) { From 4b4fcc8d54afbbe41cb4e1c7c910f023962b7f1c Mon Sep 17 00:00:00 2001 From: Max Batischev Date: Thu, 19 Dec 2024 18:17:16 +0300 Subject: [PATCH 021/132] Add Alerting About Deprecated Authorize Config Closes gh-16213 Signed-off-by: Daeho Kwon --- .../WebSecurityFilterChainValidator.java | 34 +++++++++++++++++++ .../http/DefaultFilterChainValidator.java | 26 ++++++++++++++ .../WebSecurityFilterChainValidatorTests.java | 10 ++++++ .../DefaultFilterChainValidatorTests.java | 6 ++++ 4 files changed, 76 insertions(+) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/builders/WebSecurityFilterChainValidator.java b/config/src/main/java/org/springframework/security/config/annotation/web/builders/WebSecurityFilterChainValidator.java index 5e069a446e..b0bf43fb3b 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/builders/WebSecurityFilterChainValidator.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/builders/WebSecurityFilterChainValidator.java @@ -18,10 +18,16 @@ import java.util.List; +import jakarta.servlet.Filter; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + import org.springframework.security.web.DefaultSecurityFilterChain; import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.UnreachableFilterChainException; +import org.springframework.security.web.access.intercept.AuthorizationFilter; +import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; import org.springframework.security.web.util.matcher.AnyRequestMatcher; /** @@ -33,11 +39,14 @@ */ final class WebSecurityFilterChainValidator implements FilterChainProxy.FilterChainValidator { + private final Log logger = LogFactory.getLog(getClass()); + @Override public void validate(FilterChainProxy filterChainProxy) { List chains = filterChainProxy.getFilterChains(); checkForAnyRequestRequestMatcher(chains); checkForDuplicateMatchers(chains); + checkAuthorizationFilters(chains); } private void checkForAnyRequestRequestMatcher(List chains) { @@ -76,4 +85,29 @@ private void checkForDuplicateMatchers(List chains) { } } + private void checkAuthorizationFilters(List chains) { + Filter authorizationFilter = null; + Filter filterSecurityInterceptor = null; + for (SecurityFilterChain chain : chains) { + for (Filter filter : chain.getFilters()) { + if (filter instanceof AuthorizationFilter) { + authorizationFilter = filter; + } + if (filter instanceof FilterSecurityInterceptor) { + filterSecurityInterceptor = filter; + } + } + if (authorizationFilter != null && filterSecurityInterceptor != null) { + this.logger.warn( + "It is not recommended to use authorizeRequests in the configuration. Please only use authorizeHttpRequests"); + } + if (filterSecurityInterceptor != null) { + this.logger.warn( + "Usage of authorizeRequests is deprecated. Please use authorizeHttpRequests in the configuration"); + } + authorizationFilter = null; + filterSecurityInterceptor = null; + } + } + } diff --git a/config/src/main/java/org/springframework/security/config/http/DefaultFilterChainValidator.java b/config/src/main/java/org/springframework/security/config/http/DefaultFilterChainValidator.java index 74b6fbb7ca..8f2baeb4c6 100644 --- a/config/src/main/java/org/springframework/security/config/http/DefaultFilterChainValidator.java +++ b/config/src/main/java/org/springframework/security/config/http/DefaultFilterChainValidator.java @@ -69,6 +69,7 @@ public void validate(FilterChainProxy fcp) { } checkPathOrder(new ArrayList<>(fcp.getFilterChains())); checkForDuplicateMatchers(new ArrayList<>(fcp.getFilterChains())); + checkAuthorizationFilters(new ArrayList<>(fcp.getFilterChains())); } private void checkPathOrder(List filterChains) { @@ -107,6 +108,31 @@ private void checkForDuplicateMatchers(List chains) { } } + private void checkAuthorizationFilters(List chains) { + Filter authorizationFilter = null; + Filter filterSecurityInterceptor = null; + for (SecurityFilterChain chain : chains) { + for (Filter filter : chain.getFilters()) { + if (filter instanceof AuthorizationFilter) { + authorizationFilter = filter; + } + if (filter instanceof FilterSecurityInterceptor) { + filterSecurityInterceptor = filter; + } + } + if (authorizationFilter != null && filterSecurityInterceptor != null) { + this.logger.warn( + "It is not recommended to use authorizeRequests in the configuration. Please only use authorizeHttpRequests"); + } + if (filterSecurityInterceptor != null) { + this.logger.warn( + "Usage of authorizeRequests is deprecated. Please use authorizeHttpRequests in the configuration"); + } + authorizationFilter = null; + filterSecurityInterceptor = null; + } + } + @SuppressWarnings({ "unchecked" }) private F getFilter(Class type, List filters) { for (Filter f : filters) { diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/builders/WebSecurityFilterChainValidatorTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/builders/WebSecurityFilterChainValidatorTests.java index edc0be90a6..2e6984ab9b 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/builders/WebSecurityFilterChainValidatorTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/builders/WebSecurityFilterChainValidatorTests.java @@ -35,6 +35,7 @@ import org.springframework.security.web.util.matcher.AnyRequestMatcher; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatNoException; /** * Tests for {@link WebSecurityFilterChainValidator} @@ -55,6 +56,15 @@ public class WebSecurityFilterChainValidatorTests { @Mock private FilterSecurityInterceptor authorizationInterceptor; + @Test + void validateWhenFilterSecurityInterceptorConfiguredThenValidates() { + SecurityFilterChain chain = new DefaultSecurityFilterChain(AntPathRequestMatcher.antMatcher("/api"), + this.authenticationFilter, this.exceptionTranslationFilter, this.authorizationInterceptor); + FilterChainProxy proxy = new FilterChainProxy(List.of(chain)); + + assertThatNoException().isThrownBy(() -> this.validator.validate(proxy)); + } + @Test void validateWhenAnyRequestMatcherIsPresentThenUnreachableFilterChainException() { SecurityFilterChain chain1 = new DefaultSecurityFilterChain(AntPathRequestMatcher.antMatcher("/api"), diff --git a/config/src/test/java/org/springframework/security/config/http/DefaultFilterChainValidatorTests.java b/config/src/test/java/org/springframework/security/config/http/DefaultFilterChainValidatorTests.java index f120b9d058..d75ce815d5 100644 --- a/config/src/test/java/org/springframework/security/config/http/DefaultFilterChainValidatorTests.java +++ b/config/src/test/java/org/springframework/security/config/http/DefaultFilterChainValidatorTests.java @@ -49,6 +49,7 @@ import org.springframework.test.util.ReflectionTestUtils; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatNoException; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.willThrow; @@ -103,6 +104,11 @@ public void setUp() { ReflectionTestUtils.setField(this.validator, "logger", this.logger); } + @Test + void validateWhenFilterSecurityInterceptorConfiguredThenValidates() { + assertThatNoException().isThrownBy(() -> this.validator.validate(this.chain)); + } + // SEC-1878 @SuppressWarnings("unchecked") @Test From 848c31eb5f26cc83e85a3e58636ecbf7910c7245 Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Thu, 19 Dec 2024 14:40:50 -0700 Subject: [PATCH 022/132] Add Filter Chain Validation Test Issue gh-15982 Signed-off-by: Daeho Kwon --- .../WebSecurityFilterChainValidatorTests.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/builders/WebSecurityFilterChainValidatorTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/builders/WebSecurityFilterChainValidatorTests.java index 2e6984ab9b..450a3dfdc1 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/builders/WebSecurityFilterChainValidatorTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/builders/WebSecurityFilterChainValidatorTests.java @@ -33,6 +33,8 @@ import org.springframework.security.web.authentication.AnonymousAuthenticationFilter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.AnyRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatchers; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatNoException; @@ -95,4 +97,23 @@ void validateWhenSameRequestMatchersArePresentThenUnreachableFilterChainExceptio .isThrownBy(() -> this.validator.validate(proxy)); } + @Test + void validateWhenSameComposedRequestMatchersArePresentThenUnreachableFilterChainException() { + RequestMatcher matcher1 = RequestMatchers.anyOf(RequestMatchers.allOf(AntPathRequestMatcher.antMatcher("/api"), + AntPathRequestMatcher.antMatcher("*.do")), AntPathRequestMatcher.antMatcher("/admin")); + RequestMatcher matcher2 = RequestMatchers.anyOf(RequestMatchers.allOf(AntPathRequestMatcher.antMatcher("/api"), + AntPathRequestMatcher.antMatcher("*.do")), AntPathRequestMatcher.antMatcher("/admin")); + SecurityFilterChain chain1 = new DefaultSecurityFilterChain(matcher1, this.authenticationFilter, + this.exceptionTranslationFilter, this.authorizationInterceptor); + SecurityFilterChain chain2 = new DefaultSecurityFilterChain(matcher2, this.authenticationFilter, + this.exceptionTranslationFilter, this.authorizationInterceptor); + List chains = new ArrayList<>(); + chains.add(chain2); + chains.add(chain1); + FilterChainProxy proxy = new FilterChainProxy(chains); + + assertThatExceptionOfType(UnreachableFilterChainException.class) + .isThrownBy(() -> this.validator.validate(proxy)); + } + } From 53b81be6ca4cf0b2f2d8154459a272e196327ab0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 9 Dec 2024 01:10:41 +0000 Subject: [PATCH 023/132] Support Meta-Annotation Parameters on Parameter Annotations Closes gh-16248 Signed-off-by: Daeho Kwon --- .../UniqueSecurityAnnotationScanner.java | 54 ++++++++- .../UniqueSecurityAnnotationScannerTests.java | 104 ++++++++++++++++++ 2 files changed, 157 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/springframework/security/core/annotation/UniqueSecurityAnnotationScanner.java b/core/src/main/java/org/springframework/security/core/annotation/UniqueSecurityAnnotationScanner.java index 5093e1bd1d..e3f5c9297c 100644 --- a/core/src/main/java/org/springframework/security/core/annotation/UniqueSecurityAnnotationScanner.java +++ b/core/src/main/java/org/springframework/security/core/annotation/UniqueSecurityAnnotationScanner.java @@ -18,6 +18,7 @@ import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Executable; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.ArrayList; @@ -83,6 +84,7 @@ * * @param the annotation to search for and synthesize * @author Josh Cummings + * @author DingHao * @since 6.4 */ final class UniqueSecurityAnnotationScanner extends AbstractSecurityAnnotationScanner { @@ -107,7 +109,7 @@ final class UniqueSecurityAnnotationScanner extends Abstra MergedAnnotation merge(AnnotatedElement element, Class targetClass) { if (element instanceof Parameter parameter) { return this.uniqueParameterAnnotationCache.computeIfAbsent(parameter, (p) -> { - List> annotations = findDirectAnnotations(p); + List> annotations = findParameterAnnotations(p); return requireUnique(p, annotations); }); } @@ -137,6 +139,56 @@ private MergedAnnotation requireUnique(AnnotatedElement element, List> findParameterAnnotations(Parameter current) { + List> directAnnotations = findDirectAnnotations(current); + if (!directAnnotations.isEmpty()) { + return directAnnotations; + } + Executable executable = current.getDeclaringExecutable(); + if (executable instanceof Method method) { + Class clazz = method.getDeclaringClass(); + Set> visited = new HashSet<>(); + while (clazz != null && clazz != Object.class) { + directAnnotations = findClosestParameterAnnotations(method, clazz, current, visited); + if (!directAnnotations.isEmpty()) { + return directAnnotations; + } + clazz = clazz.getSuperclass(); + } + } + return Collections.emptyList(); + } + + private List> findClosestParameterAnnotations(Method method, Class clazz, Parameter current, + Set> visited) { + if (!visited.add(clazz)) { + return Collections.emptyList(); + } + List> annotations = new ArrayList<>(findDirectParameterAnnotations(method, clazz, current)); + for (Class ifc : clazz.getInterfaces()) { + annotations.addAll(findClosestParameterAnnotations(method, ifc, current, visited)); + } + return annotations; + } + + private List> findDirectParameterAnnotations(Method method, Class clazz, Parameter current) { + try { + Method methodToUse = clazz.getDeclaredMethod(method.getName(), method.getParameterTypes()); + for (Parameter parameter : methodToUse.getParameters()) { + if (parameter.getName().equals(current.getName())) { + List> directAnnotations = findDirectAnnotations(parameter); + if (!directAnnotations.isEmpty()) { + return directAnnotations; + } + } + } + } + catch (NoSuchMethodException ex) { + // move on + } + return Collections.emptyList(); + } + private List> findMethodAnnotations(Method method, Class targetClass) { // The method may be on an interface, but we need attributes from the target // class. diff --git a/core/src/test/java/org/springframework/security/core/annotation/UniqueSecurityAnnotationScannerTests.java b/core/src/test/java/org/springframework/security/core/annotation/UniqueSecurityAnnotationScannerTests.java index b1a7a779aa..976e0879ab 100644 --- a/core/src/test/java/org/springframework/security/core/annotation/UniqueSecurityAnnotationScannerTests.java +++ b/core/src/test/java/org/springframework/security/core/annotation/UniqueSecurityAnnotationScannerTests.java @@ -16,7 +16,13 @@ package org.springframework.security.core.annotation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.List; import org.junit.jupiter.api.Test; @@ -34,6 +40,9 @@ public class UniqueSecurityAnnotationScannerTests { private UniqueSecurityAnnotationScanner scanner = new UniqueSecurityAnnotationScanner<>( PreAuthorize.class); + private UniqueSecurityAnnotationScanner parameterScanner = new UniqueSecurityAnnotationScanner<>( + CustomParameterAnnotation.class); + @Test void scanWhenAnnotationOnInterfaceThenResolves() throws Exception { Method method = AnnotationOnInterface.class.getDeclaredMethod("method"); @@ -251,6 +260,101 @@ void scanWhenClassInheritingAbstractClassNoAnnotationsThenNoAnnotation() throws assertThat(preAuthorize).isNull(); } + @Test + void scanParameterAnnotationWhenAnnotationOnInterface() throws Exception { + Parameter parameter = UserService.class.getDeclaredMethod("add", String.class).getParameters()[0]; + CustomParameterAnnotation customParameterAnnotation = this.parameterScanner.scan(parameter); + assertThat(customParameterAnnotation.value()).isEqualTo("one"); + } + + @Test + void scanParameterAnnotationWhenClassInheritingInterfaceAnnotation() throws Exception { + Parameter parameter = UserServiceImpl.class.getDeclaredMethod("add", String.class).getParameters()[0]; + CustomParameterAnnotation customParameterAnnotation = this.parameterScanner.scan(parameter); + assertThat(customParameterAnnotation.value()).isEqualTo("one"); + } + + @Test + void scanParameterAnnotationWhenClassOverridingMethodOverridingInterface() throws Exception { + Parameter parameter = UserServiceImpl.class.getDeclaredMethod("get", String.class).getParameters()[0]; + CustomParameterAnnotation customParameterAnnotation = this.parameterScanner.scan(parameter); + assertThat(customParameterAnnotation.value()).isEqualTo("five"); + } + + @Test + void scanParameterAnnotationWhenMultipleMethodInheritanceThenException() throws Exception { + Parameter parameter = UserServiceImpl.class.getDeclaredMethod("list", String.class).getParameters()[0]; + assertThatExceptionOfType(AnnotationConfigurationException.class) + .isThrownBy(() -> this.parameterScanner.scan(parameter)); + } + + @Test + void scanParameterAnnotationWhenInterfaceNoAnnotationsThenException() throws Exception { + Parameter parameter = UserServiceImpl.class.getDeclaredMethod("delete", String.class).getParameters()[0]; + assertThatExceptionOfType(AnnotationConfigurationException.class) + .isThrownBy(() -> this.parameterScanner.scan(parameter)); + } + + interface UserService { + + void add(@CustomParameterAnnotation("one") String user); + + List list(@CustomParameterAnnotation("two") String user); + + String get(@CustomParameterAnnotation("three") String user); + + void delete(@CustomParameterAnnotation("five") String user); + + } + + interface OtherUserService { + + List list(@CustomParameterAnnotation("four") String user); + + } + + interface ThirdPartyUserService { + + void delete(@CustomParameterAnnotation("five") String user); + + } + + interface RemoteUserService extends ThirdPartyUserService { + + } + + static class UserServiceImpl implements UserService, OtherUserService, RemoteUserService { + + @Override + public void add(String user) { + + } + + @Override + public List list(String user) { + return List.of(user); + } + + @Override + public String get(@CustomParameterAnnotation("five") String user) { + return user; + } + + @Override + public void delete(String user) { + + } + + } + + @Target({ ElementType.PARAMETER }) + @Retention(RetentionPolicy.RUNTIME) + @interface CustomParameterAnnotation { + + String value(); + + } + @PreAuthorize("one") private interface AnnotationOnInterface { From 2e20fff5996f74f991ec51c75142be72ad8b42d3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Dec 2024 03:46:22 +0000 Subject: [PATCH 024/132] Bump ch.qos.logback:logback-classic from 1.5.13 to 1.5.14 Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.5.13 to 1.5.14. - [Commits](https://github.com/qos-ch/logback/compare/v_1.5.13...v_1.5.14) --- updated-dependencies: - dependency-name: ch.qos.logback:logback-classic dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: Daeho Kwon --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c45c80b445..de4de91e0e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,7 +17,7 @@ org-opensaml5 = "5.1.2" org-springframework = "6.2.1" [libraries] -ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.5.13" +ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.5.14" com-fasterxml-jackson-jackson-bom = "com.fasterxml.jackson:jackson-bom:2.18.2" com-google-inject-guice = "com.google.inject:guice:3.0" com-netflix-nebula-nebula-project-plugin = "com.netflix.nebula:nebula-project-plugin:8.2.0" From e3c4c8a61fc1b9f1b18b6d63b5c7194f4638e234 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Dec 2024 04:02:01 +0000 Subject: [PATCH 025/132] Bump org.jetbrains.kotlinx:kotlinx-coroutines-bom from 1.9.0 to 1.10.0 Bumps [org.jetbrains.kotlinx:kotlinx-coroutines-bom](https://github.com/Kotlin/kotlinx.coroutines) from 1.9.0 to 1.10.0. - [Release notes](https://github.com/Kotlin/kotlinx.coroutines/releases) - [Changelog](https://github.com/Kotlin/kotlinx.coroutines/blob/master/CHANGES.md) - [Commits](https://github.com/Kotlin/kotlinx.coroutines/compare/1.9.0...1.10.0) --- updated-dependencies: - dependency-name: org.jetbrains.kotlinx:kotlinx-coroutines-bom dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: Daeho Kwon --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index de4de91e0e..7de738f473 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,7 +10,7 @@ org-aspectj = "1.9.22.1" org-bouncycastle = "1.79" org-eclipse-jetty = "11.0.24" org-jetbrains-kotlin = "1.9.25" -org-jetbrains-kotlinx = "1.9.0" +org-jetbrains-kotlinx = "1.10.0" org-mockito = "5.14.2" org-opensaml = "4.3.2" org-opensaml5 = "5.1.2" From 5eb8de6a9bb894cbbd3fe57881a6d75a3812c98f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Dec 2024 04:02:06 +0000 Subject: [PATCH 026/132] Bump org.assertj:assertj-core from 3.26.3 to 3.27.0 Bumps [org.assertj:assertj-core](https://github.com/assertj/assertj) from 3.26.3 to 3.27.0. - [Release notes](https://github.com/assertj/assertj/releases) - [Commits](https://github.com/assertj/assertj/compare/assertj-build-3.26.3...assertj-build-3.27.0) --- updated-dependencies: - dependency-name: org.assertj:assertj-core dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: Daeho Kwon --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7de738f473..6dd0c0fd15 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -64,7 +64,7 @@ org-apereo-cas-client-cas-client-core = "org.apereo.cas.client:cas-client-core:4 io-freefair-gradle-aspectj-plugin = "io.freefair.gradle:aspectj-plugin:8.11" org-aspectj-aspectjrt = { module = "org.aspectj:aspectjrt", version.ref = "org-aspectj" } org-aspectj-aspectjweaver = { module = "org.aspectj:aspectjweaver", version.ref = "org-aspectj" } -org-assertj-assertj-core = "org.assertj:assertj-core:3.26.3" +org-assertj-assertj-core = "org.assertj:assertj-core:3.27.0" org-bouncycastle-bcpkix-jdk15on = { module = "org.bouncycastle:bcpkix-jdk18on", version.ref = "org-bouncycastle" } org-bouncycastle-bcprov-jdk15on = { module = "org.bouncycastle:bcprov-jdk18on", version.ref = "org-bouncycastle" } org-eclipse-jetty-jetty-server = { module = "org.eclipse.jetty:jetty-server", version.ref = "org-eclipse-jetty" } From 795f1e238a4fba384807b86527b164127e73badc Mon Sep 17 00:00:00 2001 From: Max Batischev Date: Fri, 20 Dec 2024 11:02:24 +0300 Subject: [PATCH 027/132] Remove Unused loggers Closes gh-16319 Signed-off-by: Daeho Kwon --- .../security/web/util/matcher/AndRequestMatcher.java | 4 ---- .../security/web/util/matcher/NegatedRequestMatcher.java | 4 ---- 2 files changed, 8 deletions(-) diff --git a/web/src/main/java/org/springframework/security/web/util/matcher/AndRequestMatcher.java b/web/src/main/java/org/springframework/security/web/util/matcher/AndRequestMatcher.java index b585db8ece..0630629cf2 100644 --- a/web/src/main/java/org/springframework/security/web/util/matcher/AndRequestMatcher.java +++ b/web/src/main/java/org/springframework/security/web/util/matcher/AndRequestMatcher.java @@ -23,8 +23,6 @@ import java.util.Objects; import jakarta.servlet.http.HttpServletRequest; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.springframework.util.Assert; @@ -37,8 +35,6 @@ */ public final class AndRequestMatcher implements RequestMatcher { - private final Log logger = LogFactory.getLog(getClass()); - private final List requestMatchers; /** diff --git a/web/src/main/java/org/springframework/security/web/util/matcher/NegatedRequestMatcher.java b/web/src/main/java/org/springframework/security/web/util/matcher/NegatedRequestMatcher.java index 61da5dca62..60b7c32647 100644 --- a/web/src/main/java/org/springframework/security/web/util/matcher/NegatedRequestMatcher.java +++ b/web/src/main/java/org/springframework/security/web/util/matcher/NegatedRequestMatcher.java @@ -17,8 +17,6 @@ package org.springframework.security.web.util.matcher; import jakarta.servlet.http.HttpServletRequest; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.springframework.util.Assert; @@ -33,8 +31,6 @@ */ public class NegatedRequestMatcher implements RequestMatcher { - private final Log logger = LogFactory.getLog(getClass()); - private final RequestMatcher requestMatcher; /** From 32c416df6be3261afc5db0b1b49004a5f8c2a754 Mon Sep 17 00:00:00 2001 From: Max Batischev Date: Fri, 13 Dec 2024 17:27:40 +0300 Subject: [PATCH 028/132] Add Support JdbcUserCredentialRepository Closes gh-16224 Signed-off-by: Daeho Kwon --- .../aot/hint/UserCredentialRuntimeHints.java | 40 +++ .../JdbcUserCredentialRepository.java | 305 ++++++++++++++++++ .../resources/META-INF/spring/aot.factories | 3 +- .../security/user-credentials-schema.sql | 18 ++ .../hint/UserCredentialRuntimeHintsTests.java | 59 ++++ .../webauthn/api/TestCredentialRecord.java | 21 ++ .../JdbcUserCredentialRepositoryTests.java | 180 +++++++++++ 7 files changed, 625 insertions(+), 1 deletion(-) create mode 100644 web/src/main/java/org/springframework/security/web/aot/hint/UserCredentialRuntimeHints.java create mode 100644 web/src/main/java/org/springframework/security/web/webauthn/management/JdbcUserCredentialRepository.java create mode 100644 web/src/main/resources/org/springframework/security/user-credentials-schema.sql create mode 100644 web/src/test/java/org/springframework/security/web/aot/hint/UserCredentialRuntimeHintsTests.java create mode 100644 web/src/test/java/org/springframework/security/web/webauthn/management/JdbcUserCredentialRepositoryTests.java diff --git a/web/src/main/java/org/springframework/security/web/aot/hint/UserCredentialRuntimeHints.java b/web/src/main/java/org/springframework/security/web/aot/hint/UserCredentialRuntimeHints.java new file mode 100644 index 0000000000..c3b4c95a14 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/aot/hint/UserCredentialRuntimeHints.java @@ -0,0 +1,40 @@ +/* + * Copyright 2002-2024 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.aot.hint; + +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.jdbc.core.JdbcOperations; +import org.springframework.security.web.webauthn.api.CredentialRecord; +import org.springframework.security.web.webauthn.management.UserCredentialRepository; + +/** + * + * A JDBC implementation of an {@link UserCredentialRepository} that uses a + * {@link JdbcOperations} for {@link CredentialRecord} persistence. + * + * @author Max Batischev + * @since 6.5 + */ +class UserCredentialRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + hints.resources().registerPattern("org/springframework/security/user-credentials-schema.sql"); + } + +} diff --git a/web/src/main/java/org/springframework/security/web/webauthn/management/JdbcUserCredentialRepository.java b/web/src/main/java/org/springframework/security/web/webauthn/management/JdbcUserCredentialRepository.java new file mode 100644 index 0000000000..aa012d6964 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/webauthn/management/JdbcUserCredentialRepository.java @@ -0,0 +1,305 @@ +/* + * Copyright 2002-2024 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.webauthn.management; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.sql.Types; +import java.time.Instant; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Function; + +import org.springframework.jdbc.core.ArgumentPreparedStatementSetter; +import org.springframework.jdbc.core.JdbcOperations; +import org.springframework.jdbc.core.PreparedStatementSetter; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.SqlParameterValue; +import org.springframework.jdbc.support.lob.DefaultLobHandler; +import org.springframework.jdbc.support.lob.LobCreator; +import org.springframework.jdbc.support.lob.LobHandler; +import org.springframework.security.web.webauthn.api.AuthenticatorTransport; +import org.springframework.security.web.webauthn.api.Bytes; +import org.springframework.security.web.webauthn.api.CredentialRecord; +import org.springframework.security.web.webauthn.api.ImmutableCredentialRecord; +import org.springframework.security.web.webauthn.api.ImmutablePublicKeyCose; +import org.springframework.security.web.webauthn.api.PublicKeyCredentialType; +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; + +/** + * A JDBC implementation of an {@link UserCredentialRepository} that uses a + * {@link JdbcOperations} for {@link CredentialRecord} persistence. + * + * NOTE: This {@code UserCredentialRepository} depends on the table definition + * described in "classpath:org/springframework/security/user-credentials-schema.sql" and + * therefore MUST be defined in the database schema. + * + * @author Max Batischev + * @since 6.5 + * @see UserCredentialRepository + * @see CredentialRecord + * @see JdbcOperations + * @see RowMapper + */ +public final class JdbcUserCredentialRepository implements UserCredentialRepository { + + private RowMapper credentialRecordRowMapper = new CredentialRecordRowMapper(); + + private Function> credentialRecordParametersMapper = new CredentialRecordParametersMapper(); + + private LobHandler lobHandler = new DefaultLobHandler(); + + private final JdbcOperations jdbcOperations; + + private static final String TABLE_NAME = "user_credentials"; + + // @formatter:off + private static final String COLUMN_NAMES = "credential_id, " + + "user_entity_user_id, " + + "public_key, " + + "signature_count, " + + "uv_initialized, " + + "backup_eligible, " + + "authenticator_transports, " + + "public_key_credential_type, " + + "backup_state, " + + "attestation_object, " + + "attestation_client_data_json, " + + "created, " + + "last_used, " + + "label "; + // @formatter:on + + // @formatter:off + private static final String SAVE_CREDENTIAL_RECORD_SQL = "INSERT INTO " + TABLE_NAME + + " (" + COLUMN_NAMES + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + // @formatter:on + + private static final String ID_FILTER = "credential_id = ? "; + + private static final String USER_ID_FILTER = "user_entity_user_id = ? "; + + // @formatter:off + private static final String FIND_CREDENTIAL_RECORD_BY_ID_SQL = "SELECT " + COLUMN_NAMES + + " FROM " + TABLE_NAME + + " WHERE " + ID_FILTER; + // @formatter:on + + // @formatter:off + private static final String FIND_CREDENTIAL_RECORD_BY_USER_ID_SQL = "SELECT " + COLUMN_NAMES + + " FROM " + TABLE_NAME + + " WHERE " + USER_ID_FILTER; + // @formatter:on + + private static final String DELETE_CREDENTIAL_RECORD_SQL = "DELETE FROM " + TABLE_NAME + " WHERE " + ID_FILTER; + + /** + * Constructs a {@code JdbcUserCredentialRepository} using the provided parameters. + * @param jdbcOperations the JDBC operations + */ + public JdbcUserCredentialRepository(JdbcOperations jdbcOperations) { + Assert.notNull(jdbcOperations, "jdbcOperations cannot be null"); + this.jdbcOperations = jdbcOperations; + } + + @Override + public void delete(Bytes credentialId) { + Assert.notNull(credentialId, "credentialId cannot be null"); + SqlParameterValue[] parameters = new SqlParameterValue[] { + new SqlParameterValue(Types.VARCHAR, credentialId.toBase64UrlString()), }; + PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters); + this.jdbcOperations.update(DELETE_CREDENTIAL_RECORD_SQL, pss); + } + + @Override + public void save(CredentialRecord record) { + Assert.notNull(record, "record cannot be null"); + List parameters = this.credentialRecordParametersMapper.apply(record); + try (LobCreator lobCreator = this.lobHandler.getLobCreator()) { + PreparedStatementSetter pss = new LobCreatorArgumentPreparedStatementSetter(lobCreator, + parameters.toArray()); + this.jdbcOperations.update(SAVE_CREDENTIAL_RECORD_SQL, pss); + } + } + + @Override + public CredentialRecord findByCredentialId(Bytes credentialId) { + Assert.notNull(credentialId, "credentialId cannot be null"); + List result = this.jdbcOperations.query(FIND_CREDENTIAL_RECORD_BY_ID_SQL, + this.credentialRecordRowMapper, credentialId.toBase64UrlString()); + return !result.isEmpty() ? result.get(0) : null; + } + + @Override + public List findByUserId(Bytes userId) { + Assert.notNull(userId, "userId cannot be null"); + return this.jdbcOperations.query(FIND_CREDENTIAL_RECORD_BY_USER_ID_SQL, this.credentialRecordRowMapper, + userId.toBase64UrlString()); + } + + /** + * Sets a {@link LobHandler} for large binary fields and large text field parameters. + * @param lobHandler the lob handler + */ + public void setLobHandler(LobHandler lobHandler) { + Assert.notNull(lobHandler, "lobHandler cannot be null"); + this.lobHandler = lobHandler; + } + + private static class CredentialRecordParametersMapper + implements Function> { + + @Override + public List apply(CredentialRecord record) { + List parameters = new ArrayList<>(); + + List transports = new ArrayList<>(); + if (!CollectionUtils.isEmpty(record.getTransports())) { + for (AuthenticatorTransport transport : record.getTransports()) { + transports.add(transport.getValue()); + } + } + + parameters.add(new SqlParameterValue(Types.VARCHAR, record.getCredentialId().toBase64UrlString())); + parameters.add(new SqlParameterValue(Types.VARCHAR, record.getUserEntityUserId().toBase64UrlString())); + parameters.add(new SqlParameterValue(Types.BLOB, record.getPublicKey().getBytes())); + parameters.add(new SqlParameterValue(Types.BIGINT, record.getSignatureCount())); + parameters.add(new SqlParameterValue(Types.BOOLEAN, record.isUvInitialized())); + parameters.add(new SqlParameterValue(Types.BOOLEAN, record.isBackupEligible())); + parameters.add(new SqlParameterValue(Types.VARCHAR, + (!CollectionUtils.isEmpty(record.getTransports())) ? String.join(",", transports) : "")); + parameters.add(new SqlParameterValue(Types.VARCHAR, + (record.getCredentialType() != null) ? record.getCredentialType().getValue() : null)); + parameters.add(new SqlParameterValue(Types.BOOLEAN, record.isBackupState())); + parameters.add(new SqlParameterValue(Types.BLOB, + (record.getAttestationObject() != null) ? record.getAttestationObject().getBytes() : null)); + parameters.add(new SqlParameterValue(Types.BLOB, (record.getAttestationClientDataJSON() != null) + ? record.getAttestationClientDataJSON().getBytes() : null)); + parameters.add(new SqlParameterValue(Types.TIMESTAMP, fromInstant(record.getCreated()))); + parameters.add(new SqlParameterValue(Types.TIMESTAMP, fromInstant(record.getLastUsed()))); + parameters.add(new SqlParameterValue(Types.VARCHAR, record.getLabel())); + + return parameters; + } + + private Timestamp fromInstant(Instant instant) { + if (instant == null) { + return null; + } + return Timestamp.from(instant); + } + + } + + private static final class LobCreatorArgumentPreparedStatementSetter extends ArgumentPreparedStatementSetter { + + private final LobCreator lobCreator; + + private LobCreatorArgumentPreparedStatementSetter(LobCreator lobCreator, Object[] args) { + super(args); + this.lobCreator = lobCreator; + } + + @Override + protected void doSetValue(PreparedStatement ps, int parameterPosition, Object argValue) throws SQLException { + if (argValue instanceof SqlParameterValue paramValue) { + if (paramValue.getSqlType() == Types.BLOB) { + if (paramValue.getValue() != null) { + Assert.isInstanceOf(byte[].class, paramValue.getValue(), + "Value of blob parameter must be byte[]"); + } + byte[] valueBytes = (byte[]) paramValue.getValue(); + this.lobCreator.setBlobAsBytes(ps, parameterPosition, valueBytes); + return; + } + } + super.doSetValue(ps, parameterPosition, argValue); + } + + } + + private static class CredentialRecordRowMapper implements RowMapper { + + private LobHandler lobHandler = new DefaultLobHandler(); + + @Override + public CredentialRecord mapRow(ResultSet rs, int rowNum) throws SQLException { + Bytes credentialId = Bytes.fromBase64(new String(rs.getString("credential_id").getBytes())); + Bytes userEntityUserId = Bytes.fromBase64(new String(rs.getString("user_entity_user_id").getBytes())); + ImmutablePublicKeyCose publicKey = new ImmutablePublicKeyCose( + this.lobHandler.getBlobAsBytes(rs, "public_key")); + long signatureCount = rs.getLong("signature_count"); + boolean uvInitialized = rs.getBoolean("uv_initialized"); + boolean backupEligible = rs.getBoolean("backup_eligible"); + PublicKeyCredentialType credentialType = PublicKeyCredentialType + .valueOf(rs.getString("public_key_credential_type")); + boolean backupState = rs.getBoolean("backup_state"); + + Bytes attestationObject = null; + byte[] rawAttestationObject = this.lobHandler.getBlobAsBytes(rs, "attestation_object"); + if (rawAttestationObject != null) { + attestationObject = new Bytes(rawAttestationObject); + } + + Bytes attestationClientDataJson = null; + byte[] rawAttestationClientDataJson = this.lobHandler.getBlobAsBytes(rs, "attestation_client_data_json"); + if (rawAttestationClientDataJson != null) { + attestationClientDataJson = new Bytes(rawAttestationClientDataJson); + } + + Instant created = fromTimestamp(rs.getTimestamp("created")); + Instant lastUsed = fromTimestamp(rs.getTimestamp("last_used")); + String label = rs.getString("label"); + String[] transports = rs.getString("authenticator_transports").split(","); + + Set authenticatorTransports = new HashSet<>(); + for (String transport : transports) { + authenticatorTransports.add(AuthenticatorTransport.valueOf(transport)); + } + return ImmutableCredentialRecord.builder() + .credentialId(credentialId) + .userEntityUserId(userEntityUserId) + .publicKey(publicKey) + .signatureCount(signatureCount) + .uvInitialized(uvInitialized) + .backupEligible(backupEligible) + .credentialType(credentialType) + .backupState(backupState) + .attestationObject(attestationObject) + .attestationClientDataJSON(attestationClientDataJson) + .created(created) + .label(label) + .lastUsed(lastUsed) + .transports(authenticatorTransports) + .build(); + } + + private Instant fromTimestamp(Timestamp timestamp) { + if (timestamp == null) { + return null; + } + return timestamp.toInstant(); + } + + } + +} diff --git a/web/src/main/resources/META-INF/spring/aot.factories b/web/src/main/resources/META-INF/spring/aot.factories index dcc4be6a06..4c3991233f 100644 --- a/web/src/main/resources/META-INF/spring/aot.factories +++ b/web/src/main/resources/META-INF/spring/aot.factories @@ -1,2 +1,3 @@ org.springframework.aot.hint.RuntimeHintsRegistrar=\ -org.springframework.security.web.aot.hint.WebMvcSecurityRuntimeHints +org.springframework.security.web.aot.hint.WebMvcSecurityRuntimeHints,\ +org.springframework.security.web.aot.hint.UserCredentialRuntimeHints diff --git a/web/src/main/resources/org/springframework/security/user-credentials-schema.sql b/web/src/main/resources/org/springframework/security/user-credentials-schema.sql new file mode 100644 index 0000000000..1be48f2fb1 --- /dev/null +++ b/web/src/main/resources/org/springframework/security/user-credentials-schema.sql @@ -0,0 +1,18 @@ +create table user_credentials +( + credential_id varchar(1000) not null, + user_entity_user_id varchar(1000) not null, + public_key blob not null, + signature_count bigint, + uv_initialized boolean, + backup_eligible boolean not null, + authenticator_transports varchar(1000), + public_key_credential_type varchar(100), + backup_state boolean not null, + attestation_object blob, + attestation_client_data_json blob, + created timestamp, + last_used timestamp, + label varchar(1000) not null, + primary key (credential_id) +); diff --git a/web/src/test/java/org/springframework/security/web/aot/hint/UserCredentialRuntimeHintsTests.java b/web/src/test/java/org/springframework/security/web/aot/hint/UserCredentialRuntimeHintsTests.java new file mode 100644 index 0000000000..33799cc6f9 --- /dev/null +++ b/web/src/test/java/org/springframework/security/web/aot/hint/UserCredentialRuntimeHintsTests.java @@ -0,0 +1,59 @@ +/* + * Copyright 2002-2024 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.aot.hint; + +import java.util.stream.Stream; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; +import org.springframework.core.io.support.SpringFactoriesLoader; +import org.springframework.util.ClassUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link UserCredentialRuntimeHints} + * + * @author Max Batischev + */ +public class UserCredentialRuntimeHintsTests { + + private final RuntimeHints hints = new RuntimeHints(); + + @BeforeEach + void setup() { + SpringFactoriesLoader.forResourceLocation("META-INF/spring/aot.factories") + .load(RuntimeHintsRegistrar.class) + .forEach((registrar) -> registrar.registerHints(this.hints, ClassUtils.getDefaultClassLoader())); + } + + @ParameterizedTest + @MethodSource("getClientRecordsSqlFiles") + void credentialRecordsSqlFilesHasHints(String schemaFile) { + assertThat(RuntimeHintsPredicates.resource().forResource(schemaFile)).accepts(this.hints); + } + + private static Stream getClientRecordsSqlFiles() { + return Stream.of("org/springframework/security/user-credentials-schema.sql"); + } + +} diff --git a/web/src/test/java/org/springframework/security/web/webauthn/api/TestCredentialRecord.java b/web/src/test/java/org/springframework/security/web/webauthn/api/TestCredentialRecord.java index 917125ae67..1ed190c03d 100644 --- a/web/src/test/java/org/springframework/security/web/webauthn/api/TestCredentialRecord.java +++ b/web/src/test/java/org/springframework/security/web/webauthn/api/TestCredentialRecord.java @@ -16,6 +16,9 @@ package org.springframework.security.web.webauthn.api; +import java.time.Instant; +import java.util.Set; + public final class TestCredentialRecord { public static ImmutableCredentialRecord.ImmutableCredentialRecordBuilder userCredential() { @@ -29,6 +32,24 @@ public static ImmutableCredentialRecord.ImmutableCredentialRecordBuilder userCre .backupState(true); } + public static ImmutableCredentialRecord.ImmutableCredentialRecordBuilder fullUserCredential() { + return ImmutableCredentialRecord.builder() + .label("label") + .credentialId(Bytes.fromBase64("NauGCN7bZ5jEBwThcde51g")) + .userEntityUserId(Bytes.fromBase64("vKBFhsWT3gQnn-gHdT4VXIvjDkVXVYg5w8CLGHPunMM")) + .publicKey(ImmutablePublicKeyCose.fromBase64( + "pQECAyYgASFYIC7DAiV_trHFPjieOxXbec7q2taBcgLnIi19zrUwVhCdIlggvN6riHORK_velHcTLFK_uJhyKK0oBkJqzNqR2E-2xf8=")) + .backupEligible(true) + .created(Instant.now()) + .transports(Set.of(AuthenticatorTransport.BLE, AuthenticatorTransport.HYBRID)) + .signatureCount(100) + .uvInitialized(false) + .credentialType(PublicKeyCredentialType.PUBLIC_KEY) + .attestationObject(new Bytes("test".getBytes())) + .attestationClientDataJSON(new Bytes(("test").getBytes())) + .backupState(true); + } + private TestCredentialRecord() { } diff --git a/web/src/test/java/org/springframework/security/web/webauthn/management/JdbcUserCredentialRepositoryTests.java b/web/src/test/java/org/springframework/security/web/webauthn/management/JdbcUserCredentialRepositoryTests.java new file mode 100644 index 0000000000..4829b537f0 --- /dev/null +++ b/web/src/test/java/org/springframework/security/web/webauthn/management/JdbcUserCredentialRepositoryTests.java @@ -0,0 +1,180 @@ +/* + * Copyright 2002-2024 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.webauthn.management; + +import java.util.List; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.jdbc.core.JdbcOperations; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; +import org.springframework.security.web.webauthn.api.AuthenticatorTransport; +import org.springframework.security.web.webauthn.api.CredentialRecord; +import org.springframework.security.web.webauthn.api.PublicKeyCredentialType; +import org.springframework.security.web.webauthn.api.TestCredentialRecord; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +/** + * Tests for {@link JdbcUserCredentialRepository} + * + * @author Max Batischev + */ +public class JdbcUserCredentialRepositoryTests { + + private EmbeddedDatabase db; + + private JdbcUserCredentialRepository jdbcUserCredentialRepository; + + private static final String USER_CREDENTIALS_SQL_RESOURCE = "org/springframework/security/user-credentials-schema.sql"; + + @BeforeEach + void setUp() { + this.db = createDb(); + JdbcOperations jdbcOperations = new JdbcTemplate(this.db); + this.jdbcUserCredentialRepository = new JdbcUserCredentialRepository(jdbcOperations); + } + + @AfterEach + void tearDown() { + this.db.shutdown(); + } + + private static EmbeddedDatabase createDb() { + // @formatter:off + return new EmbeddedDatabaseBuilder() + .generateUniqueName(true) + .setType(EmbeddedDatabaseType.HSQL) + .setScriptEncoding("UTF-8") + .addScript(USER_CREDENTIALS_SQL_RESOURCE) + .build(); + // @formatter:on + } + + @Test + void constructorWhenJdbcOperationsIsNullThenThrowIllegalArgumentException() { + // @formatter:off + assertThatIllegalArgumentException() + .isThrownBy(() -> new JdbcUserCredentialRepository(null)) + .withMessage("jdbcOperations cannot be null"); + // @formatter:on + } + + @Test + void saveWhenCredentialRecordIsNullThenThrowIllegalArgumentException() { + // @formatter:off + assertThatIllegalArgumentException() + .isThrownBy(() -> this.jdbcUserCredentialRepository.save(null)) + .withMessage("record cannot be null"); + // @formatter:on + } + + @Test + void findByCredentialIdWheCredentialIdIsNullThenThrowIllegalArgumentException() { + // @formatter:off + assertThatIllegalArgumentException() + .isThrownBy(() -> this.jdbcUserCredentialRepository.findByCredentialId(null)) + .withMessage("credentialId cannot be null"); + // @formatter:on + } + + @Test + void findByCredentialIdWheUserIdIsNullThenThrowIllegalArgumentException() { + // @formatter:off + assertThatIllegalArgumentException() + .isThrownBy(() -> this.jdbcUserCredentialRepository.findByUserId(null)) + .withMessage("userId cannot be null"); + // @formatter:on + } + + @Test + void saveCredentialRecordWhenSaveThenReturnsSaved() { + CredentialRecord userCredential = TestCredentialRecord.fullUserCredential().build(); + this.jdbcUserCredentialRepository.save(userCredential); + + CredentialRecord savedUserCredential = this.jdbcUserCredentialRepository + .findByCredentialId(userCredential.getCredentialId()); + + assertThat(savedUserCredential).isNotNull(); + assertThat(savedUserCredential.getCredentialId()).isEqualTo(userCredential.getCredentialId()); + assertThat(savedUserCredential.getUserEntityUserId()).isEqualTo(userCredential.getUserEntityUserId()); + assertThat(savedUserCredential.getLabel()).isEqualTo(userCredential.getLabel()); + assertThat(savedUserCredential.getPublicKey().getBytes()).isEqualTo(userCredential.getPublicKey().getBytes()); + assertThat(savedUserCredential.isBackupEligible()).isEqualTo(userCredential.isBackupEligible()); + assertThat(savedUserCredential.isBackupState()).isEqualTo(userCredential.isBackupState()); + assertThat(savedUserCredential.getCreated()).isNotNull(); + assertThat(savedUserCredential.getLastUsed()).isNotNull(); + assertThat(savedUserCredential.isUvInitialized()).isFalse(); + assertThat(savedUserCredential.getSignatureCount()).isEqualTo(100); + assertThat(savedUserCredential.getCredentialType()).isEqualTo(PublicKeyCredentialType.PUBLIC_KEY); + assertThat(savedUserCredential.getTransports().contains(AuthenticatorTransport.HYBRID)).isTrue(); + assertThat(savedUserCredential.getTransports().contains(AuthenticatorTransport.BLE)).isTrue(); + assertThat(new String(savedUserCredential.getAttestationObject().getBytes())).isEqualTo("test"); + assertThat(new String(savedUserCredential.getAttestationClientDataJSON().getBytes())).isEqualTo("test"); + } + + @Test + void findCredentialRecordByUserIdWhenRecordExistsThenReturnsSaved() { + CredentialRecord userCredential = TestCredentialRecord.fullUserCredential().build(); + this.jdbcUserCredentialRepository.save(userCredential); + + List credentialRecords = this.jdbcUserCredentialRepository + .findByUserId(userCredential.getUserEntityUserId()); + + assertThat(credentialRecords).isNotNull(); + assertThat(credentialRecords.size()).isEqualTo(1); + } + + @Test + void findCredentialRecordByUserIdWhenRecordDoesNotExistThenReturnsEmpty() { + CredentialRecord userCredential = TestCredentialRecord.fullUserCredential().build(); + + List credentialRecords = this.jdbcUserCredentialRepository + .findByUserId(userCredential.getUserEntityUserId()); + + assertThat(credentialRecords.size()).isEqualTo(0); + } + + @Test + void findCredentialRecordByCredentialIdWhenRecordDoesNotExistThenReturnsNull() { + CredentialRecord userCredential = TestCredentialRecord.fullUserCredential().build(); + + CredentialRecord credentialRecord = this.jdbcUserCredentialRepository + .findByCredentialId(userCredential.getCredentialId()); + + assertThat(credentialRecord).isNull(); + } + + @Test + void deleteCredentialRecordWhenRecordExistThenSuccess() { + CredentialRecord userCredential = TestCredentialRecord.fullUserCredential().build(); + this.jdbcUserCredentialRepository.save(userCredential); + + this.jdbcUserCredentialRepository.delete(userCredential.getCredentialId()); + + CredentialRecord credentialRecord = this.jdbcUserCredentialRepository + .findByCredentialId(userCredential.getCredentialId()); + assertThat(credentialRecord).isNull(); + } + +} From fb17c5fa440e261e097ce41bddd8a4b07fc970e6 Mon Sep 17 00:00:00 2001 From: Max Batischev Date: Fri, 13 Dec 2024 18:16:45 +0300 Subject: [PATCH 029/132] Add Support JdbcPublicKeyCredentialUserEntityRepository Closes gh-16224 Signed-off-by: Daeho Kwon --- ...icKeyCredentialUserEntityRuntimeHints.java | 40 ++++ ...blicKeyCredentialUserEntityRepository.java | 193 ++++++++++++++++++ .../resources/META-INF/spring/aot.factories | 3 +- .../security/user-entities-schema.sql | 7 + ...CredentialUserEntityRuntimeHintsTests.java | 59 ++++++ ...eyCredentialUserEntityRepositoryTests.java | 182 +++++++++++++++++ 6 files changed, 483 insertions(+), 1 deletion(-) create mode 100644 web/src/main/java/org/springframework/security/web/aot/hint/PublicKeyCredentialUserEntityRuntimeHints.java create mode 100644 web/src/main/java/org/springframework/security/web/webauthn/management/JdbcPublicKeyCredentialUserEntityRepository.java create mode 100644 web/src/main/resources/org/springframework/security/user-entities-schema.sql create mode 100644 web/src/test/java/org/springframework/security/web/aot/hint/PublicKeyCredentialUserEntityRuntimeHintsTests.java create mode 100644 web/src/test/java/org/springframework/security/web/webauthn/management/JdbcPublicKeyCredentialUserEntityRepositoryTests.java diff --git a/web/src/main/java/org/springframework/security/web/aot/hint/PublicKeyCredentialUserEntityRuntimeHints.java b/web/src/main/java/org/springframework/security/web/aot/hint/PublicKeyCredentialUserEntityRuntimeHints.java new file mode 100644 index 0000000000..c35cf5b81c --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/aot/hint/PublicKeyCredentialUserEntityRuntimeHints.java @@ -0,0 +1,40 @@ +/* + * Copyright 2002-2024 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.aot.hint; + +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.jdbc.core.JdbcOperations; +import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity; +import org.springframework.security.web.webauthn.management.PublicKeyCredentialUserEntityRepository; + +/** + * + * A JDBC implementation of an {@link PublicKeyCredentialUserEntityRepository} that uses a + * {@link JdbcOperations} for {@link PublicKeyCredentialUserEntity} persistence. + * + * @author Max Batischev + * @since 6.5 + */ +class PublicKeyCredentialUserEntityRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + hints.resources().registerPattern("org/springframework/security/user-entities-schema.sql"); + } + +} diff --git a/web/src/main/java/org/springframework/security/web/webauthn/management/JdbcPublicKeyCredentialUserEntityRepository.java b/web/src/main/java/org/springframework/security/web/webauthn/management/JdbcPublicKeyCredentialUserEntityRepository.java new file mode 100644 index 0000000000..bfeaafb0e8 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/webauthn/management/JdbcPublicKeyCredentialUserEntityRepository.java @@ -0,0 +1,193 @@ +/* + * Copyright 2002-2024 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.webauthn.management; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +import org.springframework.dao.DuplicateKeyException; +import org.springframework.jdbc.core.ArgumentPreparedStatementSetter; +import org.springframework.jdbc.core.JdbcOperations; +import org.springframework.jdbc.core.PreparedStatementSetter; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.SqlParameterValue; +import org.springframework.security.web.webauthn.api.Bytes; +import org.springframework.security.web.webauthn.api.ImmutablePublicKeyCredentialUserEntity; +import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity; +import org.springframework.util.Assert; + +/** + * A JDBC implementation of an {@link PublicKeyCredentialUserEntityRepository} that uses a + * {@link JdbcOperations} for {@link PublicKeyCredentialUserEntity} persistence. + * + * NOTE: This {@code PublicKeyCredentialUserEntityRepository} depends on the table + * definition described in + * "classpath:org/springframework/security/user-entities-schema.sql" and therefore MUST be + * defined in the database schema. + * + * @author Max Batischev + * @since 6.5 + * @see PublicKeyCredentialUserEntityRepository + * @see PublicKeyCredentialUserEntity + * @see JdbcOperations + * @see RowMapper + */ +public final class JdbcPublicKeyCredentialUserEntityRepository implements PublicKeyCredentialUserEntityRepository { + + private RowMapper userEntityRowMapper = new UserEntityRecordRowMapper(); + + private Function> userEntityParametersMapper = new UserEntityParametersMapper(); + + private final JdbcOperations jdbcOperations; + + private static final String TABLE_NAME = "user_entities"; + + // @formatter:off + private static final String COLUMN_NAMES = "id, " + + "name, " + + "display_name "; + // @formatter:on + + // @formatter:off + private static final String SAVE_USER_SQL = "INSERT INTO " + TABLE_NAME + + " (" + COLUMN_NAMES + ") VALUES (?, ?, ?)"; + // @formatter:on + + private static final String ID_FILTER = "id = ? "; + + private static final String USER_NAME_FILTER = "name = ? "; + + // @formatter:off + private static final String FIND_USER_BY_ID_SQL = "SELECT " + COLUMN_NAMES + + " FROM " + TABLE_NAME + + " WHERE " + ID_FILTER; + // @formatter:on + + // @formatter:off + private static final String FIND_USER_BY_NAME_SQL = "SELECT " + COLUMN_NAMES + + " FROM " + TABLE_NAME + + " WHERE " + USER_NAME_FILTER; + // @formatter:on + + private static final String DELETE_USER_SQL = "DELETE FROM " + TABLE_NAME + " WHERE " + ID_FILTER; + + // @formatter:off + private static final String UPDATE_USER_SQL = "UPDATE " + TABLE_NAME + + " SET name = ?, display_name = ? " + + " WHERE " + ID_FILTER; + // @formatter:on + + /** + * Constructs a {@code JdbcPublicKeyCredentialUserEntityRepository} using the provided + * parameters. + * @param jdbcOperations the JDBC operations + */ + public JdbcPublicKeyCredentialUserEntityRepository(JdbcOperations jdbcOperations) { + Assert.notNull(jdbcOperations, "jdbcOperations cannot be null"); + this.jdbcOperations = jdbcOperations; + } + + @Override + public PublicKeyCredentialUserEntity findById(Bytes id) { + Assert.notNull(id, "id cannot be null"); + List result = this.jdbcOperations.query(FIND_USER_BY_ID_SQL, + this.userEntityRowMapper, id.toBase64UrlString()); + return !result.isEmpty() ? result.get(0) : null; + } + + @Override + public PublicKeyCredentialUserEntity findByUsername(String username) { + Assert.hasText(username, "name cannot be null or empty"); + List result = this.jdbcOperations.query(FIND_USER_BY_NAME_SQL, + this.userEntityRowMapper, username); + return !result.isEmpty() ? result.get(0) : null; + } + + @Override + public void save(PublicKeyCredentialUserEntity userEntity) { + Assert.notNull(userEntity, "userEntity cannot be null"); + boolean existsUserEntity = null != this.findById(userEntity.getId()); + if (existsUserEntity) { + updateUserEntity(userEntity); + } + else { + try { + insertUserEntity(userEntity); + } + catch (DuplicateKeyException ex) { + updateUserEntity(userEntity); + } + } + } + + private void insertUserEntity(PublicKeyCredentialUserEntity userEntity) { + List parameters = this.userEntityParametersMapper.apply(userEntity); + PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters.toArray()); + this.jdbcOperations.update(SAVE_USER_SQL, pss); + } + + private void updateUserEntity(PublicKeyCredentialUserEntity userEntity) { + List parameters = this.userEntityParametersMapper.apply(userEntity); + SqlParameterValue userEntityId = parameters.remove(0); + parameters.add(userEntityId); + PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters.toArray()); + this.jdbcOperations.update(UPDATE_USER_SQL, pss); + } + + @Override + public void delete(Bytes id) { + Assert.notNull(id, "id cannot be null"); + SqlParameterValue[] parameters = new SqlParameterValue[] { + new SqlParameterValue(Types.VARCHAR, id.toBase64UrlString()), }; + PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters); + this.jdbcOperations.update(DELETE_USER_SQL, pss); + } + + private static class UserEntityParametersMapper + implements Function> { + + @Override + public List apply(PublicKeyCredentialUserEntity userEntity) { + List parameters = new ArrayList<>(); + + parameters.add(new SqlParameterValue(Types.VARCHAR, userEntity.getId().toBase64UrlString())); + parameters.add(new SqlParameterValue(Types.VARCHAR, userEntity.getName())); + parameters.add(new SqlParameterValue(Types.VARCHAR, userEntity.getDisplayName())); + + return parameters; + } + + } + + private static class UserEntityRecordRowMapper implements RowMapper { + + @Override + public PublicKeyCredentialUserEntity mapRow(ResultSet rs, int rowNum) throws SQLException { + Bytes id = Bytes.fromBase64(new String(rs.getString("id").getBytes())); + String name = rs.getString("name"); + String displayName = rs.getString("display_name"); + + return ImmutablePublicKeyCredentialUserEntity.builder().id(id).name(name).displayName(displayName).build(); + } + + } + +} diff --git a/web/src/main/resources/META-INF/spring/aot.factories b/web/src/main/resources/META-INF/spring/aot.factories index 4c3991233f..2a3c8ad768 100644 --- a/web/src/main/resources/META-INF/spring/aot.factories +++ b/web/src/main/resources/META-INF/spring/aot.factories @@ -1,3 +1,4 @@ org.springframework.aot.hint.RuntimeHintsRegistrar=\ org.springframework.security.web.aot.hint.WebMvcSecurityRuntimeHints,\ -org.springframework.security.web.aot.hint.UserCredentialRuntimeHints +org.springframework.security.web.aot.hint.UserCredentialRuntimeHints,\ +org.springframework.security.web.aot.hint.PublicKeyCredentialUserEntityRuntimeHints diff --git a/web/src/main/resources/org/springframework/security/user-entities-schema.sql b/web/src/main/resources/org/springframework/security/user-entities-schema.sql new file mode 100644 index 0000000000..ec66c66519 --- /dev/null +++ b/web/src/main/resources/org/springframework/security/user-entities-schema.sql @@ -0,0 +1,7 @@ +create table user_entities +( + id varchar(1000) not null, + name varchar(100) not null, + display_name varchar(200), + primary key (id) +); diff --git a/web/src/test/java/org/springframework/security/web/aot/hint/PublicKeyCredentialUserEntityRuntimeHintsTests.java b/web/src/test/java/org/springframework/security/web/aot/hint/PublicKeyCredentialUserEntityRuntimeHintsTests.java new file mode 100644 index 0000000000..4909a64303 --- /dev/null +++ b/web/src/test/java/org/springframework/security/web/aot/hint/PublicKeyCredentialUserEntityRuntimeHintsTests.java @@ -0,0 +1,59 @@ +/* + * Copyright 2002-2024 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.aot.hint; + +import java.util.stream.Stream; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; +import org.springframework.core.io.support.SpringFactoriesLoader; +import org.springframework.util.ClassUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link PublicKeyCredentialUserEntityRuntimeHints} + * + * @author Max Batischev + */ +public class PublicKeyCredentialUserEntityRuntimeHintsTests { + + private final RuntimeHints hints = new RuntimeHints(); + + @BeforeEach + void setup() { + SpringFactoriesLoader.forResourceLocation("META-INF/spring/aot.factories") + .load(RuntimeHintsRegistrar.class) + .forEach((registrar) -> registrar.registerHints(this.hints, ClassUtils.getDefaultClassLoader())); + } + + @ParameterizedTest + @MethodSource("getUserEntitiesSqlFiles") + void userEntitiesSqlFilesHasHints(String schemaFile) { + assertThat(RuntimeHintsPredicates.resource().forResource(schemaFile)).accepts(this.hints); + } + + private static Stream getUserEntitiesSqlFiles() { + return Stream.of("org/springframework/security/user-entities-schema.sql"); + } + +} diff --git a/web/src/test/java/org/springframework/security/web/webauthn/management/JdbcPublicKeyCredentialUserEntityRepositoryTests.java b/web/src/test/java/org/springframework/security/web/webauthn/management/JdbcPublicKeyCredentialUserEntityRepositoryTests.java new file mode 100644 index 0000000000..503108ac4e --- /dev/null +++ b/web/src/test/java/org/springframework/security/web/webauthn/management/JdbcPublicKeyCredentialUserEntityRepositoryTests.java @@ -0,0 +1,182 @@ +/* + * Copyright 2002-2024 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.webauthn.management; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.jdbc.core.JdbcOperations; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; +import org.springframework.security.web.webauthn.api.Bytes; +import org.springframework.security.web.webauthn.api.ImmutablePublicKeyCredentialUserEntity; +import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity; +import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialUserEntity; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +/** + * Tests for {@link JdbcPublicKeyCredentialUserEntityRepository} + * + * @author Max Batischev + */ +public class JdbcPublicKeyCredentialUserEntityRepositoryTests { + + private EmbeddedDatabase db; + + private JdbcPublicKeyCredentialUserEntityRepository repository; + + private static final String USER_ENTITIES_SQL_RESOURCE = "org/springframework/security/user-entities-schema.sql"; + + @BeforeEach + void setUp() { + this.db = createDb(); + JdbcOperations jdbcOperations = new JdbcTemplate(this.db); + this.repository = new JdbcPublicKeyCredentialUserEntityRepository(jdbcOperations); + } + + @AfterEach + void tearDown() { + this.db.shutdown(); + } + + private static EmbeddedDatabase createDb() { + // @formatter:off + return new EmbeddedDatabaseBuilder() + .generateUniqueName(true) + .setType(EmbeddedDatabaseType.HSQL) + .setScriptEncoding("UTF-8") + .addScript(USER_ENTITIES_SQL_RESOURCE) + .build(); + // @formatter:on + } + + @Test + void constructorWhenJdbcOperationsIsNullThenThrowIllegalArgumentException() { + // @formatter:off + assertThatIllegalArgumentException() + .isThrownBy(() -> new JdbcPublicKeyCredentialUserEntityRepository(null)) + .withMessage("jdbcOperations cannot be null"); + // @formatter:on + } + + @Test + void saveWhenUserEntityIsNullThenThrowIllegalArgumentException() { + // @formatter:off + assertThatIllegalArgumentException() + .isThrownBy(() -> this.repository.save(null)) + .withMessage("userEntity cannot be null"); + // @formatter:on + } + + @Test + void findByUserEntityIdWheIdIsNullThenThrowIllegalArgumentException() { + // @formatter:off + assertThatIllegalArgumentException() + .isThrownBy(() -> this.repository.findById(null)) + .withMessage("id cannot be null"); + // @formatter:on + } + + @Test + void findByUserNameWheUserNameIsNullThenThrowIllegalArgumentException() { + // @formatter:off + assertThatIllegalArgumentException() + .isThrownBy(() -> this.repository.findByUsername(null)) + .withMessage("name cannot be null or empty"); + // @formatter:on + } + + @Test + void saveUserEntityWhenSaveThenReturnsSaved() { + PublicKeyCredentialUserEntity userEntity = TestPublicKeyCredentialUserEntity.userEntity().build(); + + this.repository.save(userEntity); + + PublicKeyCredentialUserEntity savedUserEntity = this.repository.findById(userEntity.getId()); + assertThat(savedUserEntity).isNotNull(); + assertThat(savedUserEntity.getId()).isEqualTo(userEntity.getId()); + assertThat(savedUserEntity.getDisplayName()).isEqualTo(userEntity.getDisplayName()); + assertThat(savedUserEntity.getName()).isEqualTo(userEntity.getName()); + } + + @Test + void saveUserEntityWhenUserEntityExistsThenUpdates() { + PublicKeyCredentialUserEntity userEntity = TestPublicKeyCredentialUserEntity.userEntity().build(); + this.repository.save(userEntity); + + this.repository.save(testUserEntity(userEntity.getId())); + + PublicKeyCredentialUserEntity savedUserEntity = this.repository.findById(userEntity.getId()); + assertThat(savedUserEntity).isNotNull(); + assertThat(savedUserEntity.getId()).isEqualTo(userEntity.getId()); + assertThat(savedUserEntity.getDisplayName()).isEqualTo("user2"); + assertThat(savedUserEntity.getName()).isEqualTo("user2"); + } + + @Test + void findUserEntityByUserNameWhenUserEntityExistsThenReturnsSaved() { + PublicKeyCredentialUserEntity userEntity = TestPublicKeyCredentialUserEntity.userEntity().build(); + this.repository.save(userEntity); + + PublicKeyCredentialUserEntity savedUserEntity = this.repository.findByUsername(userEntity.getName()); + + assertThat(savedUserEntity).isNotNull(); + } + + @Test + void deleteUserEntityWhenRecordExistThenSuccess() { + PublicKeyCredentialUserEntity userEntity = TestPublicKeyCredentialUserEntity.userEntity().build(); + this.repository.save(userEntity); + + this.repository.delete(userEntity.getId()); + + PublicKeyCredentialUserEntity savedUserEntity = this.repository.findById(userEntity.getId()); + assertThat(savedUserEntity).isNull(); + } + + @Test + void findUserEntityByIdWhenUserEntityDoesNotExistThenReturnsNull() { + PublicKeyCredentialUserEntity userEntity = TestPublicKeyCredentialUserEntity.userEntity().build(); + + PublicKeyCredentialUserEntity savedUserEntity = this.repository.findById(userEntity.getId()); + assertThat(savedUserEntity).isNull(); + } + + @Test + void findUserEntityByUserNameWhenUserEntityDoesNotExistThenReturnsEmpty() { + PublicKeyCredentialUserEntity userEntity = TestPublicKeyCredentialUserEntity.userEntity().build(); + + PublicKeyCredentialUserEntity savedUserEntity = this.repository.findByUsername(userEntity.getName()); + assertThat(savedUserEntity).isNull(); + } + + private PublicKeyCredentialUserEntity testUserEntity(Bytes id) { + // @formatter:off + return ImmutablePublicKeyCredentialUserEntity.builder() + .name("user2") + .id(id) + .displayName("user2") + .build(); + // @formatter:on + } + +} From 9a332dceaedd0314b1eeaded37e6887ac603f4a7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Dec 2024 03:31:13 +0000 Subject: [PATCH 030/132] Bump io.mockk:mockk from 1.13.13 to 1.13.14 Bumps [io.mockk:mockk](https://github.com/mockk/mockk) from 1.13.13 to 1.13.14. - [Release notes](https://github.com/mockk/mockk/releases) - [Commits](https://github.com/mockk/mockk/compare/1.13.13...1.13.14) --- updated-dependencies: - dependency-name: io.mockk:mockk dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: Daeho Kwon --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6dd0c0fd15..d6ef2b9a6f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -29,7 +29,7 @@ com-unboundid-unboundid-ldapsdk = "com.unboundid:unboundid-ldapsdk:6.0.11" com-unboundid-unboundid-ldapsdk7 = "com.unboundid:unboundid-ldapsdk:7.0.1" commons-collections = "commons-collections:commons-collections:3.2.2" io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.14.2" -io-mockk = "io.mockk:mockk:1.13.13" +io-mockk = "io.mockk:mockk:1.13.14" io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2023.0.13" io-rsocket-rsocket-bom = { module = "io.rsocket:rsocket-bom", version.ref = "io-rsocket" } io-spring-javaformat-spring-javaformat-checkstyle = { module = "io.spring.javaformat:spring-javaformat-checkstyle", version.ref = "io-spring-javaformat" } From a185671a36d66a56af5cd0374393e0fa20106f6f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Dec 2024 03:31:29 +0000 Subject: [PATCH 031/132] Bump ch.qos.logback:logback-classic from 1.5.14 to 1.5.15 Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.5.14 to 1.5.15. - [Commits](https://github.com/qos-ch/logback/compare/v_1.5.14...v_1.5.15) --- updated-dependencies: - dependency-name: ch.qos.logback:logback-classic dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: Daeho Kwon --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d6ef2b9a6f..5ad079daf1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,7 +17,7 @@ org-opensaml5 = "5.1.2" org-springframework = "6.2.1" [libraries] -ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.5.14" +ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.5.15" com-fasterxml-jackson-jackson-bom = "com.fasterxml.jackson:jackson-bom:2.18.2" com-google-inject-guice = "com.google.inject:guice:3.0" com-netflix-nebula-nebula-project-plugin = "com.netflix.nebula:nebula-project-plugin:8.2.0" From 311863d4c211704450c80ae9fda53a536c5dee85 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Dec 2024 04:03:48 +0000 Subject: [PATCH 032/132] Bump org.jetbrains.kotlinx:kotlinx-coroutines-bom from 1.10.0 to 1.10.1 Bumps [org.jetbrains.kotlinx:kotlinx-coroutines-bom](https://github.com/Kotlin/kotlinx.coroutines) from 1.10.0 to 1.10.1. - [Release notes](https://github.com/Kotlin/kotlinx.coroutines/releases) - [Changelog](https://github.com/Kotlin/kotlinx.coroutines/blob/master/CHANGES.md) - [Commits](https://github.com/Kotlin/kotlinx.coroutines/compare/1.10.0...1.10.1) --- updated-dependencies: - dependency-name: org.jetbrains.kotlinx:kotlinx-coroutines-bom dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: Daeho Kwon --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5ad079daf1..268e93b28d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,7 +10,7 @@ org-aspectj = "1.9.22.1" org-bouncycastle = "1.79" org-eclipse-jetty = "11.0.24" org-jetbrains-kotlin = "1.9.25" -org-jetbrains-kotlinx = "1.10.0" +org-jetbrains-kotlinx = "1.10.1" org-mockito = "5.14.2" org-opensaml = "4.3.2" org-opensaml5 = "5.1.2" From 0ae77e1d66f93d5a06bac7b60b13e49cb4e0c047 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Jan 2025 03:37:48 +0000 Subject: [PATCH 033/132] Bump com.webauthn4j:webauthn4j-core Bumps [com.webauthn4j:webauthn4j-core](https://github.com/webauthn4j/webauthn4j) from 0.28.3.RELEASE to 0.28.4.RELEASE. - [Release notes](https://github.com/webauthn4j/webauthn4j/releases) - [Changelog](https://github.com/webauthn4j/webauthn4j/blob/master/github-release-notes-generator.yml) - [Commits](https://github.com/webauthn4j/webauthn4j/compare/0.28.3.RELEASE...0.28.4.RELEASE) --- updated-dependencies: - dependency-name: com.webauthn4j:webauthn4j-core dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: Daeho Kwon --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 268e93b28d..fc85aa1184 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -107,7 +107,7 @@ org-jfrog-buildinfo-build-info-extractor-gradle = "org.jfrog.buildinfo:build-inf org-sonarsource-scanner-gradle-sonarqube-gradle-plugin = "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.8.0.1969" org-instancio-instancio-junit = "org.instancio:instancio-junit:3.7.1" -webauthn4j-core = 'com.webauthn4j:webauthn4j-core:0.28.3.RELEASE' +webauthn4j-core = 'com.webauthn4j:webauthn4j-core:0.28.4.RELEASE' [plugins] From 6b25e3eccccbbf2edd25593225a4cb70ed93ba1d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Jan 2025 03:43:04 +0000 Subject: [PATCH 034/132] Bump org.assertj:assertj-core from 3.27.0 to 3.27.1 Bumps [org.assertj:assertj-core](https://github.com/assertj/assertj) from 3.27.0 to 3.27.1. - [Release notes](https://github.com/assertj/assertj/releases) - [Commits](https://github.com/assertj/assertj/compare/assertj-build-3.27.0...assertj-build-3.27.1) --- updated-dependencies: - dependency-name: org.assertj:assertj-core dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: Daeho Kwon --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fc85aa1184..e37fe37b1a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -64,7 +64,7 @@ org-apereo-cas-client-cas-client-core = "org.apereo.cas.client:cas-client-core:4 io-freefair-gradle-aspectj-plugin = "io.freefair.gradle:aspectj-plugin:8.11" org-aspectj-aspectjrt = { module = "org.aspectj:aspectjrt", version.ref = "org-aspectj" } org-aspectj-aspectjweaver = { module = "org.aspectj:aspectjweaver", version.ref = "org-aspectj" } -org-assertj-assertj-core = "org.assertj:assertj-core:3.27.0" +org-assertj-assertj-core = "org.assertj:assertj-core:3.27.1" org-bouncycastle-bcpkix-jdk15on = { module = "org.bouncycastle:bcpkix-jdk18on", version.ref = "org-bouncycastle" } org-bouncycastle-bcprov-jdk15on = { module = "org.bouncycastle:bcprov-jdk18on", version.ref = "org-bouncycastle" } org-eclipse-jetty-jetty-server = { module = "org.eclipse.jetty:jetty-server", version.ref = "org-eclipse-jetty" } From 00ca29da942b8e7f72aa46a934679e7379cd7e38 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Jan 2025 03:25:42 +0000 Subject: [PATCH 035/132] Bump org.mockito:mockito-bom from 5.14.2 to 5.15.2 Bumps [org.mockito:mockito-bom](https://github.com/mockito/mockito) from 5.14.2 to 5.15.2. - [Release notes](https://github.com/mockito/mockito/releases) - [Commits](https://github.com/mockito/mockito/compare/v5.14.2...v5.15.2) --- updated-dependencies: - dependency-name: org.mockito:mockito-bom dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: Daeho Kwon --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e37fe37b1a..fc21b94f21 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,7 +11,7 @@ org-bouncycastle = "1.79" org-eclipse-jetty = "11.0.24" org-jetbrains-kotlin = "1.9.25" org-jetbrains-kotlinx = "1.10.1" -org-mockito = "5.14.2" +org-mockito = "5.15.2" org-opensaml = "4.3.2" org-opensaml5 = "5.1.2" org-springframework = "6.2.1" From 6b809c1725c7c023cf5c3d8cdf90396b70a301c8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 03:48:00 +0000 Subject: [PATCH 036/132] Bump ch.qos.logback:logback-classic from 1.5.15 to 1.5.16 Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.5.15 to 1.5.16. - [Commits](https://github.com/qos-ch/logback/compare/v_1.5.15...v_1.5.16) --- updated-dependencies: - dependency-name: ch.qos.logback:logback-classic dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: Daeho Kwon --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fc21b94f21..b7153260cb 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,7 +17,7 @@ org-opensaml5 = "5.1.2" org-springframework = "6.2.1" [libraries] -ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.5.15" +ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.5.16" com-fasterxml-jackson-jackson-bom = "com.fasterxml.jackson:jackson-bom:2.18.2" com-google-inject-guice = "com.google.inject:guice:3.0" com-netflix-nebula-nebula-project-plugin = "com.netflix.nebula:nebula-project-plugin:8.2.0" From f029f380fd3c4d87fb4d29c177436fbb8878fd13 Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Mon, 6 Jan 2025 16:33:11 -0600 Subject: [PATCH 037/132] CLA -> DCO Signed-off-by: Daeho Kwon --- .github/dco.yml | 2 ++ CONTRIBUTING.adoc | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 .github/dco.yml diff --git a/.github/dco.yml b/.github/dco.yml new file mode 100644 index 0000000000..0c4b142e9a --- /dev/null +++ b/.github/dco.yml @@ -0,0 +1,2 @@ +require: + members: false diff --git a/CONTRIBUTING.adoc b/CONTRIBUTING.adoc index e254c3e056..8ae6a0588d 100644 --- a/CONTRIBUTING.adoc +++ b/CONTRIBUTING.adoc @@ -90,8 +90,8 @@ Please do your best to follow these steps. Don't worry if you don't get them all correct the first time, we will help you. [[sign-cla]] -1. If you have not previously done so, please sign the https://cla.spring.io/sign/spring[Contributor License Agreement]. -You will be reminded automatically when you submit the PR. +1. All commits must include a __Signed-off-by__ trailer at the end of each commit message to indicate that the contributor agrees to the Developer Certificate of Origin. +For additional details, please refer to the blog post https://spring.io/blog/2025/01/06/hello-dco-goodbye-cla-simplifying-contributions-to-spring[Hello DCO, Goodbye CLA: Simplifying Contributions to Spring]. [[create-an-issue]] 1. Must you https://github.com/spring-projects/spring-security/issues/new/choose[create an issue] first? No, but it is recommended for features and larger bug fixes. It's easier discuss with the team first to determine the right fix or enhancement. For typos and straightforward bug fixes, starting with a pull request is encouraged. From 47383a5b655f527a8cbf5be5ef169e1f1db4c1c7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 03:50:42 +0000 Subject: [PATCH 038/132] Bump org.assertj:assertj-core from 3.27.1 to 3.27.2 Bumps [org.assertj:assertj-core](https://github.com/assertj/assertj) from 3.27.1 to 3.27.2. - [Release notes](https://github.com/assertj/assertj/releases) - [Commits](https://github.com/assertj/assertj/compare/assertj-build-3.27.1...assertj-build-3.27.2) --- updated-dependencies: - dependency-name: org.assertj:assertj-core dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: Daeho Kwon --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b7153260cb..e1446730ba 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -64,7 +64,7 @@ org-apereo-cas-client-cas-client-core = "org.apereo.cas.client:cas-client-core:4 io-freefair-gradle-aspectj-plugin = "io.freefair.gradle:aspectj-plugin:8.11" org-aspectj-aspectjrt = { module = "org.aspectj:aspectjrt", version.ref = "org-aspectj" } org-aspectj-aspectjweaver = { module = "org.aspectj:aspectjweaver", version.ref = "org-aspectj" } -org-assertj-assertj-core = "org.assertj:assertj-core:3.27.1" +org-assertj-assertj-core = "org.assertj:assertj-core:3.27.2" org-bouncycastle-bcpkix-jdk15on = { module = "org.bouncycastle:bcpkix-jdk18on", version.ref = "org-bouncycastle" } org-bouncycastle-bcprov-jdk15on = { module = "org.bouncycastle:bcprov-jdk18on", version.ref = "org-bouncycastle" } org-eclipse-jetty-jetty-server = { module = "org.eclipse.jetty:jetty-server", version.ref = "org-eclipse-jetty" } From 7cc4be8c1c630297938e57e8f40363629429662c Mon Sep 17 00:00:00 2001 From: wndyd Date: Tue, 31 Dec 2024 12:40:19 -0600 Subject: [PATCH 039/132] Fix missing space in documentation Signed-off-by: Daeho Kwon --- docs/modules/ROOT/pages/servlet/architecture.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/servlet/architecture.adoc b/docs/modules/ROOT/pages/servlet/architecture.adoc index 6548b67923..db92de0b7c 100644 --- a/docs/modules/ROOT/pages/servlet/architecture.adoc +++ b/docs/modules/ROOT/pages/servlet/architecture.adoc @@ -170,7 +170,7 @@ In fact, a `SecurityFilterChain` might have zero security `Filter` instances if The Security Filters are inserted into the <> with the <> API. Those filters can be used for a number of different purposes, like -xref:servlet/exploits/index.adoc[exploit protection],xref:servlet/authentication/index.adoc[authentication], xref:servlet/authorization/index.adoc[authorization], and more. +xref:servlet/exploits/index.adoc[exploit protection], xref:servlet/authentication/index.adoc[authentication], xref:servlet/authorization/index.adoc[authorization], and more. The filters are executed in a specific order to guarantee that they are invoked at the right time, for example, the `Filter` that performs authentication should be invoked before the `Filter` that performs authorization. It is typically not necessary to know the ordering of Spring Security's ``Filter``s. However, there are times that it is beneficial to know the ordering, if you want to know them, you can check the {gh-url}/config/src/main/java/org/springframework/security/config/annotation/web/builders/FilterOrderRegistration.java[`FilterOrderRegistration` code]. From 403749312034e51d0c2cea3694563b756895e48c Mon Sep 17 00:00:00 2001 From: DingHao Date: Mon, 6 Jan 2025 10:49:27 +0800 Subject: [PATCH 040/132] Polish use getBeanProvider instead of getBeanNamesForType Signed-off-by: Daeho Kwon --- .../web/configurers/AbstractHttpConfigurer.java | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractHttpConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractHttpConfigurer.java index 841783c4f6..a7251514fd 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractHttpConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractHttpConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -31,6 +31,7 @@ * {@link HttpSecurity}. * * @author Rob Winch + * @author Ding Hao */ public abstract class AbstractHttpConfigurer, B extends HttpSecurityBuilder> extends SecurityConfigurerAdapter { @@ -70,13 +71,8 @@ protected SecurityContextHolderStrategy getSecurityContextHolderStrategy() { return this.securityContextHolderStrategy; } ApplicationContext context = getBuilder().getSharedObject(ApplicationContext.class); - String[] names = context.getBeanNamesForType(SecurityContextHolderStrategy.class); - if (names.length == 1) { - this.securityContextHolderStrategy = context.getBean(SecurityContextHolderStrategy.class); - } - else { - this.securityContextHolderStrategy = SecurityContextHolder.getContextHolderStrategy(); - } + this.securityContextHolderStrategy = context.getBeanProvider(SecurityContextHolderStrategy.class) + .getIfUnique(SecurityContextHolder::getContextHolderStrategy); return this.securityContextHolderStrategy; } From 5c5603dd3c64acc4ca3bf4ca7553969341905a15 Mon Sep 17 00:00:00 2001 From: Mehdi Rahimi Date: Mon, 30 Dec 2024 11:45:55 +0330 Subject: [PATCH 041/132] Change deprecated FilterSecurityInterceptor to AuthorizationFilter with a link to authorization page. Signed-off-by: Daeho Kwon --- docs/modules/ROOT/pages/servlet/architecture.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/servlet/architecture.adoc b/docs/modules/ROOT/pages/servlet/architecture.adoc index db92de0b7c..ad143353b8 100644 --- a/docs/modules/ROOT/pages/servlet/architecture.adoc +++ b/docs/modules/ROOT/pages/servlet/architecture.adoc @@ -609,7 +609,7 @@ try { } ---- <1> As described in <>, invoking `FilterChain.doFilter(request, response)` is the equivalent of invoking the rest of the application. -This means that if another part of the application, (<> or method security) throws an `AuthenticationException` or `AccessDeniedException` it is caught and handled here. +This means that if another part of the application, (xref:servlet/authorization/authorize-http-requests.adoc[`AuthorizationFilter`] or method security) throws an `AuthenticationException` or `AccessDeniedException` it is caught and handled here. <2> If the user is not authenticated or it is an `AuthenticationException`, __Start Authentication__. <3> Otherwise, __Access Denied__ From 785e632fa6e449e5b702d4769705cdd59a3248f0 Mon Sep 17 00:00:00 2001 From: Tran Ngoc Nhan Date: Sun, 29 Dec 2024 18:24:01 +0700 Subject: [PATCH 042/132] Remove obsolete typo in OAuth 2.0 Client page Signed-off-by: Daeho Kwon --- docs/modules/ROOT/pages/servlet/oauth2/client/index.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/servlet/oauth2/client/index.adoc b/docs/modules/ROOT/pages/servlet/oauth2/client/index.adoc index e4195ea9dc..bec08cf2ef 100644 --- a/docs/modules/ROOT/pages/servlet/oauth2/client/index.adoc +++ b/docs/modules/ROOT/pages/servlet/oauth2/client/index.adoc @@ -1,5 +1,5 @@ [[oauth2-client]] -= [[oauth2client]]OAuth 2.0 Client += OAuth 2.0 Client :page-section-summary-toc: 1 The OAuth 2.0 Client features provide support for the Client role as defined in the https://tools.ietf.org/html/rfc6749#section-1.1[OAuth 2.0 Authorization Framework]. From fc3221d0c167ad61505d920c1c52b198cceb88ce Mon Sep 17 00:00:00 2001 From: Meehdi Date: Wed, 25 Dec 2024 18:33:14 +0100 Subject: [PATCH 043/132] Fix incorrect rendering of SpEL expression example tabs Signed-off-by: Daeho Kwon --- .../ROOT/pages/servlet/authorization/method-security.adoc | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc b/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc index 903fcb5ac1..6e72929dca 100644 --- a/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc +++ b/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc @@ -216,13 +216,15 @@ Java:: ---- @PreAuthorize("hasAuthority('permission:read') || hasRole('ADMIN')") ---- -====== -.Kotlin -[source,kotlin,role="kotlin"] +Kotlin:: ++ +[source,kotlin,role="secondary"] ---- @PreAuthorize("hasAuthority('permission:read') || hasRole('ADMIN')") ---- +====== + However, you could instead grant `permission:read` to those with `ROLE_ADMIN`. One way to do this is with a `RoleHierarchy` like so: From 6685f38ac43000a62a171c9cfddc693ea19bb133 Mon Sep 17 00:00:00 2001 From: mskim Date: Wed, 25 Dec 2024 23:34:29 +0900 Subject: [PATCH 044/132] Fix logout code snippet for Kotlin: Corrected deleteCookies syntax Signed-off-by: Daeho Kwon --- docs/modules/ROOT/pages/servlet/authentication/logout.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/servlet/authentication/logout.adoc b/docs/modules/ROOT/pages/servlet/authentication/logout.adoc index d94b8a8d2e..876deb3a26 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/logout.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/logout.adoc @@ -226,7 +226,7 @@ Kotlin:: ---- http { logout { - deleteCookies = "our-custom-cookie" + deleteCookies("our-custom-cookie") } } ---- From 58df1bb180ee9cac387d45b10735a1448030e7ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sim=C3=A3o=20Gomes=20Viana?= Date: Wed, 8 Jan 2025 11:53:31 +0100 Subject: [PATCH 045/132] method-security: fix invalid Kotlin syntax MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit val/var on function parameters is invalid Kotlin syntax. It has been removed quite some time ago. This change updates the method-security page to reflect that. Signed-off-by: Simão Gomes Viana Signed-off-by: Daeho Kwon --- .../authorization/method-security.adoc | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc b/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc index 6e72929dca..329270c65e 100644 --- a/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc +++ b/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc @@ -108,7 +108,7 @@ Kotlin:: open class MyCustomerService { @PreAuthorize("hasAuthority('permission:read')") @PostAuthorize("returnObject.owner == authentication.name") - fun readCustomer(val id: String): Customer { ... } + fun readCustomer(id: String): Customer { ... } } ---- ====== @@ -338,7 +338,7 @@ Kotlin:: @Component open class BankService { @PreAuthorize("hasRole('ADMIN')") - fun readAccount(val id: Long): Account { + fun readAccount(id: Long): Account { // ... is only invoked if the `Authentication` has the `ROLE_ADMIN` authority } } @@ -426,7 +426,7 @@ Kotlin:: @Component open class BankService { @PostAuthorize("returnObject.owner == authentication.name") - fun readAccount(val id: Long): Account { + fun readAccount(id: Long): Account { // ... is only returned if the `Account` belongs to the logged in user } } @@ -536,7 +536,7 @@ Kotlin:: @Component open class BankService { @RequireOwnership - fun readAccount(val id: Long): Account { + fun readAccount(id: Long): Account { // ... is only returned if the `Account` belongs to the logged in user } } @@ -993,7 +993,7 @@ Kotlin:: @Component open class BankService { @IsAdmin - fun readAccount(val id: Long): Account { + fun readAccount(id: Long): Account { // ... is only returned if the `Account` belongs to the logged in user } } @@ -1084,7 +1084,7 @@ Kotlin:: @Component open class BankService { @HasRole("ADMIN") - fun readAccount(val id: Long): Account { + fun readAccount(id: Long): Account { // ... is only returned if the `Account` belongs to the logged in user } } @@ -1144,7 +1144,7 @@ Kotlin:: @Component open class BankService { @HasAnyRole(roles = arrayOf("'USER'", "'ADMIN'")) - fun readAccount(val id: Long): Account { + fun readAccount(id: Long): Account { // ... is only returned if the `Account` belongs to the logged in user } } @@ -1271,7 +1271,7 @@ Kotlin:: ---- @Component("authz") open class AuthorizationLogic { - fun decide(val operations: MethodSecurityExpressionOperations): boolean { + fun decide(operations: MethodSecurityExpressionOperations): boolean { // ... authorization logic } } @@ -1342,7 +1342,7 @@ Kotlin:: ---- @Component("authz") open class AuthorizationLogic { - fun decide(val operations: MethodSecurityExpressionOperations): AuthorizationDecision { + fun decide(operations: MethodSecurityExpressionOperations): AuthorizationDecision { // ... authorization logic return MyAuthorizationDecision(false, details) } @@ -1435,13 +1435,13 @@ Kotlin:: class MethodSecurityConfig { @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) - fun preAuthorize(val manager: MyAuthorizationManager) : Advisor { + fun preAuthorize(manager: MyAuthorizationManager) : Advisor { return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(manager) } @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) - fun postAuthorize(val manager: MyAuthorizationManager) : Advisor { + fun postAuthorize(manager: MyAuthorizationManager) : Advisor { return AuthorizationManagerAfterMethodInterceptor.postAuthorize(manager) } } @@ -1501,7 +1501,7 @@ Kotlin:: ---- companion object { @Bean - fun methodSecurityExpressionHandler(val roleHierarchy: RoleHierarchy) : MethodSecurityExpressionHandler { + fun methodSecurityExpressionHandler(roleHierarchy: RoleHierarchy) : MethodSecurityExpressionHandler { val handler = DefaultMethodSecurityExpressionHandler() handler.setRoleHierarchy(roleHierarchy) return handler @@ -3236,7 +3236,7 @@ Kotlin:: [source,kotlin,role="secondary"] ---- class MyAuthorizer { - fun isAdmin(val root: MethodSecurityExpressionOperations): boolean { + fun isAdmin(root: MethodSecurityExpressionOperations): boolean { val decision = root.hasAuthority("ADMIN"); // custom work ... return decision; @@ -3295,7 +3295,7 @@ Kotlin:: ---- @Component class MyExpressionHandler: DefaultMethodSecurityExpressionHandler { - override fun createEvaluationContext(val authentication: Supplier, + override fun createEvaluationContext(authentication: Supplier, val mi: MethodInvocation): EvaluationContext { val context = super.createEvaluationContext(authentication, mi) as StandardEvaluationContext val delegate = context.getRootObject().getValue() as MethodSecurityExpressionOperations From 7a08d9d07ad852ceb434c7401f30917111ae17c6 Mon Sep 17 00:00:00 2001 From: DingHao Date: Fri, 27 Dec 2024 18:36:57 +0800 Subject: [PATCH 046/132] Polish remove unused code Signed-off-by: DingHao Signed-off-by: Daeho Kwon --- .../configuration/WebSecurityConfiguration.java | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration.java index d172a85d59..4490852862 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2025 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. @@ -22,7 +22,6 @@ import jakarta.servlet.Filter; -import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; @@ -65,20 +64,16 @@ * @see WebSecurity */ @Configuration(proxyBeanMethods = false) -public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware { +public class WebSecurityConfiguration implements ImportAware { private WebSecurity webSecurity; private Boolean debugEnabled; - private List> webSecurityConfigurers; - private List securityFilterChains = Collections.emptyList(); private List webSecurityCustomizers = Collections.emptyList(); - private ClassLoader beanClassLoader; - @Autowired(required = false) private HttpSecurity httpSecurity; @@ -164,7 +159,6 @@ public void setFilterChainProxySecurityConfigurer(ObjectPostProcessor ob for (SecurityConfigurer webSecurityConfigurer : webSecurityConfigurers) { this.webSecurity.apply(webSecurityConfigurer); } - this.webSecurityConfigurers = webSecurityConfigurers; } @Autowired(required = false) @@ -193,11 +187,6 @@ public void setImportMetadata(AnnotationMetadata importMetadata) { } } - @Override - public void setBeanClassLoader(ClassLoader classLoader) { - this.beanClassLoader = classLoader; - } - /** * A custom version of the Spring provided AnnotationAwareOrderComparator that uses * {@link AnnotationUtils#findAnnotation(Class, Class)} to look on super class From 500ade9501da2fd113defd3dbe56bd0951d6d391 Mon Sep 17 00:00:00 2001 From: DingHao Date: Wed, 8 Jan 2025 10:18:19 +0800 Subject: [PATCH 047/132] Avoid unnecessary instantiation of HttpSecurity when a SecurityFilterChain bean is provided Signed-off-by: DingHao Signed-off-by: Daeho Kwon --- .../WebSecurityConfiguration.java | 15 ++++---- .../WebSecurityConfigurationTests.java | 36 ++++++++++++++++++- 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration.java index 4490852862..f58e9e55fc 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration.java @@ -22,6 +22,7 @@ import jakarta.servlet.Filter; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; @@ -74,9 +75,6 @@ public class WebSecurityConfiguration implements ImportAware { private List webSecurityCustomizers = Collections.emptyList(); - @Autowired(required = false) - private HttpSecurity httpSecurity; - @Bean public static DelegatingApplicationListener delegatingApplicationListener() { return new DelegatingApplicationListener(); @@ -94,14 +92,15 @@ public SecurityExpressionHandler webSecurityExpressionHandler( * @throws Exception */ @Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME) - public Filter springSecurityFilterChain() throws Exception { + public Filter springSecurityFilterChain(ObjectProvider provider) throws Exception { boolean hasFilterChain = !this.securityFilterChains.isEmpty(); if (!hasFilterChain) { this.webSecurity.addSecurityFilterChainBuilder(() -> { - this.httpSecurity.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()); - this.httpSecurity.formLogin(Customizer.withDefaults()); - this.httpSecurity.httpBasic(Customizer.withDefaults()); - return this.httpSecurity.build(); + HttpSecurity httpSecurity = provider.getObject(); + httpSecurity.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()); + httpSecurity.formLogin(Customizer.withDefaults()); + httpSecurity.httpBasic(Customizer.withDefaults()); + return httpSecurity.build(); }); } for (SecurityFilterChain securityFilterChain : this.securityFilterChains) { diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurationTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurationTests.java index f6a53bff45..a17ed62723 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurationTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,8 +27,10 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -326,6 +328,12 @@ public void loadConfigWhenTwoSecurityFilterChainsPresentAndSecondWithAnyRequestT .isInstanceOf(IllegalArgumentException.class); } + @Test + public void avoidUnnecessaryHttpSecurityInstantiationWhenProvideOneSecurityFilterChain() { + this.spring.register(SecurityFilterChainConfig.class).autowire(); + assertThat(this.spring.getContext().getBean(CustomBeanPostProcessor.class).instantiationCount).isEqualTo(1); + } + private void assertAnotherUserPermission(WebInvocationPrivilegeEvaluator privilegeEvaluator) { Authentication anotherUser = new TestingAuthenticationToken("anotherUser", "password", "ROLE_ANOTHER"); assertThat(privilegeEvaluator.isAllowed("/user", anotherUser)).isFalse(); @@ -347,6 +355,32 @@ private void assertUserPermissions(WebInvocationPrivilegeEvaluator privilegeEval assertThat(privilegeEvaluator.isAllowed("/another", user)).isTrue(); } + @Configuration + @EnableWebSecurity + @Import(CustomBeanPostProcessor.class) + static class SecurityFilterChainConfig { + + @Bean + SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + return http.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()).build(); + } + + } + + static class CustomBeanPostProcessor implements BeanPostProcessor { + + int instantiationCount = 0; + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof HttpSecurity) { + this.instantiationCount++; + } + return bean; + } + + } + @Configuration @EnableWebSecurity @Import(AuthenticationTestConfiguration.class) From 1377f5f4fc44547243df88fa7948f11c550e3f6c Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Thu, 9 Jan 2025 17:11:08 -0600 Subject: [PATCH 048/132] CustomBeanPostProcessor -> CountHttpSecurityBeanPostProcessor Issue gh-16370 Signed-off-by: Rob Winch <362503+rwinch@users.noreply.github.com> Signed-off-by: Daeho Kwon --- .../web/configuration/WebSecurityConfigurationTests.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurationTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurationTests.java index a17ed62723..e546ffb6a1 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurationTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurationTests.java @@ -331,7 +331,8 @@ public void loadConfigWhenTwoSecurityFilterChainsPresentAndSecondWithAnyRequestT @Test public void avoidUnnecessaryHttpSecurityInstantiationWhenProvideOneSecurityFilterChain() { this.spring.register(SecurityFilterChainConfig.class).autowire(); - assertThat(this.spring.getContext().getBean(CustomBeanPostProcessor.class).instantiationCount).isEqualTo(1); + assertThat(this.spring.getContext().getBean(CountHttpSecurityBeanPostProcessor.class).instantiationCount) + .isEqualTo(1); } private void assertAnotherUserPermission(WebInvocationPrivilegeEvaluator privilegeEvaluator) { @@ -357,7 +358,7 @@ private void assertUserPermissions(WebInvocationPrivilegeEvaluator privilegeEval @Configuration @EnableWebSecurity - @Import(CustomBeanPostProcessor.class) + @Import(CountHttpSecurityBeanPostProcessor.class) static class SecurityFilterChainConfig { @Bean @@ -367,7 +368,7 @@ SecurityFilterChain filterChain(HttpSecurity http) throws Exception { } - static class CustomBeanPostProcessor implements BeanPostProcessor { + static class CountHttpSecurityBeanPostProcessor implements BeanPostProcessor { int instantiationCount = 0; From fcdfeb409995a5b54a16e27829fea212d5a4a253 Mon Sep 17 00:00:00 2001 From: Max Batischev Date: Thu, 9 Jan 2025 00:57:07 +0300 Subject: [PATCH 049/132] Add Support OAuth2AuthorizationRequestResolver As Bean Closes gh-16380 Signed-off-by: Max Batischev Signed-off-by: Daeho Kwon --- .../oauth2/client/OAuth2LoginConfigurer.java | 37 ++++++++------ .../client/OAuth2LoginConfigurerTests.java | 51 ++++++++++++++++++- 2 files changed, 72 insertions(+), 16 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java index d191bb740b..1c6f9d1cb7 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -68,6 +68,7 @@ import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; import org.springframework.security.oauth2.client.web.AuthenticatedPrincipalOAuth2AuthorizedClientRepository; import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository; +import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizationRequestResolver; import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter; import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver; import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; @@ -396,20 +397,8 @@ public void init(B http) throws Exception { @Override public void configure(B http) throws Exception { - OAuth2AuthorizationRequestRedirectFilter authorizationRequestFilter; - if (this.authorizationEndpointConfig.authorizationRequestResolver != null) { - authorizationRequestFilter = new OAuth2AuthorizationRequestRedirectFilter( - this.authorizationEndpointConfig.authorizationRequestResolver); - } - else { - String authorizationRequestBaseUri = this.authorizationEndpointConfig.authorizationRequestBaseUri; - if (authorizationRequestBaseUri == null) { - authorizationRequestBaseUri = OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI; - } - authorizationRequestFilter = new OAuth2AuthorizationRequestRedirectFilter( - OAuth2ClientConfigurerUtils.getClientRegistrationRepository(this.getBuilder()), - authorizationRequestBaseUri); - } + OAuth2AuthorizationRequestRedirectFilter authorizationRequestFilter = new OAuth2AuthorizationRequestRedirectFilter( + getAuthorizationRequestResolver()); if (this.authorizationEndpointConfig.authorizationRequestRepository != null) { authorizationRequestFilter .setAuthorizationRequestRepository(this.authorizationEndpointConfig.authorizationRequestRepository); @@ -440,6 +429,24 @@ protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingU return new AntPathRequestMatcher(loginProcessingUrl); } + private OAuth2AuthorizationRequestResolver getAuthorizationRequestResolver() { + if (this.authorizationEndpointConfig.authorizationRequestResolver != null) { + return this.authorizationEndpointConfig.authorizationRequestResolver; + } + ClientRegistrationRepository clientRegistrationRepository = OAuth2ClientConfigurerUtils + .getClientRegistrationRepository(getBuilder()); + ResolvableType resolvableType = ResolvableType.forClass(OAuth2AuthorizationRequestResolver.class); + OAuth2AuthorizationRequestResolver bean = getBeanOrNull(resolvableType); + if (bean != null) { + return bean; + } + String authorizationRequestBaseUri = this.authorizationEndpointConfig.authorizationRequestBaseUri; + if (authorizationRequestBaseUri == null) { + authorizationRequestBaseUri = OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI; + } + return new DefaultOAuth2AuthorizationRequestResolver(clientRegistrationRepository, authorizationRequestBaseUri); + } + @SuppressWarnings("unchecked") private JwtDecoderFactory getJwtDecoderFactoryBean() { ResolvableType type = ResolvableType.forClassWithGenerics(JwtDecoderFactory.class, ClientRegistration.class); diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurerTests.java index b56d047a5f..770a8a17be 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -379,6 +379,19 @@ public void oauth2LoginWithCustomAuthorizationRequestParameters() throws Excepti "https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=clientId&scope=openid+profile+email&state=state&redirect_uri=http%3A%2F%2Flocalhost%2Flogin%2Foauth2%2Fcode%2Fgoogle&custom-param1=custom-value1"); } + @Test + public void oauth2LoginWithCustomAuthorizationRequestParametersAndResolverAsBean() throws Exception { + loadConfig(OAuth2LoginConfigCustomAuthorizationRequestResolverBean.class); + // @formatter:off + // @formatter:on + String requestUri = "/oauth2/authorization/google"; + this.request = new MockHttpServletRequest("GET", requestUri); + this.request.setServletPath(requestUri); + this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); + assertThat(this.response.getRedirectedUrl()).isEqualTo( + "https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=clientId&scope=openid+profile+email&state=state&redirect_uri=http%3A%2F%2Flocalhost%2Flogin%2Foauth2%2Fcode%2Fgoogle&custom-param1=custom-value1"); + } + @Test public void requestWhenOauth2LoginWithCustomAuthorizationRequestParametersThenParametersInRedirectedUrl() throws Exception { @@ -940,6 +953,42 @@ SecurityFilterChain filterChain(HttpSecurity http) throws Exception { } + @Configuration + @EnableWebSecurity + static class OAuth2LoginConfigCustomAuthorizationRequestResolverBean extends CommonSecurityFilterChainConfig { + + private ClientRegistrationRepository clientRegistrationRepository = new InMemoryClientRegistrationRepository( + GOOGLE_CLIENT_REGISTRATION); + + @Bean + SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + // @formatter:off + http + .oauth2Login() + .clientRegistrationRepository(this.clientRegistrationRepository) + .authorizationEndpoint(); + // @formatter:on + return super.configureFilterChain(http); + } + + @Bean + OAuth2AuthorizationRequestResolver resolver() { + OAuth2AuthorizationRequestResolver resolver = mock(OAuth2AuthorizationRequestResolver.class); + // @formatter:off + OAuth2AuthorizationRequest result = OAuth2AuthorizationRequest.authorizationCode() + .authorizationUri("https://accounts.google.com/authorize") + .clientId("client-id") + .state("adsfa") + .authorizationRequestUri( + "https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=clientId&scope=openid+profile+email&state=state&redirect_uri=http%3A%2F%2Flocalhost%2Flogin%2Foauth2%2Fcode%2Fgoogle&custom-param1=custom-value1") + .build(); + given(resolver.resolve(any())).willReturn(result); + // @formatter:on + return resolver; + } + + } + @Configuration @EnableWebSecurity static class OAuth2LoginConfigCustomAuthorizationRequestResolverInLambda From 46a41867b39b7868fe83369e805472ea2542cfbf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 04:03:08 +0000 Subject: [PATCH 050/132] Bump io.mockk:mockk from 1.13.14 to 1.13.16 Bumps [io.mockk:mockk](https://github.com/mockk/mockk) from 1.13.14 to 1.13.16. - [Release notes](https://github.com/mockk/mockk/releases) - [Commits](https://github.com/mockk/mockk/compare/1.13.14...1.13.16) --- updated-dependencies: - dependency-name: io.mockk:mockk dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: Daeho Kwon --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e1446730ba..b640ee777e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -29,7 +29,7 @@ com-unboundid-unboundid-ldapsdk = "com.unboundid:unboundid-ldapsdk:6.0.11" com-unboundid-unboundid-ldapsdk7 = "com.unboundid:unboundid-ldapsdk:7.0.1" commons-collections = "commons-collections:commons-collections:3.2.2" io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.14.2" -io-mockk = "io.mockk:mockk:1.13.14" +io-mockk = "io.mockk:mockk:1.13.16" io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2023.0.13" io-rsocket-rsocket-bom = { module = "io.rsocket:rsocket-bom", version.ref = "io-rsocket" } io-spring-javaformat-spring-javaformat-checkstyle = { module = "io.spring.javaformat:spring-javaformat-checkstyle", version.ref = "io-spring-javaformat" } From ee2254b7db16e9718a31efbc3fa0b8c3af3bcf87 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 04:03:01 +0000 Subject: [PATCH 051/132] Bump org.htmlunit:htmlunit from 4.7.0 to 4.8.0 Bumps [org.htmlunit:htmlunit](https://github.com/HtmlUnit/htmlunit) from 4.7.0 to 4.8.0. - [Release notes](https://github.com/HtmlUnit/htmlunit/releases) - [Commits](https://github.com/HtmlUnit/htmlunit/compare/4.7.0...4.8.0) --- updated-dependencies: - dependency-name: org.htmlunit:htmlunit dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: Daeho Kwon --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b640ee777e..ac7e1a792d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -48,7 +48,7 @@ jakarta-websocket-jakarta-websocket-client-api = { module = "jakarta.websocket:j jakarta-xml-bind-jakarta-xml-bind-api = "jakarta.xml.bind:jakarta.xml.bind-api:4.0.2" ldapsdk = "ldapsdk:ldapsdk:4.1" net-sourceforge-htmlunit = "net.sourceforge.htmlunit:htmlunit:2.70.0" -org-htmlunit-htmlunit = "org.htmlunit:htmlunit:4.7.0" +org-htmlunit-htmlunit = "org.htmlunit:htmlunit:4.8.0" org-apache-directory-server-apacheds-core = { module = "org.apache.directory.server:apacheds-core", version.ref = "org-apache-directory-server" } org-apache-directory-server-apacheds-entry = { module = "org.apache.directory.server:apacheds-core-entry", version.ref = "org-apache-directory-server" } org-apache-directory-server-apacheds-protocol-ldap = { module = "org.apache.directory.server:apacheds-protocol-ldap", version.ref = "org-apache-directory-server" } From 1e253871d6110db073f24be58b74d3d2d6eb2927 Mon Sep 17 00:00:00 2001 From: Max Batischev Date: Sat, 11 Jan 2025 11:56:39 +0300 Subject: [PATCH 052/132] Fix Kotlin webAuthn {} Fixes the default configuration for WebAuthn Kotlin DSL Closes gh-16338 Signed-off-by: Max Batischev Signed-off-by: Daeho Kwon --- .../config/annotation/web/WebAuthnDsl.kt | 11 +++--- .../config/annotation/web/WebAuthnDslTests.kt | 39 ++++++++++++++++++- 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/config/src/main/kotlin/org/springframework/security/config/annotation/web/WebAuthnDsl.kt b/config/src/main/kotlin/org/springframework/security/config/annotation/web/WebAuthnDsl.kt index 1624817431..c8296f367d 100644 --- a/config/src/main/kotlin/org/springframework/security/config/annotation/web/WebAuthnDsl.kt +++ b/config/src/main/kotlin/org/springframework/security/config/annotation/web/WebAuthnDsl.kt @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,7 @@ import org.springframework.security.config.annotation.web.configurers.WebAuthnCo * @property the allowed origins * @since 6.4 * @author Rob Winch + * @author Max Batischev */ @SecurityMarker class WebAuthnDsl { @@ -34,10 +35,10 @@ class WebAuthnDsl { var allowedOrigins: Set? = null internal fun get(): (WebAuthnConfigurer) -> Unit { - return { webAuthn -> webAuthn - .rpId(rpId) - .rpName(rpName) - .allowedOrigins(allowedOrigins); + return { webAuthn -> + rpName?.also { webAuthn.rpName(rpName) } + rpId?.also { webAuthn.rpId(rpId) } + allowedOrigins?.also { webAuthn.allowedOrigins(allowedOrigins) } } } } diff --git a/config/src/test/kotlin/org/springframework/security/config/annotation/web/WebAuthnDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/annotation/web/WebAuthnDslTests.kt index c0705e50bc..023314cdc3 100644 --- a/config/src/test/kotlin/org/springframework/security/config/annotation/web/WebAuthnDslTests.kt +++ b/config/src/test/kotlin/org/springframework/security/config/annotation/web/WebAuthnDslTests.kt @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2025 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,6 +16,7 @@ package org.springframework.security.config.annotation.web +import org.hamcrest.Matchers import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.springframework.beans.factory.annotation.Autowired @@ -30,7 +31,9 @@ import org.springframework.security.core.userdetails.UserDetailsService import org.springframework.security.provisioning.InMemoryUserDetailsManager import org.springframework.security.web.SecurityFilterChain import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.get import org.springframework.test.web.servlet.post +import org.springframework.test.web.servlet.result.MockMvcResultMatchers /** * Tests for [WebAuthnDsl] @@ -55,6 +58,40 @@ class WebAuthnDslTests { } } + @Test + fun `webauthn and formLogin configured with default registration page`() { + spring.register(DefaultWebauthnConfig::class.java).autowire() + + this.mockMvc.get("/login/webauthn.js") + .andExpect { + MockMvcResultMatchers.status().isOk + header { + string("content-type", "text/javascript;charset=UTF-8") + } + content { + string(Matchers.containsString("async function authenticate(")) + } + } + } + + @Configuration + @EnableWebSecurity + open class DefaultWebauthnConfig { + @Bean + open fun userDetailsService(): UserDetailsService = + InMemoryUserDetailsManager() + + + @Bean + open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + http{ + formLogin { } + webAuthn { } + } + return http.build() + } + } + @Configuration @EnableWebSecurity open class WebauthnConfig { From ded6f8c21d922bf87eef0d4ad93339a69855aeac Mon Sep 17 00:00:00 2001 From: Max Batischev Date: Sat, 11 Jan 2025 12:02:46 +0300 Subject: [PATCH 053/132] Add Support disableDefaultRegistrationPage to WebAuthnDsl Closes gh-16395 Signed-off-by: Max Batischev Signed-off-by: Daeho Kwon --- .../config/annotation/web/WebAuthnDsl.kt | 3 ++ .../config/annotation/web/WebAuthnDslTests.kt | 36 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/config/src/main/kotlin/org/springframework/security/config/annotation/web/WebAuthnDsl.kt b/config/src/main/kotlin/org/springframework/security/config/annotation/web/WebAuthnDsl.kt index c8296f367d..f1a9600f00 100644 --- a/config/src/main/kotlin/org/springframework/security/config/annotation/web/WebAuthnDsl.kt +++ b/config/src/main/kotlin/org/springframework/security/config/annotation/web/WebAuthnDsl.kt @@ -24,6 +24,7 @@ import org.springframework.security.config.annotation.web.configurers.WebAuthnCo * @property rpName the relying party name * @property rpId the relying party id * @property the allowed origins + * @property disableDefaultRegistrationPage disable default webauthn registration page * @since 6.4 * @author Rob Winch * @author Max Batischev @@ -33,12 +34,14 @@ class WebAuthnDsl { var rpName: String? = null var rpId: String? = null var allowedOrigins: Set? = null + var disableDefaultRegistrationPage: Boolean? = false internal fun get(): (WebAuthnConfigurer) -> Unit { return { webAuthn -> rpName?.also { webAuthn.rpName(rpName) } rpId?.also { webAuthn.rpId(rpId) } allowedOrigins?.also { webAuthn.allowedOrigins(allowedOrigins) } + disableDefaultRegistrationPage?.also { webAuthn.disableDefaultRegistrationPage(disableDefaultRegistrationPage!!) } } } } diff --git a/config/src/test/kotlin/org/springframework/security/config/annotation/web/WebAuthnDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/annotation/web/WebAuthnDslTests.kt index 023314cdc3..8bdee169f8 100644 --- a/config/src/test/kotlin/org/springframework/security/config/annotation/web/WebAuthnDslTests.kt +++ b/config/src/test/kotlin/org/springframework/security/config/annotation/web/WebAuthnDslTests.kt @@ -74,6 +74,42 @@ class WebAuthnDslTests { } } + @Test + fun `webauthn and formLogin configured with disabled default registration page`() { + spring.register(FormLoginAndNoDefaultRegistrationPageConfiguration::class.java).autowire() + + this.mockMvc.get("/login/webauthn.js") + .andExpect { + MockMvcResultMatchers.status().isOk + header { + string("content-type", "text/javascript;charset=UTF-8") + } + content { + string(Matchers.containsString("async function authenticate(")) + } + } + } + + @Configuration + @EnableWebSecurity + open class FormLoginAndNoDefaultRegistrationPageConfiguration { + @Bean + open fun userDetailsService(): UserDetailsService = + InMemoryUserDetailsManager() + + + @Bean + open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + http{ + formLogin { } + webAuthn { + disableDefaultRegistrationPage = true + } + } + return http.build() + } + } + @Configuration @EnableWebSecurity open class DefaultWebauthnConfig { From c0b624a45857c0ee626f6882ced04ad24b63e1cd Mon Sep 17 00:00:00 2001 From: "Muhammad N. Fadhil" Date: Sun, 8 Dec 2024 15:44:03 +0300 Subject: [PATCH 054/132] Fixed a grammatical mistake in the docs. Signed-off-by: Daeho Kwon --- .../modules/ROOT/pages/servlet/authentication/architecture.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/servlet/authentication/architecture.adoc b/docs/modules/ROOT/pages/servlet/authentication/architecture.adoc index b181fa9635..2f097f9ab4 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/architecture.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/architecture.adoc @@ -2,7 +2,7 @@ = Servlet Authentication Architecture :figures: servlet/authentication/architecture -This discussion expands on xref:servlet/architecture.adoc#servlet-architecture[Servlet Security: The Big Picture] to describe the main architectural components of Spring Security's used in Servlet authentication. +This discussion expands on xref:servlet/architecture.adoc#servlet-architecture[Servlet Security: The Big Picture] to describe the main architectural components of Spring Security used in Servlet authentication. If you need concrete flows that explain how these pieces fit together, look at the xref:servlet/authentication/index.adoc#servlet-authentication-mechanisms[Authentication Mechanism] specific sections. * <> - The `SecurityContextHolder` is where Spring Security stores the details of who is xref:features/authentication/index.adoc#authentication[authenticated]. From a5713cbfaf5948d5dbffafdefed6649f3adb6379 Mon Sep 17 00:00:00 2001 From: "Muhammad N. Fadhil" Date: Sun, 8 Dec 2024 15:44:26 +0300 Subject: [PATCH 055/132] Fixed grammatical mistakes in the docs. Signed-off-by: Daeho Kwon --- .../ROOT/pages/servlet/authentication/persistence.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/modules/ROOT/pages/servlet/authentication/persistence.adoc b/docs/modules/ROOT/pages/servlet/authentication/persistence.adoc index e98615eb01..ddf930762f 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/persistence.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/persistence.adoc @@ -196,7 +196,7 @@ image::{figures}/securitycontextpersistencefilter.png[] image:{icondir}/number_1.png[] Before running the rest of the application, `SecurityContextPersistenceFilter` loads the `SecurityContext` from the `SecurityContextRepository` and sets it on the `SecurityContextHolder`. -image:{icondir}/number_2.png[] Next, the application is ran. +image:{icondir}/number_2.png[] Next, the application is run. image:{icondir}/number_3.png[] Finally, if the `SecurityContext` has changed, we save the `SecurityContext` using the `SecurityContextRepository`. This means that when using `SecurityContextPersistenceFilter`, just setting the `SecurityContextHolder` will ensure that the `SecurityContext` is persisted using `SecurityContextRepository`. @@ -219,7 +219,7 @@ image::{figures}/securitycontextholderfilter.png[] image:{icondir}/number_1.png[] Before running the rest of the application, `SecurityContextHolderFilter` loads the `SecurityContext` from the `SecurityContextRepository` and sets it on the `SecurityContextHolder`. -image:{icondir}/number_2.png[] Next, the application is ran. +image:{icondir}/number_2.png[] Next, the application is run. Unlike, xref:servlet/authentication/persistence.adoc#securitycontextpersistencefilter[`SecurityContextPersistenceFilter`], `SecurityContextHolderFilter` only loads the `SecurityContext` it does not save the `SecurityContext`. This means that when using `SecurityContextHolderFilter`, it is required that the `SecurityContext` is explicitly saved. From 8e10305dd55d79e6046a10b85be148626027faa3 Mon Sep 17 00:00:00 2001 From: "Muhammad N. Fadhil" Date: Tue, 10 Dec 2024 09:25:11 +0300 Subject: [PATCH 056/132] Improved sentence phrasing in the docs. Signed-off-by: Daeho Kwon --- .../modules/ROOT/pages/servlet/authentication/architecture.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/servlet/authentication/architecture.adoc b/docs/modules/ROOT/pages/servlet/authentication/architecture.adoc index 2f097f9ab4..7d900f9476 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/architecture.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/architecture.adoc @@ -2,7 +2,7 @@ = Servlet Authentication Architecture :figures: servlet/authentication/architecture -This discussion expands on xref:servlet/architecture.adoc#servlet-architecture[Servlet Security: The Big Picture] to describe the main architectural components of Spring Security used in Servlet authentication. +This discussion expands on xref:servlet/architecture.adoc#servlet-architecture[Servlet Security: The Big Picture] to describe the main architectural components that Spring Security uses in Servlet authentication. If you need concrete flows that explain how these pieces fit together, look at the xref:servlet/authentication/index.adoc#servlet-authentication-mechanisms[Authentication Mechanism] specific sections. * <> - The `SecurityContextHolder` is where Spring Security stores the details of who is xref:features/authentication/index.adoc#authentication[authenticated]. From 2e0389b0bb9745abfe57be70a921962a4b6b2dd8 Mon Sep 17 00:00:00 2001 From: Steve Riesenberg <5248162+sjohnr@users.noreply.github.com> Date: Mon, 13 Jan 2025 09:41:43 -0600 Subject: [PATCH 057/132] Update release-scheduler.yml Remove 5.8.x and 6.2.x branches from release-scheduler.yml Signed-off-by: Steve Riesenberg <5248162+sjohnr@users.noreply.github.com> Signed-off-by: Daeho Kwon --- .github/workflows/release-scheduler.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-scheduler.yml b/.github/workflows/release-scheduler.yml index b50ed7289a..8b2f0f1eac 100644 --- a/.github/workflows/release-scheduler.yml +++ b/.github/workflows/release-scheduler.yml @@ -11,7 +11,7 @@ jobs: strategy: matrix: # List of active maintenance branches. - branch: [ main, 6.4.x, 6.3.x, 6.2.x, 5.8.x ] + branch: [ main, 6.4.x, 6.3.x ] runs-on: ubuntu-latest steps: - name: Checkout From 31286668001a937bf6539874f7c0ea335fc22f12 Mon Sep 17 00:00:00 2001 From: Toshiaki Maki Date: Thu, 26 Dec 2024 03:41:16 +0900 Subject: [PATCH 058/132] Fix for JdbcOneTimeTokenService cleanupExpiredTokens failing with PostgreSQL Closes gh-16344 Signed-off-by: Daeho Kwon --- .../security/authentication/ott/JdbcOneTimeTokenService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/springframework/security/authentication/ott/JdbcOneTimeTokenService.java b/core/src/main/java/org/springframework/security/authentication/ott/JdbcOneTimeTokenService.java index 014541373a..4cf6753631 100644 --- a/core/src/main/java/org/springframework/security/authentication/ott/JdbcOneTimeTokenService.java +++ b/core/src/main/java/org/springframework/security/authentication/ott/JdbcOneTimeTokenService.java @@ -190,7 +190,8 @@ private ThreadPoolTaskScheduler createTaskScheduler(String cleanupCron) { } public void cleanupExpiredTokens() { - List parameters = List.of(new SqlParameterValue(Types.TIMESTAMP, Instant.now())); + List parameters = List + .of(new SqlParameterValue(Types.TIMESTAMP, Timestamp.from(Instant.now()))); PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters.toArray()); int deletedCount = this.jdbcOperations.update(DELETE_ONE_TIME_TOKENS_BY_EXPIRY_TIME_QUERY, pss); if (this.logger.isDebugEnabled()) { From 3130dcab385498bc119e7e1458b5863d85f1e5ce Mon Sep 17 00:00:00 2001 From: Max Batischev Date: Fri, 6 Dec 2024 00:43:14 +0300 Subject: [PATCH 059/132] Add support fullyAuthenticated to Kotlin DSL Closes gh-16162 Signed-off-by: Daeho Kwon --- .../web/AuthorizeHttpRequestsDsl.kt | 7 +++ .../web/AuthorizeHttpRequestsDslTests.kt | 63 ++++++++++++++++++- 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/config/src/main/kotlin/org/springframework/security/config/annotation/web/AuthorizeHttpRequestsDsl.kt b/config/src/main/kotlin/org/springframework/security/config/annotation/web/AuthorizeHttpRequestsDsl.kt index 64249d7c80..0133670a18 100644 --- a/config/src/main/kotlin/org/springframework/security/config/annotation/web/AuthorizeHttpRequestsDsl.kt +++ b/config/src/main/kotlin/org/springframework/security/config/annotation/web/AuthorizeHttpRequestsDsl.kt @@ -275,6 +275,13 @@ class AuthorizeHttpRequestsDsl : AbstractRequestMatcherDsl { val authenticated: AuthorizationManager = AuthenticatedAuthorizationManager.authenticated() + /** + * Specify that URLs are allowed by users who have authenticated and were not "remembered". + * @since 6.5 + */ + val fullyAuthenticated: AuthorizationManager = + AuthenticatedAuthorizationManager.fullyAuthenticated() + internal fun get(): (AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry) -> Unit { return { requests -> authorizationRules.forEach { rule -> diff --git a/config/src/test/kotlin/org/springframework/security/config/annotation/web/AuthorizeHttpRequestsDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/annotation/web/AuthorizeHttpRequestsDslTests.kt index 5a124b2f97..dfede958e7 100644 --- a/config/src/test/kotlin/org/springframework/security/config/annotation/web/AuthorizeHttpRequestsDslTests.kt +++ b/config/src/test/kotlin/org/springframework/security/config/annotation/web/AuthorizeHttpRequestsDslTests.kt @@ -27,6 +27,8 @@ import org.springframework.context.annotation.Configuration import org.springframework.http.HttpMethod import org.springframework.security.access.hierarchicalroles.RoleHierarchy import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl +import org.springframework.security.authentication.RememberMeAuthenticationToken +import org.springframework.security.authentication.TestAuthentication import org.springframework.security.authorization.AuthorizationDecision import org.springframework.security.authorization.AuthorizationManager import org.springframework.security.config.annotation.web.builders.HttpSecurity @@ -35,11 +37,11 @@ import org.springframework.security.config.core.GrantedAuthorityDefaults import org.springframework.security.config.test.SpringTestContext import org.springframework.security.config.test.SpringTestContextExtension import org.springframework.security.core.Authentication +import org.springframework.security.core.authority.AuthorityUtils import org.springframework.security.core.userdetails.User import org.springframework.security.core.userdetails.UserDetailsService import org.springframework.security.provisioning.InMemoryUserDetailsManager -import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf -import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic +import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.* import org.springframework.security.web.SecurityFilterChain import org.springframework.security.web.access.intercept.RequestAuthorizationContext import org.springframework.security.web.util.matcher.RegexRequestMatcher @@ -961,4 +963,61 @@ class AuthorizeHttpRequestsDslTests { } } + + @Test + fun `request when fully authenticated configured then responds ok`() { + this.spring.register(FullyAuthenticatedConfig::class.java).autowire() + + this.mockMvc.get("/path") { + with(user("user").roles("USER")) + }.andExpect { + status { + isOk() + } + } + } + + @Test + fun `request when fully authenticated configured and remember-me token then responds unauthorized`() { + this.spring.register(FullyAuthenticatedConfig::class.java).autowire() + val rememberMe = RememberMeAuthenticationToken("key", "user", + AuthorityUtils.createAuthorityList("ROLE_USER")) + + this.mockMvc.get("/path") { + with(user("user").roles("USER")) + with(authentication(rememberMe)) + }.andExpect { + status { + isUnauthorized() + } + } + } + + @Configuration + @EnableWebSecurity + @EnableWebMvc + open class FullyAuthenticatedConfig { + @Bean + open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + http { + authorizeHttpRequests { + authorize("/path", fullyAuthenticated) + } + httpBasic { } + rememberMe { } + } + return http.build() + } + + @Bean + open fun userDetailsService(): UserDetailsService = InMemoryUserDetailsManager(TestAuthentication.user()) + + @RestController + internal class PathController { + @GetMapping("/path") + fun path(): String { + return "ok" + } + } + } } From 46e53269f1d20bcb1bb3e46d4f41a604d305e6d9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Jan 2025 03:47:39 +0000 Subject: [PATCH 060/132] Bump io.micrometer:micrometer-observation from 1.14.2 to 1.14.3 Bumps [io.micrometer:micrometer-observation](https://github.com/micrometer-metrics/micrometer) from 1.14.2 to 1.14.3. - [Release notes](https://github.com/micrometer-metrics/micrometer/releases) - [Commits](https://github.com/micrometer-metrics/micrometer/compare/v1.14.2...v1.14.3) --- updated-dependencies: - dependency-name: io.micrometer:micrometer-observation dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: Daeho Kwon --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ac7e1a792d..e6a14eb90e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -28,7 +28,7 @@ com-squareup-okhttp3-okhttp = { module = "com.squareup.okhttp3:okhttp", version. com-unboundid-unboundid-ldapsdk = "com.unboundid:unboundid-ldapsdk:6.0.11" com-unboundid-unboundid-ldapsdk7 = "com.unboundid:unboundid-ldapsdk:7.0.1" commons-collections = "commons-collections:commons-collections:3.2.2" -io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.14.2" +io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.14.3" io-mockk = "io.mockk:mockk:1.13.16" io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2023.0.13" io-rsocket-rsocket-bom = { module = "io.rsocket:rsocket-bom", version.ref = "io-rsocket" } From 49a156859b5a4efc0d217e55a2d331bcade2d8ce Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Tue, 14 Jan 2025 16:06:18 -0700 Subject: [PATCH 061/132] Add Serializable to Csrf Components Issue gh-16276 Signed-off-by: Daeho Kwon --- ...ingSecurityCoreVersionSerializableTests.java | 11 +++++++++++ ...k.security.web.csrf.CsrfException.serialized | Bin 0 -> 10718 bytes ...ecurity.web.csrf.DefaultCsrfToken.serialized | Bin 0 -> 172 bytes ...eb.csrf.InvalidCsrfTokenException.serialized | Bin 0 -> 10882 bytes ...eb.csrf.MissingCsrfTokenException.serialized | Bin 0 -> 10868 bytes ...ity.web.server.csrf.CsrfException.serialized | Bin 0 -> 10725 bytes ....web.server.csrf.DefaultCsrfToken.serialized | Bin 0 -> 179 bytes .../security/web/csrf/CsrfException.java | 6 +++++- .../csrf/CsrfTokenRequestAttributeHandler.java | 1 + .../security/web/csrf/DefaultCsrfToken.java | 6 +++++- .../web/csrf/InvalidCsrfTokenException.java | 6 +++++- .../web/csrf/LazyCsrfTokenRepository.java | 1 + .../security/web/server/csrf/CsrfException.java | 6 +++++- .../web/server/csrf/DefaultCsrfToken.java | 6 +++++- 14 files changed, 38 insertions(+), 5 deletions(-) create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.web.csrf.CsrfException.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.web.csrf.DefaultCsrfToken.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.web.csrf.InvalidCsrfTokenException.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.web.csrf.MissingCsrfTokenException.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.web.server.csrf.CsrfException.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.web.server.csrf.DefaultCsrfToken.serialized diff --git a/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java b/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java index 5072da9f5c..aedbd7096c 100644 --- a/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java +++ b/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java @@ -134,6 +134,10 @@ import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationException; import org.springframework.security.web.authentication.session.SessionAuthenticationException; import org.springframework.security.web.authentication.www.NonceExpiredException; +import org.springframework.security.web.csrf.CsrfException; +import org.springframework.security.web.csrf.DefaultCsrfToken; +import org.springframework.security.web.csrf.InvalidCsrfTokenException; +import org.springframework.security.web.csrf.MissingCsrfTokenException; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; @@ -344,6 +348,13 @@ class SpringSecurityCoreVersionSerializableTests { (r) -> new SessionAuthenticationException("message")); generatorByClassName.put(NonceExpiredException.class, (r) -> new NonceExpiredException("message", new IOException("fail"))); + generatorByClassName.put(CsrfException.class, (r) -> new CsrfException("message")); + generatorByClassName.put(org.springframework.security.web.server.csrf.CsrfException.class, (r) -> new org.springframework.security.web.server.csrf.CsrfException("message")); + generatorByClassName.put(InvalidCsrfTokenException.class, (r) -> new InvalidCsrfTokenException(new DefaultCsrfToken("header", "parameter", "token"), "token")); + generatorByClassName.put(MissingCsrfTokenException.class, (r) -> new MissingCsrfTokenException("token")); + generatorByClassName.put(DefaultCsrfToken.class, (r) -> new DefaultCsrfToken("header", "parameter", "token")); + generatorByClassName.put(org.springframework.security.web.server.csrf.DefaultCsrfToken.class, (r) -> new org.springframework.security.web.server.csrf.DefaultCsrfToken("header", "parameter", "token")); + } @ParameterizedTest diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.web.csrf.CsrfException.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.web.csrf.CsrfException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..55eddf9e9f0cc57d99e9e0d3087dcfdc91e76bc9 GIT binary patch literal 10718 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 NAHMl#$dBql{XdTZ9fAM= literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.web.csrf.DefaultCsrfToken.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.web.csrf.DefaultCsrfToken.serialized new file mode 100644 index 0000000000000000000000000000000000000000..693e898c3136a9e65df47fdc39ca758e46cbb6bb GIT binary patch literal 172 zcmZ4UmVvdnh`}tsC|$3(peQphJ*_A)H?=&!C|j>MHMz7Xv!qh5JT*x#xwt4z&m}c2 zu{5W|8ORRF&rZ#YdcXaI{-WPknHZRT7`QT06H`)){D9g^7=(PX63Y_xa}x8?^@B@5 t=34tO@D?P33@?EwDrBf*VDw>NEdiMX=2R4vFt9;PDPiD*D=vYm0RXq3J!Jp@ literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.web.csrf.InvalidCsrfTokenException.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.web.csrf.InvalidCsrfTokenException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..18f8a50a348f8192945f88a31a68885efa09c1ac GIT binary patch literal 10882 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 uE~Q~cmg1EtfP`dwa?2!lDzHIR9M<(O2LA literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.web.server.csrf.CsrfException.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.web.server.csrf.CsrfException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..6556a08dde7b2047a4672d9548a33672217a0d56 GIT binary patch literal 10725 zcmeGiTZ|k>vG;7B&zCrU;8*;JbI!yjSRS8!e*3U}+@0++XP>z}n_vf{x!JzGInU0F zXL|2pg$UvBlyD*vB~m~U5Q&EbQUVsX5MKyKNc_M@KKKA3P!tJ?FTOxBp{l#5r{}SI zd)R#8{pg+PuCA)Cs;=s)o*)06EQvyL#1E@x6oigfod~TOo$3J=&(6+hK|Ea zI88&-j=~AEADY2gn+DABy_>5p{pHoyzje8dklBzNZi>jVZ5l;pAO9PmoM-rObdY@D*DEz`BUsySNsm{X$)@XgP)pX%Iq<9z}6kpxgd{O;b?V;hfd{&fyw zY%=s`tZ|pV_wN23hiC5mb`e=TOqSSIJ)(@P9Y$Qck?3v(d32a8uTW+=?pa{nssh|< z4X(^kW=AiR&WKs|)v?gB0pf|vDUf(z(4{r%F+jN{ss}*`2sHsjjI0#X>dbL>4?7V% zIvZRgFOX%7EUgLZ^)N;*gi}^jW8`foKi1oOZ`)y9d_V|!3)H(Q5mIh~we0R2+YkKl zOKqg%6Qq5@4{H`1BAu?|(R20MI1Pu%3fr}!XxO(ZG&~2^fjqSjlgBC)25|lTI4*lT zCLEV)P@Of(CjCmP$FI~=Fw1Ma=OPUwuqcjbHYgENy28jJD+m}l)zq%a+;Yp(UipVUI+p8PN1cz+h=GiEw+?Kg*gz|;RU5BZ256W$wz2On9&12x(()=U zNYcCjd$tUA=E62pB+iFE?m<6fHV*`IW2f)q^pn@ zIV?F@2At#>xFH$X-hctc`$@|N17>7RriWukBpda0UE*l5c@mp0MpjjY?&vwcLN8iw zUDrz6%N(AOMr;hlrZMPMVQT>!nI5^wz{m!HYt;9dz&9iqm zI^uC4od|a?E09vW&Xrl-bnv^9rzaZl)E78IwZH{w$tR`ny*ip(N+Ntm?`Nc4P}P7+ zz5-f_PpiPw!ye-vhW6LP5S+VoJlrGFD4t9{gde!ET~n2-iEjw$gbSLBodT_#<*rhM zM?c&P6WSeb+P|tS4V*gJ3Eg`N@eELxR>4Pk)iffp{;p=jN+`XIEaTB)+`^dHzUQFX zZT1L%2h`gR0rd?@IEj}a2r}}Bcu3HpoK9fVr+jAHhg^){icw`#b#z1pa2nej@0=$CrUX$sOR`sZmlAs;S9KCa|q|}p8nyLp5_-2#xeQ45Ih#F{* zrkFhh^0q;Sz6S?_Y?marG(aaxD>|jv4UM)yDWSnP86Kqkuz%8WJOg&UQ{gI1fEd}D zykuu6Ne}DXe7l8pGwNZqHOJs^<=5HZtn7ewXS?LOm+`2E?CP{$;k2;%6gHy}&=efT zT#UVW&0NXN+LW3#Mq$%}bIZ@H@|lcWYDvzc#;Z@?9jojY@*oGhm=7TKWJedcR6L|1Ph(be70zz@t6bhD zDQdaOQOq79XPUZm#sQs0A#CKch7C_oQfv68T0 zeL5$b-*cka@a>cb_yz}HglB}U_@+3xB5mC}ZRI;gM&9%wmr0H_z9YEfl4WTR zMo(-8cyzrDhsT}#@W_vM5d+25%#(gNXxWn^L1d^$V`KuNGXy-<6UAD=B2vJR75!X^ z=2{7Jk0^A7gr8bwo`~sIV6woBz0(7M{Evok515s^D`fmkj(Vx>|&;%O^ zaceyg1GP7?nNSvQ6$#`XAnwY^w{IK-ZP_f|N02CFyQC63Y(JQTgQQ=C{2$!3-^QVL zV1v(uRse^({>C%NYrxLaId&4ykhl#*`U+HN=`_@5AbTj&YjsGhK?j(ZhnEU4+|V&JO&#FWx8wuxy5bg)A1c*>0ugiPm~*ozoM#l> zFXPi!Ae|D4Y@h4u^i^F@hp`7r zYy93bQzCCx;c1iwL*=Q_kV<<}?&pAHU~qw%ZjOq)?%Gs5P)z(?Nn9(osk(@3)jfL> zKSh9RUQ^triCiCw*&|Ttf}B<+7f&`BEhhXUP8gaJ!KQ?-DY1xxZWDkbI0ZeT#E{R3 zQw3s?y6fQUW*j!D6=BOfNme&W=(~h*B$JS2ip#h}-d&))wESc(Vps|lZdfus#d5lU zB>5^kk&Q=k9PH)*#g(lr2l1sRIsw(cx^8tb&&x7y3hKsC*7F>FpR^TMpYYa+% zk;%XwAdM9FOv;MRT!34r^YYROD;@m8PF;>0vOo&KKn>C;E-5i$%rl>J3;7YhD2k#q zns}e%0&F~>mR4c2nvqWNfsE1Jt&9YLuD~jJRsR96Qc_(UF|Z!_O#{+94)l0Ue|~!9 zI^^<7L`r^)Y;G_T>#?*Urq~8oqyGkq#do9U;4*Al-U7uJqvAeuOQRtv{G}qEccUd{ zvL14li+u|U;0YY>Edtt!K;#%U#~T5`eJBp~W5a9NIZP-}1D3}xJEcIK`0J3069pEt zC$*c-0oetWdB_~GN)LW5?vzXGi08#JL?RpbnhfBig!dt(?fPIBaBxuDD0vS0;l`8B zihUqH^@)kX{bS=nrOy<(=YZU~rsTj>X6IrGeHAN!l8_O3Byd#GVI6Jr3H2Bm5TtXXQpCv<@+5ZNZ=HIW)Z}#|o9E2b={7>^oqX$YE$*a`U zqOk7iT6i&|g(mJQM*&p|L^jL9DwIslLV*lEO!Gwj8$YnpX;t}RF^rdbph*G*+RE`} zgLuWyOUY6WAVdDyp0XLq8(b>675JDB4OB&7Ar3}17WOM`sc?nd%mKkqYSz-6V0kWL zwAP2;HHnLQ?z)A^#?*Jr4t=I3Y Qzt#JLH~$3r(QGjLA9Z*jUjP6A literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.web.server.csrf.DefaultCsrfToken.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.web.server.csrf.DefaultCsrfToken.serialized new file mode 100644 index 0000000000000000000000000000000000000000..9cff958c4907a45a8acbea834b1143808b70d1bc GIT binary patch literal 179 zcmZ4UmVvdnh`~0$C|$3(peQphJ*_A)H?=&!C|j>MHMz7Xv!qh5JT(c(DJn}X(n~Hb zO4D;mO-n4zDRBm}L-Mmz^H|&qw@6J;D`a9|_F>@4NKH&hE%F1JQo^s5oAXRL{T9_9Rs5e18WJ$BrvC csrfTokenSupplier; diff --git a/web/src/main/java/org/springframework/security/web/csrf/DefaultCsrfToken.java b/web/src/main/java/org/springframework/security/web/csrf/DefaultCsrfToken.java index 682be4b1dd..122d95d1ce 100644 --- a/web/src/main/java/org/springframework/security/web/csrf/DefaultCsrfToken.java +++ b/web/src/main/java/org/springframework/security/web/csrf/DefaultCsrfToken.java @@ -16,6 +16,8 @@ package org.springframework.security.web.csrf; +import java.io.Serial; + import org.springframework.util.Assert; /** @@ -24,9 +26,11 @@ * @author Rob Winch * @since 3.2 */ -@SuppressWarnings("serial") public final class DefaultCsrfToken implements CsrfToken { + @Serial + private static final long serialVersionUID = 6552658053267913685L; + private final String token; private final String parameterName; diff --git a/web/src/main/java/org/springframework/security/web/csrf/InvalidCsrfTokenException.java b/web/src/main/java/org/springframework/security/web/csrf/InvalidCsrfTokenException.java index 0c57e5a604..bb4afac31d 100644 --- a/web/src/main/java/org/springframework/security/web/csrf/InvalidCsrfTokenException.java +++ b/web/src/main/java/org/springframework/security/web/csrf/InvalidCsrfTokenException.java @@ -16,6 +16,8 @@ package org.springframework.security.web.csrf; +import java.io.Serial; + import jakarta.servlet.http.HttpServletRequest; /** @@ -25,9 +27,11 @@ * @author Rob Winch * @since 3.2 */ -@SuppressWarnings("serial") public class InvalidCsrfTokenException extends CsrfException { + @Serial + private static final long serialVersionUID = -7745955098435417418L; + /** * @param expectedAccessToken * @param actualAccessToken diff --git a/web/src/main/java/org/springframework/security/web/csrf/LazyCsrfTokenRepository.java b/web/src/main/java/org/springframework/security/web/csrf/LazyCsrfTokenRepository.java index 5a6a63f4bb..a8326fa2a7 100644 --- a/web/src/main/java/org/springframework/security/web/csrf/LazyCsrfTokenRepository.java +++ b/web/src/main/java/org/springframework/security/web/csrf/LazyCsrfTokenRepository.java @@ -159,6 +159,7 @@ public String toString() { } + @SuppressWarnings("serial") private static final class SaveOnAccessCsrfToken implements CsrfToken { private transient CsrfTokenRepository tokenRepository; diff --git a/web/src/main/java/org/springframework/security/web/server/csrf/CsrfException.java b/web/src/main/java/org/springframework/security/web/server/csrf/CsrfException.java index 631c5b7fdc..bdb693e95c 100644 --- a/web/src/main/java/org/springframework/security/web/server/csrf/CsrfException.java +++ b/web/src/main/java/org/springframework/security/web/server/csrf/CsrfException.java @@ -16,6 +16,8 @@ package org.springframework.security.web.server.csrf; +import java.io.Serial; + import org.springframework.security.access.AccessDeniedException; import org.springframework.security.web.csrf.CsrfToken; @@ -25,9 +27,11 @@ * @author Rob Winch * @since 3.2 */ -@SuppressWarnings("serial") public class CsrfException extends AccessDeniedException { + @Serial + private static final long serialVersionUID = -8209680716517631141L; + public CsrfException(String message) { super(message); } diff --git a/web/src/main/java/org/springframework/security/web/server/csrf/DefaultCsrfToken.java b/web/src/main/java/org/springframework/security/web/server/csrf/DefaultCsrfToken.java index eb49369e6f..2a32018a5c 100644 --- a/web/src/main/java/org/springframework/security/web/server/csrf/DefaultCsrfToken.java +++ b/web/src/main/java/org/springframework/security/web/server/csrf/DefaultCsrfToken.java @@ -16,6 +16,8 @@ package org.springframework.security.web.server.csrf; +import java.io.Serial; + import org.springframework.util.Assert; /** @@ -24,9 +26,11 @@ * @author Rob Winch * @since 5.0 */ -@SuppressWarnings("serial") public final class DefaultCsrfToken implements CsrfToken { + @Serial + private static final long serialVersionUID = 308340117851874929L; + private final String token; private final String parameterName; From 3a6761c0769fa7f5772f1a9c1521c346df1abe6e Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Tue, 14 Jan 2025 16:28:53 -0700 Subject: [PATCH 062/132] Formatting Issue gh-16276 Signed-off-by: Daeho Kwon --- .../SpringSecurityCoreVersionSerializableTests.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java b/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java index aedbd7096c..90140fcc2d 100644 --- a/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java +++ b/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java @@ -349,11 +349,15 @@ class SpringSecurityCoreVersionSerializableTests { generatorByClassName.put(NonceExpiredException.class, (r) -> new NonceExpiredException("message", new IOException("fail"))); generatorByClassName.put(CsrfException.class, (r) -> new CsrfException("message")); - generatorByClassName.put(org.springframework.security.web.server.csrf.CsrfException.class, (r) -> new org.springframework.security.web.server.csrf.CsrfException("message")); - generatorByClassName.put(InvalidCsrfTokenException.class, (r) -> new InvalidCsrfTokenException(new DefaultCsrfToken("header", "parameter", "token"), "token")); + generatorByClassName.put(org.springframework.security.web.server.csrf.CsrfException.class, + (r) -> new org.springframework.security.web.server.csrf.CsrfException("message")); + generatorByClassName.put(InvalidCsrfTokenException.class, + (r) -> new InvalidCsrfTokenException(new DefaultCsrfToken("header", "parameter", "token"), "token")); generatorByClassName.put(MissingCsrfTokenException.class, (r) -> new MissingCsrfTokenException("token")); generatorByClassName.put(DefaultCsrfToken.class, (r) -> new DefaultCsrfToken("header", "parameter", "token")); - generatorByClassName.put(org.springframework.security.web.server.csrf.DefaultCsrfToken.class, (r) -> new org.springframework.security.web.server.csrf.DefaultCsrfToken("header", "parameter", "token")); + generatorByClassName.put(org.springframework.security.web.server.csrf.DefaultCsrfToken.class, + (r) -> new org.springframework.security.web.server.csrf.DefaultCsrfToken("header", "parameter", + "token")); } From 4f2741f9e647278374dbedc9cc1ecf74da6d9a11 Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Tue, 14 Jan 2025 17:04:36 -0700 Subject: [PATCH 063/132] Don't Support Serialization of Jackson Modules Issu gh-16276 Signed-off-by: Daeho Kwon --- .../springframework/security/cas/jackson2/CasJackson2Module.java | 1 + .../security/jackson2/SecurityJackson2Modules.java | 1 + .../security/ldap/jackson2/LdapJackson2Module.java | 1 + .../oauth2/client/jackson2/OAuth2ClientJackson2Module.java | 1 + .../security/saml2/jackson2/Saml2Jackson2Module.java | 1 + .../springframework/security/web/jackson2/WebJackson2Module.java | 1 + .../security/web/jackson2/WebServletJackson2Module.java | 1 + .../security/web/server/jackson2/WebServerJackson2Module.java | 1 + .../security/web/webauthn/jackson/WebauthnJackson2Module.java | 1 + 9 files changed, 9 insertions(+) diff --git a/cas/src/main/java/org/springframework/security/cas/jackson2/CasJackson2Module.java b/cas/src/main/java/org/springframework/security/cas/jackson2/CasJackson2Module.java index b6c7c6f8fa..fad74fdb7b 100644 --- a/cas/src/main/java/org/springframework/security/cas/jackson2/CasJackson2Module.java +++ b/cas/src/main/java/org/springframework/security/cas/jackson2/CasJackson2Module.java @@ -41,6 +41,7 @@ * @since 4.2 * @see org.springframework.security.jackson2.SecurityJackson2Modules */ +@SuppressWarnings("serial") public class CasJackson2Module extends SimpleModule { public CasJackson2Module() { diff --git a/core/src/main/java/org/springframework/security/jackson2/SecurityJackson2Modules.java b/core/src/main/java/org/springframework/security/jackson2/SecurityJackson2Modules.java index 974910bc91..5db1b2e538 100644 --- a/core/src/main/java/org/springframework/security/jackson2/SecurityJackson2Modules.java +++ b/core/src/main/java/org/springframework/security/jackson2/SecurityJackson2Modules.java @@ -201,6 +201,7 @@ private static TypeResolverBuilder createAllowlis * * @author Rob Winch */ + @SuppressWarnings("serial") static class AllowlistTypeResolverBuilder extends ObjectMapper.DefaultTypeResolverBuilder { AllowlistTypeResolverBuilder(ObjectMapper.DefaultTyping defaultTyping) { diff --git a/ldap/src/main/java/org/springframework/security/ldap/jackson2/LdapJackson2Module.java b/ldap/src/main/java/org/springframework/security/ldap/jackson2/LdapJackson2Module.java index f84e8df620..aaa4164da5 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/jackson2/LdapJackson2Module.java +++ b/ldap/src/main/java/org/springframework/security/ldap/jackson2/LdapJackson2Module.java @@ -46,6 +46,7 @@ * @since 5.7 * @see SecurityJackson2Modules */ +@SuppressWarnings("serial") public class LdapJackson2Module extends SimpleModule { public LdapJackson2Module() { diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson2/OAuth2ClientJackson2Module.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson2/OAuth2ClientJackson2Module.java index ba1eaacd2c..30f1185c9b 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson2/OAuth2ClientJackson2Module.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson2/OAuth2ClientJackson2Module.java @@ -86,6 +86,7 @@ * @see OAuth2AuthenticationExceptionMixin * @see OAuth2ErrorMixin */ +@SuppressWarnings("serial") public class OAuth2ClientJackson2Module extends SimpleModule { public OAuth2ClientJackson2Module() { diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/jackson2/Saml2Jackson2Module.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/jackson2/Saml2Jackson2Module.java index 025ffc6b36..3d99fc2cfa 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/jackson2/Saml2Jackson2Module.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/jackson2/Saml2Jackson2Module.java @@ -39,6 +39,7 @@ * @since 5.7 * @see SecurityJackson2Modules */ +@SuppressWarnings("serial") public class Saml2Jackson2Module extends SimpleModule { public Saml2Jackson2Module() { diff --git a/web/src/main/java/org/springframework/security/web/jackson2/WebJackson2Module.java b/web/src/main/java/org/springframework/security/web/jackson2/WebJackson2Module.java index a54a55a96d..87daedcc40 100644 --- a/web/src/main/java/org/springframework/security/web/jackson2/WebJackson2Module.java +++ b/web/src/main/java/org/springframework/security/web/jackson2/WebJackson2Module.java @@ -40,6 +40,7 @@ * @since 4.2 * @see SecurityJackson2Modules */ +@SuppressWarnings("serial") public class WebJackson2Module extends SimpleModule { public WebJackson2Module() { diff --git a/web/src/main/java/org/springframework/security/web/jackson2/WebServletJackson2Module.java b/web/src/main/java/org/springframework/security/web/jackson2/WebServletJackson2Module.java index 70b098e4fe..b5fd4d0777 100644 --- a/web/src/main/java/org/springframework/security/web/jackson2/WebServletJackson2Module.java +++ b/web/src/main/java/org/springframework/security/web/jackson2/WebServletJackson2Module.java @@ -44,6 +44,7 @@ * @since 5.1 * @see SecurityJackson2Modules */ +@SuppressWarnings("serial") public class WebServletJackson2Module extends SimpleModule { public WebServletJackson2Module() { diff --git a/web/src/main/java/org/springframework/security/web/server/jackson2/WebServerJackson2Module.java b/web/src/main/java/org/springframework/security/web/server/jackson2/WebServerJackson2Module.java index ceea54bdbc..001a5accf4 100644 --- a/web/src/main/java/org/springframework/security/web/server/jackson2/WebServerJackson2Module.java +++ b/web/src/main/java/org/springframework/security/web/server/jackson2/WebServerJackson2Module.java @@ -38,6 +38,7 @@ * @since 5.1 * @see SecurityJackson2Modules */ +@SuppressWarnings("serial") public class WebServerJackson2Module extends SimpleModule { private static final String NAME = WebServerJackson2Module.class.getName(); diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/WebauthnJackson2Module.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/WebauthnJackson2Module.java index 0fe386aecc..97a1c8e1f4 100644 --- a/web/src/main/java/org/springframework/security/web/webauthn/jackson/WebauthnJackson2Module.java +++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/WebauthnJackson2Module.java @@ -47,6 +47,7 @@ * @author Rob Winch * @since 6.4 */ +@SuppressWarnings("serial") public class WebauthnJackson2Module extends SimpleModule { /** From 46f24872ca26bdee0bc7be3ae8c99b08efe2fc60 Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Tue, 14 Jan 2025 17:35:33 -0700 Subject: [PATCH 064/132] Don't Support Serialization for Jackson (De)serializers Issue gh-16276 Signed-off-by: Daeho Kwon --- .../jackson/AttestationConveyancePreferenceSerializer.java | 1 + .../jackson/AuthenticationExtensionsClientInputSerializer.java | 1 + .../jackson/AuthenticationExtensionsClientInputsSerializer.java | 1 + .../AuthenticationExtensionsClientOutputsDeserializer.java | 1 + .../webauthn/jackson/AuthenticatorAttachmentDeserializer.java | 1 + .../web/webauthn/jackson/AuthenticatorAttachmentSerializer.java | 1 + .../web/webauthn/jackson/AuthenticatorTransportDeserializer.java | 1 + .../security/web/webauthn/jackson/BytesSerializer.java | 1 + .../webauthn/jackson/COSEAlgorithmIdentifierDeserializer.java | 1 + .../web/webauthn/jackson/COSEAlgorithmIdentifierSerializer.java | 1 + ...CredProtectAuthenticationExtensionsClientInputSerializer.java | 1 + .../security/web/webauthn/jackson/DurationSerializer.java | 1 + .../webauthn/jackson/PublicKeyCredentialTypeDeserializer.java | 1 + .../web/webauthn/jackson/PublicKeyCredentialTypeSerializer.java | 1 + .../web/webauthn/jackson/ResidentKeyRequirementSerializer.java | 1 + .../webauthn/jackson/UserVerificationRequirementSerializer.java | 1 + 16 files changed, 16 insertions(+) diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/AttestationConveyancePreferenceSerializer.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AttestationConveyancePreferenceSerializer.java index af2c0a23f5..03624dbe22 100644 --- a/web/src/main/java/org/springframework/security/web/webauthn/jackson/AttestationConveyancePreferenceSerializer.java +++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AttestationConveyancePreferenceSerializer.java @@ -30,6 +30,7 @@ * @author Rob Winch * @since 6.4 */ +@SuppressWarnings("serial") class AttestationConveyancePreferenceSerializer extends StdSerializer { AttestationConveyancePreferenceSerializer() { diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientInputSerializer.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientInputSerializer.java index 4d7ca1e38d..2746a0928b 100644 --- a/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientInputSerializer.java +++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientInputSerializer.java @@ -30,6 +30,7 @@ * @author Rob Winch * @since 6.4 */ +@SuppressWarnings("serial") class AuthenticationExtensionsClientInputSerializer extends StdSerializer { /** diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientInputsSerializer.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientInputsSerializer.java index 8009f0f16f..e6ad216c8c 100644 --- a/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientInputsSerializer.java +++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientInputsSerializer.java @@ -31,6 +31,7 @@ * @author Rob Winch * @since 6.4 */ +@SuppressWarnings("serial") class AuthenticationExtensionsClientInputsSerializer extends StdSerializer { /** diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientOutputsDeserializer.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientOutputsDeserializer.java index 0cfd084936..dc0d588c7c 100644 --- a/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientOutputsDeserializer.java +++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientOutputsDeserializer.java @@ -39,6 +39,7 @@ * @author Rob Winch * @since 6.4 */ +@SuppressWarnings("serial") class AuthenticationExtensionsClientOutputsDeserializer extends StdDeserializer { private static final Log logger = LogFactory.getLog(AuthenticationExtensionsClientOutputsDeserializer.class); diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorAttachmentDeserializer.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorAttachmentDeserializer.java index 0c6b9c9e74..8263081ddc 100644 --- a/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorAttachmentDeserializer.java +++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorAttachmentDeserializer.java @@ -31,6 +31,7 @@ * @author Rob Winch * @since 6.4 */ +@SuppressWarnings("serial") class AuthenticatorAttachmentDeserializer extends StdDeserializer { AuthenticatorAttachmentDeserializer() { diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorAttachmentSerializer.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorAttachmentSerializer.java index 67c1a2b9b3..a6ea540716 100644 --- a/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorAttachmentSerializer.java +++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorAttachmentSerializer.java @@ -30,6 +30,7 @@ * @author Rob Winch * @since 6.4 */ +@SuppressWarnings("serial") class AuthenticatorAttachmentSerializer extends StdSerializer { AuthenticatorAttachmentSerializer() { diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorTransportDeserializer.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorTransportDeserializer.java index 77085c4350..8cafd92aa9 100644 --- a/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorTransportDeserializer.java +++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorTransportDeserializer.java @@ -31,6 +31,7 @@ * @author Rob Winch * @since 6.4 */ +@SuppressWarnings("serial") class AuthenticatorTransportDeserializer extends StdDeserializer { AuthenticatorTransportDeserializer() { diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/BytesSerializer.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/BytesSerializer.java index b02b33eecb..894cab4ed5 100644 --- a/web/src/main/java/org/springframework/security/web/webauthn/jackson/BytesSerializer.java +++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/BytesSerializer.java @@ -30,6 +30,7 @@ * @author Rob Winch * @since 6.4 */ +@SuppressWarnings("serial") class BytesSerializer extends StdSerializer { /** diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/COSEAlgorithmIdentifierDeserializer.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/COSEAlgorithmIdentifierDeserializer.java index 343b0bde1c..ed1e6e4837 100644 --- a/web/src/main/java/org/springframework/security/web/webauthn/jackson/COSEAlgorithmIdentifierDeserializer.java +++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/COSEAlgorithmIdentifierDeserializer.java @@ -31,6 +31,7 @@ * @author Rob Winch * @since 6.4 */ +@SuppressWarnings("serial") class COSEAlgorithmIdentifierDeserializer extends StdDeserializer { COSEAlgorithmIdentifierDeserializer() { diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/COSEAlgorithmIdentifierSerializer.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/COSEAlgorithmIdentifierSerializer.java index eb408569fa..6cc3d84413 100644 --- a/web/src/main/java/org/springframework/security/web/webauthn/jackson/COSEAlgorithmIdentifierSerializer.java +++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/COSEAlgorithmIdentifierSerializer.java @@ -30,6 +30,7 @@ * @author Rob Winch * @since 6.4 */ +@SuppressWarnings("serial") class COSEAlgorithmIdentifierSerializer extends StdSerializer { COSEAlgorithmIdentifierSerializer() { diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/CredProtectAuthenticationExtensionsClientInputSerializer.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/CredProtectAuthenticationExtensionsClientInputSerializer.java index b1cd17892d..0561996566 100644 --- a/web/src/main/java/org/springframework/security/web/webauthn/jackson/CredProtectAuthenticationExtensionsClientInputSerializer.java +++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/CredProtectAuthenticationExtensionsClientInputSerializer.java @@ -31,6 +31,7 @@ * * @author Rob Winch */ +@SuppressWarnings("serial") class CredProtectAuthenticationExtensionsClientInputSerializer extends StdSerializer { diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/DurationSerializer.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/DurationSerializer.java index 442acc5fd0..f1a27e17b5 100644 --- a/web/src/main/java/org/springframework/security/web/webauthn/jackson/DurationSerializer.java +++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/DurationSerializer.java @@ -29,6 +29,7 @@ * @author Rob Winch * @since 6.4 */ +@SuppressWarnings("serial") class DurationSerializer extends StdSerializer { /** diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialTypeDeserializer.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialTypeDeserializer.java index b7709d41f2..7640d7a366 100644 --- a/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialTypeDeserializer.java +++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialTypeDeserializer.java @@ -31,6 +31,7 @@ * @author Rob Winch * @since 6.4 */ +@SuppressWarnings("serial") class PublicKeyCredentialTypeDeserializer extends StdDeserializer { /** diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialTypeSerializer.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialTypeSerializer.java index 06eb0bbbe6..23319e366a 100644 --- a/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialTypeSerializer.java +++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialTypeSerializer.java @@ -30,6 +30,7 @@ * @author Rob Winch * @since 6.4 */ +@SuppressWarnings("serial") class PublicKeyCredentialTypeSerializer extends StdSerializer { /** diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/ResidentKeyRequirementSerializer.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/ResidentKeyRequirementSerializer.java index 158e8627cd..31b85366d4 100644 --- a/web/src/main/java/org/springframework/security/web/webauthn/jackson/ResidentKeyRequirementSerializer.java +++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/ResidentKeyRequirementSerializer.java @@ -30,6 +30,7 @@ * @author Rob Winch * @since 6.4 */ +@SuppressWarnings("serial") class ResidentKeyRequirementSerializer extends StdSerializer { /** diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/UserVerificationRequirementSerializer.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/UserVerificationRequirementSerializer.java index 1bb2990446..07a6184a96 100644 --- a/web/src/main/java/org/springframework/security/web/webauthn/jackson/UserVerificationRequirementSerializer.java +++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/UserVerificationRequirementSerializer.java @@ -30,6 +30,7 @@ * @author Rob Winch * @since 6.4 */ +@SuppressWarnings("serial") class UserVerificationRequirementSerializer extends StdSerializer { /** From 0dce2a8605d5fd3a4dc5939cfa39fad7639b7c2f Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Tue, 14 Jan 2025 18:25:17 -0700 Subject: [PATCH 065/132] Support Serialization in Exceptions Issue gh-16276 Signed-off-by: Daeho Kwon --- ...gSecurityCoreVersionSerializableTests.java | 93 +++++++++++++++++- ...ty.access.AccessDeniedException.serialized | Bin 0 -> 16216 bytes ...s.AuthorizationServiceException.serialized | Bin 0 -> 16296 bytes ...ication.AccountExpiredException.serialized | Bin 0 -> 16371 bytes ...ionCredentialsNotFoundException.serialized | Bin 0 -> 16309 bytes ....AuthenticationServiceException.serialized | Bin 0 -> 16297 bytes ...ication.BadCredentialsException.serialized | Bin 0 -> 16290 bytes ...ion.CredentialsExpiredException.serialized | Bin 0 -> 16375 bytes ...uthentication.DisabledException.serialized | Bin 0 -> 16365 bytes ...fficientAuthenticationException.serialized | Bin 0 -> 16302 bytes ...lAuthenticationServiceException.serialized | Bin 0 -> 16394 bytes ....authentication.LockedException.serialized | Bin 0 -> 16363 bytes ...ation.ProviderNotFoundException.serialized | Bin 0 -> 10734 bytes ...tt.InvalidOneTimeTokenException.serialized | Bin 0 -> 10741 bytes ...rd.CompromisedPasswordException.serialized | Bin 0 -> 16304 bytes ...tails.UsernameNotFoundException.serialized | Bin 0 -> 16294 bytes ...ppolicy.PasswordPolicyException.serialized | Bin 0 -> 10970 bytes ...nt.ClientAuthorizationException.serialized | Bin 0 -> 16626 bytes ...tAuthorizationRequiredException.serialized | Bin 0 -> 11268 bytes ...e.OAuth2AuthenticationException.serialized | Bin 0 -> 16580 bytes ...re.OAuth2AuthorizationException.serialized | Bin 0 -> 16507 bytes ...rity.oauth2.jwt.BadJwtException.serialized | Bin 0 -> 16273 bytes ...tDecoderInitializationException.serialized | Bin 0 -> 16226 bytes ...oauth2.jwt.JwtEncodingException.serialized | Bin 0 -> 16280 bytes ...ecurity.oauth2.jwt.JwtException.serialized | Bin 0 -> 16205 bytes ...uth2.jwt.JwtValidationException.serialized | Bin 0 -> 11069 bytes ...rce.InvalidBearerTokenException.serialized | Bin 0 -> 16963 bytes ...pection.BadOpaqueTokenException.serialized | Bin 0 -> 16351 bytes ...on.OAuth2IntrospectionException.serialized | Bin 0 -> 16247 bytes ...rewall.RequestRejectedException.serialized | Bin 0 -> 10661 bytes ...ServerExchangeRejectedException.serialized | Bin 0 -> 10675 bytes .../access/AccessDeniedException.java | 5 + .../access/AuthorizationServiceException.java | 5 + .../AccountExpiredException.java | 5 + ...nticationCredentialsNotFoundException.java | 5 + .../AuthenticationServiceException.java | 5 + .../BadCredentialsException.java | 5 + .../CredentialsExpiredException.java | 5 + .../authentication/DisabledException.java | 5 + .../InsufficientAuthenticationException.java | 5 + ...nternalAuthenticationServiceException.java | 5 + .../authentication/LockedException.java | 5 + .../ProviderNotFoundException.java | 5 + .../ott/InvalidOneTimeTokenException.java | 5 + .../CompromisedPasswordException.java | 5 + .../AuthorizationDeniedException.java | 5 + .../UsernameNotFoundException.java | 5 + .../security/crypto/codec/Base64.java | 1 + ...ctiveDirectoryAuthenticationException.java | 1 + .../ldap/ppolicy/PasswordPolicyException.java | 5 + .../client/ClientAuthorizationException.java | 5 + .../ClientAuthorizationRequiredException.java | 5 + .../InvalidClientRegistrationIdException.java | 1 + .../core/OAuth2AuthenticationException.java | 5 + .../core/OAuth2AuthorizationException.java | 5 + .../security/oauth2/jwt/BadJwtException.java | 5 + .../JwtDecoderInitializationException.java | 5 + .../oauth2/jwt/JwtEncodingException.java | 5 + .../security/oauth2/jwt/JwtException.java | 5 + .../oauth2/jwt/JwtValidationException.java | 4 + .../resource/InvalidBearerTokenException.java | 5 + .../BadOpaqueTokenException.java | 5 + .../OAuth2IntrospectionException.java | 5 + .../firewall/RequestRejectedException.java | 5 + .../authentication/SwitchUserWebFilter.java | 1 + .../ServerExchangeRejectedException.java | 5 + 66 files changed, 249 insertions(+), 2 deletions(-) create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.access.AccessDeniedException.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.access.AuthorizationServiceException.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.AccountExpiredException.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.AuthenticationCredentialsNotFoundException.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.AuthenticationServiceException.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.BadCredentialsException.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.CredentialsExpiredException.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.DisabledException.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.InsufficientAuthenticationException.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.InternalAuthenticationServiceException.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.LockedException.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.ProviderNotFoundException.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.ott.InvalidOneTimeTokenException.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.password.CompromisedPasswordException.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.core.userdetails.UsernameNotFoundException.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.ldap.ppolicy.PasswordPolicyException.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.oauth2.client.ClientAuthorizationException.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.oauth2.client.ClientAuthorizationRequiredException.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.oauth2.core.OAuth2AuthenticationException.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.oauth2.core.OAuth2AuthorizationException.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.oauth2.jwt.BadJwtException.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.oauth2.jwt.JwtDecoderInitializationException.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.oauth2.jwt.JwtEncodingException.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.oauth2.jwt.JwtException.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.oauth2.jwt.JwtValidationException.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.oauth2.server.resource.InvalidBearerTokenException.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.oauth2.server.resource.introspection.BadOpaqueTokenException.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionException.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.web.firewall.RequestRejectedException.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.web.server.firewall.ServerExchangeRejectedException.serialized diff --git a/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java b/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java index 90140fcc2d..407a055d42 100644 --- a/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java +++ b/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java @@ -54,15 +54,29 @@ import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; import org.springframework.core.type.filter.AssignableTypeFilter; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.access.AuthorizationServiceException; import org.springframework.security.access.intercept.RunAsUserToken; import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.authentication.AccountExpiredException; import org.springframework.security.authentication.AnonymousAuthenticationToken; +import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; +import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.CredentialsExpiredException; +import org.springframework.security.authentication.DisabledException; +import org.springframework.security.authentication.InsufficientAuthenticationException; +import org.springframework.security.authentication.InternalAuthenticationServiceException; +import org.springframework.security.authentication.LockedException; +import org.springframework.security.authentication.ProviderNotFoundException; import org.springframework.security.authentication.RememberMeAuthenticationToken; import org.springframework.security.authentication.TestAuthentication; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.jaas.JaasAuthenticationToken; +import org.springframework.security.authentication.ott.InvalidOneTimeTokenException; import org.springframework.security.authentication.ott.OneTimeTokenAuthenticationToken; +import org.springframework.security.authentication.password.CompromisedPasswordException; import org.springframework.security.cas.authentication.CasAssertionAuthenticationToken; import org.springframework.security.cas.authentication.CasAuthenticationToken; import org.springframework.security.cas.authentication.CasServiceTicketAuthenticationToken; @@ -72,7 +86,12 @@ import org.springframework.security.core.session.ReactiveSessionInformation; import org.springframework.security.core.session.SessionInformation; import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.ldap.ppolicy.PasswordPolicyErrorStatus; +import org.springframework.security.ldap.ppolicy.PasswordPolicyException; import org.springframework.security.ldap.userdetails.LdapAuthority; +import org.springframework.security.oauth2.client.ClientAuthorizationException; +import org.springframework.security.oauth2.client.ClientAuthorizationRequiredException; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken; @@ -88,7 +107,10 @@ import org.springframework.security.oauth2.core.DefaultOAuth2AuthenticatedPrincipal; import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2AuthorizationException; import org.springframework.security.oauth2.core.OAuth2DeviceCode; +import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.OAuth2RefreshToken; import org.springframework.security.oauth2.core.OAuth2UserCode; import org.springframework.security.oauth2.core.TestOAuth2AccessTokens; @@ -108,14 +130,22 @@ import org.springframework.security.oauth2.core.user.DefaultOAuth2User; import org.springframework.security.oauth2.core.user.OAuth2UserAuthority; import org.springframework.security.oauth2.core.user.TestOAuth2Users; +import org.springframework.security.oauth2.jwt.BadJwtException; import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.jwt.JwtDecoderInitializationException; +import org.springframework.security.oauth2.jwt.JwtEncodingException; +import org.springframework.security.oauth2.jwt.JwtException; +import org.springframework.security.oauth2.jwt.JwtValidationException; import org.springframework.security.oauth2.jwt.TestJwts; import org.springframework.security.oauth2.server.resource.BearerTokenError; import org.springframework.security.oauth2.server.resource.BearerTokenErrors; +import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException; import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthentication; import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; +import org.springframework.security.oauth2.server.resource.introspection.BadOpaqueTokenException; import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionAuthenticatedPrincipal; +import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionException; import org.springframework.security.saml2.Saml2Exception; import org.springframework.security.saml2.core.Saml2Error; import org.springframework.security.saml2.provider.service.authentication.DefaultSaml2AuthenticatedPrincipal; @@ -138,6 +168,8 @@ import org.springframework.security.web.csrf.DefaultCsrfToken; import org.springframework.security.web.csrf.InvalidCsrfTokenException; import org.springframework.security.web.csrf.MissingCsrfTokenException; +import org.springframework.security.web.firewall.RequestRejectedException; +import org.springframework.security.web.server.firewall.ServerExchangeRejectedException; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; @@ -193,6 +225,12 @@ class SpringSecurityCoreVersionSerializableTests { generatorByClassName.put(OidcUserAuthority.class, (r) -> new OidcUserAuthority(TestOidcIdTokens.idToken().build(), new OidcUserInfo(Map.of("claim", "value")), "claim")); + generatorByClassName.put(OAuth2AuthenticationException.class, + (r) -> new OAuth2AuthenticationException(new OAuth2Error("error", "description", "uri"), "message", + new RuntimeException())); + generatorByClassName.put(OAuth2AuthorizationException.class, + (r) -> new OAuth2AuthorizationException(new OAuth2Error("error", "description", "uri"), "message", + new RuntimeException())); // oauth2-client ClientRegistration.Builder clientRegistrationBuilder = TestClientRegistrations.clientRegistration(); @@ -231,6 +269,21 @@ class SpringSecurityCoreVersionSerializableTests { return new DefaultOAuth2AuthenticatedPrincipal(principal.getName(), principal.getAttributes(), (Collection) principal.getAuthorities()); }); + generatorByClassName.put(ClientAuthorizationException.class, + (r) -> new ClientAuthorizationException(new OAuth2Error("error", "description", "uri"), "id", "message", + new RuntimeException())); + generatorByClassName.put(ClientAuthorizationRequiredException.class, + (r) -> new ClientAuthorizationRequiredException("id")); + + // oauth2-jose + generatorByClassName.put(BadJwtException.class, (r) -> new BadJwtException("token", new RuntimeException())); + generatorByClassName.put(JwtDecoderInitializationException.class, + (r) -> new JwtDecoderInitializationException("message", new RuntimeException())); + generatorByClassName.put(JwtEncodingException.class, + (r) -> new JwtEncodingException("message", new RuntimeException())); + generatorByClassName.put(JwtException.class, (r) -> new JwtException("message", new RuntimeException())); + generatorByClassName.put(JwtValidationException.class, + (r) -> new JwtValidationException("message", List.of(new OAuth2Error("error", "description", "uri")))); // oauth2-jwt generatorByClassName.put(Jwt.class, (r) -> TestJwts.user()); @@ -262,6 +315,12 @@ class SpringSecurityCoreVersionSerializableTests { generatorByClassName.put(BearerTokenError.class, (r) -> BearerTokenErrors.invalidToken("invalid token")); generatorByClassName.put(OAuth2IntrospectionAuthenticatedPrincipal.class, (r) -> TestOAuth2AuthenticatedPrincipals.active()); + generatorByClassName.put(InvalidBearerTokenException.class, + (r) -> new InvalidBearerTokenException("description", new RuntimeException())); + generatorByClassName.put(BadOpaqueTokenException.class, + (r) -> new BadOpaqueTokenException("message", new RuntimeException())); + generatorByClassName.put(OAuth2IntrospectionException.class, + (r) -> new OAuth2IntrospectionException("message", new RuntimeException())); // core generatorByClassName.put(RunAsUserToken.class, (r) -> { @@ -287,7 +346,33 @@ class SpringSecurityCoreVersionSerializableTests { }); generatorByClassName.put(OneTimeTokenAuthenticationToken.class, (r) -> applyDetails(new OneTimeTokenAuthenticationToken("username", "token"))); - + generatorByClassName.put(AccessDeniedException.class, + (r) -> new AccessDeniedException("access denied", new RuntimeException())); + generatorByClassName.put(AuthorizationServiceException.class, + (r) -> new AuthorizationServiceException("access denied", new RuntimeException())); + generatorByClassName.put(AccountExpiredException.class, + (r) -> new AccountExpiredException("error", new RuntimeException())); + generatorByClassName.put(AuthenticationCredentialsNotFoundException.class, + (r) -> new AuthenticationCredentialsNotFoundException("error", new RuntimeException())); + generatorByClassName.put(AuthenticationServiceException.class, + (r) -> new AuthenticationServiceException("error", new RuntimeException())); + generatorByClassName.put(BadCredentialsException.class, + (r) -> new BadCredentialsException("error", new RuntimeException())); + generatorByClassName.put(CredentialsExpiredException.class, + (r) -> new CredentialsExpiredException("error", new RuntimeException())); + generatorByClassName.put(DisabledException.class, + (r) -> new DisabledException("error", new RuntimeException())); + generatorByClassName.put(InsufficientAuthenticationException.class, + (r) -> new InsufficientAuthenticationException("error", new RuntimeException())); + generatorByClassName.put(InternalAuthenticationServiceException.class, + (r) -> new InternalAuthenticationServiceException("error", new RuntimeException())); + generatorByClassName.put(LockedException.class, (r) -> new LockedException("error", new RuntimeException())); + generatorByClassName.put(ProviderNotFoundException.class, (r) -> new ProviderNotFoundException("error")); + generatorByClassName.put(InvalidOneTimeTokenException.class, (r) -> new InvalidOneTimeTokenException("error")); + generatorByClassName.put(CompromisedPasswordException.class, + (r) -> new CompromisedPasswordException("error", new RuntimeException())); + generatorByClassName.put(UsernameNotFoundException.class, + (r) -> new UsernameNotFoundException("error", new RuntimeException())); generatorByClassName.put(TestingAuthenticationToken.class, (r) -> applyDetails(new TestingAuthenticationToken("username", "password"))); @@ -312,6 +397,8 @@ class SpringSecurityCoreVersionSerializableTests { // ldap generatorByClassName.put(LdapAuthority.class, (r) -> new LdapAuthority("USER", "username", Map.of("attribute", List.of("value1", "value2")))); + generatorByClassName.put(PasswordPolicyException.class, + (r) -> new PasswordPolicyException(PasswordPolicyErrorStatus.INSUFFICIENT_PASSWORD_QUALITY)); // saml2-service-provider generatorByClassName.put(Saml2AuthenticationException.class, @@ -358,7 +445,9 @@ class SpringSecurityCoreVersionSerializableTests { generatorByClassName.put(org.springframework.security.web.server.csrf.DefaultCsrfToken.class, (r) -> new org.springframework.security.web.server.csrf.DefaultCsrfToken("header", "parameter", "token")); - + generatorByClassName.put(RequestRejectedException.class, (r) -> new RequestRejectedException("message")); + generatorByClassName.put(ServerExchangeRejectedException.class, + (r) -> new ServerExchangeRejectedException("message")); } @ParameterizedTest diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.access.AccessDeniedException.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.access.AccessDeniedException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..61dae86206e8ad845035bf17e3679e5f8bdcf445 GIT binary patch literal 16216 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 a+87g6DiXP5l(fUz}~Yv&}P3=4QutbKc!q&-9+} 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 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.AuthenticationCredentialsNotFoundException.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.AuthenticationCredentialsNotFoundException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..4e99aa03653f7e2611ae861c9c6b74034f2da86a GIT binary patch literal 16309 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 E3uL)r=l}o! literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.CredentialsExpiredException.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.CredentialsExpiredException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..0ec7355f62ce128c3f1ebc7a040842f646f2e98c GIT binary patch literal 16375 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 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.DisabledException.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.DisabledException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..71d58fa87c7b5d9e86dffb061032c87827b3be12 GIT binary patch literal 16365 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 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.InsufficientAuthenticationException.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.InsufficientAuthenticationException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..24e5a933fa91da5edb3342013788fe590a83f167 GIT binary patch literal 16302 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 F{{kS0XJG&U literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.InternalAuthenticationServiceException.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.InternalAuthenticationServiceException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..3ce3a576f5ab7f6a2a2812580e442eea085a8b3e GIT binary patch literal 16394 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 V)z)jbHr?p|@mqg^{AfOy{}1ZzB!&P0 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.ott.InvalidOneTimeTokenException.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.ott.InvalidOneTimeTokenException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..72c49585259603b819e64ec594cc794a0bb1e2ab GIT binary patch literal 10741 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 R^UeMrzWHaUkLH58{{YZ3CPx4O literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.password.CompromisedPasswordException.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.password.CompromisedPasswordException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..112bcf688cefca9b2539605e1565c4e074f2fd47 GIT binary patch literal 16304 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 POSoHOg#A_KcW~^#7iek{ literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.core.userdetails.UsernameNotFoundException.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.core.userdetails.UsernameNotFoundException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..0272398b25f8c7a12dd7ceb3965312490e3b502b GIT binary patch literal 16294 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 GiT?tZcx4Iz literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.ldap.ppolicy.PasswordPolicyException.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.ldap.ppolicy.PasswordPolicyException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..148433692c0d27a79087313a253636626a5f6af9 GIT binary patch literal 10970 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 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.oauth2.client.ClientAuthorizationException.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.oauth2.client.ClientAuthorizationException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..7566a0979b5914e20ac55ff060c153f557b6692f GIT binary patch literal 16626 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 Itj#O`54(EVrvLx| literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.oauth2.client.ClientAuthorizationRequiredException.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.oauth2.client.ClientAuthorizationRequiredException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..836566955ab03aa2e820baddb9f0ee6444085250 GIT binary patch literal 11268 zcmeGiTZ|k>v3G5sU-2`3#dhMey|GO!&-oR<3EteD&u7j)>-KCQc5Kbf^zF^v?96zk z_k3rfh>#!%;Sr3KpnxJifCv;MBn-9yAwFh9_3dF*tFrBO*XdI zuv~|F%;@I-s?gj2gc1wv&%KTdRL9%f{p?Qy=`&ehtwFvh3!qP zPSL-y`u)E>w{y#NPJbQ^LqBBXzyRRx%E+!OmR*;SU6%#8y9DlDoQF)UM@|jo)EH`d z%xO?X>CMX>eT(+L_*WsnhZ8^r@y9!xo?3r&<8N~i!(*X;)f{o@d+$8C{qWUWf0#q& z4v=}4*^DS7t2OZ`$fE;fiA|a5xTk3pnRR-FJWt9O$fAgu*2HjVS}^mm3)C&xeG#tm(jeGEnNv;4`voS)$0kX_;%_ti1O`C>iVE=Fd4v?ibg#k3eZXB1r z3q~E6YEX+B6xMI2dVIT?f?3kwJ?Ci{!S>>arh*b7rKOC_F@u23vdL0Q~z#-X>+^%5c5bi<8 z0~pV8eE4EC^i0>NQ;)*_^+TiKM3&JP|Lmc#X|SJ>wfRm-d4Zfwb&97X#iv?O95X%J z1xXs`A{y4fYQdw~ip1H_x4?>#?$((BBo8a=l&RT6G-|p`=jMRq=GaH#=A_`2kAWBZ7mp3yq_>FFknVjWqLSvM6yv|*E1X~He0aS%E*ek&>cPF+w{EY zHg&C(U*+(Ov|^(_HjQ4d4qFS@$n?lXE{UmRDUe?&j<)014n`h|VbA*GzRvs)@}O2o zO-2Vt#;KLk->nQN{@kVG8qWYQvQFR{@_i=o^-E*dr&akhFtQeE;c-H#e}(>2N@9)W zd$xn|)^tyR_a5{(kWPeqq7_If7RmA~Z#wvG$knO9FE66@bQ)36fCaYh#M zz%pWD@a@=h(AaJ46Rr`cmk$B;bxGujmmml-vRHg1=ul2Q*z_v*TKW;OAFmdZbl7B%~y0$1+DpeoRu@mrxpS1`fD! zgYqqF&?yKoXpe@N-4F7%cxncjN486nn;)RRrM6Bf_Ev>wG37&$K0JP&H5Ry?QU#G;)K|oSP}ZunSl^CGIj-eAgQF0g zA>gT=DAtMz>2bi2oSs)A`rCkhTMj+op^%wd^sEz5tbkPZRrWBlCfBc)_^l%3KW!8F z-pU@F{%vdu5#8p+q&H>;y2*iFL!j6H2bk^~xsK0(n06#OsD`0AjWx82`l>TgAtLQ4r_jnt@C#hdjLesK3(%ETwW->J z_eHgB>AxMIIe$ruK@)5w#I5x}4Ag#!&4jXet4JVs0P*IWe5)f^wYFHikKmw??UG7d zVEMr`B$9p+@_%vD=I8C7!3Mt?ngJwr-K}Sk*MObJbL=FZA#oc>?{%oC(@7}h!0Dj| zg;_Y1!wJ2B6Y}HCCi`U(&_7b_E{HJ~fR=}W&n-DVb7qNCxqJ9g0TM7q?0s7S75Na@ zWjOwQYE+7YT#NOXv zb8m8jh=53D@wN92=z&_u1M?-bn=c-GvY8XwX)3kai9K3sl>-nbBkUIWJ$`*Q&bE-m ztZ97q;DQ*Q*D;9m33h)Dn-{S8ybkax_HZT>9$wM`X5}G!lt6@E)G_b?o*|(G1zF#` z55Vh+>qeebs)PbDap#zGvnZU8DbD#B^%XcyiGys_b#?OJRdC4)|39USg5c+$_JxX5 z{{BFk`6Hb;?xJ{v_2Ji7bU_`)9=L(z?+`L2@|h|;txmyEd1^JJjy-Bu;r@uP0Qob7 zdtpG{jBUt~7xi~cmvF5q;_t}SYsEHI7jey|XN^GtAKxb!;^Ix5^`V$O2$e24)5^m| z=M1C8tNtM;3{7b+HuLajiZfBrodMtoPC<7iG2~CgsRFS$y6fO;t~zW?E5g?JAvwi_ zFpgvrl1u^cLlaof?t(H!T7I$?F(8EsH!PW+Vma|jKVRiiWMf&5gD&g=4vH#USq|b$ ztAvTQ$yHU{ndbwHlspH%{|YX$9-44JZkU0ST(2=G{Y3@?dw^r4xMxyUbm|=3I-Ql5 zPFU&S7k26%y&?;w5De5HjpC9LBb8a^b8g{$#4n1XD2*oG=ePh16V%dbY}PQc2x}Wr zRN1{-83_Vif>rXW{(WAhq&hcZaM3KEKOntN06kvQpPgQ<31@jFA|*dYHntdv^;l9D z^JTai{SQzqz8gIQmtm9g7AU?L75ABswi=ScpD*J1ZnVVAsfV2AV&9Ac;4cCAO-2@I zCjybDvFXO92ng;&ai|ZQlZ8Mi&;v&CC?*Q`kF5ulK2zi_ z0=Y9Yk^@tjnvN+{Z8Lz9P$m8i`!%_HQtt^KDVh`0Pwso-yY`iMM-CfsYI?6x;ZqNA z32lUZmJDfT|9fEiu*UQZ&IBJDLI^^`{}lhM?1NH9@>A+*PT2HxExeS`!rf3nl^%qB zB@3%iGC2(eGI#;!iTXFb#nNfj`C>5~l@3Ca1PHOivA@M2Uh(r%vb0u^A$q06vC7rM zrIKqKRc@dv0t<04vc9liX-kDGEm+4U0=doU+Rg4idj)FN)p1_5h;Q;nC|0|NifDWMpkFLVyTo;mqVvG zEo1?`OX(ydbFm}}?1W@fQe~2n3Y1_mzS6i>$T6^QzTW|gu&PP+h6inEUoMv8p z=3mQVMy0*@L~+2A54TL_43Nd~{U%UC&dLL1A;ov0-Ea#fJLbUSTmCr(8h!F3y@iBC Nb;c)_fVk|~{{;Ya>hb^p literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.oauth2.core.OAuth2AuthenticationException.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.oauth2.core.OAuth2AuthenticationException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..de67c73ec2284052941c142282a55672584e350b GIT binary patch literal 16580 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 b@1wsj&<&mR&kxAY3Ch*m1BCg<1%UEDW^>8< literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.oauth2.core.OAuth2AuthorizationException.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.oauth2.core.OAuth2AuthorizationException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..b082c12d2829669619c4849cd6cad886765731c6 GIT binary patch literal 16507 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 Ezy4ZV-2eap literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.oauth2.jwt.JwtDecoderInitializationException.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.oauth2.jwt.JwtDecoderInitializationException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..39a7ada3a10a89c346cff8085cd37d52e6dbf410 GIT binary patch literal 16226 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 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.oauth2.jwt.JwtEncodingException.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.oauth2.jwt.JwtEncodingException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..e0026470c333d3b18edb37ec7c1eb424ac89c3ec GIT binary patch literal 16280 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 Dvd><2 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.oauth2.jwt.JwtException.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.oauth2.jwt.JwtException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..ac27bf9f67af2e7a9a8f4e389017a889f30a70a5 GIT binary patch literal 16205 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 RW?)X>WQ~A@=Yjyz{4c^xnIHfF literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.oauth2.server.resource.InvalidBearerTokenException.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.oauth2.server.resource.InvalidBearerTokenException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..e2cd7fbb9971580fabbd82a9678ebb6c066405d1 GIT binary patch literal 16963 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 I=t%p20eYfU%>V!Z literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.oauth2.server.resource.introspection.BadOpaqueTokenException.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.oauth2.server.resource.introspection.BadOpaqueTokenException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..098c85e9bd64d38d794dc5d160a6df216a9f4c63 GIT binary patch literal 16351 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 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionException.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..4c8b96b31edfa1f6966472685df1b8ea06986caa GIT binary patch literal 16247 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 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.web.firewall.RequestRejectedException.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.web.firewall.RequestRejectedException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..52e1faf545b6643fd232cd3de8f8ee32514a9334 GIT binary patch literal 10661 zcmeHNU2Gjk6`t!Pj$`sqQs=j6l1);Gx^dz(iR(0pV>^jc#|gepAZ;mH?;T&Sy?1xB zJJ&y@fJ9pmh>B7~A1YMwR}~}>pcWFuE2)rpfrmcu073{<35h43KnmfUnc3OdpL=~x z!V~escy{K@IcL6e=FH5Q`@_GJ6;Vje`eD_Kg3$4*lc80kbAC8uM%1o{4qGti=!7}x zgmlhwU2}}ysMCmz(P?TkS{a(RX}}!ct6cmZdF!7?_qGu-AChg;)~sc^mRB{$>K=1y zR1v=UsrK`oM_>G_ApCGbs3?AWXVqsXcP-A0YB%y7^7xhbj#K?n>r03t@#i(z%QF=Q#5O8yyY4V zBk%;yXg(+rQu;n4%d8+^HD|62)OMe^iLn>L7Y3Nw4^E}#o9J~%NKGZs~iLilH zT&p%wu?(;Jl$XiKiM!oU*)%3rLz*BN~>$&s>LAl!z;#Z-XoOAtQU6 z6G$C4g3GJXV>GI}OxNa^)aK-WrOgGQD~?-a|G-dI#gZ<(`9}dfDSdx)2^-)}o|%p6j!^>EySiP7gQG zsXuUrYk>=B$(pkFVV%vp3XvYH9c83lNY#i+HxX8jrEX%N)OL1;c)$Y+qc zv$Hm&9>R@DNqb})ETT=#3h>a8abcUre{R!|YfXcXr7YsP$nNtoN zKSO)UvEeYPZZ!kGi#rdV0=x2!QkRt?Hmc1Bk$zv{G=((>PRGI*6IFFZznme*PfsTp zT#O)&WPMAVDjw2Mr*SKqfwP-GhPQ{vnWgT6aZFcHfQ@>(Sk!WJcumcL zVXe+c6V@p^euZ zE13cKnev0ZoPd!}EYZwmxQ>DYQ>$`8b96Mk2-1_PP(G zn%9=7rtW^mAf2?}7B#rwSvAMjbw%#=95%n>Lb2ffk|%hD6EMPWgssG;cymSBdUV>w zSB%_w)14fXoN9bA@U!|?uAs0;zYZ`xn%JN}46OxB zWf`hghJ*y1wlF6Go4RV2=3NiB0Q812T7%sl4qpgC#ujb=0wD(yA<~a>1~o1~T;MoE z0!{KQU{~7IqG^xc7t^+6|5k{W{V5HD25cnY)>>c>)V{=GLM*;h1jrp=ygP?){{#SS z*(_d1a8bxMNh5aHey{)sNxz8sKe%iE7`uJ~3w$oL0yxwSG#)`-2X*%4)JY;k5;lC4k1-PqD~gP|~e|{qGbIfdR1fXISuPR0!0P z(L{E?C=drYV(TxlxHp_&M?fZv#M=8t^uR3Sf%}rh-4`!X*^?9cY3f9L5L>jPRgOU1 zjIddp@3A%;pSDoMk}odEVL|60?kCv%X)IpA;xjtItJuPoOmz61POzvB*{cLH{JhSg z>D2*FeJehIs4HFp`JqxBD3B8mj=3<4;Cw`B{xa6S0M{vTk?nU~T|AGmnHm0nqKq*8 zd1;^`^n_40ybo(%)Pd^77I;?V@1B_yd9w^pqgya!o*H$jYfmct9FqzR zE%BzClOmtHwikC43x7`v*OF~2FXCEt&z^z=KD?h&Q@o~$yFMhdFGHpa?zHl9@ko>2 zV!=P)g0Z*@MTuZ71&c`NHX(Qd`*^$%9P&5fRDxJs-4lLrFP(GPl$L}o^GmXNNy6AA zoFf^8wUm}|ioCnPcxn7(E@DK23NI{~mSUW)#1uQ5pdMJ5A(fNP|9XHs5t@hZGJU6GGYnCajT zcIt85kO@-Q3{)qL;*=62#vxjq27^09Z zd``HtlngBP>7BblgX7vl$!jnGFP?N>tOM~=pI9ioKQ``E_DqR;7Q~G<6$h>|zYt64 zuUG-3gpBwb)@$2z9|aOSS8*97yp*xRQY4^BkHET;iB(9MT!I7{ z{4mWE`EUGzm2UAWpX}p&7!=6{fr^}N)`?I2Je4fH6)?o8lmu2edw5iGD`r1;P_+XK zI2hSh*siRVf@J{&KBWQQTwr-2VzkzeziSc?^*nS7myM+#Se!_jXgR!#sRKVkbDl$k ztGW3Hz<9Rx`CDD@o_zbwKf?WJKA8UxwlNBF literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.web.server.firewall.ServerExchangeRejectedException.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.web.server.firewall.ServerExchangeRejectedException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..33fb178f627ea0d576653525e8858b0c10a6d4eb GIT binary patch literal 10675 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 literal 0 HcmV?d00001 diff --git a/core/src/main/java/org/springframework/security/access/AccessDeniedException.java b/core/src/main/java/org/springframework/security/access/AccessDeniedException.java index 3bf6ceac5a..49efd9f689 100644 --- a/core/src/main/java/org/springframework/security/access/AccessDeniedException.java +++ b/core/src/main/java/org/springframework/security/access/AccessDeniedException.java @@ -16,6 +16,8 @@ package org.springframework.security.access; +import java.io.Serial; + /** * Thrown if an {@link org.springframework.security.core.Authentication Authentication} * object does not hold a required authority. @@ -24,6 +26,9 @@ */ public class AccessDeniedException extends RuntimeException { + @Serial + private static final long serialVersionUID = 6395817500121599533L; + /** * Constructs an AccessDeniedException with the specified message. * @param msg the detail message diff --git a/core/src/main/java/org/springframework/security/access/AuthorizationServiceException.java b/core/src/main/java/org/springframework/security/access/AuthorizationServiceException.java index 6952be563a..4320b0075f 100644 --- a/core/src/main/java/org/springframework/security/access/AuthorizationServiceException.java +++ b/core/src/main/java/org/springframework/security/access/AuthorizationServiceException.java @@ -16,6 +16,8 @@ package org.springframework.security.access; +import java.io.Serial; + /** * Thrown if an authorization request could not be processed due to a system problem. *

@@ -26,6 +28,9 @@ */ public class AuthorizationServiceException extends AccessDeniedException { + @Serial + private static final long serialVersionUID = 4817857292041606900L; + /** * Constructs an AuthorizationServiceException with the specified * message. diff --git a/core/src/main/java/org/springframework/security/authentication/AccountExpiredException.java b/core/src/main/java/org/springframework/security/authentication/AccountExpiredException.java index e8ef659882..1193bf5236 100644 --- a/core/src/main/java/org/springframework/security/authentication/AccountExpiredException.java +++ b/core/src/main/java/org/springframework/security/authentication/AccountExpiredException.java @@ -16,6 +16,8 @@ package org.springframework.security.authentication; +import java.io.Serial; + /** * Thrown if an authentication request is rejected because the account has expired. Makes * no assertion as to whether or not the credentials were valid. @@ -24,6 +26,9 @@ */ public class AccountExpiredException extends AccountStatusException { + @Serial + private static final long serialVersionUID = 3732869526329993353L; + /** * Constructs a AccountExpiredException with the specified message. * @param msg the detail message diff --git a/core/src/main/java/org/springframework/security/authentication/AuthenticationCredentialsNotFoundException.java b/core/src/main/java/org/springframework/security/authentication/AuthenticationCredentialsNotFoundException.java index 91b5d616d8..0ed92018e6 100644 --- a/core/src/main/java/org/springframework/security/authentication/AuthenticationCredentialsNotFoundException.java +++ b/core/src/main/java/org/springframework/security/authentication/AuthenticationCredentialsNotFoundException.java @@ -16,6 +16,8 @@ package org.springframework.security.authentication; +import java.io.Serial; + import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; @@ -28,6 +30,9 @@ */ public class AuthenticationCredentialsNotFoundException extends AuthenticationException { + @Serial + private static final long serialVersionUID = 4153580041526791384L; + /** * Constructs an AuthenticationCredentialsNotFoundException with the * specified message. diff --git a/core/src/main/java/org/springframework/security/authentication/AuthenticationServiceException.java b/core/src/main/java/org/springframework/security/authentication/AuthenticationServiceException.java index 69d7233bdf..3bd076dfd8 100644 --- a/core/src/main/java/org/springframework/security/authentication/AuthenticationServiceException.java +++ b/core/src/main/java/org/springframework/security/authentication/AuthenticationServiceException.java @@ -16,6 +16,8 @@ package org.springframework.security.authentication; +import java.io.Serial; + import org.springframework.security.core.AuthenticationException; /** @@ -29,6 +31,9 @@ */ public class AuthenticationServiceException extends AuthenticationException { + @Serial + private static final long serialVersionUID = -1591626195291329340L; + /** * Constructs an AuthenticationServiceException with the specified * message. diff --git a/core/src/main/java/org/springframework/security/authentication/BadCredentialsException.java b/core/src/main/java/org/springframework/security/authentication/BadCredentialsException.java index e202ef7b5a..bc759f5f7a 100644 --- a/core/src/main/java/org/springframework/security/authentication/BadCredentialsException.java +++ b/core/src/main/java/org/springframework/security/authentication/BadCredentialsException.java @@ -16,6 +16,8 @@ package org.springframework.security.authentication; +import java.io.Serial; + import org.springframework.security.core.AuthenticationException; /** @@ -26,6 +28,9 @@ */ public class BadCredentialsException extends AuthenticationException { + @Serial + private static final long serialVersionUID = 2742216069043066973L; + /** * Constructs a BadCredentialsException with the specified message. * @param msg the detail message diff --git a/core/src/main/java/org/springframework/security/authentication/CredentialsExpiredException.java b/core/src/main/java/org/springframework/security/authentication/CredentialsExpiredException.java index 8e532169ae..0419417763 100644 --- a/core/src/main/java/org/springframework/security/authentication/CredentialsExpiredException.java +++ b/core/src/main/java/org/springframework/security/authentication/CredentialsExpiredException.java @@ -16,6 +16,8 @@ package org.springframework.security.authentication; +import java.io.Serial; + /** * Thrown if an authentication request is rejected because the account's credentials have * expired. Makes no assertion as to whether or not the credentials were valid. @@ -24,6 +26,9 @@ */ public class CredentialsExpiredException extends AccountStatusException { + @Serial + private static final long serialVersionUID = -3306615738048904753L; + /** * Constructs a CredentialsExpiredException with the specified message. * @param msg the detail message diff --git a/core/src/main/java/org/springframework/security/authentication/DisabledException.java b/core/src/main/java/org/springframework/security/authentication/DisabledException.java index 31a75ce0cc..fba1718590 100644 --- a/core/src/main/java/org/springframework/security/authentication/DisabledException.java +++ b/core/src/main/java/org/springframework/security/authentication/DisabledException.java @@ -16,6 +16,8 @@ package org.springframework.security.authentication; +import java.io.Serial; + /** * Thrown if an authentication request is rejected because the account is disabled. Makes * no assertion as to whether or not the credentials were valid. @@ -24,6 +26,9 @@ */ public class DisabledException extends AccountStatusException { + @Serial + private static final long serialVersionUID = 2295984593872502361L; + /** * Constructs a DisabledException with the specified message. * @param msg the detail message diff --git a/core/src/main/java/org/springframework/security/authentication/InsufficientAuthenticationException.java b/core/src/main/java/org/springframework/security/authentication/InsufficientAuthenticationException.java index 0e072b527a..f475934927 100644 --- a/core/src/main/java/org/springframework/security/authentication/InsufficientAuthenticationException.java +++ b/core/src/main/java/org/springframework/security/authentication/InsufficientAuthenticationException.java @@ -16,6 +16,8 @@ package org.springframework.security.authentication; +import java.io.Serial; + import org.springframework.security.core.AuthenticationException; /** @@ -33,6 +35,9 @@ */ public class InsufficientAuthenticationException extends AuthenticationException { + @Serial + private static final long serialVersionUID = -5514084346181236128L; + /** * Constructs an InsufficientAuthenticationException with the specified * message. diff --git a/core/src/main/java/org/springframework/security/authentication/InternalAuthenticationServiceException.java b/core/src/main/java/org/springframework/security/authentication/InternalAuthenticationServiceException.java index 3037ebaaf0..de59b2d5ef 100644 --- a/core/src/main/java/org/springframework/security/authentication/InternalAuthenticationServiceException.java +++ b/core/src/main/java/org/springframework/security/authentication/InternalAuthenticationServiceException.java @@ -16,6 +16,8 @@ package org.springframework.security.authentication; +import java.io.Serial; + /** *

* Thrown if an authentication request could not be processed due to a system problem that @@ -37,6 +39,9 @@ */ public class InternalAuthenticationServiceException extends AuthenticationServiceException { + @Serial + private static final long serialVersionUID = -6029644854192497840L; + public InternalAuthenticationServiceException(String message, Throwable cause) { super(message, cause); } diff --git a/core/src/main/java/org/springframework/security/authentication/LockedException.java b/core/src/main/java/org/springframework/security/authentication/LockedException.java index 9b2272b08f..5262fdb52e 100644 --- a/core/src/main/java/org/springframework/security/authentication/LockedException.java +++ b/core/src/main/java/org/springframework/security/authentication/LockedException.java @@ -16,6 +16,8 @@ package org.springframework.security.authentication; +import java.io.Serial; + /** * Thrown if an authentication request is rejected because the account is locked. Makes no * assertion as to whether or not the credentials were valid. @@ -24,6 +26,9 @@ */ public class LockedException extends AccountStatusException { + @Serial + private static final long serialVersionUID = 548864198455046567L; + /** * Constructs a LockedException with the specified message. * @param msg the detail message. diff --git a/core/src/main/java/org/springframework/security/authentication/ProviderNotFoundException.java b/core/src/main/java/org/springframework/security/authentication/ProviderNotFoundException.java index 629a28e8c8..870a6ea1f8 100644 --- a/core/src/main/java/org/springframework/security/authentication/ProviderNotFoundException.java +++ b/core/src/main/java/org/springframework/security/authentication/ProviderNotFoundException.java @@ -16,6 +16,8 @@ package org.springframework.security.authentication; +import java.io.Serial; + import org.springframework.security.core.AuthenticationException; /** @@ -27,6 +29,9 @@ */ public class ProviderNotFoundException extends AuthenticationException { + @Serial + private static final long serialVersionUID = 8107665253214447614L; + /** * Constructs a ProviderNotFoundException with the specified message. * @param msg the detail message diff --git a/core/src/main/java/org/springframework/security/authentication/ott/InvalidOneTimeTokenException.java b/core/src/main/java/org/springframework/security/authentication/ott/InvalidOneTimeTokenException.java index 03289f12b7..8ee8199cd0 100644 --- a/core/src/main/java/org/springframework/security/authentication/ott/InvalidOneTimeTokenException.java +++ b/core/src/main/java/org/springframework/security/authentication/ott/InvalidOneTimeTokenException.java @@ -16,6 +16,8 @@ package org.springframework.security.authentication.ott; +import java.io.Serial; + import org.springframework.security.core.AuthenticationException; /** @@ -26,6 +28,9 @@ */ public class InvalidOneTimeTokenException extends AuthenticationException { + @Serial + private static final long serialVersionUID = -3651018515682919943L; + public InvalidOneTimeTokenException(String msg) { super(msg); } diff --git a/core/src/main/java/org/springframework/security/authentication/password/CompromisedPasswordException.java b/core/src/main/java/org/springframework/security/authentication/password/CompromisedPasswordException.java index 672876164f..04d042b96a 100644 --- a/core/src/main/java/org/springframework/security/authentication/password/CompromisedPasswordException.java +++ b/core/src/main/java/org/springframework/security/authentication/password/CompromisedPasswordException.java @@ -16,6 +16,8 @@ package org.springframework.security.authentication.password; +import java.io.Serial; + import org.springframework.security.core.AuthenticationException; /** @@ -26,6 +28,9 @@ */ public class CompromisedPasswordException extends AuthenticationException { + @Serial + private static final long serialVersionUID = -885858958297842864L; + public CompromisedPasswordException(String message) { super(message); } diff --git a/core/src/main/java/org/springframework/security/authorization/AuthorizationDeniedException.java b/core/src/main/java/org/springframework/security/authorization/AuthorizationDeniedException.java index fdcb1e70aa..63385e1cbd 100644 --- a/core/src/main/java/org/springframework/security/authorization/AuthorizationDeniedException.java +++ b/core/src/main/java/org/springframework/security/authorization/AuthorizationDeniedException.java @@ -16,6 +16,8 @@ package org.springframework.security.authorization; +import java.io.Serial; + import org.springframework.security.access.AccessDeniedException; import org.springframework.util.Assert; @@ -27,6 +29,9 @@ */ public class AuthorizationDeniedException extends AccessDeniedException implements AuthorizationResult { + @Serial + private static final long serialVersionUID = 3227305845919610459L; + private final AuthorizationResult result; public AuthorizationDeniedException(String msg, AuthorizationResult authorizationResult) { diff --git a/core/src/main/java/org/springframework/security/core/userdetails/UsernameNotFoundException.java b/core/src/main/java/org/springframework/security/core/userdetails/UsernameNotFoundException.java index 22c3c1d8e5..d1d969dc26 100644 --- a/core/src/main/java/org/springframework/security/core/userdetails/UsernameNotFoundException.java +++ b/core/src/main/java/org/springframework/security/core/userdetails/UsernameNotFoundException.java @@ -16,6 +16,8 @@ package org.springframework.security.core.userdetails; +import java.io.Serial; + import org.springframework.security.core.AuthenticationException; /** @@ -26,6 +28,9 @@ */ public class UsernameNotFoundException extends AuthenticationException { + @Serial + private static final long serialVersionUID = 1410688585992297006L; + /** * Constructs a UsernameNotFoundException with the specified message. * @param msg the detail message. diff --git a/crypto/src/main/java/org/springframework/security/crypto/codec/Base64.java b/crypto/src/main/java/org/springframework/security/crypto/codec/Base64.java index d4afce73c1..b696c0c4bf 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/codec/Base64.java +++ b/crypto/src/main/java/org/springframework/security/crypto/codec/Base64.java @@ -617,6 +617,7 @@ else if (len < 4) { return out; } + @SuppressWarnings("serial") static class InvalidBase64CharacterException extends IllegalArgumentException { InvalidBase64CharacterException(String message) { diff --git a/ldap/src/main/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryAuthenticationException.java b/ldap/src/main/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryAuthenticationException.java index 42b0403740..124fce51bb 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryAuthenticationException.java +++ b/ldap/src/main/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryAuthenticationException.java @@ -40,6 +40,7 @@ * * @author Rob Winch */ +@SuppressWarnings("serial") public final class ActiveDirectoryAuthenticationException extends AuthenticationException { private final String dataCode; diff --git a/ldap/src/main/java/org/springframework/security/ldap/ppolicy/PasswordPolicyException.java b/ldap/src/main/java/org/springframework/security/ldap/ppolicy/PasswordPolicyException.java index 73ab142052..f01222d4a2 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/ppolicy/PasswordPolicyException.java +++ b/ldap/src/main/java/org/springframework/security/ldap/ppolicy/PasswordPolicyException.java @@ -16,6 +16,8 @@ package org.springframework.security.ldap.ppolicy; +import java.io.Serial; + /** * Generic exception raised by the ppolicy package. *

@@ -27,6 +29,9 @@ */ public class PasswordPolicyException extends RuntimeException { + @Serial + private static final long serialVersionUID = 2586535034047453106L; + private final PasswordPolicyErrorStatus status; public PasswordPolicyException(PasswordPolicyErrorStatus status) { diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ClientAuthorizationException.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ClientAuthorizationException.java index 8050b74a03..257f26f4f5 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ClientAuthorizationException.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ClientAuthorizationException.java @@ -16,6 +16,8 @@ package org.springframework.security.oauth2.client; +import java.io.Serial; + import org.springframework.security.oauth2.core.OAuth2AuthorizationException; import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.util.Assert; @@ -30,6 +32,9 @@ */ public class ClientAuthorizationException extends OAuth2AuthorizationException { + @Serial + private static final long serialVersionUID = 4710713969265443271L; + private final String clientRegistrationId; /** diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ClientAuthorizationRequiredException.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ClientAuthorizationRequiredException.java index ee4c0e4784..0bb5649ece 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ClientAuthorizationRequiredException.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ClientAuthorizationRequiredException.java @@ -16,6 +16,8 @@ package org.springframework.security.oauth2.client; +import java.io.Serial; + import org.springframework.security.oauth2.core.OAuth2Error; /** @@ -28,6 +30,9 @@ */ public class ClientAuthorizationRequiredException extends ClientAuthorizationException { + @Serial + private static final long serialVersionUID = -5738646355203953667L; + private static final String CLIENT_AUTHORIZATION_REQUIRED_ERROR_CODE = "client_authorization_required"; /** diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/InvalidClientRegistrationIdException.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/InvalidClientRegistrationIdException.java index f42249284f..e7e718949c 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/InvalidClientRegistrationIdException.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/InvalidClientRegistrationIdException.java @@ -20,6 +20,7 @@ * @author Steve Riesenberg * @since 5.8 */ +@SuppressWarnings("serial") class InvalidClientRegistrationIdException extends IllegalArgumentException { /** diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/OAuth2AuthenticationException.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/OAuth2AuthenticationException.java index a868f3180d..ac760c5dc4 100644 --- a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/OAuth2AuthenticationException.java +++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/OAuth2AuthenticationException.java @@ -16,6 +16,8 @@ package org.springframework.security.oauth2.core; +import java.io.Serial; + import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.util.Assert; @@ -41,6 +43,9 @@ */ public class OAuth2AuthenticationException extends AuthenticationException { + @Serial + private static final long serialVersionUID = -7832130893085581438L; + private final OAuth2Error error; /** diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/OAuth2AuthorizationException.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/OAuth2AuthorizationException.java index dbfdf98e5f..af833d1dae 100644 --- a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/OAuth2AuthorizationException.java +++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/OAuth2AuthorizationException.java @@ -16,6 +16,8 @@ package org.springframework.security.oauth2.core; +import java.io.Serial; + import org.springframework.util.Assert; /** @@ -26,6 +28,9 @@ */ public class OAuth2AuthorizationException extends RuntimeException { + @Serial + private static final long serialVersionUID = -5470222190376181102L; + private final OAuth2Error error; /** diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/BadJwtException.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/BadJwtException.java index 3a30545179..2742d0c51e 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/BadJwtException.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/BadJwtException.java @@ -16,6 +16,8 @@ package org.springframework.security.oauth2.jwt; +import java.io.Serial; + /** * An exception similar to * {@link org.springframework.security.authentication.BadCredentialsException} that @@ -26,6 +28,9 @@ */ public class BadJwtException extends JwtException { + @Serial + private static final long serialVersionUID = 7748429527132280501L; + public BadJwtException(String message) { super(message); } diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtDecoderInitializationException.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtDecoderInitializationException.java index 775da4c9a9..cd1b90a14c 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtDecoderInitializationException.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtDecoderInitializationException.java @@ -16,6 +16,8 @@ package org.springframework.security.oauth2.jwt; +import java.io.Serial; + /** * An exception thrown when a {@link JwtDecoder} or {@link ReactiveJwtDecoder}'s lazy * initialization fails. @@ -25,6 +27,9 @@ */ public class JwtDecoderInitializationException extends RuntimeException { + @Serial + private static final long serialVersionUID = 2786360018315628982L; + public JwtDecoderInitializationException(String message, Throwable cause) { super(message, cause); } diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtEncodingException.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtEncodingException.java index 9b48f5c4a2..365993c5ed 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtEncodingException.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtEncodingException.java @@ -16,6 +16,8 @@ package org.springframework.security.oauth2.jwt; +import java.io.Serial; + /** * This exception is thrown when an error occurs while attempting to encode a JSON Web * Token (JWT). @@ -25,6 +27,9 @@ */ public class JwtEncodingException extends JwtException { + @Serial + private static final long serialVersionUID = 6581840872589902213L; + /** * Constructs a {@code JwtEncodingException} using the provided parameters. * @param message the detail message diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtException.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtException.java index b13f0dff26..2004727ffb 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtException.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtException.java @@ -16,6 +16,8 @@ package org.springframework.security.oauth2.jwt; +import java.io.Serial; + /** * Base exception for all JSON Web Token (JWT) related errors. * @@ -24,6 +26,9 @@ */ public class JwtException extends RuntimeException { + @Serial + private static final long serialVersionUID = -3070197880233583797L; + /** * Constructs a {@code JwtException} using the provided parameters. * @param message the detail message diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtValidationException.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtValidationException.java index 94568d2dc6..ab3722e5fd 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtValidationException.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtValidationException.java @@ -16,6 +16,7 @@ package org.springframework.security.oauth2.jwt; +import java.io.Serial; import java.util.ArrayList; import java.util.Collection; @@ -31,6 +32,9 @@ */ public class JwtValidationException extends BadJwtException { + @Serial + private static final long serialVersionUID = 134652048447295615L; + private final Collection errors; /** diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/InvalidBearerTokenException.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/InvalidBearerTokenException.java index 0ba62813da..c82b3bd5e4 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/InvalidBearerTokenException.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/InvalidBearerTokenException.java @@ -16,6 +16,8 @@ package org.springframework.security.oauth2.server.resource; +import java.io.Serial; + import org.springframework.security.oauth2.core.OAuth2AuthenticationException; /** @@ -26,6 +28,9 @@ */ public class InvalidBearerTokenException extends OAuth2AuthenticationException { + @Serial + private static final long serialVersionUID = 6904689954809100280L; + /** * Construct an instance of {@link InvalidBearerTokenException} given the provided * description. diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/BadOpaqueTokenException.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/BadOpaqueTokenException.java index 5e155c8bce..cddd32c3b0 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/BadOpaqueTokenException.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/BadOpaqueTokenException.java @@ -16,6 +16,8 @@ package org.springframework.security.oauth2.server.resource.introspection; +import java.io.Serial; + /** * An exception similar to * {@link org.springframework.security.authentication.BadCredentialsException} that @@ -26,6 +28,9 @@ */ public class BadOpaqueTokenException extends OAuth2IntrospectionException { + @Serial + private static final long serialVersionUID = -6937847463454551076L; + public BadOpaqueTokenException(String message) { super(message); } diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/OAuth2IntrospectionException.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/OAuth2IntrospectionException.java index e2649ba975..6650d96e57 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/OAuth2IntrospectionException.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/OAuth2IntrospectionException.java @@ -16,6 +16,8 @@ package org.springframework.security.oauth2.server.resource.introspection; +import java.io.Serial; + /** * Base exception for all OAuth 2.0 Introspection related errors * @@ -24,6 +26,9 @@ */ public class OAuth2IntrospectionException extends RuntimeException { + @Serial + private static final long serialVersionUID = -7327790383594166793L; + public OAuth2IntrospectionException(String message) { super(message); } diff --git a/web/src/main/java/org/springframework/security/web/firewall/RequestRejectedException.java b/web/src/main/java/org/springframework/security/web/firewall/RequestRejectedException.java index b997031a47..ea91775b62 100644 --- a/web/src/main/java/org/springframework/security/web/firewall/RequestRejectedException.java +++ b/web/src/main/java/org/springframework/security/web/firewall/RequestRejectedException.java @@ -16,11 +16,16 @@ package org.springframework.security.web.firewall; +import java.io.Serial; + /** * @author Luke Taylor */ public class RequestRejectedException extends RuntimeException { + @Serial + private static final long serialVersionUID = 7226768874760909859L; + public RequestRejectedException(String message) { super(message); } diff --git a/web/src/main/java/org/springframework/security/web/server/authentication/SwitchUserWebFilter.java b/web/src/main/java/org/springframework/security/web/server/authentication/SwitchUserWebFilter.java index 8eab25cf1f..85686f5815 100644 --- a/web/src/main/java/org/springframework/security/web/server/authentication/SwitchUserWebFilter.java +++ b/web/src/main/java/org/springframework/security/web/server/authentication/SwitchUserWebFilter.java @@ -353,6 +353,7 @@ public void setSwitchUserMatcher(ServerWebExchangeMatcher switchUserMatcher) { this.switchUserMatcher = switchUserMatcher; } + @SuppressWarnings("serial") private static class SwitchUserAuthenticationException extends RuntimeException { SwitchUserAuthenticationException(AuthenticationException exception) { diff --git a/web/src/main/java/org/springframework/security/web/server/firewall/ServerExchangeRejectedException.java b/web/src/main/java/org/springframework/security/web/server/firewall/ServerExchangeRejectedException.java index 5246838dcf..f46140d351 100644 --- a/web/src/main/java/org/springframework/security/web/server/firewall/ServerExchangeRejectedException.java +++ b/web/src/main/java/org/springframework/security/web/server/firewall/ServerExchangeRejectedException.java @@ -16,6 +16,8 @@ package org.springframework.security.web.server.firewall; +import java.io.Serial; + /** * Thrown when a {@link org.springframework.web.server.ServerWebExchange} is rejected. * @@ -24,6 +26,9 @@ */ public class ServerExchangeRejectedException extends RuntimeException { + @Serial + private static final long serialVersionUID = 904984955691607748L; + public ServerExchangeRejectedException(String message) { super(message); } From d7f6b71e105419729f55709bff4c4c8f689b2f74 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Jan 2025 03:38:08 +0000 Subject: [PATCH 066/132] Bump org-bouncycastle from 1.79 to 1.80 Bumps `org-bouncycastle` from 1.79 to 1.80. Updates `org.bouncycastle:bcpkix-jdk18on` from 1.79 to 1.80 - [Changelog](https://github.com/bcgit/bc-java/blob/main/docs/releasenotes.html) - [Commits](https://github.com/bcgit/bc-java/commits) Updates `org.bouncycastle:bcprov-jdk18on` from 1.79 to 1.80 - [Changelog](https://github.com/bcgit/bc-java/blob/main/docs/releasenotes.html) - [Commits](https://github.com/bcgit/bc-java/commits) --- updated-dependencies: - dependency-name: org.bouncycastle:bcpkix-jdk18on dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.bouncycastle:bcprov-jdk18on dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: Daeho Kwon --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e6a14eb90e..fe597a2210 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,7 +7,7 @@ jakarta-websocket = "2.2.0" org-apache-directory-server = "1.5.5" org-apache-maven-resolver = "1.9.22" org-aspectj = "1.9.22.1" -org-bouncycastle = "1.79" +org-bouncycastle = "1.80" org-eclipse-jetty = "11.0.24" org-jetbrains-kotlin = "1.9.25" org-jetbrains-kotlinx = "1.10.1" From a117b2f40b867b501eac41fb42b7fcd7aebafdee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Jan 2025 03:38:25 +0000 Subject: [PATCH 067/132] Bump io.projectreactor:reactor-bom from 2023.0.13 to 2023.0.14 Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2023.0.13 to 2023.0.14. - [Release notes](https://github.com/reactor/reactor/releases) - [Commits](https://github.com/reactor/reactor/compare/2023.0.13...2023.0.14) --- updated-dependencies: - dependency-name: io.projectreactor:reactor-bom dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: Daeho Kwon --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fe597a2210..6e78f6f50c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -30,7 +30,7 @@ com-unboundid-unboundid-ldapsdk7 = "com.unboundid:unboundid-ldapsdk:7.0.1" commons-collections = "commons-collections:commons-collections:3.2.2" io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.14.3" io-mockk = "io.mockk:mockk:1.13.16" -io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2023.0.13" +io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2023.0.14" io-rsocket-rsocket-bom = { module = "io.rsocket:rsocket-bom", version.ref = "io-rsocket" } io-spring-javaformat-spring-javaformat-checkstyle = { module = "io.spring.javaformat:spring-javaformat-checkstyle", version.ref = "io-spring-javaformat" } io-spring-javaformat-spring-javaformat-gradle-plugin = { module = "io.spring.javaformat:spring-javaformat-gradle-plugin", version.ref = "io-spring-javaformat" } From ecba158924434843f83790260f9abecf59c8913a Mon Sep 17 00:00:00 2001 From: Steve Riesenberg <5248162+sjohnr@users.noreply.github.com> Date: Wed, 15 Jan 2025 11:41:07 -0600 Subject: [PATCH 068/132] Fix missing GChat notifications with workaround This fix was suggested by GitHub Support as a workaround for a bug where `failure()` is not working for reusable workflows that will be fixed in a few months. Closes gh-16423 Signed-off-by: Daeho Kwon --- .github/workflows/continuous-integration-workflow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index f36d30d555..c1800334ef 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -116,7 +116,7 @@ jobs: send-notification: name: Send Notification needs: [ perform-release ] - if: ${{ failure() || cancelled() }} + if: ${{ !success() }} runs-on: ubuntu-latest steps: - name: Send Notification From cfb19ab631be2648284daf4223293f47c45de284 Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Fri, 10 Jan 2025 14:24:15 -0700 Subject: [PATCH 069/132] Polish requestMatchers Logic Issue gh-13551 Signed-off-by: Daeho Kwon --- .../web/AbstractRequestMatcherRegistry.java | 107 +++--------------- .../web/ServletRegistrationsSupport.java | 77 +++++++++++++ 2 files changed, 95 insertions(+), 89 deletions(-) create mode 100644 config/src/main/java/org/springframework/security/config/annotation/web/ServletRegistrationsSupport.java diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistry.java b/config/src/main/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistry.java index d94e9d9083..e29a4d599a 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistry.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistry.java @@ -40,6 +40,7 @@ import org.springframework.http.HttpMethod; import org.springframework.lang.Nullable; import org.springframework.security.config.ObjectPostProcessor; +import org.springframework.security.config.annotation.web.ServletRegistrationsSupport.RegistrationMapping; import org.springframework.security.config.annotation.web.configurers.AbstractConfigAttributeRequestMatcherRegistry; import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @@ -235,103 +236,31 @@ private boolean anyPathsDontStartWithLeadingSlash(String... patterns) { } private RequestMatcher resolve(AntPathRequestMatcher ant, MvcRequestMatcher mvc, ServletContext servletContext) { - Map registrations = mappableServletRegistrations(servletContext); - if (registrations.isEmpty()) { + ServletRegistrationsSupport registrations = new ServletRegistrationsSupport(servletContext); + Collection mappings = registrations.mappings(); + if (mappings.isEmpty()) { return new DispatcherServletDelegatingRequestMatcher(ant, mvc, new MockMvcRequestMatcher()); } - if (!hasDispatcherServlet(registrations)) { + Collection dispatcherServletMappings = registrations.dispatcherServletMappings(); + if (dispatcherServletMappings.isEmpty()) { return new DispatcherServletDelegatingRequestMatcher(ant, mvc, new MockMvcRequestMatcher()); } - ServletRegistration dispatcherServlet = requireOneRootDispatcherServlet(registrations); - if (dispatcherServlet != null) { - if (registrations.size() == 1) { - return mvc; - } - return new DispatcherServletDelegatingRequestMatcher(ant, mvc, servletContext); + if (dispatcherServletMappings.size() > 1) { + String errorMessage = computeErrorMessage(servletContext.getServletRegistrations().values()); + throw new IllegalArgumentException(errorMessage); } - dispatcherServlet = requireOnlyPathMappedDispatcherServlet(registrations); - if (dispatcherServlet != null) { - String mapping = dispatcherServlet.getMappings().iterator().next(); - mvc.setServletPath(mapping.substring(0, mapping.length() - 2)); - return mvc; - } - String errorMessage = computeErrorMessage(registrations.values()); - throw new IllegalArgumentException(errorMessage); - } - - private Map mappableServletRegistrations(ServletContext servletContext) { - Map mappable = new LinkedHashMap<>(); - for (Map.Entry entry : servletContext.getServletRegistrations() - .entrySet()) { - if (!entry.getValue().getMappings().isEmpty()) { - mappable.put(entry.getKey(), entry.getValue()); - } + RegistrationMapping dispatcherServlet = dispatcherServletMappings.iterator().next(); + if (mappings.size() > 1 && !dispatcherServlet.isDefault()) { + String errorMessage = computeErrorMessage(servletContext.getServletRegistrations().values()); + throw new IllegalArgumentException(errorMessage); } - return mappable; - } - - private boolean hasDispatcherServlet(Map registrations) { - if (registrations == null) { - return false; - } - for (ServletRegistration registration : registrations.values()) { - if (isDispatcherServlet(registration)) { - return true; - } - } - return false; - } - - private ServletRegistration requireOneRootDispatcherServlet( - Map registrations) { - ServletRegistration rootDispatcherServlet = null; - for (ServletRegistration registration : registrations.values()) { - if (!isDispatcherServlet(registration)) { - continue; - } - if (registration.getMappings().size() > 1) { - return null; - } - if (!"/".equals(registration.getMappings().iterator().next())) { - return null; - } - rootDispatcherServlet = registration; - } - return rootDispatcherServlet; - } - - private ServletRegistration requireOnlyPathMappedDispatcherServlet( - Map registrations) { - ServletRegistration pathDispatcherServlet = null; - for (ServletRegistration registration : registrations.values()) { - if (!isDispatcherServlet(registration)) { - return null; - } - if (registration.getMappings().size() > 1) { - return null; - } - String mapping = registration.getMappings().iterator().next(); - if (!mapping.startsWith("/") || !mapping.endsWith("/*")) { - return null; - } - if (pathDispatcherServlet != null) { - return null; + if (dispatcherServlet.isDefault()) { + if (mappings.size() == 1) { + return mvc; } - pathDispatcherServlet = registration; - } - return pathDispatcherServlet; - } - - private boolean isDispatcherServlet(ServletRegistration registration) { - Class dispatcherServlet = ClassUtils.resolveClassName("org.springframework.web.servlet.DispatcherServlet", - null); - try { - Class clazz = Class.forName(registration.getClassName()); - return dispatcherServlet.isAssignableFrom(clazz); - } - catch (ClassNotFoundException ex) { - return false; + return new DispatcherServletDelegatingRequestMatcher(ant, mvc); } + return mvc; } private static String computeErrorMessage(Collection registrations) { diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/ServletRegistrationsSupport.java b/config/src/main/java/org/springframework/security/config/annotation/web/ServletRegistrationsSupport.java new file mode 100644 index 0000000000..e84b8455f1 --- /dev/null +++ b/config/src/main/java/org/springframework/security/config/annotation/web/ServletRegistrationsSupport.java @@ -0,0 +1,77 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.config.annotation.web; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; + +import jakarta.servlet.ServletContext; +import jakarta.servlet.ServletRegistration; + +import org.springframework.util.ClassUtils; + +class ServletRegistrationsSupport { + + private final Collection registrations; + + ServletRegistrationsSupport(ServletContext servletContext) { + Map registrations = servletContext.getServletRegistrations(); + Collection mappings = new ArrayList<>(); + for (Map.Entry entry : registrations.entrySet()) { + if (!entry.getValue().getMappings().isEmpty()) { + for (String mapping : entry.getValue().getMappings()) { + mappings.add(new RegistrationMapping(entry.getValue(), mapping)); + } + } + } + this.registrations = mappings; + } + + Collection dispatcherServletMappings() { + Collection mappings = new ArrayList<>(); + for (RegistrationMapping registration : this.registrations) { + if (registration.isDispatcherServlet()) { + mappings.add(registration); + } + } + return mappings; + } + + Collection mappings() { + return this.registrations; + } + + record RegistrationMapping(ServletRegistration registration, String mapping) { + boolean isDispatcherServlet() { + Class dispatcherServlet = ClassUtils + .resolveClassName("org.springframework.web.servlet.DispatcherServlet", null); + try { + Class clazz = Class.forName(this.registration.getClassName()); + return dispatcherServlet.isAssignableFrom(clazz); + } + catch (ClassNotFoundException ex) { + return false; + } + } + + boolean isDefault() { + return "/".equals(this.mapping); + } + } + +} From d1b841ec8f8d092667256fcca0811040d4cd1828 Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Fri, 10 Jan 2025 14:58:18 -0700 Subject: [PATCH 070/132] Polish Using Request ServletContext Issue gh-14418 Signed-off-by: Daeho Kwon --- .../web/AbstractRequestMatcherRegistry.java | 16 ++++------------ .../web/AbstractRequestMatcherRegistryTests.java | 12 +++++------- 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistry.java b/config/src/main/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistry.java index e29a4d599a..4f849f86fb 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistry.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistry.java @@ -447,18 +447,12 @@ public boolean matches(HttpServletRequest request) { static class DispatcherServletRequestMatcher implements RequestMatcher { - private final ServletContext servletContext; - - DispatcherServletRequestMatcher(ServletContext servletContext) { - this.servletContext = servletContext; - } - @Override public boolean matches(HttpServletRequest request) { String name = request.getHttpServletMapping().getServletName(); - ServletRegistration registration = this.servletContext.getServletRegistration(name); + ServletRegistration registration = request.getServletContext().getServletRegistration(name); Assert.notNull(registration, - () -> computeErrorMessage(this.servletContext.getServletRegistrations().values())); + () -> computeErrorMessage(request.getServletContext().getServletRegistrations().values())); try { Class clazz = Class.forName(registration.getClassName()); return DispatcherServlet.class.isAssignableFrom(clazz); @@ -478,10 +472,8 @@ static class DispatcherServletDelegatingRequestMatcher implements RequestMatcher private final RequestMatcher dispatcherServlet; - DispatcherServletDelegatingRequestMatcher(AntPathRequestMatcher ant, MvcRequestMatcher mvc, - ServletContext servletContext) { - this(ant, mvc, new OrRequestMatcher(new MockMvcRequestMatcher(), - new DispatcherServletRequestMatcher(servletContext))); + DispatcherServletDelegatingRequestMatcher(AntPathRequestMatcher ant, MvcRequestMatcher mvc) { + this(ant, mvc, new OrRequestMatcher(new MockMvcRequestMatcher(), new DispatcherServletRequestMatcher())); } DispatcherServletDelegatingRequestMatcher(AntPathRequestMatcher ant, MvcRequestMatcher mvc, diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistryTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistryTests.java index 8561390515..1fb6e580b1 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistryTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistryTests.java @@ -318,7 +318,7 @@ public void requestMatchersWhenPathBasedNonDispatcherServletThenAllows() { List requestMatchers = this.matcherRegistry.requestMatchers("/services/*"); assertThat(requestMatchers).hasSize(1); assertThat(requestMatchers.get(0)).isInstanceOf(DispatcherServletDelegatingRequestMatcher.class); - MockHttpServletRequest request = new MockHttpServletRequest("GET", "/services/endpoint"); + MockHttpServletRequest request = new MockHttpServletRequest(servletContext, "GET", "/services/endpoint"); request.setHttpServletMapping(TestMockHttpServletMappings.defaultMapping()); assertThat(requestMatchers.get(0).matcher(request).isMatch()).isTrue(); request.setHttpServletMapping(TestMockHttpServletMappings.path(request, "/services")); @@ -334,9 +334,8 @@ public void matchesWhenDispatcherServletThenMvc() { servletContext.addServlet("path", Servlet.class).addMapping("/services/*"); MvcRequestMatcher mvc = mock(MvcRequestMatcher.class); AntPathRequestMatcher ant = mock(AntPathRequestMatcher.class); - DispatcherServletDelegatingRequestMatcher requestMatcher = new DispatcherServletDelegatingRequestMatcher(ant, - mvc, servletContext); - MockHttpServletRequest request = new MockHttpServletRequest("GET", "/services/endpoint"); + RequestMatcher requestMatcher = new DispatcherServletDelegatingRequestMatcher(ant, mvc); + MockHttpServletRequest request = new MockHttpServletRequest(servletContext, "GET", "/services/endpoint"); request.setHttpServletMapping(TestMockHttpServletMappings.defaultMapping()); assertThat(requestMatcher.matches(request)).isFalse(); verify(mvc).matches(request); @@ -354,9 +353,8 @@ public void matchesWhenNoMappingThenException() { servletContext.addServlet("path", Servlet.class).addMapping("/services/*"); MvcRequestMatcher mvc = mock(MvcRequestMatcher.class); AntPathRequestMatcher ant = mock(AntPathRequestMatcher.class); - DispatcherServletDelegatingRequestMatcher requestMatcher = new DispatcherServletDelegatingRequestMatcher(ant, - mvc, servletContext); - MockHttpServletRequest request = new MockHttpServletRequest("GET", "/services/endpoint"); + RequestMatcher requestMatcher = new DispatcherServletDelegatingRequestMatcher(ant, mvc); + MockHttpServletRequest request = new MockHttpServletRequest(servletContext, "GET", "/services/endpoint"); assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> requestMatcher.matcher(request)); } From 5ae3dd8e0f1fb2335d327abbdc602c1eddefc3fc Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Wed, 15 Jan 2025 16:38:51 -0700 Subject: [PATCH 071/132] Move Servlet Mocks to Web Issue gh-13551 Signed-off-by: Daeho Kwon --- .../annotation/web/AbstractRequestMatcherRegistryTests.java | 4 ++-- .../annotation/web/configurers/AuthorizeRequestsTests.java | 2 +- .../web/configurers/HttpSecuritySecurityMatchersTests.java | 2 +- .../web/configurers/UrlAuthorizationConfigurerTests.java | 2 +- .../security/config/test/SpringTestContext.java | 2 +- .../security/web/servlet}/MockServletContext.java | 2 +- .../security/web/servlet}/TestMockHttpServletMappings.java | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) rename {config/src/test/java/org/springframework/security/config => web/src/test/java/org/springframework/security/web/servlet}/MockServletContext.java (98%) rename {config/src/test/java/org/springframework/security/config => web/src/test/java/org/springframework/security/web/servlet}/TestMockHttpServletMappings.java (96%) diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistryTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistryTests.java index 1fb6e580b1..70f383c203 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistryTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistryTests.java @@ -31,12 +31,12 @@ import org.springframework.core.ResolvableType; import org.springframework.http.HttpMethod; import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.security.config.MockServletContext; import org.springframework.security.config.ObjectPostProcessor; -import org.springframework.security.config.TestMockHttpServletMappings; import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry.DispatcherServletDelegatingRequestMatcher; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; +import org.springframework.security.web.servlet.MockServletContext; +import org.springframework.security.web.servlet.TestMockHttpServletMappings; import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.DispatcherTypeRequestMatcher; diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeRequestsTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeRequestsTests.java index 00b67e17f9..65d7c13bea 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeRequestsTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeRequestsTests.java @@ -31,7 +31,6 @@ import org.springframework.security.access.hierarchicalroles.RoleHierarchy; import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.config.MockServletContext; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.core.authority.AuthorityUtils; @@ -42,6 +41,7 @@ import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.context.HttpSessionSecurityContextRepository; +import org.springframework.security.web.servlet.MockServletContext; import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.web.bind.annotation.RequestMapping; diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecuritySecurityMatchersTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecuritySecurityMatchersTests.java index 272f7ed26a..e5c080aefe 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecuritySecurityMatchersTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecuritySecurityMatchersTests.java @@ -32,7 +32,6 @@ import org.springframework.mock.web.MockFilterChain; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.config.MockServletContext; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.core.userdetails.User; @@ -42,6 +41,7 @@ import org.springframework.security.web.DefaultSecurityFilterChain; import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.servlet.MockServletContext; import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.test.util.ReflectionTestUtils; diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/UrlAuthorizationConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/UrlAuthorizationConfigurerTests.java index 83bac3b026..f98c86bbf2 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/UrlAuthorizationConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/UrlAuthorizationConfigurerTests.java @@ -31,7 +31,6 @@ import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.config.Customizer; -import org.springframework.security.config.MockServletContext; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.core.userdetails.PasswordEncodedUser; @@ -41,6 +40,7 @@ import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.servlet.MockServletContext; import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; diff --git a/config/src/test/java/org/springframework/security/config/test/SpringTestContext.java b/config/src/test/java/org/springframework/security/config/test/SpringTestContext.java index b165c20b60..800a3f45ae 100644 --- a/config/src/test/java/org/springframework/security/config/test/SpringTestContext.java +++ b/config/src/test/java/org/springframework/security/config/test/SpringTestContext.java @@ -29,8 +29,8 @@ import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor; import org.springframework.mock.web.MockServletConfig; import org.springframework.security.config.BeanIds; -import org.springframework.security.config.MockServletContext; import org.springframework.security.config.util.InMemoryXmlWebApplicationContext; +import org.springframework.security.web.servlet.MockServletContext; import org.springframework.test.context.web.GenericXmlWebContextLoader; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.RequestPostProcessor; diff --git a/config/src/test/java/org/springframework/security/config/MockServletContext.java b/web/src/test/java/org/springframework/security/web/servlet/MockServletContext.java similarity index 98% rename from config/src/test/java/org/springframework/security/config/MockServletContext.java rename to web/src/test/java/org/springframework/security/web/servlet/MockServletContext.java index d819d4c798..fff01a5f3b 100644 --- a/config/src/test/java/org/springframework/security/config/MockServletContext.java +++ b/web/src/test/java/org/springframework/security/web/servlet/MockServletContext.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.security.config; +package org.springframework.security.web.servlet; import java.util.Arrays; import java.util.Collection; diff --git a/config/src/test/java/org/springframework/security/config/TestMockHttpServletMappings.java b/web/src/test/java/org/springframework/security/web/servlet/TestMockHttpServletMappings.java similarity index 96% rename from config/src/test/java/org/springframework/security/config/TestMockHttpServletMappings.java rename to web/src/test/java/org/springframework/security/web/servlet/TestMockHttpServletMappings.java index 3f1f7f797b..16733d100b 100644 --- a/config/src/test/java/org/springframework/security/config/TestMockHttpServletMappings.java +++ b/web/src/test/java/org/springframework/security/web/servlet/TestMockHttpServletMappings.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.security.config; +package org.springframework.security.web.servlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.MappingMatch; From 39881a848232d545f807fc10b2c6fb13b6cab7c8 Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Wed, 15 Jan 2025 17:45:05 -0700 Subject: [PATCH 072/132] Fix MVC Documentation for Kotlin Closes gh-16426 Signed-off-by: Daeho Kwon --- .../ROOT/pages/servlet/integrations/mvc.adoc | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/docs/modules/ROOT/pages/servlet/integrations/mvc.adoc b/docs/modules/ROOT/pages/servlet/integrations/mvc.adoc index 92223407e2..49314e8ba8 100644 --- a/docs/modules/ROOT/pages/servlet/integrations/mvc.adoc +++ b/docs/modules/ROOT/pages/servlet/integrations/mvc.adoc @@ -199,12 +199,10 @@ We could add additional rules for all the permutations of Spring MVC, but this w Fortunately, when using the `requestMatchers` DSL method, Spring Security automatically creates a `MvcRequestMatcher` if it detects that Spring MVC is available in the classpath. Therefore, it will protect the same URLs that Spring MVC will match on by using Spring MVC to match on the URL. -One common requirement when using Spring MVC is to specify the servlet path property, for that you can use the `MvcRequestMatcher.Builder` to create multiple `MvcRequestMatcher` instances that share the same servlet path: +One common requirement when using Spring MVC is to specify the servlet path property. + +For Java-based Configuration, you can use the `MvcRequestMatcher.Builder` to create multiple `MvcRequestMatcher` instances that share the same servlet path: -[tabs] -====== -Java:: -+ [source,java,role="primary"] ---- @Bean @@ -219,32 +217,36 @@ public SecurityFilterChain filterChain(HttpSecurity http, HandlerMappingIntrospe } ---- +For Kotlin and XML, this happens when you specify the servlet path for each path like so: + +[tabs] +====== Kotlin:: + [source,kotlin,role="secondary"] ---- @Bean -open fun filterChain(http: HttpSecurity, introspector: HandlerMappingIntrospector): SecurityFilterChain { - val mvcMatcherBuilder = MvcRequestMatcher.Builder(introspector) +open fun filterChain(http: HttpSecurity): SecurityFilterChain { http { authorizeHttpRequests { - authorize(mvcMatcherBuilder.pattern("/admin"), hasRole("ADMIN")) - authorize(mvcMatcherBuilder.pattern("/user"), hasRole("USER")) + authorize("/admin/**", "/mvc", hasRole("ADMIN")) + authorize("/user/**", "/mvc", hasRole("USER")) } } return http.build() } ---- -====== -The following XML has the same effect: - -[source,xml] +Xml:: ++ +[source,xml, role="secondary"] ---- - + + ---- +====== [[mvc-authentication-principal]] == @AuthenticationPrincipal From 78baeb94dd6714960a2aa94d6f914bb5dcadec6a Mon Sep 17 00:00:00 2001 From: Max Batischev Date: Thu, 18 Apr 2024 01:10:10 +0300 Subject: [PATCH 073/132] Add support customizing redirect URI Closes gh-14778 Signed-off-by: Daeho Kwon --- ...ntInitiatedServerLogoutSuccessHandler.java | 94 +++++++++++++++++-- ...tiatedServerLogoutSuccessHandlerTests.java | 22 ++++- 2 files changed, 105 insertions(+), 11 deletions(-) diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/web/server/logout/OidcClientInitiatedServerLogoutSuccessHandler.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/web/server/logout/OidcClientInitiatedServerLogoutSuccessHandler.java index 3cc5754cac..9d35ddc69b 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/web/server/logout/OidcClientInitiatedServerLogoutSuccessHandler.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/web/server/logout/OidcClientInitiatedServerLogoutSuccessHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2024 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. @@ -23,6 +23,7 @@ import reactor.core.publisher.Mono; +import org.springframework.core.convert.converter.Converter; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; @@ -35,6 +36,7 @@ import org.springframework.security.web.server.authentication.logout.RedirectServerLogoutSuccessHandler; import org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler; import org.springframework.util.Assert; +import org.springframework.web.server.ServerWebExchange; import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; @@ -57,6 +59,8 @@ public class OidcClientInitiatedServerLogoutSuccessHandler implements ServerLogo private String postLogoutRedirectUri; + private Converter> redirectUriResolver = new DefaultRedirectUriResolver(); + /** * Constructs an {@link OidcClientInitiatedServerLogoutSuccessHandler} with the * provided parameters @@ -79,15 +83,10 @@ public Mono onLogoutSuccess(WebFilterExchange exchange, Authentication aut .map(OAuth2AuthenticationToken.class::cast) .map(OAuth2AuthenticationToken::getAuthorizedClientRegistrationId) .flatMap(this.clientRegistrationRepository::findByRegistrationId) - .flatMap((clientRegistration) -> { - URI endSessionEndpoint = endSessionEndpoint(clientRegistration); - if (endSessionEndpoint == null) { - return Mono.empty(); - } - String idToken = idToken(authentication); - String postLogoutRedirectUri = postLogoutRedirectUri(exchange.getExchange().getRequest(), clientRegistration); - return Mono.just(endpointUri(endSessionEndpoint, idToken, postLogoutRedirectUri)); - }) + .flatMap((clientRegistration) -> + this.redirectUriResolver.convert( + new RedirectUriParameters(exchange.getExchange(), authentication, clientRegistration)) + ) .switchIfEmpty( this.serverLogoutSuccessHandler.onLogoutSuccess(exchange, authentication).then(Mono.empty()) ) @@ -189,4 +188,79 @@ public void setLogoutSuccessUrl(URI logoutSuccessUrl) { this.serverLogoutSuccessHandler.setLogoutSuccessUrl(logoutSuccessUrl); } + /** + * Set the {@link Converter} that converts {@link RedirectUriParameters} to redirect + * URI + * @param redirectUriResolver {@link Converter} + * @since 6.5 + */ + public void setRedirectUriResolver(Converter> redirectUriResolver) { + Assert.notNull(redirectUriResolver, "redirectUriResolver cannot be null"); + this.redirectUriResolver = redirectUriResolver; + } + + /** + * Parameters, required for redirect URI resolving. + * + * @author Max Batischev + * @since 6.5 + */ + public static final class RedirectUriParameters { + + private final ServerWebExchange serverWebExchange; + + private final Authentication authentication; + + private final ClientRegistration clientRegistration; + + public RedirectUriParameters(ServerWebExchange serverWebExchange, Authentication authentication, + ClientRegistration clientRegistration) { + Assert.notNull(clientRegistration, "clientRegistration cannot be null"); + Assert.notNull(serverWebExchange, "serverWebExchange cannot be null"); + Assert.notNull(authentication, "authentication cannot be null"); + this.serverWebExchange = serverWebExchange; + this.authentication = authentication; + this.clientRegistration = clientRegistration; + } + + public ServerWebExchange getServerWebExchange() { + return this.serverWebExchange; + } + + public Authentication getAuthentication() { + return this.authentication; + } + + public ClientRegistration getClientRegistration() { + return this.clientRegistration; + } + + } + + /** + * Default {@link Converter} for redirect uri resolving. + * + * @since 6.5 + */ + private final class DefaultRedirectUriResolver implements Converter> { + + @Override + public Mono convert(RedirectUriParameters redirectUriParameters) { + // @formatter:off + return Mono.just(redirectUriParameters.authentication) + .flatMap((authentication) -> { + URI endSessionEndpoint = endSessionEndpoint(redirectUriParameters.clientRegistration); + if (endSessionEndpoint == null) { + return Mono.empty(); + } + String idToken = idToken(authentication); + String postLogoutRedirectUri = postLogoutRedirectUri( + redirectUriParameters.serverWebExchange.getRequest(), redirectUriParameters.clientRegistration); + return Mono.just(endpointUri(endSessionEndpoint, idToken, postLogoutRedirectUri)); + }); + // @formatter:on + } + + } + } diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/web/server/logout/OidcClientInitiatedServerLogoutSuccessHandlerTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/web/server/logout/OidcClientInitiatedServerLogoutSuccessHandlerTests.java index 300a815caf..591ef091da 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/web/server/logout/OidcClientInitiatedServerLogoutSuccessHandlerTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/web/server/logout/OidcClientInitiatedServerLogoutSuccessHandlerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2024 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. @@ -19,6 +19,7 @@ import java.io.IOException; import java.net.URI; import java.util.Collections; +import java.util.Objects; import jakarta.servlet.ServletException; import org.junit.jupiter.api.BeforeEach; @@ -199,6 +200,25 @@ public void setPostLogoutRedirectUriTemplateWhenGivenNullThenThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> this.handler.setPostLogoutRedirectUri((String) null)); } + @Test + public void logoutWhenCustomRedirectUriResolverSetThenRedirects() { + OAuth2AuthenticationToken token = new OAuth2AuthenticationToken(TestOidcUsers.create(), + AuthorityUtils.NO_AUTHORITIES, this.registration.getRegistrationId()); + WebFilterExchange filterExchange = new WebFilterExchange(this.exchange, this.chain); + given(this.exchange.getRequest()) + .willReturn(MockServerHttpRequest.get("/").queryParam("location", "https://test.com").build()); + // @formatter:off + this.handler.setRedirectUriResolver((params) -> Mono.just( + Objects.requireNonNull(params.getServerWebExchange() + .getRequest() + .getQueryParams() + .getFirst("location")))); + // @formatter:on + this.handler.onLogoutSuccess(filterExchange, token).block(); + + assertThat(redirectedUrl(this.exchange)).isEqualTo("https://test.com"); + } + private String redirectedUrl(ServerWebExchange exchange) { return exchange.getResponse().getHeaders().getFirst("Location"); } From 8a315b1d2f6be3931244fe153dafc42df716bb5e Mon Sep 17 00:00:00 2001 From: Tran Ngoc Nhan Date: Wed, 15 Jan 2025 04:49:01 +0700 Subject: [PATCH 074/132] Fix broken link Signed-off-by: Tran Ngoc Nhan Signed-off-by: Daeho Kwon --- .../modules/ROOT/pages/servlet/test/mockmvc/authentication.adoc | 2 +- docs/modules/ROOT/pages/servlet/test/mockmvc/setup.adoc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/modules/ROOT/pages/servlet/test/mockmvc/authentication.adoc b/docs/modules/ROOT/pages/servlet/test/mockmvc/authentication.adoc index 3b7f2e2bf2..c7dcc920a6 100644 --- a/docs/modules/ROOT/pages/servlet/test/mockmvc/authentication.adoc +++ b/docs/modules/ROOT/pages/servlet/test/mockmvc/authentication.adoc @@ -5,7 +5,7 @@ It is often desirable to run tests as a specific user. There are two simple ways to populate the user: * <> -* <> +* <> [[test-mockmvc-securitycontextholder-rpp]] == Running as a User in Spring MVC Test with RequestPostProcessor diff --git a/docs/modules/ROOT/pages/servlet/test/mockmvc/setup.adoc b/docs/modules/ROOT/pages/servlet/test/mockmvc/setup.adoc index ed3bd1af17..c77a95cf92 100644 --- a/docs/modules/ROOT/pages/servlet/test/mockmvc/setup.adoc +++ b/docs/modules/ROOT/pages/servlet/test/mockmvc/setup.adoc @@ -7,7 +7,7 @@ Spring Security's testing support requires spring-test-4.1.3.RELEASE or greater. ==== To use Spring Security with Spring MVC Test, add the Spring Security `FilterChainProxy` as a `Filter`. -You also need to add Spring Security's `TestSecurityContextHolderPostProcessor` to support xref:servlet/test/mockmvc/setup.adoc#test-mockmvc-withmockuser[Running as a User in Spring MVC Test with Annotations]. +You also need to add Spring Security's `TestSecurityContextHolderPostProcessor` to support xref:servlet/test/mockmvc/authentication.adoc#test-mockmvc-withmockuser[Running as a User in Spring MVC Test with Annotations]. To do so, use Spring Security's `SecurityMockMvcConfigurers.springSecurity()`: [tabs] From 4fe1bbb0177486e3d3d197ab86c79df2c1f2914d Mon Sep 17 00:00:00 2001 From: DingHao Date: Thu, 16 Jan 2025 14:25:49 +0800 Subject: [PATCH 075/132] Use spring.security prefix instead of security.security Closes gh-16422 Signed-off-by: DingHao Signed-off-by: Daeho Kwon --- .../security/web/ObservationFilterChainDecorator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/main/java/org/springframework/security/web/ObservationFilterChainDecorator.java b/web/src/main/java/org/springframework/security/web/ObservationFilterChainDecorator.java index ed24f19f91..6578d8dcb5 100644 --- a/web/src/main/java/org/springframework/security/web/ObservationFilterChainDecorator.java +++ b/web/src/main/java/org/springframework/security/web/ObservationFilterChainDecorator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2025 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. @@ -554,7 +554,7 @@ static final class FilterChainObservationConvention private static final String CHAIN_SIZE_NAME = "spring.security.filterchain.size"; - private static final String FILTER_SECTION_NAME = "security.security.reached.filter.section"; + private static final String FILTER_SECTION_NAME = "spring.security.reached.filter.section"; private static final String FILTER_NAME = "spring.security.reached.filter.name"; From a8184a6197ebcbd9d4ee6212d876100a2ce84943 Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Thu, 16 Jan 2025 14:30:01 -0700 Subject: [PATCH 076/132] Add Breaking Change Section for 6.5 Issue gh-16422 Signed-off-by: Daeho Kwon --- docs/modules/ROOT/pages/whats-new.adoc | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/modules/ROOT/pages/whats-new.adoc b/docs/modules/ROOT/pages/whats-new.adoc index 773ad5f4f5..c6f44092c2 100644 --- a/docs/modules/ROOT/pages/whats-new.adoc +++ b/docs/modules/ROOT/pages/whats-new.adoc @@ -3,3 +3,10 @@ Spring Security 6.5 provides a number of new features. Below are the highlights of the release, or you can view https://github.com/spring-projects/spring-security/releases[the release notes] for a detailed listing of each feature and bug fix. + +== Breaking Changes + +=== Observability + +The `security.security.reached.filter.section` key name was corrected to `spring.security.reached.filter.section`. +Note that this may affect reports that operate on this key name. From 013649d00fc3f83f8f2589c66a5c292c4b0d13a2 Mon Sep 17 00:00:00 2001 From: Max Batischev Date: Tue, 14 Jan 2025 11:29:36 +0300 Subject: [PATCH 077/132] Fixed typo in WebAuthnDsl Signed-off-by: Max Batischev Signed-off-by: Daeho Kwon --- .../security/config/annotation/web/WebAuthnDsl.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/src/main/kotlin/org/springframework/security/config/annotation/web/WebAuthnDsl.kt b/config/src/main/kotlin/org/springframework/security/config/annotation/web/WebAuthnDsl.kt index f1a9600f00..41518ed191 100644 --- a/config/src/main/kotlin/org/springframework/security/config/annotation/web/WebAuthnDsl.kt +++ b/config/src/main/kotlin/org/springframework/security/config/annotation/web/WebAuthnDsl.kt @@ -23,7 +23,7 @@ import org.springframework.security.config.annotation.web.configurers.WebAuthnCo * A Kotlin DSL to configure [HttpSecurity] webauthn using idiomatic Kotlin code. * @property rpName the relying party name * @property rpId the relying party id - * @property the allowed origins + * @property allowedOrigins allowed origins * @property disableDefaultRegistrationPage disable default webauthn registration page * @since 6.4 * @author Rob Winch From 863b0e8932555e2fc9ed0710ecaa986dddfb9493 Mon Sep 17 00:00:00 2001 From: 2-say Date: Fri, 17 Jan 2025 04:25:24 +0900 Subject: [PATCH 078/132] Suggest replacing size() == 0 with isEmpty() for collection check Consider using isEmpty() instead of size() == 0 to improve code readability and follow modern Java practices. Signed-off-by: 2-say Signed-off-by: Daeho Kwon --- .../org/springframework/security/core/ComparableVersion.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/springframework/security/core/ComparableVersion.java b/core/src/main/java/org/springframework/security/core/ComparableVersion.java index 347644734c..f635e4933c 100644 --- a/core/src/main/java/org/springframework/security/core/ComparableVersion.java +++ b/core/src/main/java/org/springframework/security/core/ComparableVersion.java @@ -414,7 +414,7 @@ public int getType() { @Override public boolean isNull() { - return (size() == 0); + return isEmpty(); } void normalize() { @@ -434,7 +434,7 @@ else if (!(lastItem instanceof ListItem)) { @Override public int compareTo(Item item) { if (item == null) { - if (size() == 0) { + if (isEmpty()) { return 0; // 1-0 = 1- (normalize) = 1 } Item first = get(0); From aca3b642352bf771d7a9611bb66649db5a1290d9 Mon Sep 17 00:00:00 2001 From: Marco Haase Date: Tue, 14 Jan 2025 16:32:08 +0100 Subject: [PATCH 079/132] Fix broken link to MockMvc documentation Link to Test chapter of Spring Framework documentation is broken, this commit fixes it. Signed-off-by: Marco Haase Signed-off-by: Daeho Kwon --- docs/modules/ROOT/pages/servlet/test/mockmvc/index.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/servlet/test/mockmvc/index.adoc b/docs/modules/ROOT/pages/servlet/test/mockmvc/index.adoc index 49d97c46a6..997f9db7c5 100644 --- a/docs/modules/ROOT/pages/servlet/test/mockmvc/index.adoc +++ b/docs/modules/ROOT/pages/servlet/test/mockmvc/index.adoc @@ -2,4 +2,4 @@ = Spring MVC Test Integration :page-section-summary-toc: 1 -Spring Security provides comprehensive integration with https://docs.spring.io/spring/docs/current/spring-framework-reference/html/testing.html#spring-mvc-test-framework[Spring MVC Test] +Spring Security provides comprehensive integration with https://docs.spring.io/spring-framework/reference/testing/mockmvc.html[Spring Testing MockMVC] From 30a64a563ae7906731752f58734f9660d028be15 Mon Sep 17 00:00:00 2001 From: Tran Ngoc Nhan Date: Fri, 20 Dec 2024 00:54:14 +0700 Subject: [PATCH 080/132] Encode Introspection clientId and clientSecret Closes gh-15988 Signed-off-by: Tran Ngoc Nhan Signed-off-by: Daeho Kwon --- .../SpringOpaqueTokenIntrospector.java | 79 +++++++++++++++++- ...SpringReactiveOpaqueTokenIntrospector.java | 80 ++++++++++++++++++- .../SpringOpaqueTokenIntrospectorTests.java | 46 ++++++++++- ...gReactiveOpaqueTokenIntrospectorTests.java | 48 ++++++++++- 4 files changed, 245 insertions(+), 8 deletions(-) diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/SpringOpaqueTokenIntrospector.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/SpringOpaqueTokenIntrospector.java index 4674ab7886..ef9b4f3309 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/SpringOpaqueTokenIntrospector.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/SpringOpaqueTokenIntrospector.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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,6 +18,8 @@ import java.io.Serial; import java.net.URI; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; @@ -77,9 +79,11 @@ public class SpringOpaqueTokenIntrospector implements OpaqueTokenIntrospector { /** * Creates a {@code OpaqueTokenAuthenticationProvider} with the provided parameters * @param introspectionUri The introspection endpoint uri - * @param clientId The client id authorized to introspect - * @param clientSecret The client's secret + * @param clientId The URL-encoded client id authorized to introspect + * @param clientSecret The URL-encoded client secret authorized to introspect + * @deprecated Please use {@link SpringOpaqueTokenIntrospector.Builder} */ + @Deprecated(since = "6.5", forRemoval = true) public SpringOpaqueTokenIntrospector(String introspectionUri, String clientId, String clientSecret) { Assert.notNull(introspectionUri, "introspectionUri cannot be null"); Assert.notNull(clientId, "clientId cannot be null"); @@ -269,6 +273,18 @@ private Collection authorities(List scopes) { return authorities; } + /** + * Creates a {@code SpringOpaqueTokenIntrospector.Builder} with the given + * introspection endpoint uri + * @param introspectionUri The introspection endpoint uri + * @return the {@link SpringOpaqueTokenIntrospector.Builder} + * @since 6.5 + */ + public static Builder withIntrospectionUri(String introspectionUri) { + Assert.notNull(introspectionUri, "introspectionUri cannot be null"); + return new Builder(introspectionUri); + } + // gh-7563 private static final class ArrayListFromString extends ArrayList { @@ -295,4 +311,61 @@ default List getScopes() { } + /** + * Used to build {@link SpringOpaqueTokenIntrospector}. + * + * @author Ngoc Nhan + * @since 6.5 + */ + public static final class Builder { + + private final String introspectionUri; + + private String clientId; + + private String clientSecret; + + private Builder(String introspectionUri) { + this.introspectionUri = introspectionUri; + } + + /** + * The builder will {@link URLEncoder encode} the client id that you provide, so + * please give the unencoded value. + * @param clientId The unencoded client id + * @return the {@link SpringOpaqueTokenIntrospector.Builder} + * @since 6.5 + */ + public Builder clientId(String clientId) { + Assert.notNull(clientId, "clientId cannot be null"); + this.clientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8); + return this; + } + + /** + * The builder will {@link URLEncoder encode} the client secret that you provide, + * so please give the unencoded value. + * @param clientSecret The unencoded client secret + * @return the {@link SpringOpaqueTokenIntrospector.Builder} + * @since 6.5 + */ + public Builder clientSecret(String clientSecret) { + Assert.notNull(clientSecret, "clientSecret cannot be null"); + this.clientSecret = URLEncoder.encode(clientSecret, StandardCharsets.UTF_8); + return this; + } + + /** + * Creates a {@code SpringOpaqueTokenIntrospector} + * @return the {@link SpringOpaqueTokenIntrospector} + * @since 6.5 + */ + public SpringOpaqueTokenIntrospector build() { + RestTemplate restTemplate = new RestTemplate(); + restTemplate.getInterceptors().add(new BasicAuthenticationInterceptor(this.clientId, this.clientSecret)); + return new SpringOpaqueTokenIntrospector(this.introspectionUri, restTemplate); + } + + } + } diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/SpringReactiveOpaqueTokenIntrospector.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/SpringReactiveOpaqueTokenIntrospector.java index 7c6bf8ecb0..283317f95e 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/SpringReactiveOpaqueTokenIntrospector.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/SpringReactiveOpaqueTokenIntrospector.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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,6 +18,8 @@ import java.io.Serial; import java.net.URI; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; @@ -72,9 +74,11 @@ public class SpringReactiveOpaqueTokenIntrospector implements ReactiveOpaqueToke * Creates a {@code OpaqueTokenReactiveAuthenticationManager} with the provided * parameters * @param introspectionUri The introspection endpoint uri - * @param clientId The client id authorized to introspect - * @param clientSecret The client secret for the authorized client + * @param clientId The URL-encoded client id authorized to introspect + * @param clientSecret The URL-encoded client secret authorized to introspect + * @deprecated Please use {@link SpringReactiveOpaqueTokenIntrospector.Builder} */ + @Deprecated(since = "6.5", forRemoval = true) public SpringReactiveOpaqueTokenIntrospector(String introspectionUri, String clientId, String clientSecret) { Assert.hasText(introspectionUri, "introspectionUri cannot be empty"); Assert.hasText(clientId, "clientId cannot be empty"); @@ -223,6 +227,18 @@ private Collection authorities(List scopes) { return authorities; } + /** + * Creates a {@code SpringReactiveOpaqueTokenIntrospector.Builder} with the given + * introspection endpoint uri + * @param introspectionUri The introspection endpoint uri + * @return the {@link SpringReactiveOpaqueTokenIntrospector.Builder} + * @since 6.5 + */ + public static Builder withIntrospectionUri(String introspectionUri) { + + return new Builder(introspectionUri); + } + // gh-7563 private static final class ArrayListFromString extends ArrayList { @@ -249,4 +265,62 @@ default List getScopes() { } + /** + * Used to build {@link SpringReactiveOpaqueTokenIntrospector}. + * + * @author Ngoc Nhan + * @since 6.5 + */ + public static final class Builder { + + private final String introspectionUri; + + private String clientId; + + private String clientSecret; + + private Builder(String introspectionUri) { + this.introspectionUri = introspectionUri; + } + + /** + * The builder will {@link URLEncoder encode} the client id that you provide, so + * please give the unencoded value. + * @param clientId The unencoded client id + * @return the {@link SpringReactiveOpaqueTokenIntrospector.Builder} + * @since 6.5 + */ + public Builder clientId(String clientId) { + Assert.notNull(clientId, "clientId cannot be null"); + this.clientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8); + return this; + } + + /** + * The builder will {@link URLEncoder encode} the client secret that you provide, + * so please give the unencoded value. + * @param clientSecret The unencoded client secret + * @return the {@link SpringReactiveOpaqueTokenIntrospector.Builder} + * @since 6.5 + */ + public Builder clientSecret(String clientSecret) { + Assert.notNull(clientSecret, "clientSecret cannot be null"); + this.clientSecret = URLEncoder.encode(clientSecret, StandardCharsets.UTF_8); + return this; + } + + /** + * Creates a {@code SpringReactiveOpaqueTokenIntrospector} + * @return the {@link SpringReactiveOpaqueTokenIntrospector} + * @since 6.5 + */ + public SpringReactiveOpaqueTokenIntrospector build() { + WebClient webClient = WebClient.builder() + .defaultHeaders((h) -> h.setBasicAuth(this.clientId, this.clientSecret)) + .build(); + return new SpringReactiveOpaqueTokenIntrospector(this.introspectionUri, webClient); + } + + } + } diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/SpringOpaqueTokenIntrospectorTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/SpringOpaqueTokenIntrospectorTests.java index 01555f01fd..32afbf6798 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/SpringOpaqueTokenIntrospectorTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/SpringOpaqueTokenIntrospectorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -339,6 +339,50 @@ public void setAuthenticationConverterWhenNonNullConverterGivenThenConverterUsed verify(authenticationConverter).convert(any()); } + @Test + public void introspectWithoutEncodeClientCredentialsThenExceptionIsThrown() throws Exception { + try (MockWebServer server = new MockWebServer()) { + String response = """ + { + "active": true, + "username": "client%&1" + } + """; + server.setDispatcher(requiresAuth("client%25%261", "secret%40%242", response)); + String introspectUri = server.url("/introspect").toString(); + OpaqueTokenIntrospector introspectionClient = new SpringOpaqueTokenIntrospector(introspectUri, "client%&1", + "secret@$2"); + assertThatExceptionOfType(OAuth2IntrospectionException.class) + .isThrownBy(() -> introspectionClient.introspect("token")); + } + } + + @Test + public void introspectWithEncodeClientCredentialsThenOk() throws Exception { + try (MockWebServer server = new MockWebServer()) { + String response = """ + { + "active": true, + "username": "client&1" + } + """; + server.setDispatcher(requiresAuth("client%261", "secret%40%242", response)); + String introspectUri = server.url("/introspect").toString(); + OpaqueTokenIntrospector introspectionClient = SpringOpaqueTokenIntrospector + .withIntrospectionUri(introspectUri) + .clientId("client&1") + .clientSecret("secret@$2") + .build(); + OAuth2AuthenticatedPrincipal authority = introspectionClient.introspect("token"); + // @formatter:off + assertThat(authority.getAttributes()) + .isNotNull() + .containsEntry(OAuth2TokenIntrospectionClaimNames.ACTIVE, true) + .containsEntry(OAuth2TokenIntrospectionClaimNames.USERNAME, "client&1"); + // @formatter:on + } + } + private static ResponseEntity> response(String content) { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/SpringReactiveOpaqueTokenIntrospectorTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/SpringReactiveOpaqueTokenIntrospectorTests.java index ae0f01afd7..8fe1298360 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/SpringReactiveOpaqueTokenIntrospectorTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/SpringReactiveOpaqueTokenIntrospectorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -261,6 +261,52 @@ public void constructorWhenRestOperationsIsNullThenIllegalArgumentException() { .isThrownBy(() -> new SpringReactiveOpaqueTokenIntrospector(INTROSPECTION_URL, null)); } + @Test + public void introspectWithoutEncodeClientCredentialsThenExceptionIsThrown() throws Exception { + try (MockWebServer server = new MockWebServer()) { + String response = """ + { + "active": true, + "username": "client%&1" + } + """; + server.setDispatcher(requiresAuth("client%25%261", "secret%40%242", response)); + String introspectUri = server.url("/introspect").toString(); + ReactiveOpaqueTokenIntrospector introspectionClient = new SpringReactiveOpaqueTokenIntrospector( + introspectUri, "client%&1", "secret@$2"); + // @formatter:off + assertThatExceptionOfType(OAuth2IntrospectionException.class) + .isThrownBy(() -> introspectionClient.introspect("token").block()); + // @formatter:on + } + } + + @Test + public void introspectWithEncodeClientCredentialsThenOk() throws Exception { + try (MockWebServer server = new MockWebServer()) { + String response = """ + { + "active": true, + "username": "client&1" + } + """; + server.setDispatcher(requiresAuth("client%261", "secret%40%242", response)); + String introspectUri = server.url("/introspect").toString(); + ReactiveOpaqueTokenIntrospector introspectionClient = SpringReactiveOpaqueTokenIntrospector + .withIntrospectionUri(introspectUri) + .clientId("client&1") + .clientSecret("secret@$2") + .build(); + OAuth2AuthenticatedPrincipal authority = introspectionClient.introspect("token").block(); + // @formatter:off + assertThat(authority.getAttributes()) + .isNotNull() + .containsEntry(OAuth2TokenIntrospectionClaimNames.ACTIVE, true) + .containsEntry(OAuth2TokenIntrospectionClaimNames.USERNAME, "client&1"); + // @formatter:on + } + } + private WebClient mockResponse(String response) { return mockResponse(toMap(response)); } From f6a9d0d94d77a43804a6ff57e20066eda5baa98e Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Thu, 16 Jan 2025 16:46:46 -0700 Subject: [PATCH 081/132] Update OpaqueTokenIntrospector Documentation Issue gh-15988 Signed-off-by: Daeho Kwon --- .../oauth2/resource-server/opaque-token.adoc | 48 ++++++++++++------ .../oauth2/resource-server/opaque-token.adoc | 49 ++++++++++++------- 2 files changed, 64 insertions(+), 33 deletions(-) diff --git a/docs/modules/ROOT/pages/reactive/oauth2/resource-server/opaque-token.adoc b/docs/modules/ROOT/pages/reactive/oauth2/resource-server/opaque-token.adoc index ccdcbf5e9a..3cbda22e30 100644 --- a/docs/modules/ROOT/pages/reactive/oauth2/resource-server/opaque-token.adoc +++ b/docs/modules/ROOT/pages/reactive/oauth2/resource-server/opaque-token.adoc @@ -273,7 +273,8 @@ Java:: ---- @Bean public ReactiveOpaqueTokenIntrospector introspector() { - return new NimbusReactiveOpaqueTokenIntrospector(introspectionUri, clientId, clientSecret); + return SpringReactiveOpaqueTokenIntrospector.withIntrospectionUri(introspectionUri) + .clientId(clientId).clientSecret(clientSecret).build(); } ---- @@ -283,7 +284,8 @@ Kotlin:: ---- @Bean fun introspector(): ReactiveOpaqueTokenIntrospector { - return NimbusReactiveOpaqueTokenIntrospector(introspectionUri, clientId, clientSecret) + return SpringReactiveOpaqueTokenIntrospector.withIntrospectionUri(introspectionUri) + .clientId(clientId).clientSecret(clientSecret).build() } ---- ====== @@ -411,7 +413,8 @@ Java:: ---- @Bean public ReactiveOpaqueTokenIntrospector introspector() { - return new NimbusReactiveOpaqueTokenIntrospector(introspectionUri, clientId, clientSecret); + return SpringReactiveOpaqueTokenIntrospector.withIntrospectionUri(introspectionUri) + .clientId(clientId).clientSecret(clientSecret).build() } ---- @@ -421,7 +424,8 @@ Kotlin:: ---- @Bean fun introspector(): ReactiveOpaqueTokenIntrospector { - return NimbusReactiveOpaqueTokenIntrospector(introspectionUri, clientId, clientSecret) + return SpringReactiveOpaqueTokenIntrospector.withIntrospectionUri(introspectionUri) + .clientId(clientId).clientSecret(clientSecret).build() } ---- ====== @@ -534,8 +538,9 @@ Java:: [source,java,role="primary"] ---- public class CustomAuthoritiesOpaqueTokenIntrospector implements ReactiveOpaqueTokenIntrospector { - private ReactiveOpaqueTokenIntrospector delegate = - new NimbusReactiveOpaqueTokenIntrospector("https://idp.example.org/introspect", "client", "secret"); + private ReactiveOpaqueTokenIntrospector delegate = SpringReactiveOpaqueTokenIntrospector + .withIntrospectionUri("https://idp.example.org/introspect") + .clientId("client").clientSecret("secret").build(); public Mono introspect(String token) { return this.delegate.introspect(token) @@ -557,7 +562,9 @@ Kotlin:: [source,kotlin,role="secondary"] ---- class CustomAuthoritiesOpaqueTokenIntrospector : ReactiveOpaqueTokenIntrospector { - private val delegate: ReactiveOpaqueTokenIntrospector = NimbusReactiveOpaqueTokenIntrospector("https://idp.example.org/introspect", "client", "secret") + private val delegate: ReactiveOpaqueTokenIntrospector = SpringReactiveOpaqueTokenIntrospector + .withIntrospectionUri("https://idp.example.org/introspect") + .clientId("client").clientSecret("secret").build() override fun introspect(token: String): Mono { return delegate.introspect(token) .map { principal: OAuth2AuthenticatedPrincipal -> @@ -637,8 +644,9 @@ Java:: [source,java,role="primary"] ---- public class JwtOpaqueTokenIntrospector implements ReactiveOpaqueTokenIntrospector { - private ReactiveOpaqueTokenIntrospector delegate = - new NimbusReactiveOpaqueTokenIntrospector("https://idp.example.org/introspect", "client", "secret"); + private ReactiveOpaqueTokenIntrospector delegate = SpringReactiveOpaqueTokenIntrospector + .withIntrospectionUri("https://idp.example.org/introspect") + .clientId("client").clientSecret("secret").build(); private ReactiveJwtDecoder jwtDecoder = new NimbusReactiveJwtDecoder(new ParseOnlyJWTProcessor()); public Mono introspect(String token) { @@ -664,7 +672,9 @@ Kotlin:: [source,kotlin,role="secondary"] ---- class JwtOpaqueTokenIntrospector : ReactiveOpaqueTokenIntrospector { - private val delegate: ReactiveOpaqueTokenIntrospector = NimbusReactiveOpaqueTokenIntrospector("https://idp.example.org/introspect", "client", "secret") + private val delegate: ReactiveOpaqueTokenIntrospector = SpringReactiveOpaqueTokenIntrospector + .withIntrospectionUri("https://idp.example.org/introspect") + .clientId("client").clientSecret("secret").build() private val jwtDecoder: ReactiveJwtDecoder = NimbusReactiveJwtDecoder(ParseOnlyJWTProcessor()) override fun introspect(token: String): Mono { return delegate.introspect(token) @@ -731,8 +741,9 @@ Java:: [source,java,role="primary"] ---- public class UserInfoOpaqueTokenIntrospector implements ReactiveOpaqueTokenIntrospector { - private final ReactiveOpaqueTokenIntrospector delegate = - new NimbusReactiveOpaqueTokenIntrospector("https://idp.example.org/introspect", "client", "secret"); + private final ReactiveOpaqueTokenIntrospector delegate = SpringReactiveOpaqueTokenIntrospector + .withIntrospectionUri("https://idp.example.org/introspect") + .clientId("client").clientSecret("secret").build(); private final ReactiveOAuth2UserService oauth2UserService = new DefaultReactiveOAuth2UserService(); @@ -761,7 +772,9 @@ Kotlin:: [source,kotlin,role="secondary"] ---- class UserInfoOpaqueTokenIntrospector : ReactiveOpaqueTokenIntrospector { - private val delegate: ReactiveOpaqueTokenIntrospector = NimbusReactiveOpaqueTokenIntrospector("https://idp.example.org/introspect", "client", "secret") + private val delegate: ReactiveOpaqueTokenIntrospector = SpringReactiveOpaqueTokenIntrospector + .withIntrospectionUri("https://idp.example.org/introspect") + .clientId("client").clientSecret("secret").build() private val oauth2UserService: ReactiveOAuth2UserService = DefaultReactiveOAuth2UserService() private val repository: ReactiveClientRegistrationRepository? = null @@ -792,8 +805,9 @@ Java:: [source,java,role="primary"] ---- public class UserInfoOpaqueTokenIntrospector implements ReactiveOpaqueTokenIntrospector { - private final ReactiveOpaqueTokenIntrospector delegate = - new NimbusReactiveOpaqueTokenIntrospector("https://idp.example.org/introspect", "client", "secret"); + private final ReactiveOpaqueTokenIntrospector delegate = SpringReactiveOpaqueTokenIntrospector + .withIntrospectionUri("https://idp.example.org/introspect") + .clientId("client").clientSecret("secret").build(); private final WebClient rest = WebClient.create(); @Override @@ -809,7 +823,9 @@ Kotlin:: [source,kotlin,role="secondary"] ---- class UserInfoOpaqueTokenIntrospector : ReactiveOpaqueTokenIntrospector { - private val delegate: ReactiveOpaqueTokenIntrospector = NimbusReactiveOpaqueTokenIntrospector("https://idp.example.org/introspect", "client", "secret") + private val delegate: ReactiveOpaqueTokenIntrospector = SpringReactiveOpaqueTokenIntrospector + .withIntrospectionUri("https://idp.example.org/introspect") + .clientId("client").clientSecret("secret").build() private val rest: WebClient = WebClient.create() override fun introspect(token: String): Mono { diff --git a/docs/modules/ROOT/pages/servlet/oauth2/resource-server/opaque-token.adoc b/docs/modules/ROOT/pages/servlet/oauth2/resource-server/opaque-token.adoc index dfc2ed4733..2798fd2206 100644 --- a/docs/modules/ROOT/pages/servlet/oauth2/resource-server/opaque-token.adoc +++ b/docs/modules/ROOT/pages/servlet/oauth2/resource-server/opaque-token.adoc @@ -307,7 +307,8 @@ Java:: ---- @Bean public OpaqueTokenIntrospector introspector() { - return new NimbusOpaqueTokenIntrospector(introspectionUri, clientId, clientSecret); + return SpringOpaqueTokenIntrospector.withIntrospectionUri(introspectionUri) + .clientId(clientId).clientSecret(clientSecret).build(); } ---- @@ -317,7 +318,8 @@ Kotlin:: ---- @Bean fun introspector(): OpaqueTokenIntrospector { - return NimbusOpaqueTokenIntrospector(introspectionUri, clientId, clientSecret) + return SpringOpaqueTokenIntrospector.withIntrospectionUri(introspectionUri) + .clientId(clientId).clientSecret(clientSecret).build() } ---- ====== @@ -532,7 +534,8 @@ Or, exposing a < Date: Fri, 17 Jan 2025 03:11:18 +0000 Subject: [PATCH 082/132] Bump org.springframework:spring-framework-bom from 6.2.1 to 6.2.2 Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 6.2.1 to 6.2.2. - [Release notes](https://github.com/spring-projects/spring-framework/releases) - [Commits](https://github.com/spring-projects/spring-framework/compare/v6.2.1...v6.2.2) --- updated-dependencies: - dependency-name: org.springframework:spring-framework-bom dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: Daeho Kwon --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6e78f6f50c..e6ebda5c13 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,7 +14,7 @@ org-jetbrains-kotlinx = "1.10.1" org-mockito = "5.15.2" org-opensaml = "4.3.2" org-opensaml5 = "5.1.2" -org-springframework = "6.2.1" +org-springframework = "6.2.2" [libraries] ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.5.16" From 461d4e6edaf556de5991e571c68c6e8c20c8d243 Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Thu, 16 Jan 2025 20:01:47 -0700 Subject: [PATCH 083/132] Document OpaqueTokenIntrospector Migration Issue gh-15988 Signed-off-by: Daeho Kwon --- .../ROOT/pages/migration/authentication.adoc | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 docs/modules/ROOT/pages/migration/authentication.adoc diff --git a/docs/modules/ROOT/pages/migration/authentication.adoc b/docs/modules/ROOT/pages/migration/authentication.adoc new file mode 100644 index 0000000000..9c5407ae00 --- /dev/null +++ b/docs/modules/ROOT/pages/migration/authentication.adoc @@ -0,0 +1,68 @@ += Authentication Changes + +== Opaque Token Credentials Will Be Encoded For You + +In order to comply more closely with the Introspection RFC, Spring Security's opaque token support will encode the client id and secret before creating the authorization header. +This change means you will no longer have to encode the client id and secret yourself. + +If your client id or secret contain URL-unsafe characters, then you can prepare yourself for this change by doing the following: + +=== Replace Usage of `introspectionClientCredentials` + +Since Spring Security can now do the encoding for you, replace xref:servlet/oauth2/resource-server/opaque-token.adoc#oauth2resourceserver-opaque-introspectionuri-dsl[using `introspectionClientCredentials`] with publishing the following `@Bean`: + +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +@Bean +OpaqueTokenIntrospector introspector() { + return SpringOpaqueTokenIntrospector.withIntrospectionUri(introspectionUri) + .clientId(unencodedClientId).clientSecret(unencodedClientSecret).build(); +} +---- + +Kotlin:: ++ +[source,kotlin,role="secondary"] +---- +@Bean +fun introspector(): OpaqueTokenIntrospector { + return SpringOpaqueTokenIntrospector.withIntrospectionUri(introspectionUri) + .clientId(unencodedClientId).clientSecret(unencodedClientSecret).build() +} +---- +====== + +The above will be the default in 7.0. + +If this setting gives you trouble or you cannot apply it for now, you can use the `RestOperations` constructor instead: + +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +@Bean +OpaqueTokenIntrospector introspector() { + RestTemplate rest = new RestTemplate(); + rest.addInterceptor(new BasicAuthenticationInterceptor(encodedClientId, encodedClientSecret)); + return new SpringOpaqueTokenIntrospector(introspectionUri, rest); +} +---- + +Kotlin:: ++ +[source,kotlin,role="secondary"] +---- +@Bean +fun introspector(): OpaqueTokenIntrospector { + val rest = RestTemplate() + rest.addInterceptor(BasicAuthenticationInterceptor(encodedClientId, encodedClientSecret)) + return SpringOpaqueTokenIntrospector(introspectionUri, rest) +} +---- +====== From 1fb2e459b805f5d54fb2f82470682000cf0e6aa6 Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Fri, 17 Jan 2025 08:45:30 -0600 Subject: [PATCH 084/132] Case insenstive Signed-off-by: Daeho Kwon --- git/hooks/forward-merge | 2 +- git/hooks/prepare-forward-merge | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/git/hooks/forward-merge b/git/hooks/forward-merge index 322bdade8f..37912fc8b3 100755 --- a/git/hooks/forward-merge +++ b/git/hooks/forward-merge @@ -26,7 +26,7 @@ def find_forward_merges(message_file) forward_merges = [] message.each_line do |line| $log.debug "Checking #{line} for message" - match = /^(?:Fixes|Closes) gh-(\d+) in (\d\.\d\.[\dx](?:[\.\-](?:M|RC)\d)?)$/.match(line) + match = /^(?:Fixes|Closes) gh-(\d+) in (\d\.\d\.[\dx](?:[\.\-](?:M|RC)\d)?)$/i.match(line) if match then issue = match[1] milestone = match[2] diff --git a/git/hooks/prepare-forward-merge b/git/hooks/prepare-forward-merge index f2d019008d..c0134f2c31 100755 --- a/git/hooks/prepare-forward-merge +++ b/git/hooks/prepare-forward-merge @@ -18,7 +18,7 @@ def get_fixed_issues() message = `git log -1 --pretty=%B #{rev}` message.each_line do |line| $log.debug "Checking #{line} for message" - fixed << line.strip if /^(?:Fixes|Closes) gh-(\d+)/.match(line) + fixed << line.strip if /^(?:Fixes|Closes) gh-(\d+)/i.match(line) end $log.debug "Found fixed issues #{fixed}" return fixed; From 8a67d79662608b1642c1d1d896af5af0a1acf075 Mon Sep 17 00:00:00 2001 From: Daniel Garnier-Moiroux Date: Fri, 17 Jan 2025 15:07:24 +0100 Subject: [PATCH 085/132] webauthn: ensure allowCredentials[].id is an ArrayBuffer closes gh-16439 Signed-off-by: Daniel Garnier-Moiroux Signed-off-by: Daeho Kwon --- javascript/lib/webauthn-core.js | 8 ++++++++ javascript/test/webauthn-core.test.js | 16 ++++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/javascript/lib/webauthn-core.js b/javascript/lib/webauthn-core.js index b4c26d08f0..e2cdc0148d 100644 --- a/javascript/lib/webauthn-core.js +++ b/javascript/lib/webauthn-core.js @@ -41,8 +41,16 @@ async function authenticate(headers, contextPath, useConditionalMediation) { } // FIXME: Use https://www.w3.org/TR/webauthn-3/#sctn-parseRequestOptionsFromJSON + const decodedAllowCredentials = !options.allowCredentials + ? [] + : options.allowCredentials.map((cred) => ({ + ...cred, + id: base64url.decode(cred.id), + })); + const decodedOptions = { ...options, + allowCredentials: decodedAllowCredentials, challenge: base64url.decode(options.challenge), }; diff --git a/javascript/test/webauthn-core.test.js b/javascript/test/webauthn-core.test.js index 2c6413a33e..88dae0052e 100644 --- a/javascript/test/webauthn-core.test.js +++ b/javascript/test/webauthn-core.test.js @@ -85,7 +85,13 @@ describe("webauthn-core", () => { challenge: "nRbOrtNKTfJ1JaxfUDKs8j3B-JFqyGQw8DO4u6eV3JA", timeout: 300000, rpId: "localhost", - allowCredentials: [], + allowCredentials: [ + { + id: "nOsjw8eaaqSwVdTBBYE1FqfGdHs", + type: "public-key", + transports: [], + }, + ], userVerification: "preferred", extensions: {}, }; @@ -172,7 +178,13 @@ describe("webauthn-core", () => { challenge: base64url.decode("nRbOrtNKTfJ1JaxfUDKs8j3B-JFqyGQw8DO4u6eV3JA"), timeout: 300000, rpId: "localhost", - allowCredentials: [], + allowCredentials: [ + { + id: base64url.decode("nOsjw8eaaqSwVdTBBYE1FqfGdHs"), + type: "public-key", + transports: [], + }, + ], userVerification: "preferred", extensions: {}, }, From 88ce236b434f7c7a476c10545ae3e0452dac7a0c Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Fri, 17 Jan 2025 14:15:30 -0700 Subject: [PATCH 086/132] Support Serialization in Test Classes Issue gh-16276 Signed-off-by: Daeho Kwon --- .../security/access/annotation/BusinessServiceImpl.java | 4 ++++ .../annotation/ExpressionProtectedBusinessServiceImpl.java | 4 ++++ .../security/access/annotation/Jsr250BusinessServiceImpl.java | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/core/src/test/java/org/springframework/security/access/annotation/BusinessServiceImpl.java b/core/src/test/java/org/springframework/security/access/annotation/BusinessServiceImpl.java index 0e732bf480..587e795f5a 100644 --- a/core/src/test/java/org/springframework/security/access/annotation/BusinessServiceImpl.java +++ b/core/src/test/java/org/springframework/security/access/annotation/BusinessServiceImpl.java @@ -16,6 +16,7 @@ package org.springframework.security.access.annotation; +import java.io.Serial; import java.util.ArrayList; import java.util.List; @@ -24,6 +25,9 @@ */ public class BusinessServiceImpl implements BusinessService { + @Serial + private static final long serialVersionUID = -4249394090237180795L; + @Override @Secured({ "ROLE_USER" }) public void someUserMethod1() { diff --git a/core/src/test/java/org/springframework/security/access/annotation/ExpressionProtectedBusinessServiceImpl.java b/core/src/test/java/org/springframework/security/access/annotation/ExpressionProtectedBusinessServiceImpl.java index 9d1b066d01..1ca226709b 100644 --- a/core/src/test/java/org/springframework/security/access/annotation/ExpressionProtectedBusinessServiceImpl.java +++ b/core/src/test/java/org/springframework/security/access/annotation/ExpressionProtectedBusinessServiceImpl.java @@ -16,6 +16,7 @@ package org.springframework.security.access.annotation; +import java.io.Serial; import java.util.ArrayList; import java.util.List; @@ -25,6 +26,9 @@ public class ExpressionProtectedBusinessServiceImpl implements BusinessService { + @Serial + private static final long serialVersionUID = -3320014879907436606L; + @Override public void someAdminMethod() { } diff --git a/core/src/test/java/org/springframework/security/access/annotation/Jsr250BusinessServiceImpl.java b/core/src/test/java/org/springframework/security/access/annotation/Jsr250BusinessServiceImpl.java index b19b19bfcf..6d9f34ac61 100644 --- a/core/src/test/java/org/springframework/security/access/annotation/Jsr250BusinessServiceImpl.java +++ b/core/src/test/java/org/springframework/security/access/annotation/Jsr250BusinessServiceImpl.java @@ -16,6 +16,7 @@ package org.springframework.security.access.annotation; +import java.io.Serial; import java.util.ArrayList; import java.util.List; @@ -28,6 +29,9 @@ @PermitAll public class Jsr250BusinessServiceImpl implements BusinessService { + @Serial + private static final long serialVersionUID = -7550211450382764339L; + @Override @RolesAllowed("ROLE_USER") public void someUserMethod1() { From b6259c36063eb0bc1e1642907be6c828c23ad26d Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Fri, 17 Jan 2025 16:08:15 -0700 Subject: [PATCH 087/132] Mark Serialization Support for Events Issue gh-16276 Signed-off-by: Daeho Kwon --- ...gSecurityCoreVersionSerializableTests.java | 62 +++++++++++++++++- ...ationFailureBadCredentialsEvent.serialized | Bin 0 -> 11264 bytes ...nFailureCredentialsExpiredEvent.serialized | Bin 0 -> 11353 bytes ...henticationFailureDisabledEvent.serialized | Bin 0 -> 11333 bytes ...thenticationFailureExpiredEvent.serialized | Bin 0 -> 11338 bytes ...uthenticationFailureLockedEvent.serialized | Bin 0 -> 11329 bytes ...ionFailureProviderNotFoundEvent.serialized | Bin 0 -> 11268 bytes ...ationFailureProxyUntrustedEvent.serialized | Bin 0 -> 11271 bytes ...ionFailureServiceExceptionEvent.serialized | Bin 0 -> 11273 bytes ...vent.AuthenticationSuccessEvent.serialized | Bin 0 -> 304 bytes ...ctiveAuthenticationSuccessEvent.serialized | Bin 0 -> 414 bytes ...cation.event.LogoutSuccessEvent.serialized | Bin 0 -> 296 bytes ...t.JaasAuthenticationFailedEvent.serialized | Bin 0 -> 10968 bytes ....JaasAuthenticationSuccessEvent.serialized | Bin 0 -> 314 bytes ...re.session.AbstractSessionEvent.serialized | Bin 0 -> 198 bytes ....SessionFixationProtectionEvent.serialized | Bin 0 -> 382 bytes ...r.AuthenticationSwitchUserEvent.serialized | Bin 0 -> 1016 bytes ...session.HttpSessionCreatedEvent.serialized | Bin 0 -> 354 bytes ...uthenticationCredentialsNotFoundEvent.java | 1 + .../event/AuthorizationFailureEvent.java | 1 + .../access/event/AuthorizedEvent.java | 1 + .../access/event/PublicInvocationEvent.java | 1 + ...henticationFailureBadCredentialsEvent.java | 5 ++ ...icationFailureCredentialsExpiredEvent.java | 5 ++ .../AuthenticationFailureDisabledEvent.java | 5 ++ .../AuthenticationFailureExpiredEvent.java | 5 ++ .../AuthenticationFailureLockedEvent.java | 5 ++ ...nticationFailureProviderNotFoundEvent.java | 5 ++ ...henticationFailureProxyUntrustedEvent.java | 5 ++ ...nticationFailureServiceExceptionEvent.java | 5 ++ .../event/AuthenticationSuccessEvent.java | 5 ++ ...InteractiveAuthenticationSuccessEvent.java | 5 ++ .../event/LogoutSuccessEvent.java | 7 +- .../event/JaasAuthenticationFailedEvent.java | 5 ++ .../event/JaasAuthenticationSuccessEvent.java | 5 ++ .../event/AuthorizationDeniedEvent.java | 3 +- .../event/AuthorizationEvent.java | 7 +- .../event/AuthorizationGrantedEvent.java | 7 +- .../context/SecurityContextChangedEvent.java | 3 +- .../core/session/AbstractSessionEvent.java | 7 +- .../SessionFixationProtectionEvent.java | 7 +- .../AuthenticationSwitchUserEvent.java | 5 ++ .../web/session/HttpSessionCreatedEvent.java | 1 + .../session/HttpSessionDestroyedEvent.java | 1 + .../session/HttpSessionIdChangedEvent.java | 8 ++- .../SessionInformationExpiredEvent.java | 3 +- 46 files changed, 175 insertions(+), 10 deletions(-) create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.event.AuthenticationFailureBadCredentialsEvent.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.event.AuthenticationFailureCredentialsExpiredEvent.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.event.AuthenticationFailureDisabledEvent.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.event.AuthenticationFailureExpiredEvent.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.event.AuthenticationFailureLockedEvent.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.event.AuthenticationFailureProviderNotFoundEvent.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.event.AuthenticationFailureProxyUntrustedEvent.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.event.AuthenticationFailureServiceExceptionEvent.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.event.AuthenticationSuccessEvent.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.event.LogoutSuccessEvent.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.jaas.event.JaasAuthenticationFailedEvent.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.jaas.event.JaasAuthenticationSuccessEvent.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.core.session.AbstractSessionEvent.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.web.authentication.session.SessionFixationProtectionEvent.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.web.authentication.switchuser.AuthenticationSwitchUserEvent.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.web.session.HttpSessionCreatedEvent.serialized diff --git a/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java b/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java index 407a055d42..359a7d4880 100644 --- a/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java +++ b/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -54,6 +54,7 @@ import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; import org.springframework.core.type.filter.AssignableTypeFilter; +import org.springframework.mock.web.MockHttpSession; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.AuthorizationServiceException; import org.springframework.security.access.intercept.RunAsUserToken; @@ -73,16 +74,33 @@ import org.springframework.security.authentication.TestAuthentication; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.authentication.event.AuthenticationFailureBadCredentialsEvent; +import org.springframework.security.authentication.event.AuthenticationFailureCredentialsExpiredEvent; +import org.springframework.security.authentication.event.AuthenticationFailureDisabledEvent; +import org.springframework.security.authentication.event.AuthenticationFailureExpiredEvent; +import org.springframework.security.authentication.event.AuthenticationFailureLockedEvent; +import org.springframework.security.authentication.event.AuthenticationFailureProviderNotFoundEvent; +import org.springframework.security.authentication.event.AuthenticationFailureProxyUntrustedEvent; +import org.springframework.security.authentication.event.AuthenticationFailureServiceExceptionEvent; +import org.springframework.security.authentication.event.AuthenticationSuccessEvent; +import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent; +import org.springframework.security.authentication.event.LogoutSuccessEvent; import org.springframework.security.authentication.jaas.JaasAuthenticationToken; +import org.springframework.security.authentication.jaas.event.JaasAuthenticationFailedEvent; +import org.springframework.security.authentication.jaas.event.JaasAuthenticationSuccessEvent; import org.springframework.security.authentication.ott.InvalidOneTimeTokenException; import org.springframework.security.authentication.ott.OneTimeTokenAuthenticationToken; import org.springframework.security.authentication.password.CompromisedPasswordException; import org.springframework.security.cas.authentication.CasAssertionAuthenticationToken; import org.springframework.security.cas.authentication.CasAuthenticationToken; import org.springframework.security.cas.authentication.CasServiceTicketAuthenticationToken; +import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.SpringSecurityCoreVersion; import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextImpl; +import org.springframework.security.core.session.AbstractSessionEvent; import org.springframework.security.core.session.ReactiveSessionInformation; import org.springframework.security.core.session.SessionInformation; import org.springframework.security.core.userdetails.UserDetails; @@ -163,6 +181,8 @@ import org.springframework.security.web.authentication.rememberme.InvalidCookieException; import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationException; import org.springframework.security.web.authentication.session.SessionAuthenticationException; +import org.springframework.security.web.authentication.session.SessionFixationProtectionEvent; +import org.springframework.security.web.authentication.switchuser.AuthenticationSwitchUserEvent; import org.springframework.security.web.authentication.www.NonceExpiredException; import org.springframework.security.web.csrf.CsrfException; import org.springframework.security.web.csrf.DefaultCsrfToken; @@ -170,6 +190,7 @@ import org.springframework.security.web.csrf.MissingCsrfTokenException; import org.springframework.security.web.firewall.RequestRejectedException; import org.springframework.security.web.server.firewall.ServerExchangeRejectedException; +import org.springframework.security.web.session.HttpSessionCreatedEvent; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; @@ -200,6 +221,8 @@ class SpringSecurityCoreVersionSerializableTests { static { UserDetails user = TestAuthentication.user(); + Authentication authentication = TestAuthentication.authenticated(user); + SecurityContext securityContext = new SecurityContextImpl(authentication); // oauth2-core generatorByClassName.put(DefaultOAuth2User.class, (r) -> TestOAuth2Users.create()); @@ -375,6 +398,37 @@ class SpringSecurityCoreVersionSerializableTests { (r) -> new UsernameNotFoundException("error", new RuntimeException())); generatorByClassName.put(TestingAuthenticationToken.class, (r) -> applyDetails(new TestingAuthenticationToken("username", "password"))); + generatorByClassName.put(AuthenticationFailureBadCredentialsEvent.class, + (r) -> new AuthenticationFailureBadCredentialsEvent(authentication, + new BadCredentialsException("message"))); + generatorByClassName.put(AuthenticationFailureCredentialsExpiredEvent.class, + (r) -> new AuthenticationFailureCredentialsExpiredEvent(authentication, + new CredentialsExpiredException("message"))); + generatorByClassName.put(AuthenticationFailureDisabledEvent.class, + (r) -> new AuthenticationFailureDisabledEvent(authentication, new DisabledException("message"))); + generatorByClassName.put(AuthenticationFailureExpiredEvent.class, + (r) -> new AuthenticationFailureExpiredEvent(authentication, new AccountExpiredException("message"))); + generatorByClassName.put(AuthenticationFailureLockedEvent.class, + (r) -> new AuthenticationFailureLockedEvent(authentication, new LockedException("message"))); + generatorByClassName.put(AuthenticationFailureProviderNotFoundEvent.class, + (r) -> new AuthenticationFailureProviderNotFoundEvent(authentication, + new ProviderNotFoundException("message"))); + generatorByClassName.put(AuthenticationFailureProxyUntrustedEvent.class, + (r) -> new AuthenticationFailureProxyUntrustedEvent(authentication, + new AuthenticationServiceException("message"))); + generatorByClassName.put(AuthenticationFailureServiceExceptionEvent.class, + (r) -> new AuthenticationFailureServiceExceptionEvent(authentication, + new AuthenticationServiceException("message"))); + generatorByClassName.put(AuthenticationSuccessEvent.class, + (r) -> new AuthenticationSuccessEvent(authentication)); + generatorByClassName.put(InteractiveAuthenticationSuccessEvent.class, + (r) -> new InteractiveAuthenticationSuccessEvent(authentication, Authentication.class)); + generatorByClassName.put(LogoutSuccessEvent.class, (r) -> new LogoutSuccessEvent(authentication)); + generatorByClassName.put(JaasAuthenticationFailedEvent.class, + (r) -> new JaasAuthenticationFailedEvent(authentication, new RuntimeException("message"))); + generatorByClassName.put(JaasAuthenticationSuccessEvent.class, + (r) -> new JaasAuthenticationSuccessEvent(authentication)); + generatorByClassName.put(AbstractSessionEvent.class, (r) -> new AbstractSessionEvent(securityContext)); // cas generatorByClassName.put(CasServiceTicketAuthenticationToken.class, (r) -> { @@ -448,6 +502,12 @@ class SpringSecurityCoreVersionSerializableTests { generatorByClassName.put(RequestRejectedException.class, (r) -> new RequestRejectedException("message")); generatorByClassName.put(ServerExchangeRejectedException.class, (r) -> new ServerExchangeRejectedException("message")); + generatorByClassName.put(SessionFixationProtectionEvent.class, + (r) -> new SessionFixationProtectionEvent(authentication, "old", "new")); + generatorByClassName.put(AuthenticationSwitchUserEvent.class, + (r) -> new AuthenticationSwitchUserEvent(authentication, user)); + generatorByClassName.put(HttpSessionCreatedEvent.class, + (r) -> new HttpSessionCreatedEvent(new MockHttpSession())); } @ParameterizedTest diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.event.AuthenticationFailureBadCredentialsEvent.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.event.AuthenticationFailureBadCredentialsEvent.serialized new file mode 100644 index 0000000000000000000000000000000000000000..979b2e937adc5956bbaa1329de3221a146856e87 GIT binary patch literal 11264 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 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.event.AuthenticationFailureCredentialsExpiredEvent.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.event.AuthenticationFailureCredentialsExpiredEvent.serialized new file mode 100644 index 0000000000000000000000000000000000000000..e4afece24aad97f864f0acb7498197e5eb294865 GIT binary patch literal 11353 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

iqNHoMh{0ozU=bV{4ckah~ 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 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.event.AuthenticationFailureLockedEvent.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.event.AuthenticationFailureLockedEvent.serialized new file mode 100644 index 0000000000000000000000000000000000000000..46609358d9b1a9771dac5931fe430f815ec97c21 GIT binary patch literal 11329 zcmeGiZHygN^}cP{b_*1!EiJTAS{@WE%x)>Rlb?50f**Q_jhW)T=MM3D+tK*?v%w(x;rexpPs0zZCp9@>yIv#YgT6DxEBy#?>QCeCum9>BTiO_#3EAj7ATSn*&~`*2 zAtG_(i@UCie(~D6HdY#B9el>&0Wv69_aKn#%)%ok*A?M*fN4GhG7SPxNtu?8J@EItAKd%vzrY3$unytYcqHsv04%PW zw5M&WAzaTQ$U|e3+!2E(JBHTOKK3mn6);wMbb9F6PeHxiMMPt9WYw#57z<>{lS~o>-BT1 zqY}@hBoNQ8SFK^738K<~Pu$(UciD~i|5XCMF9FmLzd5sh_odzIUdlm?PK5q(d(7jn zy!?SpJC2|FIqRNzM3`MhfQq8qFN#VFU-B5X}T7u=uYCw#W_w!S=Sas}XW_Bg(<`f|V&$ zBRq6%&%GCQJ_@G|WZXG_U<<_tTJh}KSj8@bhQVo;w|4~rZFUuK$ZkY#R||F%GU(O; zrtY{tJP97wZO^LmIuC8(hgQvv9BXgIpVb?1j zNYXkI@o)s}%yW4~lQ|uY>H<^Dj59);6 z7F@;^6ZBLD6n}0t0VXp5f~}FbhJ9a1eEXHLo6CA=85mg;we*LfA8PeqWd2l=xER7{ z#l;N3_AnngR3{*l2zjE_NGTS{g<0NA@Y9N?%bW1j6S(_pfd_(BPuetgnrNQW64{MU zdjxBjR1Kig84a`spDux*0LPJ>5$|n;A;baYqsV#kC|=CohClFl*H2ca5;qd^aSt+r z*njZKLGr2)lIbwt01MjP`m{f#jS~Dn+YHlN3i0%Dk5|E+)vIYla{n7zz1Bh*5Uhg& z%b1Pfvwh2UtIOIdT_aF$y9ubTNg_|Y1xZk_bLB&V4&_A7?KbUR+ukNIg4afsO*ZPT zumaB(7^W=8uhsl|B-5s0DIll(HKml$phJc!u^;wM*lxWH z4#KUF3KJl~)+ax*E3~AKn%sPK9{U!&j)B=4g*?n}i2fNh0sGEQseN})07Q08T3;Yq zINXWDF^GK{4#P8s>AYs{%B@ zF-^k^n1s@U?SZq{sAq~+DtW9@IfOrc)OXe4EvFKRk0@SmdAGbGo{C@>D2guaAWcj9 zbiqdEMJu*)z0cSJ^#rdZR)^$o$0229x2WCidRG*Sag*<-DBux*V9OmmAw6|sD{+Z< z%&#IwaFD?GG+^53F%3Z0Q^^4woeSU(1RxltM1|+O!Xqbi@hDqp_R?_isZYNU?6PLB zlu2EZQg8xXzRBFqe#J~rB6#gUm0!bJ1oA;^!i7vVbS9u=^(%0DLa-L7Q+oUitrW}%PxI0v z?grvC{!^Q2w?io7QxQofuSj7YZ0%Ukjz5>%@rt|@O^kHl=rl`SaDB=Su2hov@B%G* z1S(fBU~X6T8HSFEm+syVp<0}Fv3|Ga4I9feYK+y~RIGbLv3YcXY?|g*?&IS&6kL6? zb-U&|Ca;M7L{2upC89VyS`2WK00g7raX!8&4z5Vs^iJ#P#Hh%den~PxIw`f)h%yde z!r{wFU9Ie)tji8eU7v^KaWf^4)D?r?A-8MRfFJhT&cvZ0D(lDcp>c@L5b*R&6l=wX z^f(Ym&de(j{b@jdbq+n?;gFd-{IDBv?0}TFmbVCYac*93@rfej-=8P)8_HX7`PXqM zM0A?kKVNGN^gIEP^LzY%fa$D}Yrh@DbWNgzo-nj$vFlaVPb1K~fNV>)NtWwrrQQpn z3P5WZ<~4}+kbL3pGm38e4-nau5K(rNQ)qb~_yv+P^x72O0(7O7^HlBjv!dF0>3=>z zi~gJzgCW>Rirbih7-;;GhY4lzOp!p&0OAcf`Sz%0e|(OhP{>XxB`$URU=|WdzXwkks`yU%|cz>|B*&C-Dr4+d!m`Lo=68Ln{ZWhY@~H1B!B}&8&##{i}HVS-h%<-8sOI%9s;n@NtV2n8XJq=XkL*SI*`|sfJG!9L) zh_9re@#zH|GDoRoY6IW@xBv(gfU`frfxJ5ZIia1VQmf54Ln*UNt#SaeGQ#O3r@qfjx))ryAg%{Y46;7K z=}+PCX&fFj0UpMgOkV$x2{0!Q*-`=#o-{GEEFB>0JLe7Xy7I!1l1iOWASQB-&*GEm z_fY=VYR+j7pNDcv7P38_XObu1k=fz@r<74(Z!S!z2>p-uqR#MY@2tN3$vzI#11B>9QxqVSEMHzrad2M@8L?tu200Oq@bL?A~a$>Ar|( zH|ov=H1O%W19|Z#Ykg>DAA(L7)U>L&xT3||V#41hvBOYWhQqn|GbI+;&}{*5GcG}Y zp)lkt;#7n9WDfk|(J@;z)5xMH9%ct3Y{a`KeySpb{$F zuw-V6<#a@|y%O12mE)k30E#h?^01+90|B+R-Zp0 zy*)sWdirzI8)-l-uSKNgC)m0sZ(==`^u>G|T#dd36pQahhu|`7THON07o+k%^Ri~I zl<=3xc;1DUXhwhR6dfe7Z$bgkmjDArK${VW?Zbi6m?9v!55@NfaTvlO2ZRDG!}jR1 zQwh|KKP0KRQD6&a!h#|Z8}i`7`qUI$qva>n)|STWOs%b+AioMqI9cEPrPgPP+%X_G)RG*S%FJv`p{HU8&=M-gzu~+lcTf6zf=i0l z#PpNTXv0i2n=!79_zZ6u+MaL$rfhS~oNn5IwN<~Do$_7Xx68vcjqvAh*p z8Ocwjr$u3-ZmQ^kj22qBs~iPX=`J`|s<8?!lY`J8gKyaCvj2_WVwtq6wAou|84O8) zK!e=V>Lp(BQ!81?0c40?DRHcdH@H;tDpn6!t&YG_9D-e1IIp#(!xeHf3j}X7tfe)< zk=aP_n(-N?VX`sxO>+Y&6)lE)F`e)uGkZxicPrV=phPsaPtd-6k#aS(HhjcPR}CwisKY01~qGNta1QD)1apxxW?&>bBl{YV~tF OpL*go@O3l6%zpt=Zz1OZ literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.event.AuthenticationFailureProviderNotFoundEvent.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.event.AuthenticationFailureProviderNotFoundEvent.serialized new file mode 100644 index 0000000000000000000000000000000000000000..18de70b6051208f01718a3b6d727bbf0cdab0fe8 GIT binary patch literal 11268 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 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.event.AuthenticationFailureProxyUntrustedEvent.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.event.AuthenticationFailureProxyUntrustedEvent.serialized new file mode 100644 index 0000000000000000000000000000000000000000..f348e60c844bf665224fdb5f20502093bb695520 GIT binary patch literal 11271 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 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.event.AuthenticationSuccessEvent.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.event.AuthenticationSuccessEvent.serialized new file mode 100644 index 0000000000000000000000000000000000000000..d04eb51778bdbfb327fbf11ba216260556902228 GIT binary patch literal 304 zcmZ4UmVvdnh`}enC|$3(peQphJ*_A)H?=&!C|j>MHMz7Xv!qflv9u&3HLoNyIk6-& zKTj{U49L-QL=g-wO-@cNE_MY;DVu)ZrN6>rH4_6vMG=E98Ac}+mlP!?m!Mh!Hv6Q) z^1qiFY=1(`*1=|Wa(-S(Y6Z}N1qC@!Cxf*nwY-k+a6kKyiGk6JfwLqtH?_DVF}DEd sMA59ovP8YolFS@EuuA`=tkmQZpI%NsiQL*1Agc-(7#OFN`NiA^0B2WqjQ{`u literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent.serialized new file mode 100644 index 0000000000000000000000000000000000000000..49143cf81885e7b75fc7e83f06b7a6961f800163 GIT binary patch literal 414 zcmZ4UmVvdnh#@?`C|$3(peQphJ*_A)H?=&!C|j>MHMz7Xv!qflv9u&3HLoNyIk6-& zKTj{U49L;*%qvMPN=z=vEK7Ankq<6SPEIW@b_J<=lBhIMBf`^}iGk6Ffjd1l52&Ri zHN~m2gh9|JE3qt5KPNFSUEet;vAEc}qKLtlbayx=6_b>qrMHMz7Xv!qflv9u&3HLoNyIk6-& zKTj{U49L;*$xqKOEeS47PEIW@b_EH!eYP+zP|euR#K2Hd#NbQ1k&a2lB}IwJC5|Ze zfz3Xtu>9|(2HT$yvvshUot&Rnl3G!s=U7mX19d7`Yf{VW_zw5851AMky%;!4GILXl xOA>PnfKC+6N-Rs%D=o>)(F3dWPs&P7F7fH*^pnV~T>-MHfPsN=N||5Ga{x3^aohj^ literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.jaas.event.JaasAuthenticationFailedEvent.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.jaas.event.JaasAuthenticationFailedEvent.serialized new file mode 100644 index 0000000000000000000000000000000000000000..d371ae6ae4b35e4494fa7c2ba264dfdf99d49199 GIT binary patch literal 10968 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 D#sr91 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.jaas.event.JaasAuthenticationSuccessEvent.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.authentication.jaas.event.JaasAuthenticationSuccessEvent.serialized new file mode 100644 index 0000000000000000000000000000000000000000..6532dac81f9e14282b16b768638ae7cd9ba00f72 GIT binary patch literal 314 zcmZ4UmVvdnh#@q;C|$3(peQphJ*_A)H?=&!C|j>MHMz7Xv!qflv9u&3HLoNyIk6-& zKTj_!F|k-LwG1et=LKXsqR0l9CMTyB7rTO_<+;vr|42P&%EZ7>QN-X!H9NqD*NH{H z40-%44`R3uHp7$i^GZ@HO7t8H3UZ*12Ww4gc^%*3e)b^~1EUuMXGvymYH>+oZUNAZ rqFIS$iF&0anK^o3mHtUtsmUciy_|j$xwR`mRuwQXFit7+i}?Zo9g%na literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.core.session.AbstractSessionEvent.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.core.session.AbstractSessionEvent.serialized new file mode 100644 index 0000000000000000000000000000000000000000..a22f7a0f9b301f61a27bc9a26303e0e4b63b8382 GIT binary patch literal 198 zcmZ4UmVvdnh`}zuC|$3(peQphJ*_A)H?=&!C|j>MHMz7Xv!qflIlm|s$SW?+%+J$v zOe!uZN=z;ZhVWg>Qu9g{bozcjVqL(=#K2Hd#Gr%Cu;l!_lGKV4J;#EAoXq6J5};9F ztw}Af<2&5XK4fBG^kU#F$;?eHE=kNS02(8jl~|UjS6Y&pqX$;$pOlrFT;kKq=_ip} Ry8>iY0Rsc$lrq1V{{X;DN}d1! literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.web.authentication.session.SessionFixationProtectionEvent.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.web.authentication.session.SessionFixationProtectionEvent.serialized new file mode 100644 index 0000000000000000000000000000000000000000..4fc1f92cb28c81f01751073ec619d3db40c623ee GIT binary patch literal 382 zcmZ4UmVvdnh#@S$C|$3(peQphJ*_A)H?=&!C|j>MHMz7Xv!qh5JT*x#v9u&3HLoNy zIk6-&KMyEZTnwUvA+%d&1z0?wD8D2%8KlCs45<1}kH%Ghwku9d3`{-@Jb9_*Q01N} zB@9A7S&3zd`ZH5JXAe*g$YVvbZ;A#pP>KGU+iWq$HIu6ZYsUUmx9FvMmiV~Ac z98p{icGF3P<$o_V*#2Zlo61hUq zMg)I=Vq>R;|A>f%_7>KXQp7^=%_iqwLNGYhGVjgzeee6;zTbp{&caP?o6H%j(&mcg ziR@^*&YTo2tFXs9vcY%@S7nMya8x>FuA@+_ww$zVdPk*}73x%|w5k8bkNH<`#^v!M z6hb(J+%_fpB0@PN&R`d3FyIV?wleVekwmUy7x-6~x4z3=3s?WCc$Zq~rLgISL(2v3 z6r9QYaQ5x*Fa1luiU5RtelKjHQ zCb$r~m10$=m99}%MoTcB--TMN`>RPo6(1yz-EixuOnD=gQ3!{AQ oqrVh+`1EIuSRD`iMkp;Vgq7ubt+LofIC3x5+HB;Zjre^20D41h9RL6T literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.web.session.HttpSessionCreatedEvent.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.web.session.HttpSessionCreatedEvent.serialized new file mode 100644 index 0000000000000000000000000000000000000000..95888e6e1cce1dfc2db81311a0ddccdc81ae31cd GIT binary patch literal 354 zcmZ4UmVvdnh`}MhC|$3(peQphJ*_A)H?=&!C|j>MHMz7Xv!qh5JT(c(DK5^;&(rfL zDJcksu$_xi6H8K4T+34PN@mu6liQU%*_(-hp`wVv4zF>^`9-O4^N`H~nh!Rt@Tss^ z-Ksa+NjA(esko#lF}Vb4DA=$Coxb0XSQl_Y4Aa5pvgG`{lGKV4J;#EAoXq6J5|}%Z zT3*L@xSxH<#K7pqz*&-+n_66wm|Fn!plDWNS)yKPNoI~7*b@JwtkmQZpI%NsiQL*1 QAgc-(7#OFN`NaeS0K4gj4FCWD literal 0 HcmV?d00001 diff --git a/core/src/main/java/org/springframework/security/access/event/AuthenticationCredentialsNotFoundEvent.java b/core/src/main/java/org/springframework/security/access/event/AuthenticationCredentialsNotFoundEvent.java index daae07eec9..8d7107ed5b 100644 --- a/core/src/main/java/org/springframework/security/access/event/AuthenticationCredentialsNotFoundEvent.java +++ b/core/src/main/java/org/springframework/security/access/event/AuthenticationCredentialsNotFoundEvent.java @@ -32,6 +32,7 @@ * instead. */ @Deprecated +@SuppressWarnings("serial") public class AuthenticationCredentialsNotFoundEvent extends AbstractAuthorizationEvent { private final AuthenticationCredentialsNotFoundException credentialsNotFoundException; diff --git a/core/src/main/java/org/springframework/security/access/event/AuthorizationFailureEvent.java b/core/src/main/java/org/springframework/security/access/event/AuthorizationFailureEvent.java index eac534ba6d..fba28adf0b 100644 --- a/core/src/main/java/org/springframework/security/access/event/AuthorizationFailureEvent.java +++ b/core/src/main/java/org/springframework/security/access/event/AuthorizationFailureEvent.java @@ -39,6 +39,7 @@ * instead */ @Deprecated +@SuppressWarnings("serial") public class AuthorizationFailureEvent extends AbstractAuthorizationEvent { private final AccessDeniedException accessDeniedException; diff --git a/core/src/main/java/org/springframework/security/access/event/AuthorizedEvent.java b/core/src/main/java/org/springframework/security/access/event/AuthorizedEvent.java index 7697dea90d..3ec29ce6a2 100644 --- a/core/src/main/java/org/springframework/security/access/event/AuthorizedEvent.java +++ b/core/src/main/java/org/springframework/security/access/event/AuthorizedEvent.java @@ -34,6 +34,7 @@ * instead */ @Deprecated +@SuppressWarnings("serial") public class AuthorizedEvent extends AbstractAuthorizationEvent { private final Authentication authentication; diff --git a/core/src/main/java/org/springframework/security/access/event/PublicInvocationEvent.java b/core/src/main/java/org/springframework/security/access/event/PublicInvocationEvent.java index 2aab5dba91..7289d8a1ed 100644 --- a/core/src/main/java/org/springframework/security/access/event/PublicInvocationEvent.java +++ b/core/src/main/java/org/springframework/security/access/event/PublicInvocationEvent.java @@ -34,6 +34,7 @@ * {@link AuthorizationGrantedEvent#getSource()} to deduce public invocations. */ @Deprecated +@SuppressWarnings("serial") public class PublicInvocationEvent extends AbstractAuthorizationEvent { /** diff --git a/core/src/main/java/org/springframework/security/authentication/event/AuthenticationFailureBadCredentialsEvent.java b/core/src/main/java/org/springframework/security/authentication/event/AuthenticationFailureBadCredentialsEvent.java index 796690b0e6..6c80a3e883 100644 --- a/core/src/main/java/org/springframework/security/authentication/event/AuthenticationFailureBadCredentialsEvent.java +++ b/core/src/main/java/org/springframework/security/authentication/event/AuthenticationFailureBadCredentialsEvent.java @@ -16,6 +16,8 @@ package org.springframework.security.authentication.event; +import java.io.Serial; + import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; @@ -27,6 +29,9 @@ */ public class AuthenticationFailureBadCredentialsEvent extends AbstractAuthenticationFailureEvent { + @Serial + private static final long serialVersionUID = -5245144711561130379L; + public AuthenticationFailureBadCredentialsEvent(Authentication authentication, AuthenticationException exception) { super(authentication, exception); } diff --git a/core/src/main/java/org/springframework/security/authentication/event/AuthenticationFailureCredentialsExpiredEvent.java b/core/src/main/java/org/springframework/security/authentication/event/AuthenticationFailureCredentialsExpiredEvent.java index 57f218a239..2849ba0371 100644 --- a/core/src/main/java/org/springframework/security/authentication/event/AuthenticationFailureCredentialsExpiredEvent.java +++ b/core/src/main/java/org/springframework/security/authentication/event/AuthenticationFailureCredentialsExpiredEvent.java @@ -16,6 +16,8 @@ package org.springframework.security.authentication.event; +import java.io.Serial; + import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; @@ -27,6 +29,9 @@ */ public class AuthenticationFailureCredentialsExpiredEvent extends AbstractAuthenticationFailureEvent { + @Serial + private static final long serialVersionUID = -7595086332769705203L; + public AuthenticationFailureCredentialsExpiredEvent(Authentication authentication, AuthenticationException exception) { super(authentication, exception); diff --git a/core/src/main/java/org/springframework/security/authentication/event/AuthenticationFailureDisabledEvent.java b/core/src/main/java/org/springframework/security/authentication/event/AuthenticationFailureDisabledEvent.java index 3a4604354f..79c0fd479f 100644 --- a/core/src/main/java/org/springframework/security/authentication/event/AuthenticationFailureDisabledEvent.java +++ b/core/src/main/java/org/springframework/security/authentication/event/AuthenticationFailureDisabledEvent.java @@ -16,6 +16,8 @@ package org.springframework.security.authentication.event; +import java.io.Serial; + import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; @@ -27,6 +29,9 @@ */ public class AuthenticationFailureDisabledEvent extends AbstractAuthenticationFailureEvent { + @Serial + private static final long serialVersionUID = 8037552364666766279L; + public AuthenticationFailureDisabledEvent(Authentication authentication, AuthenticationException exception) { super(authentication, exception); } diff --git a/core/src/main/java/org/springframework/security/authentication/event/AuthenticationFailureExpiredEvent.java b/core/src/main/java/org/springframework/security/authentication/event/AuthenticationFailureExpiredEvent.java index 086e16cb37..a1f680dc5d 100644 --- a/core/src/main/java/org/springframework/security/authentication/event/AuthenticationFailureExpiredEvent.java +++ b/core/src/main/java/org/springframework/security/authentication/event/AuthenticationFailureExpiredEvent.java @@ -16,6 +16,8 @@ package org.springframework.security.authentication.event; +import java.io.Serial; + import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; @@ -27,6 +29,9 @@ */ public class AuthenticationFailureExpiredEvent extends AbstractAuthenticationFailureEvent { + @Serial + private static final long serialVersionUID = -8437264795214121718L; + public AuthenticationFailureExpiredEvent(Authentication authentication, AuthenticationException exception) { super(authentication, exception); } diff --git a/core/src/main/java/org/springframework/security/authentication/event/AuthenticationFailureLockedEvent.java b/core/src/main/java/org/springframework/security/authentication/event/AuthenticationFailureLockedEvent.java index 544964cdec..5cc0702909 100644 --- a/core/src/main/java/org/springframework/security/authentication/event/AuthenticationFailureLockedEvent.java +++ b/core/src/main/java/org/springframework/security/authentication/event/AuthenticationFailureLockedEvent.java @@ -16,6 +16,8 @@ package org.springframework.security.authentication.event; +import java.io.Serial; + import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; @@ -27,6 +29,9 @@ */ public class AuthenticationFailureLockedEvent extends AbstractAuthenticationFailureEvent { + @Serial + private static final long serialVersionUID = -5126110096093568463L; + public AuthenticationFailureLockedEvent(Authentication authentication, AuthenticationException exception) { super(authentication, exception); } diff --git a/core/src/main/java/org/springframework/security/authentication/event/AuthenticationFailureProviderNotFoundEvent.java b/core/src/main/java/org/springframework/security/authentication/event/AuthenticationFailureProviderNotFoundEvent.java index 1a1cf7c87e..ee4f5538e2 100644 --- a/core/src/main/java/org/springframework/security/authentication/event/AuthenticationFailureProviderNotFoundEvent.java +++ b/core/src/main/java/org/springframework/security/authentication/event/AuthenticationFailureProviderNotFoundEvent.java @@ -16,6 +16,8 @@ package org.springframework.security.authentication.event; +import java.io.Serial; + import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; @@ -27,6 +29,9 @@ */ public class AuthenticationFailureProviderNotFoundEvent extends AbstractAuthenticationFailureEvent { + @Serial + private static final long serialVersionUID = 9122219669183263487L; + public AuthenticationFailureProviderNotFoundEvent(Authentication authentication, AuthenticationException exception) { super(authentication, exception); diff --git a/core/src/main/java/org/springframework/security/authentication/event/AuthenticationFailureProxyUntrustedEvent.java b/core/src/main/java/org/springframework/security/authentication/event/AuthenticationFailureProxyUntrustedEvent.java index 772774d3f1..31617e6caa 100644 --- a/core/src/main/java/org/springframework/security/authentication/event/AuthenticationFailureProxyUntrustedEvent.java +++ b/core/src/main/java/org/springframework/security/authentication/event/AuthenticationFailureProxyUntrustedEvent.java @@ -16,6 +16,8 @@ package org.springframework.security.authentication.event; +import java.io.Serial; + import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; @@ -27,6 +29,9 @@ */ public class AuthenticationFailureProxyUntrustedEvent extends AbstractAuthenticationFailureEvent { + @Serial + private static final long serialVersionUID = 1801476426012753252L; + public AuthenticationFailureProxyUntrustedEvent(Authentication authentication, AuthenticationException exception) { super(authentication, exception); } diff --git a/core/src/main/java/org/springframework/security/authentication/event/AuthenticationFailureServiceExceptionEvent.java b/core/src/main/java/org/springframework/security/authentication/event/AuthenticationFailureServiceExceptionEvent.java index 167d5fae3b..d84f38625e 100644 --- a/core/src/main/java/org/springframework/security/authentication/event/AuthenticationFailureServiceExceptionEvent.java +++ b/core/src/main/java/org/springframework/security/authentication/event/AuthenticationFailureServiceExceptionEvent.java @@ -16,6 +16,8 @@ package org.springframework.security.authentication.event; +import java.io.Serial; + import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; @@ -27,6 +29,9 @@ */ public class AuthenticationFailureServiceExceptionEvent extends AbstractAuthenticationFailureEvent { + @Serial + private static final long serialVersionUID = 5580062757249390756L; + public AuthenticationFailureServiceExceptionEvent(Authentication authentication, AuthenticationException exception) { super(authentication, exception); diff --git a/core/src/main/java/org/springframework/security/authentication/event/AuthenticationSuccessEvent.java b/core/src/main/java/org/springframework/security/authentication/event/AuthenticationSuccessEvent.java index 5b3b9bcd24..5b18199a6c 100644 --- a/core/src/main/java/org/springframework/security/authentication/event/AuthenticationSuccessEvent.java +++ b/core/src/main/java/org/springframework/security/authentication/event/AuthenticationSuccessEvent.java @@ -16,6 +16,8 @@ package org.springframework.security.authentication.event; +import java.io.Serial; + import org.springframework.security.core.Authentication; /** @@ -25,6 +27,9 @@ */ public class AuthenticationSuccessEvent extends AbstractAuthenticationEvent { + @Serial + private static final long serialVersionUID = 2537206344128673963L; + public AuthenticationSuccessEvent(Authentication authentication) { super(authentication); } diff --git a/core/src/main/java/org/springframework/security/authentication/event/InteractiveAuthenticationSuccessEvent.java b/core/src/main/java/org/springframework/security/authentication/event/InteractiveAuthenticationSuccessEvent.java index c93d2a9165..eac89b4eaf 100644 --- a/core/src/main/java/org/springframework/security/authentication/event/InteractiveAuthenticationSuccessEvent.java +++ b/core/src/main/java/org/springframework/security/authentication/event/InteractiveAuthenticationSuccessEvent.java @@ -16,6 +16,8 @@ package org.springframework.security.authentication.event; +import java.io.Serial; + import org.springframework.security.core.Authentication; import org.springframework.util.Assert; @@ -34,6 +36,9 @@ */ public class InteractiveAuthenticationSuccessEvent extends AbstractAuthenticationEvent { + @Serial + private static final long serialVersionUID = -1990271553478571709L; + private final Class generatedBy; public InteractiveAuthenticationSuccessEvent(Authentication authentication, Class generatedBy) { diff --git a/core/src/main/java/org/springframework/security/authentication/event/LogoutSuccessEvent.java b/core/src/main/java/org/springframework/security/authentication/event/LogoutSuccessEvent.java index 094d0a332d..1ea77c2a21 100644 --- a/core/src/main/java/org/springframework/security/authentication/event/LogoutSuccessEvent.java +++ b/core/src/main/java/org/springframework/security/authentication/event/LogoutSuccessEvent.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2025 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,6 +16,8 @@ package org.springframework.security.authentication.event; +import java.io.Serial; + import org.springframework.security.core.Authentication; /** @@ -26,6 +28,9 @@ */ public class LogoutSuccessEvent extends AbstractAuthenticationEvent { + @Serial + private static final long serialVersionUID = 5112491795571632311L; + public LogoutSuccessEvent(Authentication authentication) { super(authentication); } diff --git a/core/src/main/java/org/springframework/security/authentication/jaas/event/JaasAuthenticationFailedEvent.java b/core/src/main/java/org/springframework/security/authentication/jaas/event/JaasAuthenticationFailedEvent.java index 4b70d77950..c3b6d427bd 100644 --- a/core/src/main/java/org/springframework/security/authentication/jaas/event/JaasAuthenticationFailedEvent.java +++ b/core/src/main/java/org/springframework/security/authentication/jaas/event/JaasAuthenticationFailedEvent.java @@ -16,6 +16,8 @@ package org.springframework.security.authentication.jaas.event; +import java.io.Serial; + import org.springframework.security.core.Authentication; /** @@ -26,6 +28,9 @@ */ public class JaasAuthenticationFailedEvent extends JaasAuthenticationEvent { + @Serial + private static final long serialVersionUID = -240510538971925002L; + private final Exception exception; public JaasAuthenticationFailedEvent(Authentication auth, Exception exception) { diff --git a/core/src/main/java/org/springframework/security/authentication/jaas/event/JaasAuthenticationSuccessEvent.java b/core/src/main/java/org/springframework/security/authentication/jaas/event/JaasAuthenticationSuccessEvent.java index 0afa2b882b..ec654a2a9f 100644 --- a/core/src/main/java/org/springframework/security/authentication/jaas/event/JaasAuthenticationSuccessEvent.java +++ b/core/src/main/java/org/springframework/security/authentication/jaas/event/JaasAuthenticationSuccessEvent.java @@ -16,6 +16,8 @@ package org.springframework.security.authentication.jaas.event; +import java.io.Serial; + import org.springframework.security.core.Authentication; /** @@ -28,6 +30,9 @@ */ public class JaasAuthenticationSuccessEvent extends JaasAuthenticationEvent { + @Serial + private static final long serialVersionUID = 2236826715750256181L; + public JaasAuthenticationSuccessEvent(Authentication auth) { super(auth); } diff --git a/core/src/main/java/org/springframework/security/authorization/event/AuthorizationDeniedEvent.java b/core/src/main/java/org/springframework/security/authorization/event/AuthorizationDeniedEvent.java index 94e7d6a231..05d0fcdbc5 100644 --- a/core/src/main/java/org/springframework/security/authorization/event/AuthorizationDeniedEvent.java +++ b/core/src/main/java/org/springframework/security/authorization/event/AuthorizationDeniedEvent.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2025 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 @@ * @author Josh Cummings * @since 5.7 */ +@SuppressWarnings("serial") public class AuthorizationDeniedEvent extends AuthorizationEvent { /** diff --git a/core/src/main/java/org/springframework/security/authorization/event/AuthorizationEvent.java b/core/src/main/java/org/springframework/security/authorization/event/AuthorizationEvent.java index a848dff491..d4bce6b586 100644 --- a/core/src/main/java/org/springframework/security/authorization/event/AuthorizationEvent.java +++ b/core/src/main/java/org/springframework/security/authorization/event/AuthorizationEvent.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2025 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,6 +16,7 @@ package org.springframework.security.authorization.event; +import java.io.Serial; import java.util.function.Supplier; import org.springframework.context.ApplicationEvent; @@ -31,8 +32,12 @@ * @author Josh Cummings * @since 5.8 */ +@SuppressWarnings("serial") public class AuthorizationEvent extends ApplicationEvent { + @Serial + private static final long serialVersionUID = -9053927371500241295L; + private final Supplier authentication; private final AuthorizationResult result; diff --git a/core/src/main/java/org/springframework/security/authorization/event/AuthorizationGrantedEvent.java b/core/src/main/java/org/springframework/security/authorization/event/AuthorizationGrantedEvent.java index 693bc7e4a7..9cde351930 100644 --- a/core/src/main/java/org/springframework/security/authorization/event/AuthorizationGrantedEvent.java +++ b/core/src/main/java/org/springframework/security/authorization/event/AuthorizationGrantedEvent.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2025 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,6 +16,7 @@ package org.springframework.security.authorization.event; +import java.io.Serial; import java.util.function.Supplier; import org.springframework.context.ApplicationEvent; @@ -30,8 +31,12 @@ * @author Josh Cummings * @since 5.7 */ +@SuppressWarnings("serial") public class AuthorizationGrantedEvent extends AuthorizationEvent { + @Serial + private static final long serialVersionUID = -8690818228055810339L; + /** * @deprecated please use a constructor that takes an * {@link org.springframework.security.authorization.AuthorizationResult} diff --git a/core/src/main/java/org/springframework/security/core/context/SecurityContextChangedEvent.java b/core/src/main/java/org/springframework/security/core/context/SecurityContextChangedEvent.java index c14125c475..ac38804cff 100644 --- a/core/src/main/java/org/springframework/security/core/context/SecurityContextChangedEvent.java +++ b/core/src/main/java/org/springframework/security/core/context/SecurityContextChangedEvent.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,7 @@ * @author Josh Cummings * @since 5.6 */ +@SuppressWarnings("serial") public class SecurityContextChangedEvent extends ApplicationEvent { public static final Supplier NO_CONTEXT = () -> null; diff --git a/core/src/main/java/org/springframework/security/core/session/AbstractSessionEvent.java b/core/src/main/java/org/springframework/security/core/session/AbstractSessionEvent.java index 4c8c20da5c..a02ad09eb6 100644 --- a/core/src/main/java/org/springframework/security/core/session/AbstractSessionEvent.java +++ b/core/src/main/java/org/springframework/security/core/session/AbstractSessionEvent.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2025 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,6 +16,8 @@ package org.springframework.security.core.session; +import java.io.Serial; + import org.springframework.context.ApplicationEvent; /** @@ -26,6 +28,9 @@ */ public class AbstractSessionEvent extends ApplicationEvent { + @Serial + private static final long serialVersionUID = -6878881229287231479L; + public AbstractSessionEvent(Object source) { super(source); } diff --git a/web/src/main/java/org/springframework/security/web/authentication/session/SessionFixationProtectionEvent.java b/web/src/main/java/org/springframework/security/web/authentication/session/SessionFixationProtectionEvent.java index 1b6c36deb3..f06cec22e3 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/session/SessionFixationProtectionEvent.java +++ b/web/src/main/java/org/springframework/security/web/authentication/session/SessionFixationProtectionEvent.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2025 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,6 +16,8 @@ package org.springframework.security.web.authentication.session; +import java.io.Serial; + import org.springframework.security.authentication.event.AbstractAuthenticationEvent; import org.springframework.security.core.Authentication; import org.springframework.util.Assert; @@ -29,6 +31,9 @@ */ public class SessionFixationProtectionEvent extends AbstractAuthenticationEvent { + @Serial + private static final long serialVersionUID = -2554621992006921150L; + private final String oldSessionId; private final String newSessionId; diff --git a/web/src/main/java/org/springframework/security/web/authentication/switchuser/AuthenticationSwitchUserEvent.java b/web/src/main/java/org/springframework/security/web/authentication/switchuser/AuthenticationSwitchUserEvent.java index 70ba6108bb..5b7af22bf3 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/switchuser/AuthenticationSwitchUserEvent.java +++ b/web/src/main/java/org/springframework/security/web/authentication/switchuser/AuthenticationSwitchUserEvent.java @@ -16,6 +16,8 @@ package org.springframework.security.web.authentication.switchuser; +import java.io.Serial; + import org.springframework.security.authentication.event.AbstractAuthenticationEvent; import org.springframework.security.core.Authentication; import org.springframework.security.core.userdetails.UserDetails; @@ -27,6 +29,9 @@ */ public class AuthenticationSwitchUserEvent extends AbstractAuthenticationEvent { + @Serial + private static final long serialVersionUID = 6265996480231793939L; + private final UserDetails targetUser; /** diff --git a/web/src/main/java/org/springframework/security/web/session/HttpSessionCreatedEvent.java b/web/src/main/java/org/springframework/security/web/session/HttpSessionCreatedEvent.java index 15dcfff296..547bc7fcdb 100644 --- a/web/src/main/java/org/springframework/security/web/session/HttpSessionCreatedEvent.java +++ b/web/src/main/java/org/springframework/security/web/session/HttpSessionCreatedEvent.java @@ -27,6 +27,7 @@ * @author Ray Krueger * @author Luke Taylor */ +@SuppressWarnings("serial") public class HttpSessionCreatedEvent extends SessionCreationEvent { public HttpSessionCreatedEvent(HttpSession session) { diff --git a/web/src/main/java/org/springframework/security/web/session/HttpSessionDestroyedEvent.java b/web/src/main/java/org/springframework/security/web/session/HttpSessionDestroyedEvent.java index 944dd3c202..d3ac900ad4 100644 --- a/web/src/main/java/org/springframework/security/web/session/HttpSessionDestroyedEvent.java +++ b/web/src/main/java/org/springframework/security/web/session/HttpSessionDestroyedEvent.java @@ -33,6 +33,7 @@ * @author Luke Taylor * @author Rob Winch */ +@SuppressWarnings("serial") public class HttpSessionDestroyedEvent extends SessionDestroyedEvent { public HttpSessionDestroyedEvent(HttpSession session) { diff --git a/web/src/main/java/org/springframework/security/web/session/HttpSessionIdChangedEvent.java b/web/src/main/java/org/springframework/security/web/session/HttpSessionIdChangedEvent.java index 1320c1bb50..ec0b645d58 100644 --- a/web/src/main/java/org/springframework/security/web/session/HttpSessionIdChangedEvent.java +++ b/web/src/main/java/org/springframework/security/web/session/HttpSessionIdChangedEvent.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2025 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,6 +16,8 @@ package org.springframework.security.web.session; +import java.io.Serial; + import jakarta.servlet.http.HttpSession; import org.springframework.security.core.session.SessionIdChangedEvent; @@ -26,8 +28,12 @@ * * @since 5.4 */ +@SuppressWarnings("serial") public class HttpSessionIdChangedEvent extends SessionIdChangedEvent { + @Serial + private static final long serialVersionUID = -5725731666499807941L; + private final String oldSessionId; private final String newSessionId; diff --git a/web/src/main/java/org/springframework/security/web/session/SessionInformationExpiredEvent.java b/web/src/main/java/org/springframework/security/web/session/SessionInformationExpiredEvent.java index 1fa8e1573c..44c99a56b5 100644 --- a/web/src/main/java/org/springframework/security/web/session/SessionInformationExpiredEvent.java +++ b/web/src/main/java/org/springframework/security/web/session/SessionInformationExpiredEvent.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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 @@ * @author Rob Winch * @since 4.2 */ +@SuppressWarnings("serial") public final class SessionInformationExpiredEvent extends ApplicationEvent { private final HttpServletRequest request; From 183acab37a1c6238a2717a2fa1c83620a23be047 Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Fri, 17 Jan 2025 17:14:16 -0600 Subject: [PATCH 088/132] Fix checkstyleNohttp OutOfMemoryError Signed-off-by: Daeho Kwon --- build.gradle | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build.gradle b/build.gradle index 60089e6734..4fd368b54a 100644 --- a/build.gradle +++ b/build.gradle @@ -110,6 +110,10 @@ nohttp { source.builtBy(project(':spring-security-config').tasks.withType(RncToXsd)) } +tasks.named('checkstyleNohttp') { + maxHeapSize = '1g' +} + tasks.register('cloneRepository', IncludeRepoTask) { repository = project.getProperties().get("repositoryName") ref = project.getProperties().get("ref") From f17d009d8e3407687eeae2bae362525e118fe922 Mon Sep 17 00:00:00 2001 From: DingHao Date: Thu, 9 Jan 2025 17:32:25 +0800 Subject: [PATCH 089/132] Add ClientRegistration.clientSettings.requireProofKey to Enable PKCE Closes gh-16382 Signed-off-by: DingHao Signed-off-by: Daeho Kwon --- .../ClientRegistrationDeserializer.java | 2 +- .../registration/ClientRegistration.java | 30 +++++++- .../client/registration/ClientSettings.java | 68 +++++++++++++++++++ ...ultOAuth2AuthorizationRequestResolver.java | 5 +- .../OAuth2AuthorizedClientMixinTests.java | 5 +- ...uth2AuthorizationRequestResolverTests.java | 38 ++++++++++- 6 files changed, 139 insertions(+), 9 deletions(-) create mode 100644 oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientSettings.java diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson2/ClientRegistrationDeserializer.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson2/ClientRegistrationDeserializer.java index 77b1fdd121..d8fc1a099d 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson2/ClientRegistrationDeserializer.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson2/ClientRegistrationDeserializer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2025 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. diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistration.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistration.java index 0639a395f8..c8fa34e682 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistration.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2025 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. @@ -71,6 +71,8 @@ public final class ClientRegistration implements Serializable { private String clientName; + private ClientSettings clientSettings; + private ClientRegistration() { } @@ -162,6 +164,14 @@ public String getClientName() { return this.clientName; } + /** + * Returns the {@link ClientSettings client configuration settings}. + * @return the {@link ClientSettings} + */ + public ClientSettings getClientSettings() { + return this.clientSettings; + } + @Override public String toString() { // @formatter:off @@ -175,6 +185,7 @@ public String toString() { + '\'' + ", scopes=" + this.scopes + ", providerDetails=" + this.providerDetails + ", clientName='" + this.clientName + '\'' + + ", clientSettings='" + this.clientSettings + '\'' + '}'; // @formatter:on } @@ -367,6 +378,8 @@ public static final class Builder implements Serializable { private String clientName; + private ClientSettings clientSettings; + private Builder(String registrationId) { this.registrationId = registrationId; } @@ -391,6 +404,7 @@ private Builder(ClientRegistration clientRegistration) { this.configurationMetadata = new HashMap<>(configurationMetadata); } this.clientName = clientRegistration.clientName; + this.clientSettings = clientRegistration.clientSettings; } /** @@ -594,6 +608,16 @@ public Builder clientName(String clientName) { return this; } + /** + * Sets the {@link ClientSettings client configuration settings}. + * @param clientSettings the client configuration settings + * @return the {@link Builder} + */ + public Builder clientSettings(ClientSettings clientSettings) { + this.clientSettings = clientSettings; + return this; + } + /** * Builds a new {@link ClientRegistration}. * @return a {@link ClientRegistration} @@ -627,12 +651,14 @@ private ClientRegistration create() { clientRegistration.providerDetails = createProviderDetails(clientRegistration); clientRegistration.clientName = StringUtils.hasText(this.clientName) ? this.clientName : this.registrationId; + clientRegistration.clientSettings = (this.clientSettings == null) ? ClientSettings.builder().build() + : this.clientSettings; return clientRegistration; } private ClientAuthenticationMethod deduceClientAuthenticationMethod(ClientRegistration clientRegistration) { if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(this.authorizationGrantType) - && !StringUtils.hasText(this.clientSecret)) { + && (!StringUtils.hasText(this.clientSecret))) { return ClientAuthenticationMethod.NONE; } return ClientAuthenticationMethod.CLIENT_SECRET_BASIC; diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientSettings.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientSettings.java new file mode 100644 index 0000000000..de9c4bf7b8 --- /dev/null +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientSettings.java @@ -0,0 +1,68 @@ +/* + * Copyright 2002-2025 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.oauth2.client.registration; + +/** + * A facility for client configuration settings. + * + * @author DingHao + * @since 6.5 + */ +public final class ClientSettings { + + private boolean requireProofKey; + + private ClientSettings() { + + } + + public boolean isRequireProofKey() { + return this.requireProofKey; + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + + private boolean requireProofKey; + + private Builder() { + } + + /** + * Set to {@code true} if the client is required to provide a proof key challenge + * and verifier when performing the Authorization Code Grant flow. + * @param requireProofKey {@code true} if the client is required to provide a + * proof key challenge and verifier, {@code false} otherwise + * @return the {@link Builder} for further configuration + */ + public Builder requireProofKey(boolean requireProofKey) { + this.requireProofKey = requireProofKey; + return this; + } + + public ClientSettings build() { + ClientSettings clientSettings = new ClientSettings(); + clientSettings.requireProofKey = this.requireProofKey; + return clientSettings; + } + + } + +} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizationRequestResolver.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizationRequestResolver.java index c189317ec4..4909d0f730 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizationRequestResolver.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizationRequestResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2025 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. @@ -183,7 +183,8 @@ private OAuth2AuthorizationRequest.Builder getBuilder(ClientRegistration clientR // value. applyNonce(builder); } - if (ClientAuthenticationMethod.NONE.equals(clientRegistration.getClientAuthenticationMethod())) { + if (ClientAuthenticationMethod.NONE.equals(clientRegistration.getClientAuthenticationMethod()) + || clientRegistration.getClientSettings().isRequireProofKey()) { DEFAULT_PKCE_APPLIER.accept(builder); } return builder; diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/jackson2/OAuth2AuthorizedClientMixinTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/jackson2/OAuth2AuthorizedClientMixinTests.java index 3f696c361c..47e4a2d2e3 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/jackson2/OAuth2AuthorizedClientMixinTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/jackson2/OAuth2AuthorizedClientMixinTests.java @@ -276,7 +276,10 @@ private static String asJson(ClientRegistration clientRegistration) { " " + configurationMetadata + "\n" + " }\n" + " },\n" + - " \"clientName\": \"" + clientRegistration.getClientName() + "\"\n" + + " \"clientName\": \"" + clientRegistration.getClientName() + "\",\n" + + " \"clientSettings\": {\n" + + " \"requireProofKey\": " + clientRegistration.getClientSettings().isRequireProofKey() + "\n" + + " }\n" + "}"; // @formatter:on } diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizationRequestResolverTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizationRequestResolverTests.java index c10a3f82cf..d0d260922c 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizationRequestResolverTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizationRequestResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2025 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. @@ -28,6 +28,7 @@ import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.client.registration.ClientSettings; import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.core.AuthorizationGrantType; @@ -56,6 +57,8 @@ public class DefaultOAuth2AuthorizationRequestResolverTests { private ClientRegistration registration2; + private ClientRegistration pkceClientRegistration; + private ClientRegistration fineRedirectUriTemplateRegistration; private ClientRegistration publicClientRegistration; @@ -72,6 +75,9 @@ public class DefaultOAuth2AuthorizationRequestResolverTests { public void setUp() { this.registration1 = TestClientRegistrations.clientRegistration().build(); this.registration2 = TestClientRegistrations.clientRegistration2().build(); + + this.pkceClientRegistration = pkceClientRegistration().build(); + this.fineRedirectUriTemplateRegistration = fineRedirectUriTemplateClientRegistration().build(); // @formatter:off this.publicClientRegistration = TestClientRegistrations.clientRegistration() @@ -86,8 +92,8 @@ public void setUp() { .build(); // @formatter:on this.clientRegistrationRepository = new InMemoryClientRegistrationRepository(this.registration1, - this.registration2, this.fineRedirectUriTemplateRegistration, this.publicClientRegistration, - this.oidcRegistration); + this.registration2, this.pkceClientRegistration, this.fineRedirectUriTemplateRegistration, + this.publicClientRegistration, this.oidcRegistration); this.resolver = new DefaultOAuth2AuthorizationRequestResolver(this.clientRegistrationRepository, this.authorizationRequestBaseUri); } @@ -563,6 +569,32 @@ public void resolveWhenAuthorizationRequestCustomizerOverridesParameterThenQuery + "nonce=([a-zA-Z0-9\\-\\.\\_\\~]){43}&" + "appid=client-id"); } + @Test + public void resolveWhenAuthorizationRequestProvideCodeChallengeMethod() { + ClientRegistration clientRegistration = this.pkceClientRegistration; + String requestUri = this.authorizationRequestBaseUri + "/" + clientRegistration.getRegistrationId(); + MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); + request.setServletPath(requestUri); + OAuth2AuthorizationRequest authorizationRequest = this.resolver.resolve(request); + assertThat(authorizationRequest.getAdditionalParameters().containsKey(PkceParameterNames.CODE_CHALLENGE_METHOD)) + .isTrue(); + } + + private static ClientRegistration.Builder pkceClientRegistration() { + return ClientRegistration.withRegistrationId("pkce") + .redirectUri("{baseUrl}/{action}/oauth2/code/{registrationId}") + .clientSettings(ClientSettings.builder().requireProofKey(true).build()) + .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) + .scope("read:user") + .authorizationUri("https://example.com/login/oauth/authorize") + .tokenUri("https://example.com/login/oauth/access_token") + .userInfoUri("https://api.example.com/user") + .userNameAttributeName("id") + .clientName("Client Name") + .clientId("client-id-3") + .clientSecret("client-secret"); + } + private static ClientRegistration.Builder fineRedirectUriTemplateClientRegistration() { // @formatter:off return ClientRegistration.withRegistrationId("fine-redirect-uri-template-client-registration") From b4608a99c5381d813758371b7598620158f02917 Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Fri, 17 Jan 2025 14:14:55 -0600 Subject: [PATCH 090/132] DefaultServerOAuth2AuthorizationRequestResolver requireProofKey support When requireProofKey=true, DefaultServerOAuth2AuthorizationRequestResolver enables PKCE support. Issue gh-16382 Signed-off-by: Daeho Kwon --- ...tServerOAuth2AuthorizationRequestResolver.java | 3 ++- ...erOAuth2AuthorizationRequestResolverTests.java | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/DefaultServerOAuth2AuthorizationRequestResolver.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/DefaultServerOAuth2AuthorizationRequestResolver.java index bb95dd20b7..0123a2aab7 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/DefaultServerOAuth2AuthorizationRequestResolver.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/DefaultServerOAuth2AuthorizationRequestResolver.java @@ -196,7 +196,8 @@ private OAuth2AuthorizationRequest.Builder getBuilder(ClientRegistration clientR // value. applyNonce(builder); } - if (ClientAuthenticationMethod.NONE.equals(clientRegistration.getClientAuthenticationMethod())) { + if (ClientAuthenticationMethod.NONE.equals(clientRegistration.getClientAuthenticationMethod()) + || clientRegistration.getClientSettings().isRequireProofKey()) { DEFAULT_PKCE_APPLIER.accept(builder); } return builder; diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/DefaultServerOAuth2AuthorizationRequestResolverTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/DefaultServerOAuth2AuthorizationRequestResolverTests.java index ec293997f5..9772ed1b61 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/DefaultServerOAuth2AuthorizationRequestResolverTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/DefaultServerOAuth2AuthorizationRequestResolverTests.java @@ -27,6 +27,7 @@ import org.springframework.mock.http.server.reactive.MockServerHttpRequest; import org.springframework.mock.web.server.MockServerWebExchange; import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.ClientSettings; import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestCustomizers; @@ -169,6 +170,20 @@ public void resolveWhenAuthorizationRequestApplyPkceToSpecificConfidentialClient assertPkceNotApplied(request, registration2); } + @Test + void resolveWhenRequireProofKeyTrueThenPkceEnabled() { + ClientSettings pkceEnabled = ClientSettings.builder().requireProofKey(true).build(); + ClientRegistration clientWithPkceEnabled = TestClientRegistrations.clientRegistration() + .clientSettings(pkceEnabled) + .build(); + given(this.clientRegistrationRepository.findByRegistrationId(any())) + .willReturn(Mono.just(clientWithPkceEnabled)); + + OAuth2AuthorizationRequest request = resolve( + "/oauth2/authorization/" + clientWithPkceEnabled.getRegistrationId()); + assertPkceApplied(request, clientWithPkceEnabled); + } + private void assertPkceApplied(OAuth2AuthorizationRequest authorizationRequest, ClientRegistration clientRegistration) { assertThat(authorizationRequest.getAdditionalParameters()).containsKey(PkceParameterNames.CODE_CHALLENGE); From bfc3bf0b043907b865e662fa4777511596713ec8 Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Fri, 17 Jan 2025 10:56:42 -0600 Subject: [PATCH 091/132] Ensure that ClientSettings cannot be null This ensures that ClientRegistration.Builder.ClientSettings cannot be null. This has a slight advantage in terms of null safety to making this check happen in the build method since the Builder does not have a null field either. Issue gh-16382 Signed-off-by: Daeho Kwon --- .../registration/ClientRegistration.java | 6 ++--- .../registration/ClientRegistrationTests.java | 23 +++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistration.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistration.java index c8fa34e682..8c5c09c140 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistration.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistration.java @@ -378,7 +378,7 @@ public static final class Builder implements Serializable { private String clientName; - private ClientSettings clientSettings; + private ClientSettings clientSettings = ClientSettings.builder().build(); private Builder(String registrationId) { this.registrationId = registrationId; @@ -614,6 +614,7 @@ public Builder clientName(String clientName) { * @return the {@link Builder} */ public Builder clientSettings(ClientSettings clientSettings) { + Assert.notNull(clientSettings, "clientSettings cannot be null"); this.clientSettings = clientSettings; return this; } @@ -651,8 +652,7 @@ private ClientRegistration create() { clientRegistration.providerDetails = createProviderDetails(clientRegistration); clientRegistration.clientName = StringUtils.hasText(this.clientName) ? this.clientName : this.registrationId; - clientRegistration.clientSettings = (this.clientSettings == null) ? ClientSettings.builder().build() - : this.clientSettings; + clientRegistration.clientSettings = this.clientSettings; return clientRegistration; } diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/ClientRegistrationTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/ClientRegistrationTests.java index 070e2040bd..2c4ee41a07 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/ClientRegistrationTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/ClientRegistrationTests.java @@ -753,4 +753,27 @@ public void buildWhenCustomClientAuthenticationMethodProvidedThenSet() { assertThat(clientRegistration.getClientAuthenticationMethod()).isEqualTo(clientAuthenticationMethod); } + @Test + void clientSettingsWhenNullThenThrowIllegalArgumentException() { + assertThatIllegalArgumentException() + .isThrownBy(() -> ClientRegistration.withRegistrationId(REGISTRATION_ID).clientSettings(null)); + } + + // gh-16382 + @Test + void buildWhenDefaultClientSettingsThenDefaulted() { + ClientRegistration clientRegistration = ClientRegistration.withRegistrationId(REGISTRATION_ID) + .clientId(CLIENT_ID) + .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) + .redirectUri(REDIRECT_URI) + .authorizationUri(AUTHORIZATION_URI) + .tokenUri(TOKEN_URI) + .build(); + + // should not be null + assertThat(clientRegistration.getClientSettings()).isNotNull(); + // proof key should be false for passivity + assertThat(clientRegistration.getClientSettings().isRequireProofKey()).isFalse(); + } + } From e8a53fd897c08b0304449c9fdc79dd8ab4975a29 Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Fri, 17 Jan 2025 14:13:30 -0600 Subject: [PATCH 092/132] ClientSettings equals, hashCode, toString Issue gh-16382 Signed-off-by: Daeho Kwon --- .../client/registration/ClientSettings.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientSettings.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientSettings.java index de9c4bf7b8..92c5f4b491 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientSettings.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientSettings.java @@ -16,6 +16,8 @@ package org.springframework.security.oauth2.client.registration; +import java.util.Objects; + /** * A facility for client configuration settings. * @@ -34,6 +36,27 @@ public boolean isRequireProofKey() { return this.requireProofKey; } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ClientSettings that)) { + return false; + } + return this.requireProofKey == that.requireProofKey; + } + + @Override + public int hashCode() { + return Objects.hashCode(this.requireProofKey); + } + + @Override + public String toString() { + return "ClientSettings{" + "requireProofKey=" + this.requireProofKey + '}'; + } + public static Builder builder() { return new Builder(); } From 25afd2e562a43b612c07a7507c72b0d9915603d7 Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Fri, 17 Jan 2025 10:58:08 -0600 Subject: [PATCH 093/132] Add AuthorizationGrantType.toString() This adds AuthorizationGrantType.toString() which makes debuging easier. In particular, it will help when performing unit tests which validate the AuthorizationGrantType. Issue gh-16382 Signed-off-by: Daeho Kwon --- .../security/oauth2/core/AuthorizationGrantType.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/AuthorizationGrantType.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/AuthorizationGrantType.java index e1321bd759..433811a781 100644 --- a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/AuthorizationGrantType.java +++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/AuthorizationGrantType.java @@ -111,4 +111,9 @@ public int hashCode() { return this.getValue().hashCode(); } + @Override + public String toString() { + return "AuthorizationGrantType{" + "value='" + this.value + '\'' + '}'; + } + } From c3c68282b17a45b3581add2b808d00572ec17c0f Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Fri, 17 Jan 2025 10:59:48 -0600 Subject: [PATCH 094/132] PKCE cannot be true and AuthorizationGrantType != AUTHORIZATION_CODE PKCE is only valid for AuthorizationGrantType.AUTHORIZATION_CODE so the code should validate this. Issue gh-16382 Signed-off-by: Daeho Kwon --- .../registration/ClientRegistration.java | 6 ++ .../registration/ClientRegistrationTests.java | 62 +++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistration.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistration.java index 8c5c09c140..199a54dca2 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistration.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistration.java @@ -711,6 +711,12 @@ private void validateAuthorizationGrantTypes() { "AuthorizationGrantType: %s does not match the pre-defined constant %s and won't match a valid OAuth2AuthorizedClientProvider", this.authorizationGrantType, authorizationGrantType)); } + if (!AuthorizationGrantType.AUTHORIZATION_CODE.equals(this.authorizationGrantType) + && this.clientSettings.isRequireProofKey()) { + throw new IllegalStateException( + "clientSettings.isRequireProofKey=true is only valid with authorizationGrantType=AUTHORIZATION_CODE. Got authorizationGrantType=" + + this.authorizationGrantType); + } } } diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/ClientRegistrationTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/ClientRegistrationTests.java index 2c4ee41a07..1816843286 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/ClientRegistrationTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/ClientRegistrationTests.java @@ -16,14 +16,20 @@ package org.springframework.security.oauth2.client.registration; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import org.springframework.security.oauth2.core.AuthenticationMethod; import org.springframework.security.oauth2.core.AuthorizationGrantType; @@ -31,6 +37,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; /** * Tests for {@link ClientRegistration}. @@ -776,4 +783,59 @@ void buildWhenDefaultClientSettingsThenDefaulted() { assertThat(clientRegistration.getClientSettings().isRequireProofKey()).isFalse(); } + // gh-16382 + @Test + void buildWhenNewAuthorizationCodeAndPkceThenBuilds() { + ClientSettings pkceEnabled = ClientSettings.builder().requireProofKey(true).build(); + ClientRegistration clientRegistration = ClientRegistration.withRegistrationId(REGISTRATION_ID) + .clientId(CLIENT_ID) + .clientSettings(pkceEnabled) + .authorizationGrantType(new AuthorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue())) + .redirectUri(REDIRECT_URI) + .authorizationUri(AUTHORIZATION_URI) + .tokenUri(TOKEN_URI) + .build(); + + // proof key should be false for passivity + assertThat(clientRegistration.getClientSettings().isRequireProofKey()).isTrue(); + } + + @ParameterizedTest + @MethodSource("invalidPkceGrantTypes") + void buildWhenInvalidGrantTypeForPkceThenException(AuthorizationGrantType invalidGrantType) { + ClientSettings pkceEnabled = ClientSettings.builder().requireProofKey(true).build(); + ClientRegistration.Builder builder = ClientRegistration.withRegistrationId(REGISTRATION_ID) + .clientId(CLIENT_ID) + .clientSettings(pkceEnabled) + .authorizationGrantType(invalidGrantType) + .redirectUri(REDIRECT_URI) + .authorizationUri(AUTHORIZATION_URI) + .tokenUri(TOKEN_URI); + + assertThatIllegalStateException().describedAs( + "clientSettings.isRequireProofKey=true is only valid with authorizationGrantType=AUTHORIZATION_CODE. Got authorizationGrantType={}", + invalidGrantType) + .isThrownBy(builder::build); + } + + static List invalidPkceGrantTypes() { + return Arrays.stream(AuthorizationGrantType.class.getFields()) + .filter((field) -> Modifier.isFinal(field.getModifiers()) + && field.getType() == AuthorizationGrantType.class) + .map((field) -> getStaticValue(field, AuthorizationGrantType.class)) + .filter((grantType) -> grantType != AuthorizationGrantType.AUTHORIZATION_CODE) + // ensure works with .equals + .map((grantType) -> new AuthorizationGrantType(grantType.getValue())) + .collect(Collectors.toList()); + } + + private static T getStaticValue(Field field, Class clazz) { + try { + return (T) field.get(null); + } + catch (IllegalAccessException ex) { + throw new RuntimeException(ex); + } + } + } From 4524fda093ac60d9d2af1358499232b61b5b21ac Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Fri, 17 Jan 2025 11:19:52 -0600 Subject: [PATCH 095/132] Ensure missing ClientRegistration.clientSettings JSON node works Issue gh-16382 Signed-off-by: Daeho Kwon --- .../OAuth2AuthorizedClientMixinTests.java | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/jackson2/OAuth2AuthorizedClientMixinTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/jackson2/OAuth2AuthorizedClientMixinTests.java index 47e4a2d2e3..d6d0e81927 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/jackson2/OAuth2AuthorizedClientMixinTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/jackson2/OAuth2AuthorizedClientMixinTests.java @@ -214,6 +214,71 @@ public void deserializeWhenRequiredAttributesOnlyThenDeserializes() throws Excep assertThat(authorizedClient.getRefreshToken()).isNull(); } + @Test + void deserializeWhenClientSettingsPropertyDoesNotExistThenDefaulted() throws JsonProcessingException { + // ClientRegistration.clientSettings was added later, so old values will be + // serialized without that property + // this test checks for passivity + ClientRegistration clientRegistration = this.clientRegistrationBuilder.build(); + ClientRegistration.ProviderDetails providerDetails = clientRegistration.getProviderDetails(); + ClientRegistration.ProviderDetails.UserInfoEndpoint userInfoEndpoint = providerDetails.getUserInfoEndpoint(); + String scopes = ""; + if (!CollectionUtils.isEmpty(clientRegistration.getScopes())) { + scopes = StringUtils.collectionToDelimitedString(clientRegistration.getScopes(), ",", "\"", "\""); + } + String configurationMetadata = "\"@class\": \"java.util.Collections$UnmodifiableMap\""; + if (!CollectionUtils.isEmpty(providerDetails.getConfigurationMetadata())) { + configurationMetadata += "," + providerDetails.getConfigurationMetadata() + .keySet() + .stream() + .map((key) -> "\"" + key + "\": \"" + providerDetails.getConfigurationMetadata().get(key) + "\"") + .collect(Collectors.joining(",")); + } + // @formatter:off + String json = "{\n" + + " \"@class\": \"org.springframework.security.oauth2.client.registration.ClientRegistration\",\n" + + " \"registrationId\": \"" + clientRegistration.getRegistrationId() + "\",\n" + + " \"clientId\": \"" + clientRegistration.getClientId() + "\",\n" + + " \"clientSecret\": \"" + clientRegistration.getClientSecret() + "\",\n" + + " \"clientAuthenticationMethod\": {\n" + + " \"value\": \"" + clientRegistration.getClientAuthenticationMethod().getValue() + "\"\n" + + " },\n" + + " \"authorizationGrantType\": {\n" + + " \"value\": \"" + clientRegistration.getAuthorizationGrantType().getValue() + "\"\n" + + " },\n" + + " \"redirectUri\": \"" + clientRegistration.getRedirectUri() + "\",\n" + + " \"scopes\": [\n" + + " \"java.util.Collections$UnmodifiableSet\",\n" + + " [" + scopes + "]\n" + + " ],\n" + + " \"providerDetails\": {\n" + + " \"@class\": \"org.springframework.security.oauth2.client.registration.ClientRegistration$ProviderDetails\",\n" + + " \"authorizationUri\": \"" + providerDetails.getAuthorizationUri() + "\",\n" + + " \"tokenUri\": \"" + providerDetails.getTokenUri() + "\",\n" + + " \"userInfoEndpoint\": {\n" + + " \"@class\": \"org.springframework.security.oauth2.client.registration.ClientRegistration$ProviderDetails$UserInfoEndpoint\",\n" + + " \"uri\": " + ((userInfoEndpoint.getUri() != null) ? "\"" + userInfoEndpoint.getUri() + "\"" : null) + ",\n" + + " \"authenticationMethod\": {\n" + + " \"value\": \"" + userInfoEndpoint.getAuthenticationMethod().getValue() + "\"\n" + + " },\n" + + " \"userNameAttributeName\": " + ((userInfoEndpoint.getUserNameAttributeName() != null) ? "\"" + userInfoEndpoint.getUserNameAttributeName() + "\"" : null) + "\n" + + " },\n" + + " \"jwkSetUri\": " + ((providerDetails.getJwkSetUri() != null) ? "\"" + providerDetails.getJwkSetUri() + "\"" : null) + ",\n" + + " \"issuerUri\": " + ((providerDetails.getIssuerUri() != null) ? "\"" + providerDetails.getIssuerUri() + "\"" : null) + ",\n" + + " \"configurationMetadata\": {\n" + + " " + configurationMetadata + "\n" + + " }\n" + + " },\n" + + " \"clientName\": \"" + clientRegistration.getClientName() + "\"\n" + + "}"; + // @formatter:on + // validate the test input + assertThat(json).doesNotContain("clientSettings"); + ClientRegistration registration = this.mapper.readValue(json, ClientRegistration.class); + // the default value of requireProofKey is false + assertThat(registration.getClientSettings().isRequireProofKey()).isFalse(); + } + private static String asJson(OAuth2AuthorizedClient authorizedClient) { // @formatter:off return "{\n" + From 879e218ee4d198cb56b513b4da3bdf4e67355597 Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Fri, 17 Jan 2025 17:18:30 -0600 Subject: [PATCH 096/132] Move ClientSettings to ClientRegistration Initially it was proposed to put ClientSettings as a top level class, but to be consistent with ProviderDetails, this commit moves ClientSettings to be an inner class of ClientRegistration Issue gh-16382 # Conflicts: # oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientSettings.java Signed-off-by: Daeho Kwon --- .../registration/ClientRegistration.java | 73 +++++++++++++++ .../client/registration/ClientSettings.java | 91 ------------------- .../registration/ClientRegistrationTests.java | 8 +- ...uth2AuthorizationRequestResolverTests.java | 3 +- ...uth2AuthorizationRequestResolverTests.java | 5 +- 5 files changed, 83 insertions(+), 97 deletions(-) delete mode 100644 oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientSettings.java diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistration.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistration.java index 199a54dca2..0b4179ff03 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistration.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistration.java @@ -26,6 +26,7 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import org.apache.commons.logging.Log; @@ -741,4 +742,76 @@ private static boolean withinTheRangeOf(int c, int min, int max) { } + /** + * A facility for client configuration settings. + * + * @author DingHao + * @since 6.5 + */ + public static final class ClientSettings { + + private boolean requireProofKey; + + private ClientSettings() { + + } + + public boolean isRequireProofKey() { + return this.requireProofKey; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ClientSettings that)) { + return false; + } + return this.requireProofKey == that.requireProofKey; + } + + @Override + public int hashCode() { + return Objects.hashCode(this.requireProofKey); + } + + @Override + public String toString() { + return "ClientSettings{" + "requireProofKey=" + this.requireProofKey + '}'; + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + + private boolean requireProofKey; + + private Builder() { + } + + /** + * Set to {@code true} if the client is required to provide a proof key + * challenge and verifier when performing the Authorization Code Grant flow. + * @param requireProofKey {@code true} if the client is required to provide a + * proof key challenge and verifier, {@code false} otherwise + * @return the {@link Builder} for further configuration + */ + public Builder requireProofKey(boolean requireProofKey) { + this.requireProofKey = requireProofKey; + return this; + } + + public ClientSettings build() { + ClientSettings clientSettings = new ClientSettings(); + clientSettings.requireProofKey = this.requireProofKey; + return clientSettings; + } + + } + + } + } diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientSettings.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientSettings.java deleted file mode 100644 index 92c5f4b491..0000000000 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientSettings.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2002-2025 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.oauth2.client.registration; - -import java.util.Objects; - -/** - * A facility for client configuration settings. - * - * @author DingHao - * @since 6.5 - */ -public final class ClientSettings { - - private boolean requireProofKey; - - private ClientSettings() { - - } - - public boolean isRequireProofKey() { - return this.requireProofKey; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof ClientSettings that)) { - return false; - } - return this.requireProofKey == that.requireProofKey; - } - - @Override - public int hashCode() { - return Objects.hashCode(this.requireProofKey); - } - - @Override - public String toString() { - return "ClientSettings{" + "requireProofKey=" + this.requireProofKey + '}'; - } - - public static Builder builder() { - return new Builder(); - } - - public static final class Builder { - - private boolean requireProofKey; - - private Builder() { - } - - /** - * Set to {@code true} if the client is required to provide a proof key challenge - * and verifier when performing the Authorization Code Grant flow. - * @param requireProofKey {@code true} if the client is required to provide a - * proof key challenge and verifier, {@code false} otherwise - * @return the {@link Builder} for further configuration - */ - public Builder requireProofKey(boolean requireProofKey) { - this.requireProofKey = requireProofKey; - return this; - } - - public ClientSettings build() { - ClientSettings clientSettings = new ClientSettings(); - clientSettings.requireProofKey = this.requireProofKey; - return clientSettings; - } - - } - -} diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/ClientRegistrationTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/ClientRegistrationTests.java index 1816843286..9dbcbd5a5c 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/ClientRegistrationTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/ClientRegistrationTests.java @@ -786,7 +786,9 @@ void buildWhenDefaultClientSettingsThenDefaulted() { // gh-16382 @Test void buildWhenNewAuthorizationCodeAndPkceThenBuilds() { - ClientSettings pkceEnabled = ClientSettings.builder().requireProofKey(true).build(); + ClientRegistration.ClientSettings pkceEnabled = ClientRegistration.ClientSettings.builder() + .requireProofKey(true) + .build(); ClientRegistration clientRegistration = ClientRegistration.withRegistrationId(REGISTRATION_ID) .clientId(CLIENT_ID) .clientSettings(pkceEnabled) @@ -803,7 +805,9 @@ void buildWhenNewAuthorizationCodeAndPkceThenBuilds() { @ParameterizedTest @MethodSource("invalidPkceGrantTypes") void buildWhenInvalidGrantTypeForPkceThenException(AuthorizationGrantType invalidGrantType) { - ClientSettings pkceEnabled = ClientSettings.builder().requireProofKey(true).build(); + ClientRegistration.ClientSettings pkceEnabled = ClientRegistration.ClientSettings.builder() + .requireProofKey(true) + .build(); ClientRegistration.Builder builder = ClientRegistration.withRegistrationId(REGISTRATION_ID) .clientId(CLIENT_ID) .clientSettings(pkceEnabled) diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizationRequestResolverTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizationRequestResolverTests.java index d0d260922c..a0abf7132e 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizationRequestResolverTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizationRequestResolverTests.java @@ -28,7 +28,6 @@ import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; -import org.springframework.security.oauth2.client.registration.ClientSettings; import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.core.AuthorizationGrantType; @@ -583,7 +582,7 @@ public void resolveWhenAuthorizationRequestProvideCodeChallengeMethod() { private static ClientRegistration.Builder pkceClientRegistration() { return ClientRegistration.withRegistrationId("pkce") .redirectUri("{baseUrl}/{action}/oauth2/code/{registrationId}") - .clientSettings(ClientSettings.builder().requireProofKey(true).build()) + .clientSettings(ClientRegistration.ClientSettings.builder().requireProofKey(true).build()) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .scope("read:user") .authorizationUri("https://example.com/login/oauth/authorize") diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/DefaultServerOAuth2AuthorizationRequestResolverTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/DefaultServerOAuth2AuthorizationRequestResolverTests.java index 9772ed1b61..bf7ab09678 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/DefaultServerOAuth2AuthorizationRequestResolverTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/DefaultServerOAuth2AuthorizationRequestResolverTests.java @@ -27,7 +27,6 @@ import org.springframework.mock.http.server.reactive.MockServerHttpRequest; import org.springframework.mock.web.server.MockServerWebExchange; import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.client.registration.ClientSettings; import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestCustomizers; @@ -172,7 +171,9 @@ public void resolveWhenAuthorizationRequestApplyPkceToSpecificConfidentialClient @Test void resolveWhenRequireProofKeyTrueThenPkceEnabled() { - ClientSettings pkceEnabled = ClientSettings.builder().requireProofKey(true).build(); + ClientRegistration.ClientSettings pkceEnabled = ClientRegistration.ClientSettings.builder() + .requireProofKey(true) + .build(); ClientRegistration clientWithPkceEnabled = TestClientRegistrations.clientRegistration() .clientSettings(pkceEnabled) .build(); From d86723196544afc7e988d6cea9259209aa940dab Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Fri, 17 Jan 2025 16:43:27 -0600 Subject: [PATCH 097/132] Document requireProofKey Issue gh-16386 Signed-off-by: Daeho Kwon --- .../pages/reactive/oauth2/client/authorization-grants.adoc | 4 ++++ docs/modules/ROOT/pages/reactive/oauth2/client/core.adoc | 5 +++++ .../pages/servlet/oauth2/client/authorization-grants.adoc | 7 ++++++- docs/modules/ROOT/pages/servlet/oauth2/client/core.adoc | 5 +++++ docs/modules/ROOT/pages/whats-new.adoc | 4 ++++ 5 files changed, 24 insertions(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/reactive/oauth2/client/authorization-grants.adoc b/docs/modules/ROOT/pages/reactive/oauth2/client/authorization-grants.adoc index bd002f31e8..59def321a4 100644 --- a/docs/modules/ROOT/pages/reactive/oauth2/client/authorization-grants.adoc +++ b/docs/modules/ROOT/pages/reactive/oauth2/client/authorization-grants.adoc @@ -79,6 +79,10 @@ If the client is running in an untrusted environment (eg. native application or . `client-secret` is omitted (or empty) . `client-authentication-method` is set to "none" (`ClientAuthenticationMethod.NONE`) +or + +. When `ClientRegistration.clientSettings.requireProofKey` is `true` (in this case `ClientRegistration.authorizationGrantType` must be `authorization_code`) + [TIP] ==== If the OAuth 2.0 Provider supports PKCE for https://tools.ietf.org/html/rfc6749#section-2.1[Confidential Clients], you may (optionally) configure it using `DefaultServerOAuth2AuthorizationRequestResolver.setAuthorizationRequestCustomizer(OAuth2AuthorizationRequestCustomizers.withPkce())`. diff --git a/docs/modules/ROOT/pages/reactive/oauth2/client/core.adoc b/docs/modules/ROOT/pages/reactive/oauth2/client/core.adoc index b6eee52585..e1ca19df49 100644 --- a/docs/modules/ROOT/pages/reactive/oauth2/client/core.adoc +++ b/docs/modules/ROOT/pages/reactive/oauth2/client/core.adoc @@ -39,6 +39,10 @@ public final class ClientRegistration { } } + + public static final class ClientSettings { + private boolean requireProofKey; // <17> + } } ---- <1> `registrationId`: The ID that uniquely identifies the `ClientRegistration`. @@ -64,6 +68,7 @@ The name may be used in certain scenarios, such as when displaying the name of t <15> `(userInfoEndpoint)authenticationMethod`: The authentication method used when sending the access token to the UserInfo Endpoint. The supported values are *header*, *form* and *query*. <16> `userNameAttributeName`: The name of the attribute returned in the UserInfo Response that references the Name or Identifier of the end-user. +<17> [[oauth2Client-client-registration-requireProofKey]]`requireProofKey`: If `true` or if `authorizationGrantType` is `none`, then PKCE will be enabled by default. A `ClientRegistration` can be initially configured using discovery of an OpenID Connect Provider's https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig[Configuration endpoint] or an Authorization Server's https://tools.ietf.org/html/rfc8414#section-3[Metadata endpoint]. diff --git a/docs/modules/ROOT/pages/servlet/oauth2/client/authorization-grants.adoc b/docs/modules/ROOT/pages/servlet/oauth2/client/authorization-grants.adoc index 4cab6a8472..f2f14be45e 100644 --- a/docs/modules/ROOT/pages/servlet/oauth2/client/authorization-grants.adoc +++ b/docs/modules/ROOT/pages/servlet/oauth2/client/authorization-grants.adoc @@ -77,9 +77,14 @@ spring: Public Clients are supported by using https://tools.ietf.org/html/rfc7636[Proof Key for Code Exchange] (PKCE). If the client is running in an untrusted environment (such as a native application or web browser-based application) and is therefore incapable of maintaining the confidentiality of its credentials, PKCE is automatically used when the following conditions are true: -. `client-secret` is omitted (or empty) +. `client-secret` is omitted (or empty) and . `client-authentication-method` is set to `none` (`ClientAuthenticationMethod.NONE`) +or + +. When `ClientRegistration.clientSettings.requireProofKey` is `true` (in this case `ClientRegistration.authorizationGrantType` must be `authorization_code`) + + [TIP] ==== If the OAuth 2.0 Provider supports PKCE for https://tools.ietf.org/html/rfc6749#section-2.1[Confidential Clients], you may (optionally) configure it using `DefaultOAuth2AuthorizationRequestResolver.setAuthorizationRequestCustomizer(OAuth2AuthorizationRequestCustomizers.withPkce())`. diff --git a/docs/modules/ROOT/pages/servlet/oauth2/client/core.adoc b/docs/modules/ROOT/pages/servlet/oauth2/client/core.adoc index 375c8a12a8..0418877371 100644 --- a/docs/modules/ROOT/pages/servlet/oauth2/client/core.adoc +++ b/docs/modules/ROOT/pages/servlet/oauth2/client/core.adoc @@ -40,6 +40,10 @@ public final class ClientRegistration { } } + + public static final class ClientSettings { + private boolean requireProofKey; // <17> + } } ---- <1> `registrationId`: The ID that uniquely identifies the `ClientRegistration`. @@ -65,6 +69,7 @@ This information is available only if the Spring Boot property `spring.security. <15> `(userInfoEndpoint)authenticationMethod`: The authentication method used when sending the access token to the UserInfo Endpoint. The supported values are *header*, *form*, and *query*. <16> `userNameAttributeName`: The name of the attribute returned in the UserInfo Response that references the Name or Identifier of the end-user. +<17> [[oauth2Client-client-registration-requireProofKey]]`requireProofKey`: If `true` or if `authorizationGrantType` is `none`, then PKCE will be enabled by default. You can initially configure a `ClientRegistration` by using discovery of an OpenID Connect Provider's https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig[Configuration endpoint] or an Authorization Server's https://tools.ietf.org/html/rfc8414#section-3[Metadata endpoint]. diff --git a/docs/modules/ROOT/pages/whats-new.adoc b/docs/modules/ROOT/pages/whats-new.adoc index c6f44092c2..0fc13497b5 100644 --- a/docs/modules/ROOT/pages/whats-new.adoc +++ b/docs/modules/ROOT/pages/whats-new.adoc @@ -10,3 +10,7 @@ Below are the highlights of the release, or you can view https://github.com/spri The `security.security.reached.filter.section` key name was corrected to `spring.security.reached.filter.section`. Note that this may affect reports that operate on this key name. + +== OAuth + +* https://github.com/spring-projects/spring-security/pull/16386[gh-16386] - Enable PKCE for confidential clients using `ClientRegistration.clientSettings.requireProofKey=true` for xref:servlet/oauth2/client/core.adoc#oauth2Client-client-registration-requireProofKey[servlet] and xref:reactive/oauth2/client/core.adoc#oauth2Client-client-registration-requireProofKey[reactive] applications From e543ae6dc0e83933410a5a2a94c282e4e09c96ff Mon Sep 17 00:00:00 2001 From: DingHao Date: Sun, 12 Jan 2025 14:47:56 +0800 Subject: [PATCH 098/132] Set HttpMessageConverter by DSL Closes gh-16369 Signed-off-by: DingHao Signed-off-by: Daeho Kwon --- .../web/configurers/WebAuthnConfigurer.java | 27 ++++++- .../configurers/WebAuthnConfigurerTests.java | 79 ++++++++++++++++++- ...licKeyCredentialCreationOptionsFilter.java | 17 +++- 3 files changed, 117 insertions(+), 6 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurer.java index 1a955e523d..9ca5a3f6e7 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -23,6 +23,7 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.context.ApplicationContext; +import org.springframework.http.converter.HttpMessageConverter; import org.springframework.security.authentication.ProviderManager; import org.springframework.security.config.annotation.web.HttpSecurityBuilder; import org.springframework.security.core.userdetails.UserDetailsService; @@ -63,6 +64,8 @@ public class WebAuthnConfigurer> private boolean disableDefaultRegistrationPage = false; + private HttpMessageConverter converter; + /** * The Relying Party id. * @param rpId the relying party id @@ -116,6 +119,16 @@ public WebAuthnConfigurer disableDefaultRegistrationPage(boolean disable) { return this; } + /** + * Sets PublicKeyCredentialCreationOptionsRepository + * @param converter the creationOptionsRepository + * @return the {@link WebAuthnConfigurer} for further customization + */ + public WebAuthnConfigurer messageConverter(HttpMessageConverter converter) { + this.converter = converter; + return this; + } + @Override public void configure(H http) throws Exception { UserDetailsService userDetailsService = getSharedOrBean(http, UserDetailsService.class).orElseGet(() -> { @@ -130,9 +143,17 @@ public void configure(H http) throws Exception { WebAuthnAuthenticationFilter webAuthnAuthnFilter = new WebAuthnAuthenticationFilter(); webAuthnAuthnFilter.setAuthenticationManager( new ProviderManager(new WebAuthnAuthenticationProvider(rpOperations, userDetailsService))); + WebAuthnRegistrationFilter webAuthnRegistrationFilter = new WebAuthnRegistrationFilter(userCredentials, + rpOperations); + PublicKeyCredentialCreationOptionsFilter creationOptionsFilter = new PublicKeyCredentialCreationOptionsFilter( + rpOperations); + if (this.converter != null) { + webAuthnRegistrationFilter.setConverter(this.converter); + creationOptionsFilter.setConverter(this.converter); + } http.addFilterBefore(webAuthnAuthnFilter, BasicAuthenticationFilter.class); - http.addFilterAfter(new WebAuthnRegistrationFilter(userCredentials, rpOperations), AuthorizationFilter.class); - http.addFilterBefore(new PublicKeyCredentialCreationOptionsFilter(rpOperations), AuthorizationFilter.class); + http.addFilterAfter(webAuthnRegistrationFilter, AuthorizationFilter.class); + http.addFilterBefore(creationOptionsFilter, AuthorizationFilter.class); http.addFilterBefore(new PublicKeyCredentialRequestOptionsFilter(rpOperations), AuthorizationFilter.class); DefaultLoginPageGeneratingFilter loginPageGeneratingFilter = http diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurerTests.java index a90c43f312..1e46d03c3f 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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,6 +16,7 @@ package org.springframework.security.config.annotation.web.configurers; +import java.io.IOException; import java.util.List; import org.junit.jupiter.api.Test; @@ -24,21 +25,37 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpInputMessage; +import org.springframework.http.HttpOutputMessage; +import org.springframework.http.converter.AbstractHttpMessageConverter; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.http.converter.HttpMessageNotWritableException; +import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextImpl; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.ui.DefaultResourcesFilter; +import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions; +import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialCreationOptions; +import org.springframework.security.web.webauthn.management.WebAuthnRelyingPartyOperations; import org.springframework.test.web.servlet.MockMvc; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.containsString; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -126,6 +143,66 @@ public void webauthnWhenConfiguredAndNoDefaultRegistrationPageThenDoesNotServeJa this.mvc.perform(get("/login/webauthn.js")).andExpect(status().isNotFound()); } + @Test + public void webauthnWhenConfiguredMessageConverter() throws Exception { + TestingAuthenticationToken user = new TestingAuthenticationToken("user", "password", "ROLE_USER"); + SecurityContextHolder.setContext(new SecurityContextImpl(user)); + PublicKeyCredentialCreationOptions options = TestPublicKeyCredentialCreationOptions + .createPublicKeyCredentialCreationOptions() + .build(); + WebAuthnRelyingPartyOperations rpOperations = mock(WebAuthnRelyingPartyOperations.class); + ConfigMessageConverter.rpOperations = rpOperations; + given(rpOperations.createPublicKeyCredentialCreationOptions(any())).willReturn(options); + HttpMessageConverter converter = new AbstractHttpMessageConverter<>() { + @Override + protected boolean supports(Class clazz) { + return true; + } + + @Override + protected Object readInternal(Class clazz, HttpInputMessage inputMessage) + throws IOException, HttpMessageNotReadableException { + return null; + } + + @Override + protected void writeInternal(Object o, HttpOutputMessage outputMessage) + throws IOException, HttpMessageNotWritableException { + outputMessage.getBody().write("123".getBytes()); + } + }; + ConfigMessageConverter.converter = converter; + this.spring.register(ConfigMessageConverter.class).autowire(); + this.mvc.perform(post("/webauthn/register/options")) + .andExpect(status().isOk()) + .andExpect(content().string("123")); + } + + @Configuration + @EnableWebSecurity + static class ConfigMessageConverter { + + private static HttpMessageConverter converter; + + private static WebAuthnRelyingPartyOperations rpOperations; + + @Bean + WebAuthnRelyingPartyOperations webAuthnRelyingPartyOperations() { + return ConfigMessageConverter.rpOperations; + } + + @Bean + UserDetailsService userDetailsService() { + return new InMemoryUserDetailsManager(); + } + + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + return http.csrf(AbstractHttpConfigurer::disable).webAuthn((c) -> c.messageConverter(converter)).build(); + } + + } + @Configuration @EnableWebSecurity static class DefaultWebauthnConfiguration { diff --git a/web/src/main/java/org/springframework/security/web/webauthn/registration/PublicKeyCredentialCreationOptionsFilter.java b/web/src/main/java/org/springframework/security/web/webauthn/registration/PublicKeyCredentialCreationOptionsFilter.java index 3f163b0cc2..965408621f 100644 --- a/web/src/main/java/org/springframework/security/web/webauthn/registration/PublicKeyCredentialCreationOptionsFilter.java +++ b/web/src/main/java/org/springframework/security/web/webauthn/registration/PublicKeyCredentialCreationOptionsFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -53,6 +53,8 @@ * {@link PublicKeyCredentialCreationOptions} for creating * a new credential. + * + * @author DingHao */ public class PublicKeyCredentialCreationOptionsFilter extends OncePerRequestFilter { @@ -67,7 +69,7 @@ public class PublicKeyCredentialCreationOptionsFilter extends OncePerRequestFilt private final WebAuthnRelyingPartyOperations rpOperations; - private final HttpMessageConverter converter = new MappingJackson2HttpMessageConverter( + private HttpMessageConverter converter = new MappingJackson2HttpMessageConverter( Jackson2ObjectMapperBuilder.json().modules(new WebauthnJackson2Module()).build()); /** @@ -103,4 +105,15 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse this.converter.write(options, MediaType.APPLICATION_JSON, new ServletServerHttpResponse(response)); } + /** + * Set the {@link HttpMessageConverter} to read the + * {@link WebAuthnRegistrationFilter.WebAuthnRegistrationRequest} and write the + * response. The default is {@link MappingJackson2HttpMessageConverter}. + * @param converter the {@link HttpMessageConverter} to use. Cannot be null. + */ + public void setConverter(HttpMessageConverter converter) { + Assert.notNull(converter, "converter cannot be null"); + this.converter = converter; + } + } From 94d92ac196a07693aebb4deb65a50e86ce3e73c1 Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Fri, 17 Jan 2025 18:28:26 -0600 Subject: [PATCH 099/132] Fix WebAuthnConfigurer Javadoc Issue gh-16397 Signed-off-by: Daeho Kwon --- .../annotation/web/configurers/WebAuthnConfigurer.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurer.java index 9ca5a3f6e7..79164a082c 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurer.java @@ -120,8 +120,9 @@ public WebAuthnConfigurer disableDefaultRegistrationPage(boolean disable) { } /** - * Sets PublicKeyCredentialCreationOptionsRepository - * @param converter the creationOptionsRepository + * Sets {@link HttpMessageConverter} used for WebAuthn to read/write to the HTTP + * request/response. + * @param converter the {@link HttpMessageConverter} * @return the {@link WebAuthnConfigurer} for further customization */ public WebAuthnConfigurer messageConverter(HttpMessageConverter converter) { From 6d531848e339b953b1d133a6cfa7238818da9074 Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Fri, 17 Jan 2025 18:29:07 -0600 Subject: [PATCH 100/132] webauthnWhenConfiguredMessageConverter uses mock Issue gh-16397 Signed-off-by: Daeho Kwon --- .../configurers/WebAuthnConfigurerTests.java | 35 ++++++------------- 1 file changed, 11 insertions(+), 24 deletions(-) diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurerTests.java index 1e46d03c3f..f51b20a094 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurerTests.java @@ -16,7 +16,7 @@ package org.springframework.security.config.annotation.web.configurers; -import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.List; import org.junit.jupiter.api.Test; @@ -25,12 +25,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; -import org.springframework.http.converter.AbstractHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.http.converter.HttpMessageNotReadableException; -import org.springframework.http.converter.HttpMessageNotWritableException; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -53,6 +49,7 @@ import static org.hamcrest.Matchers.containsString; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.willAnswer; import static org.mockito.Mockito.mock; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; @@ -153,29 +150,19 @@ public void webauthnWhenConfiguredMessageConverter() throws Exception { WebAuthnRelyingPartyOperations rpOperations = mock(WebAuthnRelyingPartyOperations.class); ConfigMessageConverter.rpOperations = rpOperations; given(rpOperations.createPublicKeyCredentialCreationOptions(any())).willReturn(options); - HttpMessageConverter converter = new AbstractHttpMessageConverter<>() { - @Override - protected boolean supports(Class clazz) { - return true; - } - - @Override - protected Object readInternal(Class clazz, HttpInputMessage inputMessage) - throws IOException, HttpMessageNotReadableException { - return null; - } - - @Override - protected void writeInternal(Object o, HttpOutputMessage outputMessage) - throws IOException, HttpMessageNotWritableException { - outputMessage.getBody().write("123".getBytes()); - } - }; + HttpMessageConverter converter = mock(HttpMessageConverter.class); + given(converter.canWrite(any(), any())).willReturn(true); + String expectedBody = "123"; + willAnswer((args) -> { + HttpOutputMessage out = (HttpOutputMessage) args.getArguments()[2]; + out.getBody().write(expectedBody.getBytes(StandardCharsets.UTF_8)); + return null; + }).given(converter).write(any(), any(), any()); ConfigMessageConverter.converter = converter; this.spring.register(ConfigMessageConverter.class).autowire(); this.mvc.perform(post("/webauthn/register/options")) .andExpect(status().isOk()) - .andExpect(content().string("123")); + .andExpect(content().string(expectedBody)); } @Configuration From 7702681b8f238a2406f5c3659c302ece933057e9 Mon Sep 17 00:00:00 2001 From: DingHao Date: Sun, 12 Jan 2025 14:11:02 +0800 Subject: [PATCH 101/132] Set PublicKeyCredentialCreationOptionsRepository by DSL or Bean Closes gh-16369 Signed-off-by: DingHao Signed-off-by: Daeho Kwon --- .../web/configurers/WebAuthnConfigurer.java | 27 +++++ .../configurers/WebAuthnConfigurerTests.java | 105 +++++++++++++++++- ...licKeyCredentialCreationOptionsFilter.java | 11 ++ 3 files changed, 140 insertions(+), 3 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurer.java index 79164a082c..104a0be328 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurer.java @@ -44,6 +44,7 @@ import org.springframework.security.web.webauthn.management.Webauthn4JRelyingPartyOperations; import org.springframework.security.web.webauthn.registration.DefaultWebAuthnRegistrationPageGeneratingFilter; import org.springframework.security.web.webauthn.registration.PublicKeyCredentialCreationOptionsFilter; +import org.springframework.security.web.webauthn.registration.PublicKeyCredentialCreationOptionsRepository; import org.springframework.security.web.webauthn.registration.WebAuthnRegistrationFilter; /** @@ -64,6 +65,8 @@ public class WebAuthnConfigurer> private boolean disableDefaultRegistrationPage = false; + private PublicKeyCredentialCreationOptionsRepository creationOptionsRepository; + private HttpMessageConverter converter; /** @@ -130,6 +133,17 @@ public WebAuthnConfigurer messageConverter(HttpMessageConverter conve return this; } + /** + * Sets PublicKeyCredentialCreationOptionsRepository + * @param creationOptionsRepository the creationOptionsRepository + * @return the {@link WebAuthnConfigurer} for further customization + */ + public WebAuthnConfigurer creationOptionsRepository( + PublicKeyCredentialCreationOptionsRepository creationOptionsRepository) { + this.creationOptionsRepository = creationOptionsRepository; + return this; + } + @Override public void configure(H http) throws Exception { UserDetailsService userDetailsService = getSharedOrBean(http, UserDetailsService.class).orElseGet(() -> { @@ -141,6 +155,7 @@ public void configure(H http) throws Exception { UserCredentialRepository userCredentials = getSharedOrBean(http, UserCredentialRepository.class) .orElse(userCredentialRepository()); WebAuthnRelyingPartyOperations rpOperations = webAuthnRelyingPartyOperations(userEntities, userCredentials); + PublicKeyCredentialCreationOptionsRepository creationOptionsRepository = creationOptionsRepository(); WebAuthnAuthenticationFilter webAuthnAuthnFilter = new WebAuthnAuthenticationFilter(); webAuthnAuthnFilter.setAuthenticationManager( new ProviderManager(new WebAuthnAuthenticationProvider(rpOperations, userDetailsService))); @@ -148,6 +163,10 @@ public void configure(H http) throws Exception { rpOperations); PublicKeyCredentialCreationOptionsFilter creationOptionsFilter = new PublicKeyCredentialCreationOptionsFilter( rpOperations); + if (creationOptionsRepository != null) { + webAuthnRegistrationFilter.setCreationOptionsRepository(creationOptionsRepository); + creationOptionsFilter.setCreationOptionsRepository(creationOptionsRepository); + } if (this.converter != null) { webAuthnRegistrationFilter.setConverter(this.converter); creationOptionsFilter.setConverter(this.converter); @@ -181,6 +200,14 @@ public void configure(H http) throws Exception { } } + private PublicKeyCredentialCreationOptionsRepository creationOptionsRepository() { + if (this.creationOptionsRepository != null) { + return this.creationOptionsRepository; + } + ApplicationContext context = getBuilder().getSharedObject(ApplicationContext.class); + return context.getBeanProvider(PublicKeyCredentialCreationOptionsRepository.class).getIfUnique(); + } + private Optional getSharedOrBean(H http, Class type) { C shared = http.getSharedObject(type); return Optional.ofNullable(shared).or(() -> getBeanOrNull(type)); diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurerTests.java index f51b20a094..37665b38da 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurerTests.java @@ -43,6 +43,7 @@ import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions; import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialCreationOptions; import org.springframework.security.web.webauthn.management.WebAuthnRelyingPartyOperations; +import org.springframework.security.web.webauthn.registration.HttpSessionPublicKeyCredentialCreationOptionsRepository; import org.springframework.test.web.servlet.MockMvc; import static org.assertj.core.api.Assertions.assertThat; @@ -55,6 +56,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** @@ -141,13 +143,53 @@ public void webauthnWhenConfiguredAndNoDefaultRegistrationPageThenDoesNotServeJa } @Test - public void webauthnWhenConfiguredMessageConverter() throws Exception { + public void webauthnWhenConfiguredPublicKeyCredentialCreationOptionsRepository() throws Exception { + TestingAuthenticationToken user = new TestingAuthenticationToken("user", "password", "ROLE_USER"); + SecurityContextHolder.setContext(new SecurityContextImpl(user)); + PublicKeyCredentialCreationOptions options = TestPublicKeyCredentialCreationOptions + .createPublicKeyCredentialCreationOptions() + .build(); + WebAuthnRelyingPartyOperations rpOperations = mock(WebAuthnRelyingPartyOperations.class); + ConfigCredentialCreationOptionsRepository.rpOperations = rpOperations; + given(rpOperations.createPublicKeyCredentialCreationOptions(any())).willReturn(options); + String attrName = "attrName"; + HttpSessionPublicKeyCredentialCreationOptionsRepository creationOptionsRepository = new HttpSessionPublicKeyCredentialCreationOptionsRepository(); + creationOptionsRepository.setAttrName(attrName); + ConfigCredentialCreationOptionsRepository.creationOptionsRepository = creationOptionsRepository; + this.spring.register(ConfigCredentialCreationOptionsRepository.class).autowire(); + this.mvc.perform(post("/webauthn/register/options")) + .andExpect(status().isOk()) + .andExpect(request().sessionAttribute(attrName, options)); + } + + @Test + public void webauthnWhenConfiguredPublicKeyCredentialCreationOptionsRepositoryBeanPresent() throws Exception { TestingAuthenticationToken user = new TestingAuthenticationToken("user", "password", "ROLE_USER"); SecurityContextHolder.setContext(new SecurityContextImpl(user)); PublicKeyCredentialCreationOptions options = TestPublicKeyCredentialCreationOptions .createPublicKeyCredentialCreationOptions() .build(); WebAuthnRelyingPartyOperations rpOperations = mock(WebAuthnRelyingPartyOperations.class); + ConfigCredentialCreationOptionsRepositoryFromBean.rpOperations = rpOperations; + given(rpOperations.createPublicKeyCredentialCreationOptions(any())).willReturn(options); + String attrName = "attrName"; + HttpSessionPublicKeyCredentialCreationOptionsRepository creationOptionsRepository = new HttpSessionPublicKeyCredentialCreationOptionsRepository(); + creationOptionsRepository.setAttrName(attrName); + ConfigCredentialCreationOptionsRepositoryFromBean.creationOptionsRepository = creationOptionsRepository; + this.spring.register(ConfigCredentialCreationOptionsRepositoryFromBean.class).autowire(); + this.mvc.perform(post("/webauthn/register/options")) + .andExpect(status().isOk()) + .andExpect(request().sessionAttribute(attrName, options)); + } + + @Test + public void webauthnWhenConfiguredMessageConverter() throws Exception { + TestingAuthenticationToken user = new TestingAuthenticationToken("user", "password", "ROLE_USER"); + SecurityContextHolder.setContext(new SecurityContextImpl(user)); + PublicKeyCredentialCreationOptions options = TestPublicKeyCredentialCreationOptions + .createPublicKeyCredentialCreationOptions() + .build(); + WebAuthnRelyingPartyOperations rpOperations = mock(WebAuthnRelyingPartyOperations.class); ConfigMessageConverter.rpOperations = rpOperations; given(rpOperations.createPublicKeyCredentialCreationOptions(any())).willReturn(options); HttpMessageConverter converter = mock(HttpMessageConverter.class); @@ -161,8 +203,65 @@ public void webauthnWhenConfiguredMessageConverter() throws Exception { ConfigMessageConverter.converter = converter; this.spring.register(ConfigMessageConverter.class).autowire(); this.mvc.perform(post("/webauthn/register/options")) - .andExpect(status().isOk()) - .andExpect(content().string(expectedBody)); + .andExpect(status().isOk()) + .andExpect(content().string(expectedBody)); + } + + @Configuration + @EnableWebSecurity + static class ConfigCredentialCreationOptionsRepository { + + private static HttpSessionPublicKeyCredentialCreationOptionsRepository creationOptionsRepository; + + private static WebAuthnRelyingPartyOperations rpOperations; + + @Bean + WebAuthnRelyingPartyOperations webAuthnRelyingPartyOperations() { + return ConfigCredentialCreationOptionsRepository.rpOperations; + } + + @Bean + UserDetailsService userDetailsService() { + return new InMemoryUserDetailsManager(); + } + + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + return http.csrf(AbstractHttpConfigurer::disable) + .webAuthn((c) -> c.creationOptionsRepository(creationOptionsRepository)) + .build(); + } + + } + + @Configuration + @EnableWebSecurity + static class ConfigCredentialCreationOptionsRepositoryFromBean { + + private static HttpSessionPublicKeyCredentialCreationOptionsRepository creationOptionsRepository; + + private static WebAuthnRelyingPartyOperations rpOperations; + + @Bean + WebAuthnRelyingPartyOperations webAuthnRelyingPartyOperations() { + return ConfigCredentialCreationOptionsRepositoryFromBean.rpOperations; + } + + @Bean + UserDetailsService userDetailsService() { + return new InMemoryUserDetailsManager(); + } + + @Bean + HttpSessionPublicKeyCredentialCreationOptionsRepository creationOptionsRepository() { + return ConfigCredentialCreationOptionsRepositoryFromBean.creationOptionsRepository; + } + + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + return http.csrf(AbstractHttpConfigurer::disable).webAuthn(Customizer.withDefaults()).build(); + } + } @Configuration diff --git a/web/src/main/java/org/springframework/security/web/webauthn/registration/PublicKeyCredentialCreationOptionsFilter.java b/web/src/main/java/org/springframework/security/web/webauthn/registration/PublicKeyCredentialCreationOptionsFilter.java index 965408621f..0863925c8c 100644 --- a/web/src/main/java/org/springframework/security/web/webauthn/registration/PublicKeyCredentialCreationOptionsFilter.java +++ b/web/src/main/java/org/springframework/security/web/webauthn/registration/PublicKeyCredentialCreationOptionsFilter.java @@ -105,6 +105,17 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse this.converter.write(options, MediaType.APPLICATION_JSON, new ServletServerHttpResponse(response)); } + /** + * Sets the {@link PublicKeyCredentialCreationOptionsRepository} to use. The default + * is {@link HttpSessionPublicKeyCredentialCreationOptionsRepository}. + * @param creationOptionsRepository the + * {@link PublicKeyCredentialCreationOptionsRepository} to use. Cannot be null. + */ + public void setCreationOptionsRepository(PublicKeyCredentialCreationOptionsRepository creationOptionsRepository) { + Assert.notNull(creationOptionsRepository, "creationOptionsRepository cannot be null"); + this.repository = creationOptionsRepository; + } + /** * Set the {@link HttpMessageConverter} to read the * {@link WebAuthnRegistrationFilter.WebAuthnRegistrationRequest} and write the From 6f870a1c718891fa147d1bc9b05fe9260e53032f Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Fri, 17 Jan 2025 20:49:10 -0600 Subject: [PATCH 102/132] Fix whitespace Signed-off-by: Daeho Kwon --- .../web/configurers/WebAuthnConfigurerTests.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurerTests.java index 37665b38da..201fbc4553 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurerTests.java @@ -187,8 +187,8 @@ public void webauthnWhenConfiguredMessageConverter() throws Exception { TestingAuthenticationToken user = new TestingAuthenticationToken("user", "password", "ROLE_USER"); SecurityContextHolder.setContext(new SecurityContextImpl(user)); PublicKeyCredentialCreationOptions options = TestPublicKeyCredentialCreationOptions - .createPublicKeyCredentialCreationOptions() - .build(); + .createPublicKeyCredentialCreationOptions() + .build(); WebAuthnRelyingPartyOperations rpOperations = mock(WebAuthnRelyingPartyOperations.class); ConfigMessageConverter.rpOperations = rpOperations; given(rpOperations.createPublicKeyCredentialCreationOptions(any())).willReturn(options); @@ -203,8 +203,8 @@ public void webauthnWhenConfiguredMessageConverter() throws Exception { ConfigMessageConverter.converter = converter; this.spring.register(ConfigMessageConverter.class).autowire(); this.mvc.perform(post("/webauthn/register/options")) - .andExpect(status().isOk()) - .andExpect(content().string(expectedBody)); + .andExpect(status().isOk()) + .andExpect(content().string(expectedBody)); } @Configuration From b0cce01423763dfc1cc0d7cd33d42dab76fee76f Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Fri, 17 Jan 2025 20:47:07 -0600 Subject: [PATCH 103/132] Add WebAuthenticationDsl.creationOptionsRepository Issue gh-16396 Signed-off-by: Daeho Kwon --- .../config/annotation/web/WebAuthnDsl.kt | 3 ++ .../config/annotation/web/WebAuthnDslTests.kt | 38 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/config/src/main/kotlin/org/springframework/security/config/annotation/web/WebAuthnDsl.kt b/config/src/main/kotlin/org/springframework/security/config/annotation/web/WebAuthnDsl.kt index 41518ed191..c48827c92d 100644 --- a/config/src/main/kotlin/org/springframework/security/config/annotation/web/WebAuthnDsl.kt +++ b/config/src/main/kotlin/org/springframework/security/config/annotation/web/WebAuthnDsl.kt @@ -18,6 +18,7 @@ package org.springframework.security.config.annotation.web import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configurers.WebAuthnConfigurer +import org.springframework.security.web.webauthn.registration.PublicKeyCredentialCreationOptionsRepository /** * A Kotlin DSL to configure [HttpSecurity] webauthn using idiomatic Kotlin code. @@ -35,6 +36,7 @@ class WebAuthnDsl { var rpId: String? = null var allowedOrigins: Set? = null var disableDefaultRegistrationPage: Boolean? = false + var creationOptionsRepository: PublicKeyCredentialCreationOptionsRepository? = null internal fun get(): (WebAuthnConfigurer) -> Unit { return { webAuthn -> @@ -42,6 +44,7 @@ class WebAuthnDsl { rpId?.also { webAuthn.rpId(rpId) } allowedOrigins?.also { webAuthn.allowedOrigins(allowedOrigins) } disableDefaultRegistrationPage?.also { webAuthn.disableDefaultRegistrationPage(disableDefaultRegistrationPage!!) } + creationOptionsRepository?.also { webAuthn.creationOptionsRepository(creationOptionsRepository) } } } } diff --git a/config/src/test/kotlin/org/springframework/security/config/annotation/web/WebAuthnDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/annotation/web/WebAuthnDslTests.kt index 8bdee169f8..feb580e4b9 100644 --- a/config/src/test/kotlin/org/springframework/security/config/annotation/web/WebAuthnDslTests.kt +++ b/config/src/test/kotlin/org/springframework/security/config/annotation/web/WebAuthnDslTests.kt @@ -30,6 +30,7 @@ import org.springframework.security.core.userdetails.User import org.springframework.security.core.userdetails.UserDetailsService import org.springframework.security.provisioning.InMemoryUserDetailsManager import org.springframework.security.web.SecurityFilterChain +import org.springframework.security.web.webauthn.registration.HttpSessionPublicKeyCredentialCreationOptionsRepository import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.get import org.springframework.test.web.servlet.post @@ -58,6 +59,16 @@ class WebAuthnDslTests { } } + @Test + fun `explicit PublicKeyCredentialCreationOptionsRepository`() { + this.spring.register(ExplicitPublicKeyCredentialCreationOptionsRepositoryConfig::class.java).autowire() + + this.mockMvc.post("/test1") + .andExpect { + status { isForbidden() } + } + } + @Test fun `webauthn and formLogin configured with default registration page`() { spring.register(DefaultWebauthnConfig::class.java).autowire() @@ -128,6 +139,33 @@ class WebAuthnDslTests { } } + @Configuration + @EnableWebSecurity + open class ExplicitPublicKeyCredentialCreationOptionsRepositoryConfig { + @Bean + open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + http { + webAuthn { + rpName = "Spring Security Relying Party" + rpId = "example.com" + allowedOrigins = setOf("https://example.com") + creationOptionsRepository = HttpSessionPublicKeyCredentialCreationOptionsRepository() + } + } + return http.build() + } + + @Bean + open fun userDetailsService(): UserDetailsService { + val userDetails = User.withDefaultPasswordEncoder() + .username("rod") + .password("password") + .roles("USER") + .build() + return InMemoryUserDetailsManager(userDetails) + } + } + @Configuration @EnableWebSecurity open class WebauthnConfig { From 81dd0c4b4b7285ad86a8ae30b1c5bbb46a974251 Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Fri, 17 Jan 2025 20:47:45 -0600 Subject: [PATCH 104/132] Document PublicKeyCredentialCreationOptionsRepository Issue gh-16396 Signed-off-by: Daeho Kwon --- .../servlet/authentication/passkeys.adoc | 36 +++++++++++++++++++ docs/modules/ROOT/pages/whats-new.adoc | 4 +++ 2 files changed, 40 insertions(+) diff --git a/docs/modules/ROOT/pages/servlet/authentication/passkeys.adoc b/docs/modules/ROOT/pages/servlet/authentication/passkeys.adoc index 9b0cd52356..4e3f58607d 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/passkeys.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/passkeys.adoc @@ -60,6 +60,7 @@ Java:: ---- @Bean SecurityFilterChain filterChain(HttpSecurity http) { + // ... http // ... .formLogin(withDefaults()) @@ -67,6 +68,8 @@ SecurityFilterChain filterChain(HttpSecurity http) { .rpName("Spring Security Relying Party") .rpId("example.com") .allowedOrigins("https://example.com") + // optional properties + .creationOptionsRepository(new CustomPublicKeyCredentialCreationOptionsRepository()) ); return http.build(); } @@ -89,11 +92,14 @@ Kotlin:: ---- @Bean open fun filterChain(http: HttpSecurity): SecurityFilterChain { + // ... http { webAuthn { rpName = "Spring Security Relying Party" rpId = "example.com" allowedOrigins = setOf("https://example.com") + // optional properties + creationOptionsRepository = CustomPublicKeyCredentialCreationOptionsRepository() } } } @@ -110,6 +116,36 @@ open fun userDetailsService(): UserDetailsService { ---- ====== +[[passkeys-configuration-pkccor]] +=== Custom PublicKeyCredentialCreationOptionsRepository + +The `PublicKeyCredentialCreationOptionsRepository` is used to persist the `PublicKeyCredentialCreationOptions` between requests. +The default is to persist it the `HttpSession`, but at times users may need to customize this behavior. +This can be done by setting the optional property `creationOptionsRepository` demonstrated in xref:./passkeys.adoc#passkeys-configuration[Configuration] or by exposing a `PublicKeyCredentialCreationOptionsRepository` Bean: + +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +@Bean +CustomPublicKeyCredentialCreationOptionsRepository creationOptionsRepository() { + return new CustomPublicKeyCredentialCreationOptionsRepository(); +} +---- + +Kotlin:: ++ +[source,kotlin,role="secondary"] +---- +@Bean +open fun creationOptionsRepository(): CustomPublicKeyCredentialCreationOptionsRepository { + return CustomPublicKeyCredentialCreationOptionsRepository() +} +---- +====== + [[passkeys-register]] == Register a New Credential diff --git a/docs/modules/ROOT/pages/whats-new.adoc b/docs/modules/ROOT/pages/whats-new.adoc index 0fc13497b5..a07394496f 100644 --- a/docs/modules/ROOT/pages/whats-new.adoc +++ b/docs/modules/ROOT/pages/whats-new.adoc @@ -14,3 +14,7 @@ Note that this may affect reports that operate on this key name. == OAuth * https://github.com/spring-projects/spring-security/pull/16386[gh-16386] - Enable PKCE for confidential clients using `ClientRegistration.clientSettings.requireProofKey=true` for xref:servlet/oauth2/client/core.adoc#oauth2Client-client-registration-requireProofKey[servlet] and xref:reactive/oauth2/client/core.adoc#oauth2Client-client-registration-requireProofKey[reactive] applications + +== WebAuthn + +* https://github.com/spring-projects/spring-security/pull/16396[gh-16396] - Added the ability to configure a custom xref:servlet/authentication/passkeys.adoc#passkeys-configuration-pkccor[`PublicKeyCredentialCreationOptionsRepository`] From 2ac8c751f520a1f290c45de9a1627dc795765af1 Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Fri, 17 Jan 2025 21:07:46 -0600 Subject: [PATCH 105/132] Add HttpMessageConverter WebAuthnDsl Support Issue gh-16397 Signed-off-by: Daeho Kwon --- .../config/annotation/web/WebAuthnDsl.kt | 3 ++ .../config/annotation/web/WebAuthnDslTests.kt | 38 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/config/src/main/kotlin/org/springframework/security/config/annotation/web/WebAuthnDsl.kt b/config/src/main/kotlin/org/springframework/security/config/annotation/web/WebAuthnDsl.kt index c48827c92d..23447c1b6d 100644 --- a/config/src/main/kotlin/org/springframework/security/config/annotation/web/WebAuthnDsl.kt +++ b/config/src/main/kotlin/org/springframework/security/config/annotation/web/WebAuthnDsl.kt @@ -16,6 +16,7 @@ package org.springframework.security.config.annotation.web +import org.springframework.http.converter.HttpMessageConverter import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configurers.WebAuthnConfigurer import org.springframework.security.web.webauthn.registration.PublicKeyCredentialCreationOptionsRepository @@ -37,6 +38,7 @@ class WebAuthnDsl { var allowedOrigins: Set? = null var disableDefaultRegistrationPage: Boolean? = false var creationOptionsRepository: PublicKeyCredentialCreationOptionsRepository? = null + var messageConverter: HttpMessageConverter? = null internal fun get(): (WebAuthnConfigurer) -> Unit { return { webAuthn -> @@ -45,6 +47,7 @@ class WebAuthnDsl { allowedOrigins?.also { webAuthn.allowedOrigins(allowedOrigins) } disableDefaultRegistrationPage?.also { webAuthn.disableDefaultRegistrationPage(disableDefaultRegistrationPage!!) } creationOptionsRepository?.also { webAuthn.creationOptionsRepository(creationOptionsRepository) } + messageConverter?.also { webAuthn.messageConverter(messageConverter) } } } } diff --git a/config/src/test/kotlin/org/springframework/security/config/annotation/web/WebAuthnDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/annotation/web/WebAuthnDslTests.kt index feb580e4b9..00e02f5821 100644 --- a/config/src/test/kotlin/org/springframework/security/config/annotation/web/WebAuthnDslTests.kt +++ b/config/src/test/kotlin/org/springframework/security/config/annotation/web/WebAuthnDslTests.kt @@ -22,6 +22,7 @@ import org.junit.jupiter.api.extension.ExtendWith import org.springframework.beans.factory.annotation.Autowired import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity import org.springframework.security.config.test.SpringTestContext @@ -69,6 +70,16 @@ class WebAuthnDslTests { } } + @Test + fun `explicit HttpMessageConverter`() { + this.spring.register(ExplicitHttpMessageConverterConfig::class.java).autowire() + + this.mockMvc.post("/test1") + .andExpect { + status { isForbidden() } + } + } + @Test fun `webauthn and formLogin configured with default registration page`() { spring.register(DefaultWebauthnConfig::class.java).autowire() @@ -166,6 +177,33 @@ class WebAuthnDslTests { } } + @Configuration + @EnableWebSecurity + open class ExplicitHttpMessageConverterConfig { + @Bean + open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + http { + webAuthn { + rpName = "Spring Security Relying Party" + rpId = "example.com" + allowedOrigins = setOf("https://example.com") + messageConverter = MappingJackson2HttpMessageConverter() + } + } + return http.build() + } + + @Bean + open fun userDetailsService(): UserDetailsService { + val userDetails = User.withDefaultPasswordEncoder() + .username("rod") + .password("password") + .roles("USER") + .build() + return InMemoryUserDetailsManager(userDetails) + } + } + @Configuration @EnableWebSecurity open class WebauthnConfig { From 583f2b19ae2d8e3c8c53f8888e42dd068d4b6147 Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Fri, 17 Jan 2025 21:08:16 -0600 Subject: [PATCH 106/132] Document custom HttpMessageConverter support for WebAuthn Issue gh-16397 Signed-off-by: Daeho Kwon --- docs/modules/ROOT/pages/servlet/authentication/passkeys.adoc | 2 ++ docs/modules/ROOT/pages/whats-new.adoc | 1 + 2 files changed, 3 insertions(+) diff --git a/docs/modules/ROOT/pages/servlet/authentication/passkeys.adoc b/docs/modules/ROOT/pages/servlet/authentication/passkeys.adoc index 4e3f58607d..8f21a0cdb5 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/passkeys.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/passkeys.adoc @@ -70,6 +70,7 @@ SecurityFilterChain filterChain(HttpSecurity http) { .allowedOrigins("https://example.com") // optional properties .creationOptionsRepository(new CustomPublicKeyCredentialCreationOptionsRepository()) + .messageConverter(new CustomHttpMessageConverter()) ); return http.build(); } @@ -100,6 +101,7 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain { allowedOrigins = setOf("https://example.com") // optional properties creationOptionsRepository = CustomPublicKeyCredentialCreationOptionsRepository() + messageConverter = CustomHttpMessageConverter() } } } diff --git a/docs/modules/ROOT/pages/whats-new.adoc b/docs/modules/ROOT/pages/whats-new.adoc index a07394496f..e9dd519510 100644 --- a/docs/modules/ROOT/pages/whats-new.adoc +++ b/docs/modules/ROOT/pages/whats-new.adoc @@ -17,4 +17,5 @@ Note that this may affect reports that operate on this key name. == WebAuthn +* https://github.com/spring-projects/spring-security/pull/16397[gh-16397] - Added the ability to configure a custom `HttpMessageConverter` for Passkeys using the optional xref:servlet/authentication/passkeys.adoc#passkeys-configuration[`messageConverter` property] on the `webAuthn` DSL. * https://github.com/spring-projects/spring-security/pull/16396[gh-16396] - Added the ability to configure a custom xref:servlet/authentication/passkeys.adoc#passkeys-configuration-pkccor[`PublicKeyCredentialCreationOptionsRepository`] From ace83310b36b36edd8bd8adfb7c5d87d7e6a8fa9 Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Fri, 17 Jan 2025 21:37:27 -0600 Subject: [PATCH 107/132] Document JDBC Persistence for WebAuthn Issue gh-16282 Signed-off-by: Daeho Kwon --- .../servlet/authentication/passkeys.adoc | 43 +++++++++++++++++++ docs/modules/ROOT/pages/whats-new.adoc | 1 + 2 files changed, 44 insertions(+) diff --git a/docs/modules/ROOT/pages/servlet/authentication/passkeys.adoc b/docs/modules/ROOT/pages/servlet/authentication/passkeys.adoc index 8f21a0cdb5..0ccbf18cc1 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/passkeys.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/passkeys.adoc @@ -118,6 +118,49 @@ open fun userDetailsService(): UserDetailsService { ---- ====== + +[[passkeys-configuration-persistence]] +=== JDBC & Custom Persistence + +WebAuthn performs persistence with javadoc:org.springframework.security.web.webauthn.management.PublicKeyCredentialUserEntityRepository[] and javadoc:org.springframework.security.web.webauthn.management.UserCredentialRepository[]. +The default is to use in memory persistence, but JDBC persistence is support with javadoc:org.springframework.security.web.webauthn.management.JdbcPublicKeyCredentialUserEntityRepository[] and javadoc:org.springframework.security.web.webauthn.management.JdbcUserCredentialRepository[]. +To configure JDBC based persistence, expose the repositories as a Bean: + +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +@Bean +JdbcPublicKeyCredentialUserEntityRepository jdbcPublicKeyCredentialRepository(JdbcOperations jdbc) { + return new JdbcPublicKeyCredentialUserEntityRepository(jdbc); +} + +@Bean +JdbcUserCredentialRepository jdbcUserCredentialRepository(JdbcOperations jdbc) { + return new JdbcUserCredentialRepository(jdbc); +} +---- + +Kotlin:: ++ +[source,kotlin,role="secondary"] +---- +@Bean +fun jdbcPublicKeyCredentialRepository(jdbc: JdbcOperations): JdbcPublicKeyCredentialUserEntityRepository { + return JdbcPublicKeyCredentialUserEntityRepository(jdbc) +} + +@Bean +fun jdbcUserCredentialRepository(jdbc: JdbcOperations): JdbcUserCredentialRepository { + return JdbcUserCredentialRepository(jdbc) +} +---- +====== + +If JDBC does not meet your needs, you can create your own implementations of the interfaces and use them by exposing them as a Bean similar to the example above. + [[passkeys-configuration-pkccor]] === Custom PublicKeyCredentialCreationOptionsRepository diff --git a/docs/modules/ROOT/pages/whats-new.adoc b/docs/modules/ROOT/pages/whats-new.adoc index e9dd519510..01f5e85150 100644 --- a/docs/modules/ROOT/pages/whats-new.adoc +++ b/docs/modules/ROOT/pages/whats-new.adoc @@ -17,5 +17,6 @@ Note that this may affect reports that operate on this key name. == WebAuthn +* https://github.com/spring-projects/spring-security/pull/16282[gh-16282] - xref:servlet/authentication/passkeys.adoc#passkeys-configuration-persistence[JDBC Persistence] for WebAuthn/Passkeys * https://github.com/spring-projects/spring-security/pull/16397[gh-16397] - Added the ability to configure a custom `HttpMessageConverter` for Passkeys using the optional xref:servlet/authentication/passkeys.adoc#passkeys-configuration[`messageConverter` property] on the `webAuthn` DSL. * https://github.com/spring-projects/spring-security/pull/16396[gh-16396] - Added the ability to configure a custom xref:servlet/authentication/passkeys.adoc#passkeys-configuration-pkccor[`PublicKeyCredentialCreationOptionsRepository`] From ae7bd9bca4fedb07a1b09eaa6293a635bdf5e626 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Jan 2025 03:10:56 +0000 Subject: [PATCH 108/132] Bump org.hibernate.orm:hibernate-core from 6.6.4.Final to 6.6.5.Final Bumps [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm) from 6.6.4.Final to 6.6.5.Final. - [Release notes](https://github.com/hibernate/hibernate-orm/releases) - [Changelog](https://github.com/hibernate/hibernate-orm/blob/6.6.5/changelog.txt) - [Commits](https://github.com/hibernate/hibernate-orm/compare/6.6.4...6.6.5) --- updated-dependencies: - dependency-name: org.hibernate.orm:hibernate-core dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: Daeho Kwon --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e6ebda5c13..3337ba8315 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -70,7 +70,7 @@ org-bouncycastle-bcprov-jdk15on = { module = "org.bouncycastle:bcprov-jdk18on", org-eclipse-jetty-jetty-server = { module = "org.eclipse.jetty:jetty-server", version.ref = "org-eclipse-jetty" } org-eclipse-jetty-jetty-servlet = { module = "org.eclipse.jetty:jetty-servlet", version.ref = "org-eclipse-jetty" } org-hamcrest = "org.hamcrest:hamcrest:2.2" -org-hibernate-orm-hibernate-core = "org.hibernate.orm:hibernate-core:6.6.4.Final" +org-hibernate-orm-hibernate-core = "org.hibernate.orm:hibernate-core:6.6.5.Final" org-hsqldb = "org.hsqldb:hsqldb:2.7.4" org-jetbrains-kotlin-kotlin-bom = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "org-jetbrains-kotlin" } org-jetbrains-kotlin-kotlin-gradle-plugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25" From d5f08460909240ecb41f6250087b2bc94ab9602c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Jan 2025 03:11:01 +0000 Subject: [PATCH 109/132] Bump org.springframework.data:spring-data-bom from 2024.1.1 to 2024.1.2 Bumps [org.springframework.data:spring-data-bom](https://github.com/spring-projects/spring-data-bom) from 2024.1.1 to 2024.1.2. - [Release notes](https://github.com/spring-projects/spring-data-bom/releases) - [Commits](https://github.com/spring-projects/spring-data-bom/compare/2024.1.1...2024.1.2) --- updated-dependencies: - dependency-name: org.springframework.data:spring-data-bom dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: Daeho Kwon --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3337ba8315..a6212d624c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -88,7 +88,7 @@ org-seleniumhq-selenium-selenium-support = "org.seleniumhq.selenium:selenium-sup org-skyscreamer-jsonassert = "org.skyscreamer:jsonassert:1.5.3" org-slf4j-log4j-over-slf4j = "org.slf4j:log4j-over-slf4j:1.7.36" org-slf4j-slf4j-api = "org.slf4j:slf4j-api:2.0.16" -org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2024.1.1" +org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2024.1.2" org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:3.2.10" org-springframework-spring-framework-bom = { module = "org.springframework:spring-framework-bom", version.ref = "org-springframework" } org-synchronoss-cloud-nio-multipart-parser = "org.synchronoss.cloud:nio-multipart-parser:1.1.0" From 2d0f71cb94fcf97812d6a99dfac16ef21219c33b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Jan 2025 03:10:49 +0000 Subject: [PATCH 110/132] Bump org.assertj:assertj-core from 3.27.2 to 3.27.3 Bumps [org.assertj:assertj-core](https://github.com/assertj/assertj) from 3.27.2 to 3.27.3. - [Release notes](https://github.com/assertj/assertj/releases) - [Commits](https://github.com/assertj/assertj/compare/assertj-build-3.27.2...assertj-build-3.27.3) --- updated-dependencies: - dependency-name: org.assertj:assertj-core dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: Daeho Kwon --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a6212d624c..13a21b1f94 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -64,7 +64,7 @@ org-apereo-cas-client-cas-client-core = "org.apereo.cas.client:cas-client-core:4 io-freefair-gradle-aspectj-plugin = "io.freefair.gradle:aspectj-plugin:8.11" org-aspectj-aspectjrt = { module = "org.aspectj:aspectjrt", version.ref = "org-aspectj" } org-aspectj-aspectjweaver = { module = "org.aspectj:aspectjweaver", version.ref = "org-aspectj" } -org-assertj-assertj-core = "org.assertj:assertj-core:3.27.2" +org-assertj-assertj-core = "org.assertj:assertj-core:3.27.3" org-bouncycastle-bcpkix-jdk15on = { module = "org.bouncycastle:bcpkix-jdk18on", version.ref = "org-bouncycastle" } org-bouncycastle-bcprov-jdk15on = { module = "org.bouncycastle:bcprov-jdk18on", version.ref = "org-bouncycastle" } org-eclipse-jetty-jetty-server = { module = "org.eclipse.jetty:jetty-server", version.ref = "org-eclipse-jetty" } From 13ca28449290e78a5a893c57ae6921bcbd6940e6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 20 Jan 2025 15:28:02 +0000 Subject: [PATCH 111/132] Release 6.5.0-M1 Signed-off-by: Daeho Kwon --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 88de36ef8d..328b9fb5a3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,7 +14,7 @@ # limitations under the License. # springBootVersion=3.3.3 -version=6.5.0-SNAPSHOT +version=6.5.0-M1 samplesBranch=main org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError org.gradle.parallel=true From c03b2c3d0cb6a55d68fc809b8ca1524e7dd8154d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 20 Jan 2025 15:50:53 +0000 Subject: [PATCH 112/132] Next development version Signed-off-by: Daeho Kwon --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 328b9fb5a3..88de36ef8d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,7 +14,7 @@ # limitations under the License. # springBootVersion=3.3.3 -version=6.5.0-M1 +version=6.5.0-SNAPSHOT samplesBranch=main org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError org.gradle.parallel=true From 6484e3cf8ddaef3b8edc1da5c4390fdc6b36a698 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Jan 2025 04:03:09 +0000 Subject: [PATCH 113/132] Bump org.seleniumhq.selenium:selenium-java from 4.27.0 to 4.28.0 Bumps [org.seleniumhq.selenium:selenium-java](https://github.com/SeleniumHQ/selenium) from 4.27.0 to 4.28.0. - [Release notes](https://github.com/SeleniumHQ/selenium/releases) - [Commits](https://github.com/SeleniumHQ/selenium/compare/selenium-4.27.0...selenium-4.28.0) --- updated-dependencies: - dependency-name: org.seleniumhq.selenium:selenium-java dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: Daeho Kwon --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 13a21b1f94..b472dbde72 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -83,7 +83,7 @@ org-opensaml-opensaml5-saml-api = { module = "org.opensaml:opensaml-saml-api", v org-opensaml-opensaml5-saml-impl = { module = "org.opensaml:opensaml-saml-impl", version.ref = "org-opensaml5" } org-python-jython = { module = "org.python:jython", version = "2.5.3" } org-seleniumhq-selenium-htmlunit-driver = "org.seleniumhq.selenium:htmlunit3-driver:4.27.0" -org-seleniumhq-selenium-selenium-java = "org.seleniumhq.selenium:selenium-java:4.27.0" +org-seleniumhq-selenium-selenium-java = "org.seleniumhq.selenium:selenium-java:4.28.0" org-seleniumhq-selenium-selenium-support = "org.seleniumhq.selenium:selenium-support:3.141.59" org-skyscreamer-jsonassert = "org.skyscreamer:jsonassert:1.5.3" org-slf4j-log4j-over-slf4j = "org.slf4j:log4j-over-slf4j:1.7.36" From 31656f6156dd5a7ebdefd0bc0dbf8b024bacea03 Mon Sep 17 00:00:00 2001 From: Max Batischev Date: Mon, 23 Dec 2024 00:17:14 +0300 Subject: [PATCH 114/132] Add GenerateOneTimeTokenFilterTests Signed-off-by: Daeho Kwon --- .../ott/GenerateOneTimeTokenFilterTests.java | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 web/src/test/java/org/springframework/security/web/authentication/ott/GenerateOneTimeTokenFilterTests.java diff --git a/web/src/test/java/org/springframework/security/web/authentication/ott/GenerateOneTimeTokenFilterTests.java b/web/src/test/java/org/springframework/security/web/authentication/ott/GenerateOneTimeTokenFilterTests.java new file mode 100644 index 0000000000..f3cdb2fd51 --- /dev/null +++ b/web/src/test/java/org/springframework/security/web/authentication/ott/GenerateOneTimeTokenFilterTests.java @@ -0,0 +1,115 @@ +/* + * Copyright 2002-2024 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.authentication.ott; + +import java.io.IOException; +import java.time.Instant; + +import jakarta.servlet.ServletException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentMatchers; + +import org.springframework.mock.web.MockFilterChain; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.authentication.ott.DefaultOneTimeToken; +import org.springframework.security.authentication.ott.GenerateOneTimeTokenRequest; +import org.springframework.security.authentication.ott.OneTimeTokenService; +import org.springframework.security.web.server.authentication.ott.GenerateOneTimeTokenWebFilter; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +/** + * Tests for {@link GenerateOneTimeTokenWebFilter} + * + * @author Max Batischev + */ +public class GenerateOneTimeTokenFilterTests { + + private final OneTimeTokenService oneTimeTokenService = mock(OneTimeTokenService.class); + + private final RedirectOneTimeTokenGenerationSuccessHandler successHandler = new RedirectOneTimeTokenGenerationSuccessHandler( + "/login/ott"); + + private static final String TOKEN = "token"; + + private static final String USERNAME = "user"; + + private final MockHttpServletRequest request = new MockHttpServletRequest(); + + private final MockHttpServletResponse response = new MockHttpServletResponse(); + + private final MockFilterChain filterChain = new MockFilterChain(); + + @BeforeEach + void setup() { + this.request.setMethod("POST"); + this.request.setServletPath("/ott/generate"); + } + + @Test + void filterWhenUsernameFormParamIsPresentThenSuccess() throws ServletException, IOException { + given(this.oneTimeTokenService.generate(ArgumentMatchers.any(GenerateOneTimeTokenRequest.class))) + .willReturn(new DefaultOneTimeToken(TOKEN, USERNAME, Instant.now())); + this.request.setParameter("username", USERNAME); + + GenerateOneTimeTokenFilter filter = new GenerateOneTimeTokenFilter(this.oneTimeTokenService, + this.successHandler); + + filter.doFilter(this.request, this.response, this.filterChain); + + verify(this.oneTimeTokenService).generate(ArgumentMatchers.any(GenerateOneTimeTokenRequest.class)); + assertThat(this.response.getRedirectedUrl()).isEqualTo("/login/ott"); + } + + @Test + void filterWhenUsernameFormParamIsEmptyThenNull() throws ServletException, IOException { + given(this.oneTimeTokenService.generate(ArgumentMatchers.any(GenerateOneTimeTokenRequest.class))) + .willReturn((new DefaultOneTimeToken(TOKEN, USERNAME, Instant.now()))); + GenerateOneTimeTokenFilter filter = new GenerateOneTimeTokenFilter(this.oneTimeTokenService, + this.successHandler); + + filter.doFilter(this.request, this.response, this.filterChain); + + verify(this.oneTimeTokenService, never()).generate(ArgumentMatchers.any(GenerateOneTimeTokenRequest.class)); + } + + @Test + public void constructorWhenOneTimeTokenServiceNullThenIllegalArgumentException() { + // @formatter:off + assertThatIllegalArgumentException() + .isThrownBy(() -> new GenerateOneTimeTokenFilter(null, this.successHandler)); + // @formatter:on + } + + @Test + public void setWhenRequestMatcherNullThenIllegalArgumentException() { + GenerateOneTimeTokenFilter filter = new GenerateOneTimeTokenFilter(this.oneTimeTokenService, + this.successHandler); + // @formatter:off + assertThatIllegalArgumentException() + .isThrownBy(() -> filter.setRequestMatcher(null)); + // @formatter:on + } + +} From 5d96921a64d77036e09c8cea3f01d3fad227125f Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Tue, 21 Jan 2025 13:19:27 -0600 Subject: [PATCH 115/132] Add TestBytes Closes gh-16461 Signed-off-by: Daeho Kwon --- .../security/web/webauthn/api/TestBytes.java | 31 +++++++++++++++++++ .../TestPublicKeyCredentialUserEntity.java | 2 +- .../MapUserCredentialRepositoryTests.java | 12 +++---- ...RegistrationPageGeneratingFilterTests.java | 14 ++++----- 4 files changed, 45 insertions(+), 14 deletions(-) create mode 100644 web/src/test/java/org/springframework/security/web/webauthn/api/TestBytes.java diff --git a/web/src/test/java/org/springframework/security/web/webauthn/api/TestBytes.java b/web/src/test/java/org/springframework/security/web/webauthn/api/TestBytes.java new file mode 100644 index 0000000000..b8850c12de --- /dev/null +++ b/web/src/test/java/org/springframework/security/web/webauthn/api/TestBytes.java @@ -0,0 +1,31 @@ +/* + * Copyright 2002-2025 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.webauthn.api; + +/** + * @author Rob Winch + */ +public final class TestBytes { + + public static Bytes get() { + return Bytes.fromBase64("OSCtNugR-n4YR4ozlHRa-CKXzY9v-yMKtQGcvui5xN8"); + } + + private TestBytes() { + } + +} diff --git a/web/src/test/java/org/springframework/security/web/webauthn/api/TestPublicKeyCredentialUserEntity.java b/web/src/test/java/org/springframework/security/web/webauthn/api/TestPublicKeyCredentialUserEntity.java index 704e6ce17f..cc35752d15 100644 --- a/web/src/test/java/org/springframework/security/web/webauthn/api/TestPublicKeyCredentialUserEntity.java +++ b/web/src/test/java/org/springframework/security/web/webauthn/api/TestPublicKeyCredentialUserEntity.java @@ -21,7 +21,7 @@ public final class TestPublicKeyCredentialUserEntity { public static PublicKeyCredentialUserEntityBuilder userEntity() { - return ImmutablePublicKeyCredentialUserEntity.builder().name("user").id(Bytes.random()).displayName("user"); + return ImmutablePublicKeyCredentialUserEntity.builder().name("user").id(TestBytes.get()).displayName("user"); } private TestPublicKeyCredentialUserEntity() { diff --git a/web/src/test/java/org/springframework/security/web/webauthn/management/MapUserCredentialRepositoryTests.java b/web/src/test/java/org/springframework/security/web/webauthn/management/MapUserCredentialRepositoryTests.java index 36081973f8..d14e98df12 100644 --- a/web/src/test/java/org/springframework/security/web/webauthn/management/MapUserCredentialRepositoryTests.java +++ b/web/src/test/java/org/springframework/security/web/webauthn/management/MapUserCredentialRepositoryTests.java @@ -20,9 +20,9 @@ import org.junit.jupiter.api.Test; -import org.springframework.security.web.webauthn.api.Bytes; import org.springframework.security.web.webauthn.api.CredentialRecord; import org.springframework.security.web.webauthn.api.ImmutableCredentialRecord; +import org.springframework.security.web.webauthn.api.TestBytes; import org.springframework.security.web.webauthn.api.TestCredentialRecord; import static org.assertj.core.api.Assertions.assertThat; @@ -41,7 +41,7 @@ class MapUserCredentialRepositoryTests { @Test void findByUserIdWhenNotFoundThenEmpty() { - assertThat(this.userCredentials.findByUserId(Bytes.random())).isEmpty(); + assertThat(this.userCredentials.findByUserId(TestBytes.get())).isEmpty(); } @Test @@ -56,7 +56,7 @@ void findByCredentialIdWhenIdNullThenIllegalArgumentException() { @Test void findByCredentialIdWhenNotFoundThenIllegalArgumentException() { - assertThat(this.userCredentials.findByCredentialId(Bytes.random())).isNull(); + assertThat(this.userCredentials.findByCredentialId(TestBytes.get())).isNull(); } @Test @@ -114,7 +114,7 @@ void saveWhenSameUserThenUpdated() { ImmutableCredentialRecord credentialRecord = TestCredentialRecord.userCredential().build(); this.userCredentials.save(credentialRecord); CredentialRecord newCredentialRecord = ImmutableCredentialRecord.fromCredentialRecord(credentialRecord) - .credentialId(Bytes.random()) + .credentialId(TestBytes.get()) .build(); this.userCredentials.save(newCredentialRecord); assertThat(this.userCredentials.findByCredentialId(credentialRecord.getCredentialId())) @@ -130,8 +130,8 @@ void saveWhenDifferentUserThenNewEntryAdded() { ImmutableCredentialRecord credentialRecord = TestCredentialRecord.userCredential().build(); this.userCredentials.save(credentialRecord); CredentialRecord newCredentialRecord = ImmutableCredentialRecord.fromCredentialRecord(credentialRecord) - .userEntityUserId(Bytes.random()) - .credentialId(Bytes.random()) + .userEntityUserId(TestBytes.get()) + .credentialId(TestBytes.get()) .build(); this.userCredentials.save(newCredentialRecord); assertThat(this.userCredentials.findByCredentialId(credentialRecord.getCredentialId())) diff --git a/web/src/test/java/org/springframework/security/web/webauthn/registration/DefaultWebAuthnRegistrationPageGeneratingFilterTests.java b/web/src/test/java/org/springframework/security/web/webauthn/registration/DefaultWebAuthnRegistrationPageGeneratingFilterTests.java index 03fe8d0fec..7f681cc1dc 100644 --- a/web/src/test/java/org/springframework/security/web/webauthn/registration/DefaultWebAuthnRegistrationPageGeneratingFilterTests.java +++ b/web/src/test/java/org/springframework/security/web/webauthn/registration/DefaultWebAuthnRegistrationPageGeneratingFilterTests.java @@ -31,10 +31,10 @@ import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.web.csrf.CsrfToken; import org.springframework.security.web.csrf.DefaultCsrfToken; -import org.springframework.security.web.webauthn.api.Bytes; import org.springframework.security.web.webauthn.api.ImmutableCredentialRecord; import org.springframework.security.web.webauthn.api.ImmutablePublicKeyCredentialUserEntity; import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity; +import org.springframework.security.web.webauthn.api.TestBytes; import org.springframework.security.web.webauthn.api.TestCredentialRecord; import org.springframework.security.web.webauthn.management.PublicKeyCredentialUserEntityRepository; import org.springframework.security.web.webauthn.management.UserCredentialRepository; @@ -88,7 +88,7 @@ void doFilterWhenNotMatchThenNoInteractions() throws Exception { void doFilterThenCsrfDataAttrsPresent() throws Exception { PublicKeyCredentialUserEntity userEntity = ImmutablePublicKeyCredentialUserEntity.builder() .name("user") - .id(Bytes.random()) + .id(TestBytes.get()) .displayName("User") .build(); given(this.userEntities.findByUsername(any())).willReturn(userEntity); @@ -115,7 +115,7 @@ void doFilterWhenNullPublicKeyCredentialUserEntityThenNoResults() throws Excepti void doFilterWhenNoCredentialsThenNoResults() throws Exception { PublicKeyCredentialUserEntity userEntity = ImmutablePublicKeyCredentialUserEntity.builder() .name("user") - .id(Bytes.random()) + .id(TestBytes.get()) .displayName("User") .build(); given(this.userEntities.findByUsername(any())).willReturn(userEntity); @@ -129,7 +129,7 @@ void doFilterWhenNoCredentialsThenNoResults() throws Exception { void doFilterWhenResultsThenDisplayed() throws Exception { PublicKeyCredentialUserEntity userEntity = ImmutablePublicKeyCredentialUserEntity.builder() .name("user") - .id(Bytes.random()) + .id(TestBytes.get()) .displayName("User") .build(); @@ -225,7 +225,7 @@ void doFilterWhenResultsContainEntitiesThenEncoded() throws Exception { assertThat(label).isNotEqualTo(htmlEncodedLabel); PublicKeyCredentialUserEntity userEntity = ImmutablePublicKeyCredentialUserEntity.builder() .name("user") - .id(Bytes.random()) + .id(TestBytes.get()) .displayName("User") .build(); ImmutableCredentialRecord credential = TestCredentialRecord.userCredential().label(label).build(); @@ -240,7 +240,7 @@ void doFilterWhenResultsContainEntitiesThenEncoded() throws Exception { void doFilterWhenContextEmptyThenUrlsEmptyPrefix() throws Exception { PublicKeyCredentialUserEntity userEntity = ImmutablePublicKeyCredentialUserEntity.builder() .name("user") - .id(Bytes.random()) + .id(TestBytes.get()) .displayName("User") .build(); ImmutableCredentialRecord credential = TestCredentialRecord.userCredential().build(); @@ -256,7 +256,7 @@ void doFilterWhenContextEmptyThenUrlsEmptyPrefix() throws Exception { void doFilterWhenContextNotEmptyThenUrlsPrefixed() throws Exception { PublicKeyCredentialUserEntity userEntity = ImmutablePublicKeyCredentialUserEntity.builder() .name("user") - .id(Bytes.random()) + .id(TestBytes.get()) .displayName("User") .build(); ImmutableCredentialRecord credential = TestCredentialRecord.userCredential().build(); From 1fddf4fa20b7448a939f25e8cc35b2be67da2524 Mon Sep 17 00:00:00 2001 From: Tran Ngoc Nhan Date: Sun, 19 Jan 2025 00:47:12 +0700 Subject: [PATCH 116/132] Implement Serial Signed-off-by: Tran Ngoc Nhan Signed-off-by: Daeho Kwon --- ...ringSecurityCoreVersionSerializableTests.java | 8 +++++--- ...uth2.client.OAuth2AuthorizedClient.serialized | Bin 0 -> 3455 bytes ...thorizationCodeAuthenticationToken.serialized | Bin 0 -> 5546 bytes ...ion.OAuth2LoginAuthenticationToken.serialized | Bin 0 -> 5624 bytes ...tration.ClientRegistration$Builder.serialized | Bin 0 -> 1845 bytes ....ClientRegistration$ClientSettings.serialized | Bin 0 -> 129 bytes ...nt.registration.ClientRegistration.serialized | Bin 0 -> 2471 bytes ...urity.oauth2.core.OAuth2DeviceCode.serialized | Bin 0 -> 313 bytes ...ity.oauth2.core.OAuth2RefreshToken.serialized | Bin 0 -> 322 bytes ...ecurity.oauth2.core.OAuth2UserCode.serialized | Bin 0 -> 311 bytes .../client/registration/ClientRegistration.java | 6 +++++- 11 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 config/src/test/resources/serialized/6.5.x/org.springframework.security.oauth2.client.OAuth2AuthorizedClient.serialized create mode 100644 config/src/test/resources/serialized/6.5.x/org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken.serialized create mode 100644 config/src/test/resources/serialized/6.5.x/org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken.serialized create mode 100644 config/src/test/resources/serialized/6.5.x/org.springframework.security.oauth2.client.registration.ClientRegistration$Builder.serialized create mode 100644 config/src/test/resources/serialized/6.5.x/org.springframework.security.oauth2.client.registration.ClientRegistration$ClientSettings.serialized create mode 100644 config/src/test/resources/serialized/6.5.x/org.springframework.security.oauth2.client.registration.ClientRegistration.serialized create mode 100644 config/src/test/resources/serialized/6.5.x/org.springframework.security.oauth2.core.OAuth2DeviceCode.serialized create mode 100644 config/src/test/resources/serialized/6.5.x/org.springframework.security.oauth2.core.OAuth2RefreshToken.serialized create mode 100644 config/src/test/resources/serialized/6.5.x/org.springframework.security.oauth2.core.OAuth2UserCode.serialized diff --git a/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java b/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java index 359a7d4880..7c9b4cdf89 100644 --- a/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java +++ b/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java @@ -121,6 +121,7 @@ import org.springframework.security.oauth2.client.oidc.session.OidcSessionInformation; import org.springframework.security.oauth2.client.oidc.session.TestOidcSessionInformations; import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.ClientRegistration.ClientSettings; import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.core.DefaultOAuth2AuthenticatedPrincipal; import org.springframework.security.oauth2.core.OAuth2AccessToken; @@ -239,11 +240,12 @@ class SpringSecurityCoreVersionSerializableTests { (r) -> new ReactiveSessionInformation(user, r.alphanumeric(4), Instant.ofEpochMilli(1704378933936L))); generatorByClassName.put(OAuth2AccessToken.class, (r) -> TestOAuth2AccessTokens.scopes("scope")); generatorByClassName.put(OAuth2DeviceCode.class, - (r) -> new OAuth2DeviceCode("token", Instant.now(), Instant.now())); + (r) -> new OAuth2DeviceCode("token", Instant.now(), Instant.now().plusSeconds(1))); generatorByClassName.put(OAuth2RefreshToken.class, - (r) -> new OAuth2RefreshToken("refreshToken", Instant.now(), Instant.now())); + (r) -> new OAuth2RefreshToken("refreshToken", Instant.now(), Instant.now().plusSeconds(1))); generatorByClassName.put(OAuth2UserCode.class, - (r) -> new OAuth2UserCode("token", Instant.now(), Instant.now())); + (r) -> new OAuth2UserCode("token", Instant.now(), Instant.now().plusSeconds(1))); + generatorByClassName.put(ClientSettings.class, (r) -> ClientSettings.builder().build()); generatorByClassName.put(DefaultOidcUser.class, (r) -> TestOidcUsers.create()); generatorByClassName.put(OidcUserAuthority.class, (r) -> new OidcUserAuthority(TestOidcIdTokens.idToken().build(), diff --git a/config/src/test/resources/serialized/6.5.x/org.springframework.security.oauth2.client.OAuth2AuthorizedClient.serialized b/config/src/test/resources/serialized/6.5.x/org.springframework.security.oauth2.client.OAuth2AuthorizedClient.serialized new file mode 100644 index 0000000000000000000000000000000000000000..9c6c667a11a6bdedb2cc73eb17a2ed12c1474946 GIT binary patch literal 3455 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 W1dlzt_R5BDFTMp=1c~0BuKxg;)w8Jp literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/6.5.x/org.springframework.security.oauth2.client.registration.ClientRegistration$Builder.serialized b/config/src/test/resources/serialized/6.5.x/org.springframework.security.oauth2.client.registration.ClientRegistration$Builder.serialized new file mode 100644 index 0000000000000000000000000000000000000000..5db839c1aba30abe2da240a9fea886e0f82bb676 GIT binary patch literal 1845 zcma)6zfTlF7@a!<5kY>4U^GZ9u+ZR+F~$O75Kf}R#Tb4RVB%%iox2-uZ)cgAg*yp` zhT6mg6XU;Nukl~7kxC1#t*mV<{ATBtU0{QI-F-Xrz4yKEz4`DPbx460xTsn(5X`Su z1ZmJMF6x$~PAHgaS)7EbcE@r&Mtx-oT4hoRq8RtBQdC<=mBwa5=DAd$=vVZLXuw9p zx&s&NWpwM2Aii2^1yrGF8<6oaWE>-7gNz+6D4x!2F=?azBh8nAGP+=|lTCs>;#cva z(qqB7D`o)Q2j*Z-#ATiWF;}74{6x&mS#1F1tO1#GH_$6ow9z^9|0#&tuJoA7B6S2+ z?OJbKQwq3Bg`Vf56RD5W8^NWM8!@FtYMKMdA&2`FRt?P(K?33uMHK2YvW1Fyc%B4s zvWrO>QURj2nRcyj)uB^T=oZvvf;y>EZ<^4d5}nfF0fpJpD9}oarg)wDNzai`QZeUO zc$UZtP303OHqG&617LRykYEn!l3vra0>i9@iXQH)89+p)azz9%Elv8Q*ONRHY}Bzy zywDh;8Gv0$`byd1E=1!-O{qXmU`(5&5qQ*ccmsR9%6t z9)-Dguf92W{eE{@6BcOv|96t9FVCReFE9R#f?75zzz&B~nNN;QMxMWvbhepar|x4S zYk>dV`?k9O^CmRW_yftQ_V>>Z_x9g@`gr$@7IdozjMYE&AhK5hOd}|9Cv@7H3XNx} udc@?8L!~T74+Q9QKbMW-6dq6hX%6qK0WB&N8i^^9fLZD6Y03%}nfnjL-j5Uj literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/6.5.x/org.springframework.security.oauth2.client.registration.ClientRegistration$ClientSettings.serialized b/config/src/test/resources/serialized/6.5.x/org.springframework.security.oauth2.client.registration.ClientRegistration$ClientSettings.serialized new file mode 100644 index 0000000000000000000000000000000000000000..14e74db3b465c6e822cae112c123e8c8e4eefa45 GIT binary patch literal 129 zcmZ4UmVvdnh#@k+C|$3(peQphJ*_A)H?=&!C|j>MHMz7Xv!qflKe4nV!$>bVCo?s# zM6W0{J+ru^D6u3nKTppYEEa?$qyiBNPAw?`npT{_dQsnC=ARHI2F54`{-V^v(#)dN PfTH~TH1E{PiULLeyTLIu literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/6.5.x/org.springframework.security.oauth2.client.registration.ClientRegistration.serialized b/config/src/test/resources/serialized/6.5.x/org.springframework.security.oauth2.client.registration.ClientRegistration.serialized new file mode 100644 index 0000000000000000000000000000000000000000..9eb1e5f751c68e768a4ea4bc96accbd37a45678a GIT binary patch literal 2471 zcmcIlzi$*r6n;L(KOxwF!3qjiic1j$JAk4TpvW<{MGQ+6VXnYISmWKfyYcPqEVFOz zb1X-0s3S#kBt(rwkv@ojfd(p4Bz3A3DWWtH%9|Z8dv{);EzFj0XWsYC_rCYuFMq*M ztl@^z4JVGY3>$UL+hSAcmJ0XqYaZgAtwSZoCQ2=uw^t_(f@J#{~HqR0sH`iE*!OyW#MUo`>|tL z43VzI2;Y^Xm#WLU6=(AY6tCLQvardC)I_A!hV+GA5Xfafc=rwy@7vz&1b9RXUuxlD zP0Q@b-K>}V{pfJ)sYno^2`grTGGN5~Y9}Ha&X%$vnZhYHMee-;7lt-?kl3PhBB~zm z_R*@Rd_jowr4g;jMJ{<4>PcJpRdP1DVReni(nA=t(F>AXb;KtA#5f4y!~H+@JQ2KG zieRbE?DI);=oP#8=)XG^7+8S`EgmFPK`O1P-xOP&2nc-T5W)LQlZ!Uu!emcvau1#I zR9Kgd#10#3ZSHgAz2>>aBQoHajN?S;{OZ{HW{Z542ZpgJ+M2zAqjEN3!M}crW)njpz6WH_ z{rrIoQ%2r{J88C@53;4#pMcC}O?=7dG33Hv@^;#uGOjZtWDaRFttBYlZlttGUzU<0 zo2>oF`Qr}G)#0Y#v}p{bX@rln67fhn-R3j=5GrQkv27ZIqNal#w#`$s?PRq!Q<-h| zZjK%5_wEQy&2pxt_TN4G)2kmJ@79d@G@LJXYPZqz2KeY2?7q1F?~L_34eiri8!JC% z7uR&2*_B~S_+Rt5N%+47uUGc|I7dkg{_*_O{{QC-5BGk4_Uq?^ra3l`lFpN}U!`gf N(3a4o>=h$i=YInhbe{kK literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/6.5.x/org.springframework.security.oauth2.core.OAuth2DeviceCode.serialized b/config/src/test/resources/serialized/6.5.x/org.springframework.security.oauth2.core.OAuth2DeviceCode.serialized new file mode 100644 index 0000000000000000000000000000000000000000..8a382b26102dc2696aa13fd0149bda9c89f92276 GIT binary patch literal 313 zcmZ4UmVvdnh`}MHMz7Xv!qflKe4nV!$>bVzbIAD z-x0)iNiEAvPIb;tN&Uk3^UBoshr*Z`7%GYwY=}0;F{!wuC^5MNVqQpoc4{627%=59 zF);fuaHduiWEQ0sJC>9%2>WCumL=+!Wag&od*&6FB<7V^`!H~178jSMrZ|=qGSo3J z`7m&mfD8#s%qdMRVGx37$w|yh*AFfM`OCVZ0O(O(kV$$Vlk|d9i>Ai5?2=aU*u(>L jd_g%c&<_j@>D|xjjroEiDvCkovLZz5z@jA#tYCWp>J4f) literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/6.5.x/org.springframework.security.oauth2.core.OAuth2RefreshToken.serialized b/config/src/test/resources/serialized/6.5.x/org.springframework.security.oauth2.core.OAuth2RefreshToken.serialized new file mode 100644 index 0000000000000000000000000000000000000000..aad2554caeec679eef3567790338353a4d68005e GIT binary patch literal 322 zcmZ4UmVvdnh`~C)C|$3(peQphJ*_A)H?=&!C|j>MHMz7Xv!qflKe4nV!$>bVzbIAD z-x0(PN=++DEzStZ&rZ!d_Eh|ZY!m|+Fy$~Y zF#9lYrdAYW0&Q_DDPa)y$x19s)Gx`*P1X0zD=taQE3x)r;K(d4E=^5wEGcBDV_@=O z;3@&RCoD0iG_{052%;q?F)v*|xCG=g>xu%PS9w7u>48kr3r;PX8r!l5Um4?mN4)XA$cDF+)8bB literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/6.5.x/org.springframework.security.oauth2.core.OAuth2UserCode.serialized b/config/src/test/resources/serialized/6.5.x/org.springframework.security.oauth2.core.OAuth2UserCode.serialized new file mode 100644 index 0000000000000000000000000000000000000000..a3fd001c78683ca627a4eb914cc5af5ee59ab26a GIT binary patch literal 311 zcmZ4UmVvdnh`~I+C|$3(peQphJ*_A)H?=&!C|j>MHMz7Xv!qflKe4nV!$>bVzbIAD z-x0(PElw?R&QD1_X}tByAFrh>ObiSaMGQ7Xo8p*MTvC*nTmmsIBtJVfj{yvra+nyH zeHb`XD+)4;Qi~l+N*IKFvJ%S@^-D5yQ}sRbic1pnO00btI5LZiOH)%EO9~n47?^w* zxJp2VgeB&brj{@WLA2x~=B4Wgmw^e|@Pa2ARu>5Um4?mN2k_?EwIe>umY} literal 0 HcmV?d00001 diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistration.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistration.java index 0b4179ff03..b492a6d801 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistration.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistration.java @@ -16,6 +16,7 @@ package org.springframework.security.oauth2.client.registration; +import java.io.Serial; import java.io.Serializable; import java.util.Arrays; import java.util.Collection; @@ -748,7 +749,10 @@ private static boolean withinTheRangeOf(int c, int min, int max) { * @author DingHao * @since 6.5 */ - public static final class ClientSettings { + public static final class ClientSettings implements Serializable { + + @Serial + private static final long serialVersionUID = 7495627155437124692L; private boolean requireProofKey; From b010f84b9fc4bf81f63ccbc6f52381d3ea05fc6b Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Wed, 22 Jan 2025 14:06:11 -0600 Subject: [PATCH 117/132] Add serializeAndDeserializeAreEqual Checks that serialization/deserialization can be performed. Issue gh-16443 Signed-off-by: Daeho Kwon --- ...gSecurityCoreVersionSerializableTests.java | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java b/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java index 7c9b4cdf89..6d7d289778 100644 --- a/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java +++ b/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java @@ -16,6 +16,8 @@ package org.springframework.security; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -35,12 +37,14 @@ import java.util.Collection; import java.util.Date; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.apache.commons.lang3.ObjectUtils; import org.apereo.cas.client.validation.AssertionImpl; import org.instancio.Instancio; import org.instancio.InstancioApi; @@ -192,6 +196,7 @@ import org.springframework.security.web.firewall.RequestRejectedException; import org.springframework.security.web.server.firewall.ServerExchangeRejectedException; import org.springframework.security.web.session.HttpSessionCreatedEvent; +import org.springframework.util.ReflectionUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; @@ -512,6 +517,52 @@ class SpringSecurityCoreVersionSerializableTests { (r) -> new HttpSessionCreatedEvent(new MockHttpSession())); } + @ParameterizedTest + @MethodSource("getClassesToSerialize") + void serializeAndDeserializeAreEqual(Class clazz) throws Exception { + Object expected = instancioWithDefaults(clazz).create(); + assertThat(expected).isInstanceOf(clazz); + try (ByteArrayOutputStream out = new ByteArrayOutputStream(); + ObjectOutputStream objectOutputStream = new ObjectOutputStream(out)) { + objectOutputStream.writeObject(expected); + objectOutputStream.flush(); + + try (ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); + ObjectInputStream objectInputStream = new ObjectInputStream(in)) { + Object deserialized = objectInputStream.readObject(); + // Ignore transient fields Event classes extend from EventObject which has + // transient source property + Set transientFieldNames = new HashSet(); + Set> visitedClasses = new HashSet(); + collectTransientFieldNames(transientFieldNames, visitedClasses, clazz); + assertThat(deserialized).usingRecursiveComparison() + .ignoringFields(transientFieldNames.toArray(new String[0])) + // RuntimeExceptions do not fully work but ensure the message does + .withComparatorForType((lhs, rhs) -> ObjectUtils.compare(lhs.getMessage(), rhs.getMessage()), + RuntimeException.class) + .isEqualTo(expected); + } + } + } + + private static void collectTransientFieldNames(Set transientFieldNames, Set> visitedClasses, + Class clazz) { + if (!visitedClasses.add(clazz) || clazz.isPrimitive()) { + return; + } + ReflectionUtils.doWithFields(clazz, (field) -> { + if (Modifier.isTransient(field.getModifiers())) { + transientFieldNames.add(field.getName()); + } + collectTransientFieldNames(transientFieldNames, visitedClasses, field.getType()); + }); + } + + @Test + void debug() throws Exception { + serializeAndDeserializeAreEqual(JaasAuthenticationFailedEvent.class); + } + @ParameterizedTest @MethodSource("getClassesToSerialize") @Disabled("This method should only be used to serialize the classes once") From 8751eb7867cc9ae2907a00c6b0b7c14580f0b985 Mon Sep 17 00:00:00 2001 From: Daniel Garnier-Moiroux Date: Tue, 21 Jan 2025 22:49:14 +0100 Subject: [PATCH 118/132] fix flakey test in WebAuthnWebDriverTests Closes gh-16463 Signed-off-by: Daniel Garnier-Moiroux Signed-off-by: Daeho Kwon --- .../annotation/configurers/WebAuthnWebDriverTests.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/config/src/integration-test/java/org/springframework/security/config/annotation/configurers/WebAuthnWebDriverTests.java b/config/src/integration-test/java/org/springframework/security/config/annotation/configurers/WebAuthnWebDriverTests.java index 075856f3a4..cc5d7a3501 100644 --- a/config/src/integration-test/java/org/springframework/security/config/annotation/configurers/WebAuthnWebDriverTests.java +++ b/config/src/integration-test/java/org/springframework/security/config/annotation/configurers/WebAuthnWebDriverTests.java @@ -33,6 +33,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.openqa.selenium.By; +import org.openqa.selenium.WebDriverException; import org.openqa.selenium.WebElement; import org.openqa.selenium.chrome.ChromeDriverService; import org.openqa.selenium.chrome.ChromeOptions; @@ -273,12 +274,14 @@ private AbstractStringAssert assertHasAlertStartingWith(String alertType, Str /** * Await until the assertion passes. If the assertion fails, it will display the - * assertion error in stdout. + * assertion error in stdout. WebDriver-related exceptions are ignored, so that + * {@code assertion}s can interact with the page and be retried on error, e.g. + * {@code assertThat(this.driver.findElement(By.Id("some-id")).isNotNull()}. */ private void await(Supplier> assertion) { new FluentWait<>(this.driver).withTimeout(Duration.ofSeconds(2)) .pollingEvery(Duration.ofMillis(100)) - .ignoring(AssertionError.class) + .ignoring(AssertionError.class, WebDriverException.class) .until((d) -> { assertion.get(); return true; From 958925183212298b7d44351cecaf88c5d7cdb993 Mon Sep 17 00:00:00 2001 From: Daniel Garnier-Moiroux Date: Tue, 21 Jan 2025 15:45:57 +0100 Subject: [PATCH 119/132] Fix GenerateOneTimeTokenWebFilter double publish of chain.filter(...) closes gh-16458 Signed-off-by: Daniel Garnier-Moiroux Signed-off-by: Daeho Kwon --- .../server/authentication/ott/GenerateOneTimeTokenWebFilter.java | 1 - 1 file changed, 1 deletion(-) diff --git a/web/src/main/java/org/springframework/security/web/server/authentication/ott/GenerateOneTimeTokenWebFilter.java b/web/src/main/java/org/springframework/security/web/server/authentication/ott/GenerateOneTimeTokenWebFilter.java index 8301e17dcf..170d1d0b68 100644 --- a/web/src/main/java/org/springframework/security/web/server/authentication/ott/GenerateOneTimeTokenWebFilter.java +++ b/web/src/main/java/org/springframework/security/web/server/authentication/ott/GenerateOneTimeTokenWebFilter.java @@ -58,7 +58,6 @@ public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { // @formatter:off return this.matcher.matches(exchange) .filter(ServerWebExchangeMatcher.MatchResult::isMatch) - .switchIfEmpty(chain.filter(exchange).then(Mono.empty())) .then(exchange.getFormData()) .mapNotNull((data) -> data.getFirst(USERNAME)) .switchIfEmpty(chain.filter(exchange).then(Mono.empty())) From 8a51ef59e7d4d75fd6572753a407452ea06f7e77 Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Wed, 22 Jan 2025 16:11:25 -0600 Subject: [PATCH 120/132] Remove debug test Issue gh-16443 Signed-off-by: Daeho Kwon --- .../security/SpringSecurityCoreVersionSerializableTests.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java b/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java index 6d7d289778..2b037d1e1a 100644 --- a/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java +++ b/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java @@ -558,11 +558,6 @@ private static void collectTransientFieldNames(Set transientFieldNames, }); } - @Test - void debug() throws Exception { - serializeAndDeserializeAreEqual(JaasAuthenticationFailedEvent.class); - } - @ParameterizedTest @MethodSource("getClassesToSerialize") @Disabled("This method should only be used to serialize the classes once") From 154edbf2f6007745c82e1a5f38342de722ddb10a Mon Sep 17 00:00:00 2001 From: Max Batischev Date: Wed, 22 Jan 2025 15:20:06 +0300 Subject: [PATCH 121/132] Add Support GenerateOneTimeTokenRequestResolver Closes gh-16291 Signed-off-by: Max Batischev Signed-off-by: Daeho Kwon --- .../ott/OneTimeTokenLoginConfigurer.java | 31 ++++++++- .../annotation/web/OneTimeTokenLoginDsl.kt | 8 +++ .../ott/OneTimeTokenLoginConfigurerTests.java | 57 +++++++++++++++- .../web/OneTimeTokenLoginDslTests.kt | 59 +++++++++++++++- .../ott/GenerateOneTimeTokenRequest.java | 18 ++++- .../ott/InMemoryOneTimeTokenService.java | 6 +- .../ott/JdbcOneTimeTokenService.java | 7 +- .../servlet/authentication/onetimetoken.adoc | 34 ++++++++++ docs/modules/ROOT/pages/whats-new.adoc | 4 ++ ...ltGenerateOneTimeTokenRequestResolver.java | 58 ++++++++++++++++ .../ott/GenerateOneTimeTokenFilter.java | 21 +++++- .../GenerateOneTimeTokenRequestResolver.java | 41 ++++++++++++ ...erateOneTimeTokenRequestResolverTests.java | 67 +++++++++++++++++++ 13 files changed, 398 insertions(+), 13 deletions(-) create mode 100644 web/src/main/java/org/springframework/security/web/authentication/ott/DefaultGenerateOneTimeTokenRequestResolver.java create mode 100644 web/src/main/java/org/springframework/security/web/authentication/ott/GenerateOneTimeTokenRequestResolver.java create mode 100644 web/src/test/java/org/springframework/security/web/authentication/ott/DefaultGenerateOneTimeTokenRequestResolverTests.java diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ott/OneTimeTokenLoginConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ott/OneTimeTokenLoginConfigurer.java index 15718bf51b..654c277e49 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ott/OneTimeTokenLoginConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ott/OneTimeTokenLoginConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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,6 +18,7 @@ import java.util.Collections; import java.util.Map; +import java.util.Objects; import jakarta.servlet.http.HttpServletRequest; @@ -25,6 +26,7 @@ import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.ott.GenerateOneTimeTokenRequest; import org.springframework.security.authentication.ott.InMemoryOneTimeTokenService; import org.springframework.security.authentication.ott.OneTimeToken; import org.springframework.security.authentication.ott.OneTimeTokenAuthenticationProvider; @@ -40,7 +42,9 @@ import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; +import org.springframework.security.web.authentication.ott.DefaultGenerateOneTimeTokenRequestResolver; import org.springframework.security.web.authentication.ott.GenerateOneTimeTokenFilter; +import org.springframework.security.web.authentication.ott.GenerateOneTimeTokenRequestResolver; import org.springframework.security.web.authentication.ott.OneTimeTokenAuthenticationConverter; import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler; import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter; @@ -79,6 +83,8 @@ public final class OneTimeTokenLoginConfigurer> private AuthenticationProvider authenticationProvider; + private GenerateOneTimeTokenRequestResolver requestResolver; + public OneTimeTokenLoginConfigurer(ApplicationContext context) { this.context = context; } @@ -135,6 +141,7 @@ private void configureOttGenerateFilter(H http) { GenerateOneTimeTokenFilter generateFilter = new GenerateOneTimeTokenFilter(getOneTimeTokenService(http), getOneTimeTokenGenerationSuccessHandler(http)); generateFilter.setRequestMatcher(antMatcher(HttpMethod.POST, this.tokenGeneratingUrl)); + generateFilter.setRequestResolver(getGenerateRequestResolver(http)); http.addFilter(postProcess(generateFilter)); http.addFilter(DefaultResourcesFilter.css()); } @@ -301,6 +308,28 @@ private AuthenticationFailureHandler getAuthenticationFailureHandler() { return this.authenticationFailureHandler; } + /** + * Use this {@link GenerateOneTimeTokenRequestResolver} when resolving + * {@link GenerateOneTimeTokenRequest} from {@link HttpServletRequest}. By default, + * the {@link DefaultGenerateOneTimeTokenRequestResolver} is used. + * @param requestResolver the {@link GenerateOneTimeTokenRequestResolver} + * @since 6.5 + */ + public OneTimeTokenLoginConfigurer generateRequestResolver(GenerateOneTimeTokenRequestResolver requestResolver) { + Assert.notNull(requestResolver, "requestResolver cannot be null"); + this.requestResolver = requestResolver; + return this; + } + + private GenerateOneTimeTokenRequestResolver getGenerateRequestResolver(H http) { + if (this.requestResolver != null) { + return this.requestResolver; + } + GenerateOneTimeTokenRequestResolver bean = getBeanOrNull(http, GenerateOneTimeTokenRequestResolver.class); + this.requestResolver = Objects.requireNonNullElseGet(bean, DefaultGenerateOneTimeTokenRequestResolver::new); + return this.requestResolver; + } + private OneTimeTokenService getOneTimeTokenService(H http) { if (this.oneTimeTokenService != null) { return this.oneTimeTokenService; diff --git a/config/src/main/kotlin/org/springframework/security/config/annotation/web/OneTimeTokenLoginDsl.kt b/config/src/main/kotlin/org/springframework/security/config/annotation/web/OneTimeTokenLoginDsl.kt index 025e65e741..2345bc5a67 100644 --- a/config/src/main/kotlin/org/springframework/security/config/annotation/web/OneTimeTokenLoginDsl.kt +++ b/config/src/main/kotlin/org/springframework/security/config/annotation/web/OneTimeTokenLoginDsl.kt @@ -23,6 +23,7 @@ import org.springframework.security.config.annotation.web.configurers.ott.OneTim import org.springframework.security.web.authentication.AuthenticationConverter import org.springframework.security.web.authentication.AuthenticationFailureHandler import org.springframework.security.web.authentication.AuthenticationSuccessHandler +import org.springframework.security.web.authentication.ott.GenerateOneTimeTokenRequestResolver import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler /** @@ -34,6 +35,7 @@ import org.springframework.security.web.authentication.ott.OneTimeTokenGeneratio * @property authenticationConverter Use this [AuthenticationConverter] when converting incoming requests to an authentication * @property authenticationFailureHandler the [AuthenticationFailureHandler] to use when authentication * @property authenticationSuccessHandler the [AuthenticationSuccessHandler] to be used + * @property generateRequestResolver the [GenerateOneTimeTokenRequestResolver] to be used * @property defaultSubmitPageUrl sets the URL that the default submit page will be generated * @property showDefaultSubmitPage configures whether the default one-time token submit page should be shown * @property loginProcessingUrl the URL to process the login request @@ -47,6 +49,7 @@ class OneTimeTokenLoginDsl { var authenticationConverter: AuthenticationConverter? = null var authenticationFailureHandler: AuthenticationFailureHandler? = null var authenticationSuccessHandler: AuthenticationSuccessHandler? = null + var generateRequestResolver: GenerateOneTimeTokenRequestResolver? = null var defaultSubmitPageUrl: String? = null var loginProcessingUrl: String? = null var tokenGeneratingUrl: String? = null @@ -68,6 +71,11 @@ class OneTimeTokenLoginDsl { authenticationSuccessHandler ) } + generateRequestResolver?.also { + oneTimeTokenLoginConfigurer.generateRequestResolver( + generateRequestResolver + ) + } defaultSubmitPageUrl?.also { oneTimeTokenLoginConfigurer.defaultSubmitPageUrl(defaultSubmitPageUrl) } showDefaultSubmitPage?.also { oneTimeTokenLoginConfigurer.showDefaultSubmitPage(showDefaultSubmitPage!!) } loginProcessingUrl?.also { oneTimeTokenLoginConfigurer.loginProcessingUrl(loginProcessingUrl) } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ott/OneTimeTokenLoginConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ott/OneTimeTokenLoginConfigurerTests.java index f89a37ae40..b3c97b9201 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ott/OneTimeTokenLoginConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ott/OneTimeTokenLoginConfigurerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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,9 @@ package org.springframework.security.config.annotation.web.configurers.ott; import java.io.IOException; +import java.time.Duration; +import java.time.Instant; +import java.time.ZoneOffset; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; @@ -29,6 +32,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.springframework.security.authentication.ott.GenerateOneTimeTokenRequest; import org.springframework.security.authentication.ott.OneTimeToken; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -40,6 +44,8 @@ import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; +import org.springframework.security.web.authentication.ott.DefaultGenerateOneTimeTokenRequestResolver; +import org.springframework.security.web.authentication.ott.GenerateOneTimeTokenRequestResolver; import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler; import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenGenerationSuccessHandler; import org.springframework.security.web.csrf.CsrfToken; @@ -194,6 +200,55 @@ Please provide it as a bean or pass it to the oneTimeTokenLogin() DSL. """); } + @Test + void oneTimeTokenWhenCustomTokenExpirationTimeSetThenAuthenticate() throws Exception { + this.spring.register(OneTimeTokenConfigWithCustomTokenExpirationTime.class).autowire(); + this.mvc.perform(post("/ott/generate").param("username", "user").with(csrf())) + .andExpectAll(status().isFound(), redirectedUrl("/login/ott")); + + OneTimeToken token = TestOneTimeTokenGenerationSuccessHandler.lastToken; + + this.mvc.perform(post("/login/ott").param("token", token.getTokenValue()).with(csrf())) + .andExpectAll(status().isFound(), redirectedUrl("/"), authenticated()); + assertThat(getCurrentMinutes(token.getExpiresAt())).isEqualTo(10); + } + + private int getCurrentMinutes(Instant expiresAt) { + int expiresMinutes = expiresAt.atZone(ZoneOffset.UTC).getMinute(); + int currentMinutes = Instant.now().atZone(ZoneOffset.UTC).getMinute(); + return expiresMinutes - currentMinutes; + } + + @Configuration(proxyBeanMethods = false) + @EnableWebSecurity + @Import(UserDetailsServiceConfig.class) + static class OneTimeTokenConfigWithCustomTokenExpirationTime { + + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeHttpRequests((authz) -> authz + .anyRequest().authenticated() + ) + .oneTimeTokenLogin((ott) -> ott + .tokenGenerationSuccessHandler(new TestOneTimeTokenGenerationSuccessHandler()) + ); + // @formatter:on + return http.build(); + } + + @Bean + GenerateOneTimeTokenRequestResolver generateOneTimeTokenRequestResolver() { + DefaultGenerateOneTimeTokenRequestResolver delegate = new DefaultGenerateOneTimeTokenRequestResolver(); + return (request) -> { + GenerateOneTimeTokenRequest generate = delegate.resolve(request); + return new GenerateOneTimeTokenRequest(generate.getUsername(), Duration.ofSeconds(600)); + }; + } + + } + @Configuration(proxyBeanMethods = false) @EnableWebSecurity @Import(UserDetailsServiceConfig.class) diff --git a/config/src/test/kotlin/org/springframework/security/config/annotation/web/OneTimeTokenLoginDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/annotation/web/OneTimeTokenLoginDslTests.kt index 07833e283f..a8b52c5137 100644 --- a/config/src/test/kotlin/org/springframework/security/config/annotation/web/OneTimeTokenLoginDslTests.kt +++ b/config/src/test/kotlin/org/springframework/security/config/annotation/web/OneTimeTokenLoginDslTests.kt @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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,6 +18,7 @@ package org.springframework.security.config.annotation.web import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse +import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.springframework.beans.factory.annotation.Autowired @@ -36,11 +37,15 @@ import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequ import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers import org.springframework.security.web.SecurityFilterChain import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler +import org.springframework.security.web.authentication.ott.DefaultGenerateOneTimeTokenRequestResolver import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenGenerationSuccessHandler import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.request.MockMvcRequestBuilders import org.springframework.test.web.servlet.result.MockMvcResultMatchers +import java.time.Duration +import java.time.Instant +import java.time.ZoneOffset /** * Tests for [OneTimeTokenLoginDsl] @@ -104,6 +109,32 @@ class OneTimeTokenLoginDslTests { ) } + @Test + fun `oneTimeToken when custom resolver set then use custom token`() { + spring.register(OneTimeTokenConfigWithCustomTokenResolver::class.java).autowire() + + this.mockMvc.perform( + MockMvcRequestBuilders.post("/ott/generate").param("username", "user") + .with(SecurityMockMvcRequestPostProcessors.csrf()) + ).andExpectAll( + MockMvcResultMatchers + .status() + .isFound(), + MockMvcResultMatchers + .redirectedUrl("/login/ott") + ) + + val token = TestOneTimeTokenGenerationSuccessHandler.lastToken + + assertThat(getCurrentMinutes(token!!.expiresAt)).isEqualTo(10) + } + + private fun getCurrentMinutes(expiresAt: Instant): Int { + val expiresMinutes = expiresAt.atZone(ZoneOffset.UTC).minute + val currentMinutes = Instant.now().atZone(ZoneOffset.UTC).minute + return expiresMinutes - currentMinutes + } + @Configuration @EnableWebSecurity @Import(UserDetailsServiceConfig::class) @@ -125,6 +156,32 @@ class OneTimeTokenLoginDslTests { } } + @Configuration + @EnableWebSecurity + @Import(UserDetailsServiceConfig::class) + open class OneTimeTokenConfigWithCustomTokenResolver { + + @Bean + open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + // @formatter:off + http { + authorizeHttpRequests { + authorize(anyRequest, authenticated) + } + oneTimeTokenLogin { + oneTimeTokenGenerationSuccessHandler = TestOneTimeTokenGenerationSuccessHandler() + generateRequestResolver = DefaultGenerateOneTimeTokenRequestResolver().apply { + this.setExpiresIn(Duration.ofMinutes(10)) + } + } + } + // @formatter:on + return http.build() + } + + + } + @EnableWebSecurity @Configuration(proxyBeanMethods = false) @Import(UserDetailsServiceConfig::class) diff --git a/core/src/main/java/org/springframework/security/authentication/ott/GenerateOneTimeTokenRequest.java b/core/src/main/java/org/springframework/security/authentication/ott/GenerateOneTimeTokenRequest.java index c9a023ef83..b03e65dd18 100644 --- a/core/src/main/java/org/springframework/security/authentication/ott/GenerateOneTimeTokenRequest.java +++ b/core/src/main/java/org/springframework/security/authentication/ott/GenerateOneTimeTokenRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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,6 +16,8 @@ package org.springframework.security.authentication.ott; +import java.time.Duration; + import org.springframework.util.Assert; /** @@ -26,15 +28,29 @@ */ public class GenerateOneTimeTokenRequest { + private static final Duration DEFAULT_EXPIRES_IN = Duration.ofMinutes(5); + private final String username; + private final Duration expiresIn; + public GenerateOneTimeTokenRequest(String username) { + this(username, DEFAULT_EXPIRES_IN); + } + + public GenerateOneTimeTokenRequest(String username, Duration expiresIn) { Assert.hasText(username, "username cannot be empty"); + Assert.notNull(expiresIn, "expiresIn cannot be null"); this.username = username; + this.expiresIn = expiresIn; } public String getUsername() { return this.username; } + public Duration getExpiresIn() { + return this.expiresIn; + } + } diff --git a/core/src/main/java/org/springframework/security/authentication/ott/InMemoryOneTimeTokenService.java b/core/src/main/java/org/springframework/security/authentication/ott/InMemoryOneTimeTokenService.java index 6365bdb5f1..0d67961794 100644 --- a/core/src/main/java/org/springframework/security/authentication/ott/InMemoryOneTimeTokenService.java +++ b/core/src/main/java/org/springframework/security/authentication/ott/InMemoryOneTimeTokenService.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -44,8 +44,8 @@ public final class InMemoryOneTimeTokenService implements OneTimeTokenService { @NonNull public OneTimeToken generate(GenerateOneTimeTokenRequest request) { String token = UUID.randomUUID().toString(); - Instant fiveMinutesFromNow = this.clock.instant().plusSeconds(300); - OneTimeToken ott = new DefaultOneTimeToken(token, request.getUsername(), fiveMinutesFromNow); + Instant expiresAt = this.clock.instant().plus(request.getExpiresIn()); + OneTimeToken ott = new DefaultOneTimeToken(token, request.getUsername(), expiresAt); this.oneTimeTokenByToken.put(token, ott); cleanExpiredTokensIfNeeded(); return ott; diff --git a/core/src/main/java/org/springframework/security/authentication/ott/JdbcOneTimeTokenService.java b/core/src/main/java/org/springframework/security/authentication/ott/JdbcOneTimeTokenService.java index 4cf6753631..a58665bd1e 100644 --- a/core/src/main/java/org/springframework/security/authentication/ott/JdbcOneTimeTokenService.java +++ b/core/src/main/java/org/springframework/security/authentication/ott/JdbcOneTimeTokenService.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,6 @@ import java.sql.Timestamp; import java.sql.Types; import java.time.Clock; -import java.time.Duration; import java.time.Instant; import java.util.ArrayList; import java.util.List; @@ -132,8 +131,8 @@ public void setCleanupCron(String cleanupCron) { public OneTimeToken generate(GenerateOneTimeTokenRequest request) { Assert.notNull(request, "generateOneTimeTokenRequest cannot be null"); String token = UUID.randomUUID().toString(); - Instant fiveMinutesFromNow = this.clock.instant().plus(Duration.ofMinutes(5)); - OneTimeToken oneTimeToken = new DefaultOneTimeToken(token, request.getUsername(), fiveMinutesFromNow); + Instant expiresAt = this.clock.instant().plus(request.getExpiresIn()); + OneTimeToken oneTimeToken = new DefaultOneTimeToken(token, request.getUsername(), expiresAt); insertOneTimeToken(oneTimeToken); return oneTimeToken; } diff --git a/docs/modules/ROOT/pages/servlet/authentication/onetimetoken.adoc b/docs/modules/ROOT/pages/servlet/authentication/onetimetoken.adoc index db67a98527..b799f6637a 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/onetimetoken.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/onetimetoken.adoc @@ -545,3 +545,37 @@ class MagicLinkOneTimeTokenGenerationSuccessHandler : OneTimeTokenGenerationSucc } ---- ====== + +[[customize-generate-token-request]] +== Customize GenerateOneTimeTokenRequest Instance +There are a number of reasons that you may want to adjust an GenerateOneTimeTokenRequest. For example, you may want expiresIn to be set to 10 mins, which Spring Security sets to 5 mins by default. + +You can customize elements of GenerateOneTimeTokenRequest by publishing an GenerateOneTimeTokenRequestResolver as a @Bean, like so: +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +@Bean +GenerateOneTimeTokenRequestResolver generateOneTimeTokenRequestResolver() { + DefaultGenerateOneTimeTokenRequestResolver delegate = new DefaultGenerateOneTimeTokenRequestResolver(); + return (request) -> { + GenerateOneTimeTokenRequest generate = delegate.resolve(request); + return new GenerateOneTimeTokenRequest(generate.getUsername(), Duration.ofSeconds(600)); + }; +} +---- + +Kotlin:: ++ +[source,kotlin,role="secondary"] +---- +@Bean +fun generateRequestResolver() : GenerateOneTimeTokenRequestResolver { + return DefaultGenerateOneTimeTokenRequestResolver().apply { + this.setExpiresIn(Duration.ofMinutes(10)) + } +} +---- +====== diff --git a/docs/modules/ROOT/pages/whats-new.adoc b/docs/modules/ROOT/pages/whats-new.adoc index 01f5e85150..ce39420774 100644 --- a/docs/modules/ROOT/pages/whats-new.adoc +++ b/docs/modules/ROOT/pages/whats-new.adoc @@ -20,3 +20,7 @@ Note that this may affect reports that operate on this key name. * https://github.com/spring-projects/spring-security/pull/16282[gh-16282] - xref:servlet/authentication/passkeys.adoc#passkeys-configuration-persistence[JDBC Persistence] for WebAuthn/Passkeys * https://github.com/spring-projects/spring-security/pull/16397[gh-16397] - Added the ability to configure a custom `HttpMessageConverter` for Passkeys using the optional xref:servlet/authentication/passkeys.adoc#passkeys-configuration[`messageConverter` property] on the `webAuthn` DSL. * https://github.com/spring-projects/spring-security/pull/16396[gh-16396] - Added the ability to configure a custom xref:servlet/authentication/passkeys.adoc#passkeys-configuration-pkccor[`PublicKeyCredentialCreationOptionsRepository`] + +== One-Time Token Login + +* https://github.com/spring-projects/spring-security/issues/16291[gh-16291] - `oneTimeTokenLogin()` now supports customizing GenerateOneTimeTokenRequest xref:servlet/authentication/onetimetoken.adoc#customize-generate-token-request[via GenerateOneTimeTokenRequestResolver] diff --git a/web/src/main/java/org/springframework/security/web/authentication/ott/DefaultGenerateOneTimeTokenRequestResolver.java b/web/src/main/java/org/springframework/security/web/authentication/ott/DefaultGenerateOneTimeTokenRequestResolver.java new file mode 100644 index 0000000000..87c5034905 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/authentication/ott/DefaultGenerateOneTimeTokenRequestResolver.java @@ -0,0 +1,58 @@ +/* + * Copyright 2002-2025 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.authentication.ott; + +import java.time.Duration; + +import jakarta.servlet.http.HttpServletRequest; + +import org.springframework.security.authentication.ott.GenerateOneTimeTokenRequest; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Default implementation of {@link GenerateOneTimeTokenRequestResolver}. Resolves + * {@link GenerateOneTimeTokenRequest} from username parameter. + * + * @author Max Batischev + * @since 6.5 + */ +public final class DefaultGenerateOneTimeTokenRequestResolver implements GenerateOneTimeTokenRequestResolver { + + private static final Duration DEFAULT_EXPIRES_IN = Duration.ofMinutes(5); + + private Duration expiresIn = DEFAULT_EXPIRES_IN; + + @Override + public GenerateOneTimeTokenRequest resolve(HttpServletRequest request) { + String username = request.getParameter("username"); + if (!StringUtils.hasText(username)) { + return null; + } + return new GenerateOneTimeTokenRequest(username, this.expiresIn); + } + + /** + * Sets one-time token expiration time + * @param expiresIn one-time token expiration time + */ + public void setExpiresIn(Duration expiresIn) { + Assert.notNull(expiresIn, "expiresAt cannot be null"); + this.expiresIn = expiresIn; + } + +} diff --git a/web/src/main/java/org/springframework/security/web/authentication/ott/GenerateOneTimeTokenFilter.java b/web/src/main/java/org/springframework/security/web/authentication/ott/GenerateOneTimeTokenFilter.java index 8c9cbf65b6..2ad462993e 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/ott/GenerateOneTimeTokenFilter.java +++ b/web/src/main/java/org/springframework/security/web/authentication/ott/GenerateOneTimeTokenFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -49,6 +49,8 @@ public final class GenerateOneTimeTokenFilter extends OncePerRequestFilter { private RequestMatcher requestMatcher = antMatcher(HttpMethod.POST, "/ott/generate"); + private GenerateOneTimeTokenRequestResolver requestResolver = new DefaultGenerateOneTimeTokenRequestResolver(); + public GenerateOneTimeTokenFilter(OneTimeTokenService tokenService, OneTimeTokenGenerationSuccessHandler tokenGenerationSuccessHandler) { Assert.notNull(tokenService, "tokenService cannot be null"); @@ -69,8 +71,12 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse filterChain.doFilter(request, response); return; } - GenerateOneTimeTokenRequest generateRequest = new GenerateOneTimeTokenRequest(username); + GenerateOneTimeTokenRequest generateRequest = this.requestResolver.resolve(request); OneTimeToken ott = this.tokenService.generate(generateRequest); + if (generateRequest == null) { + filterChain.doFilter(request, response); + return; + } this.tokenGenerationSuccessHandler.handle(request, response, ott); } @@ -83,4 +89,15 @@ public void setRequestMatcher(RequestMatcher requestMatcher) { this.requestMatcher = requestMatcher; } + /** + * Use the given {@link GenerateOneTimeTokenRequestResolver} to resolve + * {@link GenerateOneTimeTokenRequest}. + * @param requestResolver {@link GenerateOneTimeTokenRequestResolver} + * @since 6.5 + */ + public void setRequestResolver(GenerateOneTimeTokenRequestResolver requestResolver) { + Assert.notNull(requestResolver, "requestResolver cannot be null"); + this.requestResolver = requestResolver; + } + } diff --git a/web/src/main/java/org/springframework/security/web/authentication/ott/GenerateOneTimeTokenRequestResolver.java b/web/src/main/java/org/springframework/security/web/authentication/ott/GenerateOneTimeTokenRequestResolver.java new file mode 100644 index 0000000000..9fa8873ed2 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/authentication/ott/GenerateOneTimeTokenRequestResolver.java @@ -0,0 +1,41 @@ +/* + * Copyright 2002-2025 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.authentication.ott; + +import jakarta.servlet.http.HttpServletRequest; + +import org.springframework.lang.Nullable; +import org.springframework.security.authentication.ott.GenerateOneTimeTokenRequest; + +/** + * A strategy for resolving a {@link GenerateOneTimeTokenRequest} from the + * {@link HttpServletRequest}. + * + * @author Max Batischev + * @since 6.5 + */ +public interface GenerateOneTimeTokenRequestResolver { + + /** + * Resolves {@link GenerateOneTimeTokenRequest} from {@link HttpServletRequest} + * @param request {@link HttpServletRequest} to resolve + * @return {@link GenerateOneTimeTokenRequest} + */ + @Nullable + GenerateOneTimeTokenRequest resolve(HttpServletRequest request); + +} diff --git a/web/src/test/java/org/springframework/security/web/authentication/ott/DefaultGenerateOneTimeTokenRequestResolverTests.java b/web/src/test/java/org/springframework/security/web/authentication/ott/DefaultGenerateOneTimeTokenRequestResolverTests.java new file mode 100644 index 0000000000..12a491230e --- /dev/null +++ b/web/src/test/java/org/springframework/security/web/authentication/ott/DefaultGenerateOneTimeTokenRequestResolverTests.java @@ -0,0 +1,67 @@ +/* + * Copyright 2002-2025 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.authentication.ott; + +import java.time.Duration; + +import org.junit.jupiter.api.Test; + +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.security.authentication.ott.GenerateOneTimeTokenRequest; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link DefaultGenerateOneTimeTokenRequestResolver} + * + * @author Max Batischev + */ +public class DefaultGenerateOneTimeTokenRequestResolverTests { + + private final DefaultGenerateOneTimeTokenRequestResolver requestResolver = new DefaultGenerateOneTimeTokenRequestResolver(); + + @Test + void resolveWhenUsernameParameterIsPresentThenResolvesGenerateRequest() { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setParameter("username", "test"); + + GenerateOneTimeTokenRequest generateRequest = this.requestResolver.resolve(request); + + assertThat(generateRequest).isNotNull(); + assertThat(generateRequest.getUsername()).isEqualTo("test"); + assertThat(generateRequest.getExpiresIn()).isEqualTo(Duration.ofSeconds(300)); + } + + @Test + void resolveWhenUsernameParameterIsNotPresentThenNull() { + GenerateOneTimeTokenRequest generateRequest = this.requestResolver.resolve(new MockHttpServletRequest()); + + assertThat(generateRequest).isNull(); + } + + @Test + void resolveWhenExpiresInSetThenResolvesGenerateRequest() { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setParameter("username", "test"); + this.requestResolver.setExpiresIn(Duration.ofSeconds(600)); + + GenerateOneTimeTokenRequest generateRequest = this.requestResolver.resolve(request); + + assertThat(generateRequest.getExpiresIn()).isEqualTo(Duration.ofSeconds(600)); + } + +} From cc907355bb050b7d9c88dfb38da562ea49f5dcfb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Jan 2025 03:40:34 +0000 Subject: [PATCH 122/132] Bump io.freefair.gradle:aspectj-plugin from 8.11 to 8.12 Bumps [io.freefair.gradle:aspectj-plugin](https://github.com/freefair/gradle-plugins) from 8.11 to 8.12. - [Release notes](https://github.com/freefair/gradle-plugins/releases) - [Commits](https://github.com/freefair/gradle-plugins/compare/8.11...8.12) --- updated-dependencies: - dependency-name: io.freefair.gradle:aspectj-plugin dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: Daeho Kwon --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b472dbde72..20d57c5b0d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -61,7 +61,7 @@ org-apache-maven-resolver-maven-resolver-connector-basic = { module = "org.apach org-apache-maven-resolver-maven-resolver-impl = { module = "org.apache.maven.resolver:maven-resolver-impl", version.ref = "org-apache-maven-resolver" } org-apache-maven-resolver-maven-resolver-transport-http = { module = "org.apache.maven.resolver:maven-resolver-transport-http", version.ref = "org-apache-maven-resolver" } org-apereo-cas-client-cas-client-core = "org.apereo.cas.client:cas-client-core:4.0.4" -io-freefair-gradle-aspectj-plugin = "io.freefair.gradle:aspectj-plugin:8.11" +io-freefair-gradle-aspectj-plugin = "io.freefair.gradle:aspectj-plugin:8.12" org-aspectj-aspectjrt = { module = "org.aspectj:aspectjrt", version.ref = "org-aspectj" } org-aspectj-aspectjweaver = { module = "org.aspectj:aspectjweaver", version.ref = "org-aspectj" } org-assertj-assertj-core = "org.assertj:assertj-core:3.27.3" From 82a285ae318949b902b48743f0abba297fd51896 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Jan 2025 03:40:12 +0000 Subject: [PATCH 123/132] Bump org.htmlunit:htmlunit from 4.8.0 to 4.9.0 Bumps [org.htmlunit:htmlunit](https://github.com/HtmlUnit/htmlunit) from 4.8.0 to 4.9.0. - [Release notes](https://github.com/HtmlUnit/htmlunit/releases) - [Commits](https://github.com/HtmlUnit/htmlunit/compare/4.8.0...4.9.0) --- updated-dependencies: - dependency-name: org.htmlunit:htmlunit dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: Daeho Kwon --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 20d57c5b0d..48ed5b97de 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -48,7 +48,7 @@ jakarta-websocket-jakarta-websocket-client-api = { module = "jakarta.websocket:j jakarta-xml-bind-jakarta-xml-bind-api = "jakarta.xml.bind:jakarta.xml.bind-api:4.0.2" ldapsdk = "ldapsdk:ldapsdk:4.1" net-sourceforge-htmlunit = "net.sourceforge.htmlunit:htmlunit:2.70.0" -org-htmlunit-htmlunit = "org.htmlunit:htmlunit:4.8.0" +org-htmlunit-htmlunit = "org.htmlunit:htmlunit:4.9.0" org-apache-directory-server-apacheds-core = { module = "org.apache.directory.server:apacheds-core", version.ref = "org-apache-directory-server" } org-apache-directory-server-apacheds-entry = { module = "org.apache.directory.server:apacheds-core-entry", version.ref = "org-apache-directory-server" } org-apache-directory-server-apacheds-protocol-ldap = { module = "org.apache.directory.server:apacheds-protocol-ldap", version.ref = "org-apache-directory-server" } From bdaff37ce1d88ea89793856bd589aa458d76c01c Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Thu, 23 Jan 2025 16:44:10 -0700 Subject: [PATCH 124/132] Support Serialization for SecurityConfig Issue gh-16276 Signed-off-by: Daeho Kwon --- ...pringSecurityCoreVersionSerializableTests.java | 2 ++ ...work.security.access.SecurityConfig.serialized | Bin 0 -> 109 bytes .../security/access/SecurityConfig.java | 4 ++++ .../access/annotation/Jsr250SecurityConfig.java | 1 + 4 files changed, 7 insertions(+) create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.access.SecurityConfig.serialized diff --git a/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java b/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java index 2b037d1e1a..e1c45e6156 100644 --- a/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java +++ b/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java @@ -61,6 +61,7 @@ import org.springframework.mock.web.MockHttpSession; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.AuthorizationServiceException; +import org.springframework.security.access.SecurityConfig; import org.springframework.security.access.intercept.RunAsUserToken; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.authentication.AccountExpiredException; @@ -436,6 +437,7 @@ class SpringSecurityCoreVersionSerializableTests { generatorByClassName.put(JaasAuthenticationSuccessEvent.class, (r) -> new JaasAuthenticationSuccessEvent(authentication)); generatorByClassName.put(AbstractSessionEvent.class, (r) -> new AbstractSessionEvent(securityContext)); + generatorByClassName.put(SecurityConfig.class, (r) -> new SecurityConfig("value")); // cas generatorByClassName.put(CasServiceTicketAuthenticationToken.class, (r) -> { diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.access.SecurityConfig.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.access.SecurityConfig.serialized new file mode 100644 index 0000000000000000000000000000000000000000..ae659612d7304086cc379fafb84075a5e5d1f7db GIT binary patch literal 109 zcmZ4UmVvdnh`}hoC|$3(peQphJ*_A)H?=&!C|j>MHMz7Xv!qflF*!N4xL7Y3%6HDs zOUq23^C9;|nf9jwCI&_y2DZeKlA_F{5(Xikti-ZJ{hY+Sbp2qUP+q!qML`JzYguAW GX(|8>RVfkx literal 0 HcmV?d00001 diff --git a/core/src/main/java/org/springframework/security/access/SecurityConfig.java b/core/src/main/java/org/springframework/security/access/SecurityConfig.java index 3079174e52..2cbc640b3a 100644 --- a/core/src/main/java/org/springframework/security/access/SecurityConfig.java +++ b/core/src/main/java/org/springframework/security/access/SecurityConfig.java @@ -16,6 +16,7 @@ package org.springframework.security.access; +import java.io.Serial; import java.util.ArrayList; import java.util.List; @@ -29,6 +30,9 @@ */ public class SecurityConfig implements ConfigAttribute { + @Serial + private static final long serialVersionUID = -7138084564199804304L; + private final String attrib; public SecurityConfig(String config) { diff --git a/core/src/main/java/org/springframework/security/access/annotation/Jsr250SecurityConfig.java b/core/src/main/java/org/springframework/security/access/annotation/Jsr250SecurityConfig.java index 3a3ccdf91e..f129fdbe17 100644 --- a/core/src/main/java/org/springframework/security/access/annotation/Jsr250SecurityConfig.java +++ b/core/src/main/java/org/springframework/security/access/annotation/Jsr250SecurityConfig.java @@ -30,6 +30,7 @@ * @deprecated Use {@link AuthorizationManagerBeforeMethodInterceptor#jsr250()} instead */ @Deprecated +@SuppressWarnings("serial") public class Jsr250SecurityConfig extends SecurityConfig { public static final Jsr250SecurityConfig PERMIT_ALL_ATTRIBUTE = new Jsr250SecurityConfig(PermitAll.class.getName()); From 068a1024241d70590110d12ef5679d1beb188c85 Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Thu, 23 Jan 2025 16:46:38 -0700 Subject: [PATCH 125/132] Don't Support Serialzation of Deprecated Access Classes Issue gh-16276 Signed-off-by: Daeho Kwon --- .../expression/method/PostInvocationExpressionAttribute.java | 1 + .../expression/method/PreInvocationExpressionAttribute.java | 1 + .../aopalliance/MethodSecurityMetadataSourceAdvisor.java | 1 + 3 files changed, 3 insertions(+) diff --git a/core/src/main/java/org/springframework/security/access/expression/method/PostInvocationExpressionAttribute.java b/core/src/main/java/org/springframework/security/access/expression/method/PostInvocationExpressionAttribute.java index 3dc86cc5a1..8642484a41 100644 --- a/core/src/main/java/org/springframework/security/access/expression/method/PostInvocationExpressionAttribute.java +++ b/core/src/main/java/org/springframework/security/access/expression/method/PostInvocationExpressionAttribute.java @@ -28,6 +28,7 @@ * instead */ @Deprecated +@SuppressWarnings("serial") class PostInvocationExpressionAttribute extends AbstractExpressionBasedMethodConfigAttribute implements PostInvocationAttribute { diff --git a/core/src/main/java/org/springframework/security/access/expression/method/PreInvocationExpressionAttribute.java b/core/src/main/java/org/springframework/security/access/expression/method/PreInvocationExpressionAttribute.java index 26af51a6f1..41ec280bc7 100644 --- a/core/src/main/java/org/springframework/security/access/expression/method/PreInvocationExpressionAttribute.java +++ b/core/src/main/java/org/springframework/security/access/expression/method/PreInvocationExpressionAttribute.java @@ -28,6 +28,7 @@ * instead */ @Deprecated +@SuppressWarnings("serial") class PreInvocationExpressionAttribute extends AbstractExpressionBasedMethodConfigAttribute implements PreInvocationAttribute { diff --git a/core/src/main/java/org/springframework/security/access/intercept/aopalliance/MethodSecurityMetadataSourceAdvisor.java b/core/src/main/java/org/springframework/security/access/intercept/aopalliance/MethodSecurityMetadataSourceAdvisor.java index 4bc3d19b5b..58174d9d1a 100644 --- a/core/src/main/java/org/springframework/security/access/intercept/aopalliance/MethodSecurityMetadataSourceAdvisor.java +++ b/core/src/main/java/org/springframework/security/access/intercept/aopalliance/MethodSecurityMetadataSourceAdvisor.java @@ -54,6 +54,7 @@ * @deprecated Use {@link EnableMethodSecurity} or publish interceptors directly */ @Deprecated +@SuppressWarnings("serial") public class MethodSecurityMetadataSourceAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware { private transient MethodSecurityMetadataSource attributeSource; From 6e146997844b006fcb6b13dd25cd9f4f80e76f1a Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Thu, 23 Jan 2025 16:50:30 -0700 Subject: [PATCH 126/132] Serialization Support of Core Components Issue gh-16276 Signed-off-by: Daeho Kwon --- ...ringSecurityCoreVersionSerializableTests.java | 2 ++ ...e.context.TransientSecurityContext.serialized | Bin 0 -> 1294 bytes .../security/core/ComparableVersion.java | 1 + .../core/context/TransientSecurityContext.java | 5 +++++ 4 files changed, 8 insertions(+) create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.core.context.TransientSecurityContext.serialized diff --git a/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java b/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java index e1c45e6156..171b7a7b6a 100644 --- a/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java +++ b/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java @@ -105,6 +105,7 @@ import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextImpl; +import org.springframework.security.core.context.TransientSecurityContext; import org.springframework.security.core.session.AbstractSessionEvent; import org.springframework.security.core.session.ReactiveSessionInformation; import org.springframework.security.core.session.SessionInformation; @@ -438,6 +439,7 @@ class SpringSecurityCoreVersionSerializableTests { (r) -> new JaasAuthenticationSuccessEvent(authentication)); generatorByClassName.put(AbstractSessionEvent.class, (r) -> new AbstractSessionEvent(securityContext)); generatorByClassName.put(SecurityConfig.class, (r) -> new SecurityConfig("value")); + generatorByClassName.put(TransientSecurityContext.class, (r) -> new TransientSecurityContext(authentication)); // cas generatorByClassName.put(CasServiceTicketAuthenticationToken.class, (r) -> { diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.core.context.TransientSecurityContext.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.core.context.TransientSecurityContext.serialized new file mode 100644 index 0000000000000000000000000000000000000000..5a4ccd07b4d0d97d12c13023f0eb3ea4b04079f6 GIT binary patch literal 1294 zcmb7E&ubGw6n^E==zAkwgOa=;@v zYuzw__(=z8NO_QFJ$ncH37=Vkyu z)|oM=Mr#tVt~y*MEYlv$_?owfB@2v&bKVZS$J~I)wp-oW;l3?-FiraUB4oi`*n=rl zsd5@d zYjMj&UycPtdf+E7gi zr@i)nxTMXcW!V3G`{(5tH02YkTl1qP=-#j688K;F@UBRfyM7n8F9=Sx9_HT$>CB;_c5Je0lz6zd?TmIzNLai^<8l z<{YcOzgmCs^6<&63|5g=H%_nBJY9>}s1?->nPyh$L_dTbWPvyoBMbQlQmOFaXjBVX Gn(_}s!NceP literal 0 HcmV?d00001 diff --git a/core/src/main/java/org/springframework/security/core/ComparableVersion.java b/core/src/main/java/org/springframework/security/core/ComparableVersion.java index f635e4933c..88708cecd4 100644 --- a/core/src/main/java/org/springframework/security/core/ComparableVersion.java +++ b/core/src/main/java/org/springframework/security/core/ComparableVersion.java @@ -405,6 +405,7 @@ public String toString() { * Represents a version list item. This class is used both for the global item list * and for sub-lists (which start with '-(number)' in the version specification). */ + @SuppressWarnings("serial") private static class ListItem extends ArrayList implements Item { @Override diff --git a/core/src/main/java/org/springframework/security/core/context/TransientSecurityContext.java b/core/src/main/java/org/springframework/security/core/context/TransientSecurityContext.java index 0089ae455d..7a4b3d30fe 100644 --- a/core/src/main/java/org/springframework/security/core/context/TransientSecurityContext.java +++ b/core/src/main/java/org/springframework/security/core/context/TransientSecurityContext.java @@ -16,6 +16,8 @@ package org.springframework.security.core.context; +import java.io.Serial; + import org.springframework.security.core.Authentication; import org.springframework.security.core.Transient; @@ -30,6 +32,9 @@ @Transient public class TransientSecurityContext extends SecurityContextImpl { + @Serial + private static final long serialVersionUID = -7925492364422193347L; + public TransientSecurityContext() { } From d02776ba0d931648792b15e00b7c7c6d198fa0ba Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Thu, 23 Jan 2025 16:55:30 -0700 Subject: [PATCH 127/132] Support Serialization for LDAP Components Issue gh-16276 Signed-off-by: Daeho Kwon --- ...pringSecurityCoreVersionSerializableTests.java | 7 +++++++ ....ldap.ppolicy.PasswordPolicyControl.serialized | Bin 0 -> 96 bytes ...olicy.PasswordPolicyResponseControl.serialized | Bin 0 -> 512 bytes .../ldap/ppolicy/PasswordPolicyControl.java | 5 +++++ .../ppolicy/PasswordPolicyResponseControl.java | 4 ++++ 5 files changed, 16 insertions(+) create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.ldap.ppolicy.PasswordPolicyControl.serialized create mode 100644 config/src/test/resources/serialized/6.4.x/org.springframework.security.ldap.ppolicy.PasswordPolicyResponseControl.serialized diff --git a/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java b/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java index 171b7a7b6a..4807eda046 100644 --- a/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java +++ b/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java @@ -111,8 +111,10 @@ import org.springframework.security.core.session.SessionInformation; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.ldap.ppolicy.PasswordPolicyControl; import org.springframework.security.ldap.ppolicy.PasswordPolicyErrorStatus; import org.springframework.security.ldap.ppolicy.PasswordPolicyException; +import org.springframework.security.ldap.ppolicy.PasswordPolicyResponseControl; import org.springframework.security.ldap.userdetails.LdapAuthority; import org.springframework.security.oauth2.client.ClientAuthorizationException; import org.springframework.security.oauth2.client.ClientAuthorizationRequiredException; @@ -464,6 +466,11 @@ class SpringSecurityCoreVersionSerializableTests { (r) -> new LdapAuthority("USER", "username", Map.of("attribute", List.of("value1", "value2")))); generatorByClassName.put(PasswordPolicyException.class, (r) -> new PasswordPolicyException(PasswordPolicyErrorStatus.INSUFFICIENT_PASSWORD_QUALITY)); + generatorByClassName.put(PasswordPolicyControl.class, (r) -> new PasswordPolicyControl(true)); + generatorByClassName.put(PasswordPolicyResponseControl.class, (r) -> { + byte[] encodedResponse = { 0x30, 0x05, (byte) 0xA0, 0x03, (byte) 0xA0, 0x1, 0x21 }; + return new PasswordPolicyResponseControl(encodedResponse); + }); // saml2-service-provider generatorByClassName.put(Saml2AuthenticationException.class, diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.ldap.ppolicy.PasswordPolicyControl.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.ldap.ppolicy.PasswordPolicyControl.serialized new file mode 100644 index 0000000000000000000000000000000000000000..51e783d58cf04c32fe63631115cce63f01a00412 GIT binary patch literal 96 zcmZ4UmVvdnh`~O;C|$3(peQphJ*_A)H?=&!C|j>MHMz7Xv!qflCnd2!ub?15Co{QH uFCekF7$}_*0OmU9=am%Y=ct!j>38*Q=3!!BjAGzO2I|U8PRyw&U<3gFp(BI< literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/6.4.x/org.springframework.security.ldap.ppolicy.PasswordPolicyResponseControl.serialized b/config/src/test/resources/serialized/6.4.x/org.springframework.security.ldap.ppolicy.PasswordPolicyResponseControl.serialized new file mode 100644 index 0000000000000000000000000000000000000000..911742c9818701cb092cf886a0e0901295caf660 GIT binary patch literal 512 zcmbV|ze)o^5QitH(SX511Z`8<-B~Ll`E!VHdLb7Km?A89n`8BEcbVPOOQYCY*x6VG zY}5GyK7KB4?e!%j6R^K5RL@Kwvxzr+((kqYk`-xqG5(i59}l& z(w0&rEF9QPqLhiF=5%dQB?VWsF1SXKyw;zd@7^8{ELc8<%@|2YJrOgmS~Mk$8@xGd zmeLyS3#4u?84}F|??RdKP((C3BS}UzSlyZjYZOsnpoz|u2G=u|&Ob}%$I_WtIx|b> zuciBo+0PLU7i~Lhxs&ZV_YW^+3kv5@3eAsLNRnI@ZpY&>08}!Z_>VrfzE(=@cMBkw z=DfOF94-wDl^cZmq6TwK3~@Re29L0QL9U3M5FXnu&(ir&YS?a8gP`NL8od+0(L43q Ly{hN=9k=lf=w`Jj literal 0 HcmV?d00001 diff --git a/ldap/src/main/java/org/springframework/security/ldap/ppolicy/PasswordPolicyControl.java b/ldap/src/main/java/org/springframework/security/ldap/ppolicy/PasswordPolicyControl.java index 84eb48cdf9..629513cc8b 100755 --- a/ldap/src/main/java/org/springframework/security/ldap/ppolicy/PasswordPolicyControl.java +++ b/ldap/src/main/java/org/springframework/security/ldap/ppolicy/PasswordPolicyControl.java @@ -16,6 +16,8 @@ package org.springframework.security.ldap.ppolicy; +import java.io.Serial; + import javax.naming.ldap.Control; /** @@ -37,6 +39,9 @@ public class PasswordPolicyControl implements Control { */ public static final String OID = "1.3.6.1.4.1.42.2.27.8.5.1"; + @Serial + private static final long serialVersionUID = 2843242715616817932L; + private final boolean critical; /** diff --git a/ldap/src/main/java/org/springframework/security/ldap/ppolicy/PasswordPolicyResponseControl.java b/ldap/src/main/java/org/springframework/security/ldap/ppolicy/PasswordPolicyResponseControl.java index 2aa2b330e0..a6ac94590d 100755 --- a/ldap/src/main/java/org/springframework/security/ldap/ppolicy/PasswordPolicyResponseControl.java +++ b/ldap/src/main/java/org/springframework/security/ldap/ppolicy/PasswordPolicyResponseControl.java @@ -19,6 +19,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import java.io.Serial; import netscape.ldap.ber.stream.BERChoice; import netscape.ldap.ber.stream.BERElement; @@ -53,6 +54,9 @@ public class PasswordPolicyResponseControl extends PasswordPolicyControl { private static final Log logger = LogFactory.getLog(PasswordPolicyResponseControl.class); + @Serial + private static final long serialVersionUID = -4592657167939234499L; + private final byte[] encodedValue; private PasswordPolicyErrorStatus errorStatus; From dfd0511efcbae8f3230eafc3f41873260c71a19f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Jan 2025 03:27:44 +0000 Subject: [PATCH 128/132] Bump com.github.ben-manes:gradle-versions-plugin from 0.51.0 to 0.52.0 Bumps com.github.ben-manes:gradle-versions-plugin from 0.51.0 to 0.52.0. --- updated-dependencies: - dependency-name: com.github.ben-manes:gradle-versions-plugin dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: Daeho Kwon --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 48ed5b97de..769a85dfe7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -100,7 +100,7 @@ org-yaml-snakeyaml = "org.yaml:snakeyaml:1.33" org-apache-commons-commons-io = "org.apache.commons:commons-io:1.3.2" io-github-gradle-nexus-publish-plugin = "io.github.gradle-nexus:publish-plugin:1.3.0" org-gretty-gretty = "org.gretty:gretty:4.1.6" -com-github-ben-manes-gradle-versions-plugin = "com.github.ben-manes:gradle-versions-plugin:0.51.0" +com-github-ben-manes-gradle-versions-plugin = "com.github.ben-manes:gradle-versions-plugin:0.52.0" com-github-spullara-mustache-java-compiler = "com.github.spullara.mustache.java:compiler:0.9.14" org-hidetake-gradle-ssh-plugin = "org.hidetake:gradle-ssh-plugin:2.10.1" org-jfrog-buildinfo-build-info-extractor-gradle = "org.jfrog.buildinfo:build-info-extractor-gradle:4.33.23" From 5a6e72a705419bba7e6abd1a57c8f0fa4846c0d4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Jan 2025 03:28:22 +0000 Subject: [PATCH 129/132] Bump org.seleniumhq.selenium:selenium-java from 4.28.0 to 4.28.1 Bumps [org.seleniumhq.selenium:selenium-java](https://github.com/SeleniumHQ/selenium) from 4.28.0 to 4.28.1. - [Release notes](https://github.com/SeleniumHQ/selenium/releases) - [Commits](https://github.com/SeleniumHQ/selenium/commits) --- updated-dependencies: - dependency-name: org.seleniumhq.selenium:selenium-java dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: Daeho Kwon --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 769a85dfe7..bbd150c53b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -83,7 +83,7 @@ org-opensaml-opensaml5-saml-api = { module = "org.opensaml:opensaml-saml-api", v org-opensaml-opensaml5-saml-impl = { module = "org.opensaml:opensaml-saml-impl", version.ref = "org-opensaml5" } org-python-jython = { module = "org.python:jython", version = "2.5.3" } org-seleniumhq-selenium-htmlunit-driver = "org.seleniumhq.selenium:htmlunit3-driver:4.27.0" -org-seleniumhq-selenium-selenium-java = "org.seleniumhq.selenium:selenium-java:4.28.0" +org-seleniumhq-selenium-selenium-java = "org.seleniumhq.selenium:selenium-java:4.28.1" org-seleniumhq-selenium-selenium-support = "org.seleniumhq.selenium:selenium-support:3.141.59" org-skyscreamer-jsonassert = "org.skyscreamer:jsonassert:1.5.3" org-slf4j-log4j-over-slf4j = "org.slf4j:log4j-over-slf4j:1.7.36" From e7d77950582a607c7f88eb9f822c83a0d78fd9a4 Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Fri, 24 Jan 2025 11:25:26 -0700 Subject: [PATCH 130/132] S101 Depends On Assemble Closes gh-16482 Signed-off-by: Daeho Kwon --- .github/workflows/continuous-integration-workflow.yml | 2 +- buildSrc/src/main/java/s101/S101Plugin.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index c1800334ef..90d7128cf8 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -79,7 +79,7 @@ jobs: env: STRUCTURE101_LICENSEID: ${{ secrets.STRUCTURE101_LICENSEID }} run: | - ./gradlew check s101 -Ps101.licenseId="$STRUCTURE101_LICENSEID" --stacktrace + ./gradlew assemble s101 -Ps101.licenseId="$STRUCTURE101_LICENSEID" --stacktrace deploy-artifacts: name: Deploy Artifacts needs: [ build, test, check-samples, check-tangles ] diff --git a/buildSrc/src/main/java/s101/S101Plugin.java b/buildSrc/src/main/java/s101/S101Plugin.java index 6d2e01abc0..628b4ad52a 100644 --- a/buildSrc/src/main/java/s101/S101Plugin.java +++ b/buildSrc/src/main/java/s101/S101Plugin.java @@ -50,7 +50,7 @@ private void configure(S101Configure configure) { private void configure(JavaExec exec) { exec.setDescription("Runs Structure101 headless analysis, installing and configuring if necessary"); - exec.dependsOn("check"); + exec.dependsOn("assemble"); Project project = exec.getProject(); S101PluginExtension extension = project.getExtensions().getByType(S101PluginExtension.class); exec From ed5cccc71c869cb03a6c0e3ce9fdebb014fb5a7a Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Fri, 24 Jan 2025 11:31:22 -0700 Subject: [PATCH 131/132] Ensure s101 Runs After Assemble Issue gh-16482 Signed-off-by: Daeho Kwon --- .github/workflows/continuous-integration-workflow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index 90d7128cf8..b7baf20f2b 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -79,7 +79,7 @@ jobs: env: STRUCTURE101_LICENSEID: ${{ secrets.STRUCTURE101_LICENSEID }} run: | - ./gradlew assemble s101 -Ps101.licenseId="$STRUCTURE101_LICENSEID" --stacktrace + ./gradlew assemble && ./gradlew s101 -Ps101.licenseId="$STRUCTURE101_LICENSEID" --stacktrace deploy-artifacts: name: Deploy Artifacts needs: [ build, test, check-samples, check-tangles ] From f5a669e2e13ec39214c77791a32102d6f699ce1f Mon Sep 17 00:00:00 2001 From: Daeho Kwon Date: Wed, 5 Feb 2025 02:14:43 +0900 Subject: [PATCH 132/132] Remove Deprecated Usages of RemoteJWKSet Closes gh-16251 Signed-off-by: Daeho Kwon --- .../security/oauth2/jwt/NimbusJwtDecoder.java | 233 ++++++++---------- .../security/oauth2/jwt/JwtDecodersTests.java | 3 +- .../oauth2/jwt/NimbusJwtDecoderTests.java | 8 +- 3 files changed, 105 insertions(+), 139 deletions(-) diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java index 936eb8b1dd..732ecc2476 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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,8 +20,8 @@ import com.nimbusds.jose.jwk.JWK; import com.nimbusds.jose.jwk.JWKMatcher; import com.nimbusds.jose.jwk.JWKSelector; -import com.nimbusds.jose.jwk.source.JWKSetCacheRefreshEvaluator; -import com.nimbusds.jose.jwk.source.URLBasedJWKSetSource; +import com.nimbusds.jose.jwk.source.JWKSetParseException; +import com.nimbusds.jose.jwk.source.JWKSetRetrievalException; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; @@ -35,6 +35,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.locks.ReentrantLock; import java.util.function.Consumer; import java.util.function.Function; @@ -48,8 +49,6 @@ import com.nimbusds.jose.proc.JWSVerificationKeySelector; import com.nimbusds.jose.proc.SecurityContext; import com.nimbusds.jose.proc.SingleKeyJWSKeySelector; -import com.nimbusds.jose.util.Resource; -import com.nimbusds.jose.util.ResourceRetriever; import com.nimbusds.jwt.JWT; import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.JWTParser; @@ -61,6 +60,7 @@ import org.apache.commons.logging.LogFactory; import org.springframework.cache.Cache; +import org.springframework.cache.support.NoOpCache; import org.springframework.core.convert.converter.Converter; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -278,7 +278,7 @@ public static final class JwkSetUriJwtDecoderBuilder { private RestOperations restOperations = new RestTemplate(); - private Cache cache; + private Cache cache = new NoOpCache("default"); private Consumer> jwtProcessorCustomizer; @@ -381,19 +381,13 @@ JWSKeySelector jwsKeySelector(JWKSource jwkSou return new JWSVerificationKeySelector<>(jwsAlgorithms, jwkSource); } - JWKSource jwkSource(ResourceRetriever jwkSetRetriever, String jwkSetUri) { - URLBasedJWKSetSource urlBasedJWKSetSource = new URLBasedJWKSetSource(toURL(jwkSetUri), jwkSetRetriever); - if(this.cache == null) { - return new SpringURLBasedJWKSource(urlBasedJWKSetSource); - } - SpringJWKSetCache jwkSetCache = new SpringJWKSetCache(jwkSetUri, this.cache); - return new SpringURLBasedJWKSource<>(urlBasedJWKSetSource, jwkSetCache); + JWKSource jwkSource() { + String jwkSetUri = this.jwkSetUri.apply(this.restOperations); + return new SpringJWKSource<>(this.restOperations, this.cache, toURL(jwkSetUri), jwkSetUri); } JWTProcessor processor() { - ResourceRetriever jwkSetRetriever = new RestOperationsResourceRetriever(this.restOperations); - String jwkSetUri = this.jwkSetUri.apply(this.restOperations); - JWKSource jwkSource = jwkSource(jwkSetRetriever, jwkSetUri); + JWKSource jwkSource = jwkSource(); ConfigurableJWTProcessor jwtProcessor = new DefaultJWTProcessor<>(); jwtProcessor.setJWSKeySelector(jwsKeySelector(jwkSource)); // Spring Security validates the claim set independent from Nimbus @@ -420,153 +414,130 @@ private static URL toURL(String url) { } } - private static final class SpringURLBasedJWKSource implements JWKSource { + private static final class SpringJWKSource implements JWKSource { - private final URLBasedJWKSetSource urlBasedJWKSetSource; + private static final MediaType APPLICATION_JWK_SET_JSON = new MediaType("application", "jwk-set+json"); - private final SpringJWKSetCache jwkSetCache; + private final ReentrantLock reentrantLock = new ReentrantLock(); - private SpringURLBasedJWKSource(URLBasedJWKSetSource urlBasedJWKSetSource) { - this(urlBasedJWKSetSource, null); - } + private final RestOperations restOperations; + + private final Cache cache; + + private final URL url; + + private final String jwkSetUri; - private SpringURLBasedJWKSource(URLBasedJWKSetSource urlBasedJWKSetSource, SpringJWKSetCache jwkSetCache) { - this.urlBasedJWKSetSource = urlBasedJWKSetSource; - this.jwkSetCache = jwkSetCache; + private SpringJWKSource(RestOperations restOperations, Cache cache, URL url, String jwkSetUri) { + Assert.notNull(restOperations, "restOperations cannot be null"); + this.restOperations = restOperations; + this.cache = cache; + this.url = url; + this.jwkSetUri = jwkSetUri; } + @Override public List get(JWKSelector jwkSelector, SecurityContext context) throws KeySourceException { - if (this.jwkSetCache != null) { - JWKSet jwkSet = this.jwkSetCache.get(); - if (this.jwkSetCache.requiresRefresh() || jwkSet == null) { - synchronized (this) { - jwkSet = fetchJWKSet(); - this.jwkSetCache.put(jwkSet); - } - } - List matches = jwkSelector.select(jwkSet); - if(!matches.isEmpty()) { - return matches; - } - String soughtKeyID = getFirstSpecifiedKeyID(jwkSelector.getMatcher()); - if (soughtKeyID == null) { - return Collections.emptyList(); - } - if (jwkSet.getKeyByKeyId(soughtKeyID) != null) { - return Collections.emptyList(); - } - synchronized (this) { - if(jwkSet == this.jwkSetCache.get()) { - jwkSet = fetchJWKSet(); - this.jwkSetCache.put(jwkSet); - } else { - jwkSet = this.jwkSetCache.get(); + String cachedJwkSet = this.cache.get(this.jwkSetUri, String.class); + JWKSet jwkSet = null; + if (cachedJwkSet != null) { + jwkSet = parse(cachedJwkSet); + } + if (jwkSet == null) { + if(reentrantLock.tryLock()) { + try { + String cachedJwkSetAfterLock = this.cache.get(this.jwkSetUri, String.class); + if (cachedJwkSetAfterLock != null) { + jwkSet = parse(cachedJwkSetAfterLock); + } + if(jwkSet == null) { + try { + jwkSet = fetchJWKSet(); + } catch (IOException e) { + throw new JWKSetRetrievalException("Couldn't retrieve JWK set from URL: " + e.getMessage(), e); + } + } + } finally { + reentrantLock.unlock(); } } - if(jwkSet == null) { - return Collections.emptyList(); - } - return jwkSelector.select(jwkSet); } - return jwkSelector.select(fetchJWKSet()); - } - - private JWKSet fetchJWKSet() throws KeySourceException { - return this.urlBasedJWKSetSource.getJWKSet(JWKSetCacheRefreshEvaluator.noRefresh(), - System.currentTimeMillis(), null); - } - - private String getFirstSpecifiedKeyID(JWKMatcher jwkMatcher) { - Set keyIDs = jwkMatcher.getKeyIDs(); - - if (keyIDs == null || keyIDs.isEmpty()) { - return null; + List matches = jwkSelector.select(jwkSet); + if(!matches.isEmpty()) { + return matches; } - - for (String id: keyIDs) { - if (id != null) { - return id; - } + String soughtKeyID = getFirstSpecifiedKeyID(jwkSelector.getMatcher()); + if (soughtKeyID == null) { + return Collections.emptyList(); + } + if (jwkSet.getKeyByKeyId(soughtKeyID) != null) { + return Collections.emptyList(); } - return null; - } - } - - private static final class SpringJWKSetCache { - - private final String jwkSetUri; - - private final Cache cache; - - private JWKSet jwkSet; - - SpringJWKSetCache(String jwkSetUri, Cache cache) { - this.jwkSetUri = jwkSetUri; - this.cache = cache; - this.updateJwkSetFromCache(); - } - private void updateJwkSetFromCache() { - String cachedJwkSet = this.cache.get(this.jwkSetUri, String.class); - if (cachedJwkSet != null) { + if(reentrantLock.tryLock()) { try { - this.jwkSet = JWKSet.parse(cachedJwkSet); - } - catch (ParseException ignored) { - // Ignore invalid cache value + String jwkSetUri = this.cache.get(this.jwkSetUri, String.class); + JWKSet cacheJwkSet = parse(jwkSetUri); + if(jwkSetUri != null && cacheJwkSet.toString().equals(jwkSet.toString())) { + try { + jwkSet = fetchJWKSet(); + } catch (IOException e) { + throw new JWKSetRetrievalException("Couldn't retrieve JWK set from URL: " + e.getMessage(), e); + } + } else if (jwkSetUri != null) { + jwkSet = parse(jwkSetUri); + } + } finally { + reentrantLock.unlock(); } } + if(jwkSet == null) { + return Collections.emptyList(); + } + return jwkSelector.select(jwkSet); } - // Note: Only called from inside a synchronized block in SpringURLBasedJWKSource. - public void put(JWKSet jwkSet) { - this.jwkSet = jwkSet; - this.cache.put(this.jwkSetUri, jwkSet.toString(false)); - } - - public JWKSet get() { - return (!requiresRefresh()) ? this.jwkSet : null; - } - - public boolean requiresRefresh() { - return this.cache.get(this.jwkSetUri) == null; - } - - } - - private static class RestOperationsResourceRetriever implements ResourceRetriever { - - private static final MediaType APPLICATION_JWK_SET_JSON = new MediaType("application", "jwk-set+json"); - - private final RestOperations restOperations; - - RestOperationsResourceRetriever(RestOperations restOperations) { - Assert.notNull(restOperations, "restOperations cannot be null"); - this.restOperations = restOperations; - } - - @Override - public Resource retrieveResource(URL url) throws IOException { + private JWKSet fetchJWKSet() throws IOException, KeySourceException { HttpHeaders headers = new HttpHeaders(); headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON, APPLICATION_JWK_SET_JSON)); - ResponseEntity response = getResponse(url, headers); + ResponseEntity response = getResponse(headers); if (response.getStatusCode().value() != 200) { throw new IOException(response.toString()); } - return new Resource(response.getBody(), "UTF-8"); + try { + String jwkSet = response.getBody(); + this.cache.put(this.jwkSetUri, jwkSet); + return JWKSet.parse(jwkSet); + } catch (ParseException e) { + throw new JWKSetParseException("Unable to parse JWK set", e); + } } - private ResponseEntity getResponse(URL url, HttpHeaders headers) throws IOException { + private ResponseEntity getResponse(HttpHeaders headers) throws IOException { try { - RequestEntity request = new RequestEntity<>(headers, HttpMethod.GET, url.toURI()); + RequestEntity request = new RequestEntity<>(headers, HttpMethod.GET, this.url.toURI()); return this.restOperations.exchange(request, String.class); - } - catch (Exception ex) { + } catch (Exception ex) { throw new IOException(ex); } } + private JWKSet parse(String cachedJwkSet) { + JWKSet jwkSet = null; + try { + jwkSet = JWKSet.parse(cachedJwkSet); + } catch (ParseException ignored) { + // Ignore invalid cache value + } + return jwkSet; + } + + private String getFirstSpecifiedKeyID(JWKMatcher jwkMatcher) { + Set keyIDs = jwkMatcher.getKeyIDs(); + return (keyIDs == null || keyIDs.isEmpty()) ? + null : keyIDs.stream().filter(id -> id != null).findFirst().orElse(null); + } } } diff --git a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/JwtDecodersTests.java b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/JwtDecodersTests.java index f343cd2b69..378a6dbd41 100644 --- a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/JwtDecodersTests.java +++ b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/JwtDecodersTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2025 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. @@ -308,6 +308,7 @@ private void prepareConfigurationResponse() { private void prepareConfigurationResponse(String body) { this.server.enqueue(response(body)); this.server.enqueue(response(JWK_SET)); + this.server.enqueue(response(JWK_SET)); // default NoOpCache } private void prepareConfigurationResponseOidc() { diff --git a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoderTests.java b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoderTests.java index fb4535f240..c45b4a958b 100644 --- a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoderTests.java +++ b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2025 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. @@ -60,7 +60,6 @@ import org.springframework.cache.Cache; import org.springframework.cache.concurrent.ConcurrentMapCache; -import org.springframework.cache.support.SimpleValueWrapper; import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.convert.converter.Converter; import org.springframework.http.HttpStatus; @@ -704,7 +703,6 @@ public void decodeWhenCacheThenRetrieveFromCache() throws Exception { 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(mock(Cache.ValueWrapper.class)); // @formatter:off NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withJwkSetUri(JWK_SET_URI) .cache(cache) @@ -713,7 +711,6 @@ public void decodeWhenCacheThenRetrieveFromCache() throws Exception { // @formatter:on jwtDecoder.decode(SIGNED_JWT); verify(cache).get(eq(JWK_SET_URI), eq(String.class)); - verify(cache, times(2)).get(eq(JWK_SET_URI)); verifyNoMoreInteractions(cache); verifyNoInteractions(restOperations); } @@ -724,7 +721,6 @@ public void decodeWhenCacheAndUnknownKidShouldTriggerFetchOfJwkSet() throws JOSE 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)); @@ -796,7 +792,6 @@ public void decodeWhenCacheIsConfiguredAndParseFailsOnCachedValueThenExceptionIg 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(mock(Cache.ValueWrapper.class)); // @formatter:off NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withJwkSetUri(JWK_SET_URI) .cache(cache) @@ -805,7 +800,6 @@ public void decodeWhenCacheIsConfiguredAndParseFailsOnCachedValueThenExceptionIg // @formatter:on jwtDecoder.decode(SIGNED_JWT); verify(cache).get(eq(JWK_SET_URI), eq(String.class)); - verify(cache, times(2)).get(eq(JWK_SET_URI)); verifyNoMoreInteractions(cache); verifyNoInteractions(restOperations);