diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LogoutConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LogoutConfigurer.java index f8303a81b9..fc9950d964 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LogoutConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LogoutConfigurer.java @@ -33,7 +33,9 @@ import org.springframework.security.config.annotation.web.configurers.AbstractHt import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolderStrategy; -import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationInfo; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; +import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; +import org.springframework.security.saml2.provider.service.authentication.Saml2ResponseAssertionAccessor; import org.springframework.security.saml2.provider.service.authentication.logout.OpenSaml4LogoutRequestValidator; import org.springframework.security.saml2.provider.service.authentication.logout.OpenSaml4LogoutResponseValidator; import org.springframework.security.saml2.provider.service.authentication.logout.OpenSaml5LogoutRequestValidator; @@ -531,7 +533,16 @@ public final class Saml2LogoutConfigurer> @Override public boolean matches(HttpServletRequest request) { Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication(); - return Saml2AuthenticationInfo.fromAuthentication(authentication) != null; + if (authentication == null) { + return false; + } + if (authentication.getPrincipal() instanceof Saml2AuthenticatedPrincipal) { + return true; + } + if (authentication.getCredentials() instanceof Saml2ResponseAssertionAccessor) { + return true; + } + return authentication instanceof Saml2Authentication; } } 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 dfeed0d981..749127027c 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 @@ -31,7 +31,9 @@ import org.springframework.beans.factory.xml.ParserContext; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolderStrategy; -import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationInfo; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; +import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; +import org.springframework.security.saml2.provider.service.authentication.Saml2ResponseAssertionAccessor; import org.springframework.security.saml2.provider.service.web.DefaultRelyingPartyRegistrationResolver; import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestFilter; import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutResponseFilter; @@ -236,7 +238,16 @@ final class Saml2LogoutBeanDefinitionParser implements BeanDefinitionParser { @Override public boolean matches(HttpServletRequest request) { Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication(); - return Saml2AuthenticationInfo.fromAuthentication(authentication) != null; + if (authentication == null) { + return false; + } + if (authentication.getPrincipal() instanceof Saml2AuthenticatedPrincipal) { + return true; + } + if (authentication.getCredentials() instanceof Saml2ResponseAssertionAccessor) { + return true; + } + return authentication instanceof Saml2Authentication; } public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) { diff --git a/config/src/test/java/org/springframework/security/SerializationSamples.java b/config/src/test/java/org/springframework/security/SerializationSamples.java index 396828ca33..d120a6ebd0 100644 --- a/config/src/test/java/org/springframework/security/SerializationSamples.java +++ b/config/src/test/java/org/springframework/security/SerializationSamples.java @@ -170,11 +170,14 @@ import org.springframework.security.saml2.core.Saml2Error; import org.springframework.security.saml2.core.Saml2X509Credential; import org.springframework.security.saml2.credentials.TestSaml2X509Credentials; import org.springframework.security.saml2.provider.service.authentication.DefaultSaml2AuthenticatedPrincipal; +import org.springframework.security.saml2.provider.service.authentication.Saml2AssertionAuthentication; 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.Saml2AuthenticationToken; 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.Saml2ResponseAssertion; +import org.springframework.security.saml2.provider.service.authentication.Saml2ResponseAssertionAccessor; import org.springframework.security.saml2.provider.service.authentication.TestSaml2AuthenticationTokens; import org.springframework.security.saml2.provider.service.authentication.TestSaml2Authentications; import org.springframework.security.saml2.provider.service.authentication.TestSaml2LogoutRequests; @@ -520,8 +523,16 @@ final class SerializationSamples { generatorByClassName.put(Saml2Exception.class, (r) -> new Saml2Exception("message", new IOException("fail"))); generatorByClassName.put(DefaultSaml2AuthenticatedPrincipal.class, (r) -> TestSaml2Authentications.authentication().getPrincipal()); - generatorByClassName.put(Saml2Authentication.class, - (r) -> applyDetails(TestSaml2Authentications.authentication())); + Saml2Authentication saml2 = TestSaml2Authentications.authentication(); + generatorByClassName.put(Saml2Authentication.class, (r) -> applyDetails(saml2)); + Saml2ResponseAssertionAccessor assertion = Saml2ResponseAssertion.withResponseValue("response") + .nameId("name") + .sessionIndexes(List.of("id")) + .attributes(Map.of("key", List.of("value"))) + .build(); + generatorByClassName.put(Saml2ResponseAssertion.class, (r) -> assertion); + generatorByClassName.put(Saml2AssertionAuthentication.class, (r) -> applyDetails( + new Saml2AssertionAuthentication(assertion, authentication.getAuthorities(), "id"))); generatorByClassName.put(Saml2PostAuthenticationRequest.class, (r) -> TestSaml2PostAuthenticationRequests.create()); generatorByClassName.put(Saml2RedirectAuthenticationRequest.class, diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.saml2.provider.service.authentication.Saml2AssertionAuthentication.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.saml2.provider.service.authentication.Saml2AssertionAuthentication.serialized new file mode 100644 index 0000000000..61898671f8 Binary files /dev/null and b/config/src/test/resources/serialized/7.0.x/org.springframework.security.saml2.provider.service.authentication.Saml2AssertionAuthentication.serialized differ diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.saml2.provider.service.authentication.Saml2ResponseAssertion.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.saml2.provider.service.authentication.Saml2ResponseAssertion.serialized new file mode 100644 index 0000000000..0bc93ea8af Binary files /dev/null and b/config/src/test/resources/serialized/7.0.x/org.springframework.security.saml2.provider.service.authentication.Saml2ResponseAssertion.serialized differ diff --git a/docs/modules/ROOT/pages/migration/servlet/saml2.adoc b/docs/modules/ROOT/pages/migration/servlet/saml2.adoc index c87597aafb..8ca5cc3d55 100644 --- a/docs/modules/ROOT/pages/migration/servlet/saml2.adoc +++ b/docs/modules/ROOT/pages/migration/servlet/saml2.adoc @@ -54,3 +54,57 @@ fun logoutResponseResolver(registrations: RelyingPartyRegistrationRepository?): ---- ====== +== Favor `Saml2ResponseAuthenticationAccessor` over `Saml2AuthenticatedPrincipal` + +Spring Security 7 separates `` details from the principal. +This allows Spring Security to retrieve needed assertion details to perform Single Logout. + +This deprecates `Saml2AuthenticatedPrincipal`. +You no longer need to implement it to use `Saml2Authentication`. + +Instead, the credential implements `Saml2ResponseAssertionAccessor`, which Spring Security 7 favors when determining the appropriate action based on the authentication. + +This change is made automatically for you when using the defaults. + +If this causes you trouble when upgrading, you can publish a custom `ResponseAuhenticationConverter` to return a `Saml2Authentication` instead of returning a `Saml2AssertionAuthentication` like so: + +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +@Bean +OpenSaml5AuthenticationProvider authenticationProvider() { + OpenSaml5AuthenticationProvider authenticationProvider = + new OpenSaml5AuthenticationProvider(); + ResponseAuthenticationConverter defaults = new ResponseAuthenticationConverter(); + authenticationProvider.setResponseAuthenticationConverter( + defaults.andThen((authentication) -> new Saml2Authentication( + authentication.getPrincipal(), + authentication.getSaml2Response(), + authentication.getAuthorities()))); + return authenticationProvider; +} +---- + +Kotlin:: ++ +[source,kotlin,role="secondary"] +---- +@Bean +fun authenticationProvider(): OpenSaml5AuthenticationProvider { + val authenticationProvider = OpenSaml5AuthenticationProvider() + val defaults = ResponseAuthenticationConverter() + authenticationProvider.setResponseAuthenticationConverter( + defaults.andThen { authentication -> + Saml2Authentication(authentication.getPrincipal(), + authentication.getSaml2Response(), + authentication.getAuthorities()) + }) + return authenticationProvider +} +---- +====== + +If you are constructing a `Saml2Authentication` instance yourself, consider changing to `Saml2AssertionAuthentication` to get the same benefit as the current default. diff --git a/docs/modules/ROOT/pages/servlet/saml2/login/authentication.adoc b/docs/modules/ROOT/pages/servlet/saml2/login/authentication.adoc index 1c517914dc..400a66ad3a 100644 --- a/docs/modules/ROOT/pages/servlet/saml2/login/authentication.adoc +++ b/docs/modules/ROOT/pages/servlet/saml2/login/authentication.adoc @@ -341,8 +341,10 @@ class MyUserDetailsResponseAuthenticationConverter implements Converter UserDetails principal = this.userDetailsService.loadByUsername(username); <2> String saml2Response = authentication.getSaml2Response(); + Saml2ResponseAssertionAccessor assertion = new OpenSamlResponseAssertionAccessor( + saml2Response, CollectionUtils.getFirst(response.getAssertions())); Collection authorities = principal.getAuthorities(); - return new Saml2Authentication((AuthenticatedPrincipal) userDetails, saml2Response, authorities); <3> + return new Saml2AssertionAuthentication(userDetails, assertion, authorities); <3> } } @@ -361,8 +363,10 @@ open class MyUserDetailsResponseAuthenticationConverter(val delegate: ResponseAu val authentication = this.delegate.convert(responseToken) <1> val principal = this.userDetailsService.loadByUsername(username) <2> val saml2Response = authentication.getSaml2Response() + val assertion = OpenSamlResponseAssertionAccessor( + saml2Response, CollectionUtils.getFirst(response.getAssertions())) val authorities = principal.getAuthorities() - return Saml2Authentication(userDetails as AuthenticatedPrincipal, saml2Response, authorities) <3> + return Saml2AssertionAuthentication(userDetails, assertion, authorities) <3> } } diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/jackson2/Saml2AssertionAuthenticationMixin.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/jackson2/Saml2AssertionAuthenticationMixin.java new file mode 100644 index 0000000000..d3d3156446 --- /dev/null +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/jackson2/Saml2AssertionAuthenticationMixin.java @@ -0,0 +1,59 @@ +/* + * 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.saml2.jackson2; + +import java.util.Collection; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.jackson2.SecurityJackson2Modules; +import org.springframework.security.saml2.provider.service.authentication.Saml2AssertionAuthentication; +import org.springframework.security.saml2.provider.service.authentication.Saml2ResponseAssertionAccessor; + +/** + * Jackson Mixin class helps in serialize/deserialize + * {@link Saml2AssertionAuthentication}. + * + *
+ *     ObjectMapper mapper = new ObjectMapper();
+ *     mapper.registerModule(new Saml2Jackson2Module());
+ * 
+ * + * @author Josh Cummings + * @since 7.0 + * @see Saml2Jackson2Module + * @see SecurityJackson2Modules + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) +@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, + isGetterVisibility = JsonAutoDetect.Visibility.NONE) +@JsonIgnoreProperties(value = { "authenticated" }, ignoreUnknown = true) +class Saml2AssertionAuthenticationMixin { + + @JsonCreator + Saml2AssertionAuthenticationMixin(@JsonProperty("principal") Object principal, + @JsonProperty("assertion") Saml2ResponseAssertionAccessor assertion, + @JsonProperty("authorities") Collection authorities, + @JsonProperty("relyingPartyRegistrationId") String relyingPartyRegistrationId) { + } + +} 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 3d99fc2cfa..b53fcd15bc 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 @@ -22,10 +22,12 @@ import com.fasterxml.jackson.databind.module.SimpleModule; import org.springframework.security.jackson2.SecurityJackson2Modules; import org.springframework.security.saml2.core.Saml2Error; import org.springframework.security.saml2.provider.service.authentication.DefaultSaml2AuthenticatedPrincipal; +import org.springframework.security.saml2.provider.service.authentication.Saml2AssertionAuthentication; 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.Saml2ResponseAssertion; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequest; /** @@ -49,6 +51,8 @@ public class Saml2Jackson2Module extends SimpleModule { @Override public void setupModule(SetupContext context) { context.setMixInAnnotations(Saml2Authentication.class, Saml2AuthenticationMixin.class); + context.setMixInAnnotations(Saml2AssertionAuthentication.class, Saml2AssertionAuthenticationMixin.class); + context.setMixInAnnotations(Saml2ResponseAssertion.class, SimpleSaml2ResponseAssertionAccessorMixin.class); context.setMixInAnnotations(DefaultSaml2AuthenticatedPrincipal.class, DefaultSaml2AuthenticatedPrincipalMixin.class); context.setMixInAnnotations(Saml2LogoutRequest.class, Saml2LogoutRequestMixin.class); diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/jackson2/SimpleSaml2ResponseAssertionAccessorMixin.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/jackson2/SimpleSaml2ResponseAssertionAccessorMixin.java new file mode 100644 index 0000000000..7f4d02b4a0 --- /dev/null +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/jackson2/SimpleSaml2ResponseAssertionAccessorMixin.java @@ -0,0 +1,56 @@ +/* + * 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.saml2.jackson2; + +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +import org.springframework.security.jackson2.SecurityJackson2Modules; +import org.springframework.security.saml2.provider.service.authentication.Saml2ResponseAssertion; + +/** + * Jackson Mixin class helps in serialize/deserialize {@link Saml2ResponseAssertion}. + * + *
+ *     ObjectMapper mapper = new ObjectMapper();
+ *     mapper.registerModule(new Saml2Jackson2Module());
+ * 
+ * + * @author Josh Cummings + * @since 7.0 + * @see Saml2Jackson2Module + * @see SecurityJackson2Modules + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) +@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, + isGetterVisibility = JsonAutoDetect.Visibility.NONE) +@JsonIgnoreProperties(value = { "authenticated" }, ignoreUnknown = true) +class SimpleSaml2ResponseAssertionAccessorMixin { + + @JsonCreator + SimpleSaml2ResponseAssertionAccessorMixin(@JsonProperty("responseValue") String responseValue, + @JsonProperty("nameId") String nameId, @JsonProperty("sessionIndexes") List sessionIndexes, + @JsonProperty("attributes") Map> attributes) { + } + +} diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/DefaultSaml2AuthenticatedPrincipal.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/DefaultSaml2AuthenticatedPrincipal.java index d99f640cb5..d2e9658fd0 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/DefaultSaml2AuthenticatedPrincipal.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/DefaultSaml2AuthenticatedPrincipal.java @@ -30,7 +30,9 @@ import org.springframework.util.Assert; * * @author Clement Stoquart * @since 5.4 + * @deprecated Please use {@link Saml2ResponseAssertionAccessor} */ +@Deprecated public class DefaultSaml2AuthenticatedPrincipal implements Saml2AuthenticatedPrincipal, Serializable { @Serial @@ -58,6 +60,12 @@ public class DefaultSaml2AuthenticatedPrincipal implements Saml2AuthenticatedPri this.sessionIndexes = sessionIndexes; } + public DefaultSaml2AuthenticatedPrincipal(String name, Saml2ResponseAssertionAccessor assertion) { + this.name = name; + this.attributes = assertion.getAttributes(); + this.sessionIndexes = assertion.getSessionIndexes(); + } + @Override public String getName() { return this.name; diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AssertionAuthentication.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AssertionAuthentication.java new file mode 100644 index 0000000000..c38cfbfbd1 --- /dev/null +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AssertionAuthentication.java @@ -0,0 +1,65 @@ +/* + * 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.saml2.provider.service.authentication; + +import java.io.Serial; +import java.util.Collection; + +import org.springframework.security.core.GrantedAuthority; + +/** + * An authentication based off of a SAML 2.0 Assertion + * + * @author Josh Cummings + * @since 7.0 + * @see Saml2ResponseAssertionAccessor + * @see Saml2ResponseAssertion + */ +public class Saml2AssertionAuthentication extends Saml2Authentication { + + @Serial + private static final long serialVersionUID = -4194323643788693205L; + + private final Saml2ResponseAssertionAccessor assertion; + + private final String relyingPartyRegistrationId; + + public Saml2AssertionAuthentication(Saml2ResponseAssertionAccessor assertion, + Collection authorities, String relyingPartyRegistrationId) { + super(assertion, assertion.getResponseValue(), authorities); + this.assertion = assertion; + this.relyingPartyRegistrationId = relyingPartyRegistrationId; + } + + public Saml2AssertionAuthentication(Object principal, Saml2ResponseAssertionAccessor assertion, + Collection authorities, String relyingPartyRegistrationId) { + super(principal, assertion.getResponseValue(), authorities); + this.assertion = assertion; + this.relyingPartyRegistrationId = relyingPartyRegistrationId; + setAuthenticated(true); + } + + @Override + public Saml2ResponseAssertionAccessor getCredentials() { + return this.assertion; + } + + public String getRelyingPartyRegistrationId() { + return this.relyingPartyRegistrationId; + } + +} diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticatedPrincipal.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticatedPrincipal.java index 80e67376e6..6f3e32c888 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticatedPrincipal.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticatedPrincipal.java @@ -30,8 +30,12 @@ import org.springframework.util.CollectionUtils; * * @author Clement Stoquart * @since 5.2.2 + * @deprecated Please use + * {@link Saml2AssertionAuthentication#getRelyingPartyRegistrationId()} and + * {@link Saml2ResponseAssertionAccessor} instead */ -public interface Saml2AuthenticatedPrincipal extends AuthenticatedPrincipal, Saml2AuthenticationInfo { +@Deprecated +public interface Saml2AuthenticatedPrincipal extends AuthenticatedPrincipal { /** * Get the first value of Saml2 token attribute by name @@ -72,17 +76,10 @@ public interface Saml2AuthenticatedPrincipal extends AuthenticatedPrincipal, Sam * @return the {@link RelyingPartyRegistration} identifier * @since 5.6 */ - @Override default String getRelyingPartyRegistrationId() { return null; } - @Override - default String getNameId() { - return getName(); - } - - @Override default List getSessionIndexes() { return Collections.emptyList(); } diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2Authentication.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2Authentication.java index 2292f52a37..e628f260e8 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2Authentication.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2Authentication.java @@ -41,7 +41,7 @@ public class Saml2Authentication extends AbstractAuthenticationToken { @Serial private static final long serialVersionUID = 405897702378720477L; - private final AuthenticatedPrincipal principal; + private final Object principal; private final String saml2Response; @@ -61,6 +61,14 @@ public class Saml2Authentication extends AbstractAuthenticationToken { setAuthenticated(true); } + public Saml2Authentication(Object principal, String saml2Response, + Collection authorities) { + super(authorities); + this.principal = principal; + this.saml2Response = saml2Response; + setAuthenticated(true); + } + @Override public Object getPrincipal() { return this.principal; diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationInfo.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationInfo.java deleted file mode 100644 index db412f026e..0000000000 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationInfo.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2002-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.saml2.provider.service.authentication; - -import java.util.List; - -import org.opensaml.saml.saml2.core.NameID; -import org.opensaml.saml.saml2.core.SessionIndex; - -import org.springframework.security.core.Authentication; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; - -/** - * Additional SAML 2.0 authentication information - * - *

- * SAML 2.0 Single Logout requires that the {@link Authentication#getPrincipal() - * authenticated principal} or the {@link Authentication} itself implements this - * interface. - * - * @author Christian Schuster - */ -public interface Saml2AuthenticationInfo { - - /** - * Get the {@link RelyingPartyRegistration} identifier - * @return the {@link RelyingPartyRegistration} identifier - */ - String getRelyingPartyRegistrationId(); - - /** - * Get the {@link NameID} value of the authenticated principal - * @return the {@link NameID} value of the authenticated principal - */ - String getNameId(); - - /** - * Get the {@link SessionIndex} values of the authenticated principal - * @return the {@link SessionIndex} values of the authenticated principal - */ - List getSessionIndexes(); - - /** - * Try to obtain a {@link Saml2AuthenticationInfo} instance from an - * {@link Authentication} - * - *

- * The result is either the {@link Authentication#getPrincipal() authenticated - * principal}, the {@link Authentication} itself, or {@code null}. - * - *

- * Returning {@code null} indicates that the given {@link Authentication} does not - * represent a SAML 2.0 authentication. - * @param authentication the {@link Authentication} - * @return the {@link Saml2AuthenticationInfo} or {@code null} if unavailable - */ - static Saml2AuthenticationInfo fromAuthentication(Authentication authentication) { - if (authentication == null) { - return null; - } - Object principal = authentication.getPrincipal(); - if (principal instanceof Saml2AuthenticationInfo) { - return (Saml2AuthenticationInfo) principal; - } - if (authentication instanceof Saml2AuthenticationInfo) { - return (Saml2AuthenticationInfo) authentication; - } - return null; - } - -} diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2ResponseAssertion.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2ResponseAssertion.java new file mode 100644 index 0000000000..ea5dd2a8a9 --- /dev/null +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2ResponseAssertion.java @@ -0,0 +1,115 @@ +/* + * 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.saml2.provider.service.authentication; + +import java.io.Serial; +import java.util.List; +import java.util.Map; + +import org.springframework.util.Assert; + +/** + * An OpenSAML-based implementation of {@link Saml2ResponseAssertionAccessor} + * + * @author Josh Cummings + * @since 7.0 + */ +public class Saml2ResponseAssertion implements Saml2ResponseAssertionAccessor { + + @Serial + private static final long serialVersionUID = -7505233045395024212L; + + private final String responseValue; + + private final String nameId; + + private final List sessionIndexes; + + private final Map> attributes; + + Saml2ResponseAssertion(String responseValue, String nameId, List sessionIndexes, + Map> attributes) { + Assert.notNull(responseValue, "response value cannot be null"); + Assert.notNull(nameId, "nameId cannot be null"); + Assert.notNull(sessionIndexes, "sessionIndexes cannot be null"); + Assert.notNull(attributes, "attributes cannot be null"); + this.responseValue = responseValue; + this.nameId = nameId; + this.sessionIndexes = sessionIndexes; + this.attributes = attributes; + } + + public static Builder withResponseValue(String responseValue) { + return new Builder(responseValue); + } + + @Override + public String getNameId() { + return this.nameId; + } + + @Override + public List getSessionIndexes() { + return this.sessionIndexes; + } + + @Override + public Map> getAttributes() { + return this.attributes; + } + + @Override + public String getResponseValue() { + return this.responseValue; + } + + public static final class Builder { + + private final String responseValue; + + private String nameId; + + private List sessionIndexes = List.of(); + + private Map> attributes = Map.of(); + + Builder(String responseValue) { + this.responseValue = responseValue; + } + + public Builder nameId(String nameId) { + this.nameId = nameId; + return this; + } + + public Builder sessionIndexes(List sessionIndexes) { + this.sessionIndexes = sessionIndexes; + return this; + } + + public Builder attributes(Map> attributes) { + this.attributes = attributes; + return this; + } + + public Saml2ResponseAssertion build() { + return new Saml2ResponseAssertion(this.responseValue, this.nameId, this.sessionIndexes, this.attributes); + } + + } + +} diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2ResponseAssertionAccessor.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2ResponseAssertionAccessor.java new file mode 100644 index 0000000000..f137b79848 --- /dev/null +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2ResponseAssertionAccessor.java @@ -0,0 +1,65 @@ +/* + * 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.saml2.provider.service.authentication; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; + +import org.jspecify.annotations.Nullable; + +import org.springframework.util.CollectionUtils; + +/** + * An interface that represents key details from a SAML 2.0 Assertion + * + * @author Josh Cummings + * @since 7.0 + * @see Saml2ResponseAssertion + */ +public interface Saml2ResponseAssertionAccessor extends Serializable { + + String getNameId(); + + List getSessionIndexes(); + + /** + * Get the first value of Saml2 token attribute by name + * @param name the name of the attribute + * @param the type of the attribute + * @return the first attribute value or {@code null} otherwise + */ + @Nullable default A getFirstAttribute(String name) { + List values = getAttribute(name); + return CollectionUtils.firstElement(values); + } + + /** + * Get the Saml2 token attribute by name + * @param name the name of the attribute + * @param the type of the attribute + * @return the attribute or {@code null} otherwise + */ + @Nullable default List getAttribute(String name) { + return (List) getAttributes().get(name); + } + + Map> getAttributes(); + + String getResponseValue(); + +} diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/logout/BaseOpenSamlLogoutRequestValidator.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/logout/BaseOpenSamlLogoutRequestValidator.java index b96cb947c6..9165113684 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/logout/BaseOpenSamlLogoutRequestValidator.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/logout/BaseOpenSamlLogoutRequestValidator.java @@ -27,6 +27,7 @@ import org.springframework.security.saml2.core.OpenSamlInitializationService; import org.springframework.security.saml2.core.Saml2Error; import org.springframework.security.saml2.core.Saml2ErrorCodes; import org.springframework.security.saml2.core.Saml2X509Credential; +import org.springframework.security.saml2.provider.service.authentication.Saml2ResponseAssertionAccessor; import org.springframework.security.saml2.provider.service.authentication.logout.OpenSamlOperations.VerificationConfigurer; import org.springframework.security.saml2.provider.service.authentication.logout.OpenSamlOperations.VerificationConfigurer.RedirectParameters; import org.springframework.security.saml2.provider.service.registration.AssertingPartyMetadata; @@ -142,8 +143,9 @@ class BaseOpenSamlLogoutRequestValidator implements Saml2LogoutRequestValidator } private void validateNameId(NameID nameId, Authentication authentication, Collection errors) { - String name = nameId.getValue(); - if (!name.equals(authentication.getName())) { + String name = (authentication.getCredentials() instanceof Saml2ResponseAssertionAccessor assertion) + ? assertion.getNameId() : authentication.getName(); + if (!nameId.getValue().equals(name)) { errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_REQUEST, "Failed to match subject in LogoutRequest with currently logged in user")); } diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutRequestResolver.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutRequestResolver.java index 1f0e99db32..def1ab7afe 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutRequestResolver.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutRequestResolver.java @@ -42,7 +42,9 @@ import org.springframework.core.convert.converter.Converter; import org.springframework.security.core.Authentication; import org.springframework.security.saml2.core.OpenSamlInitializationService; import org.springframework.security.saml2.core.Saml2ParameterNames; -import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationInfo; +import org.springframework.security.saml2.provider.service.authentication.Saml2AssertionAuthentication; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; +import org.springframework.security.saml2.provider.service.authentication.Saml2ResponseAssertionAccessor; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequest; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; @@ -148,17 +150,25 @@ final class BaseOpenSamlLogoutRequestResolver implements Saml2LogoutRequestResol logoutRequest.setIssuer(issuer); NameID nameId = this.nameIdBuilder.buildObject(); logoutRequest.setNameID(nameId); - Saml2AuthenticationInfo info = Saml2AuthenticationInfo.fromAuthentication(authentication); - if (info != null) { + if (authentication.getCredentials() instanceof Saml2ResponseAssertionAccessor info) { nameId.setValue(info.getNameId()); + } + else { + nameId.setValue(authentication.getName()); + } + if (authentication.getCredentials() instanceof Saml2ResponseAssertionAccessor info) { for (String index : info.getSessionIndexes()) { SessionIndex sessionIndex = this.sessionIndexBuilder.buildObject(); sessionIndex.setValue(index); logoutRequest.getSessionIndexes().add(sessionIndex); } } - else { - nameId.setValue(authentication.getName()); + else if (authentication.getPrincipal() instanceof Saml2AuthenticatedPrincipal info) { + for (String index : info.getSessionIndexes()) { + SessionIndex sessionIndex = this.sessionIndexBuilder.buildObject(); + sessionIndex.setValue(index); + logoutRequest.getSessionIndexes().add(sessionIndex); + } } logoutRequest.setIssueInstant(Instant.now(this.clock)); this.parametersConsumer @@ -194,9 +204,14 @@ final class BaseOpenSamlLogoutRequestResolver implements Saml2LogoutRequestResol if (this.logger.isTraceEnabled()) { this.logger.trace("Attempting to resolve registrationId from " + authentication); } - Saml2AuthenticationInfo info = Saml2AuthenticationInfo.fromAuthentication(authentication); - if (info != null) { - return info.getRelyingPartyRegistrationId(); + if (authentication == null) { + return null; + } + if (authentication instanceof Saml2AssertionAuthentication response) { + return response.getRelyingPartyRegistrationId(); + } + if (authentication.getPrincipal() instanceof Saml2AuthenticatedPrincipal principal) { + return principal.getRelyingPartyRegistrationId(); } return null; } diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutRequestValidatorParametersResolver.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutRequestValidatorParametersResolver.java index 975f83c930..3fc54e88bf 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutRequestValidatorParametersResolver.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutRequestValidatorParametersResolver.java @@ -24,8 +24,9 @@ import org.springframework.security.core.Authentication; import org.springframework.security.saml2.core.OpenSamlInitializationService; import org.springframework.security.saml2.core.Saml2Error; import org.springframework.security.saml2.core.Saml2ParameterNames; +import org.springframework.security.saml2.provider.service.authentication.Saml2AssertionAuthentication; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; -import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationInfo; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequest; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequestValidatorParameters; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; @@ -130,9 +131,14 @@ final class BaseOpenSamlLogoutRequestValidatorParametersResolver if (registrationId != null) { return registrationId; } - Saml2AuthenticationInfo info = Saml2AuthenticationInfo.fromAuthentication(authentication); - if (info != null) { - return info.getRelyingPartyRegistrationId(); + if (authentication == null) { + return null; + } + if (authentication instanceof Saml2AssertionAuthentication saml2) { + return saml2.getRelyingPartyRegistrationId(); + } + if (authentication.getPrincipal() instanceof Saml2AuthenticatedPrincipal saml2) { + return saml2.getRelyingPartyRegistrationId(); } return null; } diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutResponseResolver.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutResponseResolver.java index 4fe929e372..48dd0bc47a 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutResponseResolver.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutResponseResolver.java @@ -46,8 +46,9 @@ import org.springframework.security.saml2.core.OpenSamlInitializationService; import org.springframework.security.saml2.core.Saml2Error; import org.springframework.security.saml2.core.Saml2ErrorCodes; import org.springframework.security.saml2.core.Saml2ParameterNames; +import org.springframework.security.saml2.provider.service.authentication.Saml2AssertionAuthentication; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; -import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationInfo; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutResponse; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; @@ -217,9 +218,14 @@ final class BaseOpenSamlLogoutResponseResolver implements Saml2LogoutResponseRes if (this.logger.isTraceEnabled()) { this.logger.trace("Attempting to resolve registrationId from " + authentication); } - Saml2AuthenticationInfo info = Saml2AuthenticationInfo.fromAuthentication(authentication); - if (info != null) { - return info.getRelyingPartyRegistrationId(); + if (authentication == null) { + return null; + } + if (authentication instanceof Saml2AssertionAuthentication saml2) { + return saml2.getRelyingPartyRegistrationId(); + } + if (authentication.getPrincipal() instanceof Saml2AuthenticatedPrincipal saml2) { + return saml2.getRelyingPartyRegistrationId(); } return null; } diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestFilter.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestFilter.java index 18169605ca..29fe0921c5 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestFilter.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestFilter.java @@ -33,8 +33,9 @@ import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.saml2.core.Saml2Error; import org.springframework.security.saml2.core.Saml2ErrorCodes; import org.springframework.security.saml2.core.Saml2ParameterNames; +import org.springframework.security.saml2.provider.service.authentication.Saml2AssertionAuthentication; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; -import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationInfo; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequest; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequestValidator; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequestValidatorParameters; @@ -329,9 +330,14 @@ public final class Saml2LogoutRequestFilter extends OncePerRequestFilter { if (registrationId != null) { return registrationId; } - Saml2AuthenticationInfo info = Saml2AuthenticationInfo.fromAuthentication(authentication); - if (info != null) { - return info.getRelyingPartyRegistrationId(); + if (authentication == null) { + return null; + } + if (authentication instanceof Saml2AssertionAuthentication saml2) { + return saml2.getRelyingPartyRegistrationId(); + } + if (authentication.getPrincipal() instanceof Saml2AuthenticatedPrincipal saml2) { + return saml2.getRelyingPartyRegistrationId(); } return null; } diff --git a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutRequestValidatorParametersResolver.java b/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutRequestValidatorParametersResolver.java index ce8bc94300..aae95e28c7 100644 --- a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutRequestValidatorParametersResolver.java +++ b/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutRequestValidatorParametersResolver.java @@ -28,8 +28,8 @@ import org.springframework.security.core.Authentication; import org.springframework.security.saml2.core.OpenSamlInitializationService; import org.springframework.security.saml2.core.Saml2Error; import org.springframework.security.saml2.core.Saml2ParameterNames; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; -import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationInfo; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequest; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequestValidatorParameters; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; @@ -144,9 +144,11 @@ public final class OpenSamlLogoutRequestValidatorParametersResolver if (registrationId != null) { return registrationId; } - Saml2AuthenticationInfo info = Saml2AuthenticationInfo.fromAuthentication(authentication); - if (info != null) { - return info.getRelyingPartyRegistrationId(); + if (authentication == null) { + return null; + } + if (authentication.getPrincipal() instanceof Saml2AuthenticatedPrincipal principal) { + return principal.getRelyingPartyRegistrationId(); } return null; } diff --git a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml5AuthenticationProvider.java b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml5AuthenticationProvider.java index d2dbb5ba0c..6d49c6ef2f 100644 --- a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml5AuthenticationProvider.java +++ b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml5AuthenticationProvider.java @@ -893,14 +893,15 @@ public final class OpenSaml5AuthenticationProvider implements AuthenticationProv Saml2AuthenticationToken token = responseToken.token; Assertion assertion = CollectionUtils.firstElement(response.getAssertions()); String username = this.principalNameConverter.convert(assertion); - Map> attributes = BaseOpenSamlAuthenticationProvider.getAssertionAttributes(assertion); - List sessionIndexes = BaseOpenSamlAuthenticationProvider.getSessionIndexes(assertion); - DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal(username, attributes, - sessionIndexes); String registrationId = responseToken.token.getRelyingPartyRegistration().getRegistrationId(); - principal.setRelyingPartyRegistrationId(registrationId); - return new Saml2Authentication(principal, token.getSaml2Response(), - this.grantedAuthoritiesConverter.convert(assertion)); + Saml2ResponseAssertionAccessor accessor = Saml2ResponseAssertion.withResponseValue(token.getSaml2Response()) + .nameId(authenticatedPrincipal(assertion)) + .sessionIndexes(BaseOpenSamlAuthenticationProvider.getSessionIndexes(assertion)) + .attributes(BaseOpenSamlAuthenticationProvider.getAssertionAttributes(assertion)) + .build(); + Saml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal(username, accessor); + Collection authorities = this.grantedAuthoritiesConverter.convert(assertion); + return new Saml2AssertionAuthentication(principal, accessor, authorities, registrationId); } /** diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/DefaultSaml2AuthenticatedPrincipalTests.java b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/DefaultSaml2AuthenticatedPrincipalTests.java index 4ea06059a6..75ab69d108 100644 --- a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/DefaultSaml2AuthenticatedPrincipalTests.java +++ b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/DefaultSaml2AuthenticatedPrincipalTests.java @@ -48,7 +48,8 @@ public class DefaultSaml2AuthenticatedPrincipalTests { @Test public void createDefaultSaml2AuthenticatedPrincipalWhenAttributesNullThenException() { - assertThatIllegalArgumentException().isThrownBy(() -> new DefaultSaml2AuthenticatedPrincipal("user", null)) + assertThatIllegalArgumentException() + .isThrownBy(() -> new DefaultSaml2AuthenticatedPrincipal("user", (Map>) null)) .withMessageContaining("attributes cannot be null"); }