Rework Saml2 Authentication Statement

This commit separates the authentication principal, the assertion details,
and the relying party tenant into separate components. This allows the
principal to be completely decoupled from how Spring Security triggers and
processes SLO.

Specifically, it adds Saml2AssertionAuthentication, a new authentication
implementation that allows an Object principal and a Saml2ResponseAssertionAccessor
credential. It also moves the relying party registration id from
Saml2AuthenticatedPrincipal to Saml2AssertionAuthentication.

As such, Saml2AuthenticatedPrincipal is now deprecated in favor of
placing its assertion components in Saml2ResponseAssertionAccessor and
the relying party registration id in Saml2AssertionAuthentication.

Closes gh-10820
This commit is contained in:
Josh Cummings 2025-06-06 14:24:12 -06:00
parent 759c88998a
commit c160801e2d
No known key found for this signature in database
GPG Key ID: 869B37A20E876129
25 changed files with 558 additions and 136 deletions

View File

@ -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.config.annotation.web.configurers.LogoutConfigurer;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolderStrategy; 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.OpenSaml4LogoutRequestValidator;
import org.springframework.security.saml2.provider.service.authentication.logout.OpenSaml4LogoutResponseValidator; import org.springframework.security.saml2.provider.service.authentication.logout.OpenSaml4LogoutResponseValidator;
import org.springframework.security.saml2.provider.service.authentication.logout.OpenSaml5LogoutRequestValidator; import org.springframework.security.saml2.provider.service.authentication.logout.OpenSaml5LogoutRequestValidator;
@ -531,7 +533,16 @@ public final class Saml2LogoutConfigurer<H extends HttpSecurityBuilder<H>>
@Override @Override
public boolean matches(HttpServletRequest request) { public boolean matches(HttpServletRequest request) {
Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication(); 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;
} }
} }

View File

@ -31,7 +31,9 @@ import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy; 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.DefaultRelyingPartyRegistrationResolver;
import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestFilter; import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestFilter;
import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutResponseFilter; import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutResponseFilter;
@ -236,7 +238,16 @@ final class Saml2LogoutBeanDefinitionParser implements BeanDefinitionParser {
@Override @Override
public boolean matches(HttpServletRequest request) { public boolean matches(HttpServletRequest request) {
Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication(); 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) { public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {

View File

@ -170,11 +170,14 @@ import org.springframework.security.saml2.core.Saml2Error;
import org.springframework.security.saml2.core.Saml2X509Credential; import org.springframework.security.saml2.core.Saml2X509Credential;
import org.springframework.security.saml2.credentials.TestSaml2X509Credentials; import org.springframework.security.saml2.credentials.TestSaml2X509Credentials;
import org.springframework.security.saml2.provider.service.authentication.DefaultSaml2AuthenticatedPrincipal; 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.Saml2Authentication;
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; 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.Saml2AuthenticationToken;
import org.springframework.security.saml2.provider.service.authentication.Saml2PostAuthenticationRequest; 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.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.TestSaml2AuthenticationTokens;
import org.springframework.security.saml2.provider.service.authentication.TestSaml2Authentications; import org.springframework.security.saml2.provider.service.authentication.TestSaml2Authentications;
import org.springframework.security.saml2.provider.service.authentication.TestSaml2LogoutRequests; 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(Saml2Exception.class, (r) -> new Saml2Exception("message", new IOException("fail")));
generatorByClassName.put(DefaultSaml2AuthenticatedPrincipal.class, generatorByClassName.put(DefaultSaml2AuthenticatedPrincipal.class,
(r) -> TestSaml2Authentications.authentication().getPrincipal()); (r) -> TestSaml2Authentications.authentication().getPrincipal());
generatorByClassName.put(Saml2Authentication.class, Saml2Authentication saml2 = TestSaml2Authentications.authentication();
(r) -> applyDetails(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, generatorByClassName.put(Saml2PostAuthenticationRequest.class,
(r) -> TestSaml2PostAuthenticationRequests.create()); (r) -> TestSaml2PostAuthenticationRequests.create());
generatorByClassName.put(Saml2RedirectAuthenticationRequest.class, generatorByClassName.put(Saml2RedirectAuthenticationRequest.class,

View File

@ -54,3 +54,57 @@ fun logoutResponseResolver(registrations: RelyingPartyRegistrationRepository?):
---- ----
====== ======
== Favor `Saml2ResponseAuthenticationAccessor` over `Saml2AuthenticatedPrincipal`
Spring Security 7 separates `<saml2:Assertion>` 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.

View File

@ -341,8 +341,10 @@ class MyUserDetailsResponseAuthenticationConverter implements Converter<Response
Saml2Authentication authentication = this.delegate.convert(responseToken); <1> Saml2Authentication authentication = this.delegate.convert(responseToken); <1>
UserDetails principal = this.userDetailsService.loadByUsername(username); <2> UserDetails principal = this.userDetailsService.loadByUsername(username); <2>
String saml2Response = authentication.getSaml2Response(); String saml2Response = authentication.getSaml2Response();
Saml2ResponseAssertionAccessor assertion = new OpenSamlResponseAssertionAccessor(
saml2Response, CollectionUtils.getFirst(response.getAssertions()));
Collection<GrantedAuthority> authorities = principal.getAuthorities(); Collection<GrantedAuthority> 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 authentication = this.delegate.convert(responseToken) <1>
val principal = this.userDetailsService.loadByUsername(username) <2> val principal = this.userDetailsService.loadByUsername(username) <2>
val saml2Response = authentication.getSaml2Response() val saml2Response = authentication.getSaml2Response()
val assertion = OpenSamlResponseAssertionAccessor(
saml2Response, CollectionUtils.getFirst(response.getAssertions()))
val authorities = principal.getAuthorities() val authorities = principal.getAuthorities()
return Saml2Authentication(userDetails as AuthenticatedPrincipal, saml2Response, authorities) <3> return Saml2AssertionAuthentication(userDetails, assertion, authorities) <3>
} }
} }

View File

@ -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}.
*
* <pre>
* ObjectMapper mapper = new ObjectMapper();
* mapper.registerModule(new Saml2Jackson2Module());
* </pre>
*
* @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<? extends GrantedAuthority> authorities,
@JsonProperty("relyingPartyRegistrationId") String relyingPartyRegistrationId) {
}
}

View File

@ -22,10 +22,12 @@ import com.fasterxml.jackson.databind.module.SimpleModule;
import org.springframework.security.jackson2.SecurityJackson2Modules; import org.springframework.security.jackson2.SecurityJackson2Modules;
import org.springframework.security.saml2.core.Saml2Error; import org.springframework.security.saml2.core.Saml2Error;
import org.springframework.security.saml2.provider.service.authentication.DefaultSaml2AuthenticatedPrincipal; 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.Saml2Authentication;
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; 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.Saml2PostAuthenticationRequest;
import org.springframework.security.saml2.provider.service.authentication.Saml2RedirectAuthenticationRequest; 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; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequest;
/** /**
@ -49,6 +51,8 @@ public class Saml2Jackson2Module extends SimpleModule {
@Override @Override
public void setupModule(SetupContext context) { public void setupModule(SetupContext context) {
context.setMixInAnnotations(Saml2Authentication.class, Saml2AuthenticationMixin.class); context.setMixInAnnotations(Saml2Authentication.class, Saml2AuthenticationMixin.class);
context.setMixInAnnotations(Saml2AssertionAuthentication.class, Saml2AssertionAuthenticationMixin.class);
context.setMixInAnnotations(Saml2ResponseAssertion.class, SimpleSaml2ResponseAssertionAccessorMixin.class);
context.setMixInAnnotations(DefaultSaml2AuthenticatedPrincipal.class, context.setMixInAnnotations(DefaultSaml2AuthenticatedPrincipal.class,
DefaultSaml2AuthenticatedPrincipalMixin.class); DefaultSaml2AuthenticatedPrincipalMixin.class);
context.setMixInAnnotations(Saml2LogoutRequest.class, Saml2LogoutRequestMixin.class); context.setMixInAnnotations(Saml2LogoutRequest.class, Saml2LogoutRequestMixin.class);

View File

@ -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}.
*
* <pre>
* ObjectMapper mapper = new ObjectMapper();
* mapper.registerModule(new Saml2Jackson2Module());
* </pre>
*
* @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<String> sessionIndexes,
@JsonProperty("attributes") Map<String, List<Object>> attributes) {
}
}

View File

@ -30,7 +30,9 @@ import org.springframework.util.Assert;
* *
* @author Clement Stoquart * @author Clement Stoquart
* @since 5.4 * @since 5.4
* @deprecated Please use {@link Saml2ResponseAssertionAccessor}
*/ */
@Deprecated
public class DefaultSaml2AuthenticatedPrincipal implements Saml2AuthenticatedPrincipal, Serializable { public class DefaultSaml2AuthenticatedPrincipal implements Saml2AuthenticatedPrincipal, Serializable {
@Serial @Serial
@ -58,6 +60,12 @@ public class DefaultSaml2AuthenticatedPrincipal implements Saml2AuthenticatedPri
this.sessionIndexes = sessionIndexes; this.sessionIndexes = sessionIndexes;
} }
public DefaultSaml2AuthenticatedPrincipal(String name, Saml2ResponseAssertionAccessor assertion) {
this.name = name;
this.attributes = assertion.getAttributes();
this.sessionIndexes = assertion.getSessionIndexes();
}
@Override @Override
public String getName() { public String getName() {
return this.name; return this.name;

View File

@ -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<? extends GrantedAuthority> authorities, String relyingPartyRegistrationId) {
super(assertion, assertion.getResponseValue(), authorities);
this.assertion = assertion;
this.relyingPartyRegistrationId = relyingPartyRegistrationId;
}
public Saml2AssertionAuthentication(Object principal, Saml2ResponseAssertionAccessor assertion,
Collection<? extends GrantedAuthority> 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;
}
}

View File

@ -30,8 +30,12 @@ import org.springframework.util.CollectionUtils;
* *
* @author Clement Stoquart * @author Clement Stoquart
* @since 5.2.2 * @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 * 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 * @return the {@link RelyingPartyRegistration} identifier
* @since 5.6 * @since 5.6
*/ */
@Override
default String getRelyingPartyRegistrationId() { default String getRelyingPartyRegistrationId() {
return null; return null;
} }
@Override
default String getNameId() {
return getName();
}
@Override
default List<String> getSessionIndexes() { default List<String> getSessionIndexes() {
return Collections.emptyList(); return Collections.emptyList();
} }

View File

@ -41,7 +41,7 @@ public class Saml2Authentication extends AbstractAuthenticationToken {
@Serial @Serial
private static final long serialVersionUID = 405897702378720477L; private static final long serialVersionUID = 405897702378720477L;
private final AuthenticatedPrincipal principal; private final Object principal;
private final String saml2Response; private final String saml2Response;
@ -61,6 +61,14 @@ public class Saml2Authentication extends AbstractAuthenticationToken {
setAuthenticated(true); setAuthenticated(true);
} }
public Saml2Authentication(Object principal, String saml2Response,
Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.saml2Response = saml2Response;
setAuthenticated(true);
}
@Override @Override
public Object getPrincipal() { public Object getPrincipal() {
return this.principal; return this.principal;

View File

@ -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
*
* <p>
* 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<String> getSessionIndexes();
/**
* Try to obtain a {@link Saml2AuthenticationInfo} instance from an
* {@link Authentication}
*
* <p>
* The result is either the {@link Authentication#getPrincipal() authenticated
* principal}, the {@link Authentication} itself, or {@code null}.
*
* <p>
* 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;
}
}

View File

@ -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<String> sessionIndexes;
private final Map<String, List<Object>> attributes;
Saml2ResponseAssertion(String responseValue, String nameId, List<String> sessionIndexes,
Map<String, List<Object>> 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<String> getSessionIndexes() {
return this.sessionIndexes;
}
@Override
public Map<String, List<Object>> 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<String> sessionIndexes = List.of();
private Map<String, List<Object>> attributes = Map.of();
Builder(String responseValue) {
this.responseValue = responseValue;
}
public Builder nameId(String nameId) {
this.nameId = nameId;
return this;
}
public Builder sessionIndexes(List<String> sessionIndexes) {
this.sessionIndexes = sessionIndexes;
return this;
}
public Builder attributes(Map<String, List<Object>> attributes) {
this.attributes = attributes;
return this;
}
public Saml2ResponseAssertion build() {
return new Saml2ResponseAssertion(this.responseValue, this.nameId, this.sessionIndexes, this.attributes);
}
}
}

View File

@ -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<String> getSessionIndexes();
/**
* Get the first value of Saml2 token attribute by name
* @param name the name of the attribute
* @param <A> the type of the attribute
* @return the first attribute value or {@code null} otherwise
*/
@Nullable default <A> A getFirstAttribute(String name) {
List<A> values = getAttribute(name);
return CollectionUtils.firstElement(values);
}
/**
* Get the Saml2 token attribute by name
* @param name the name of the attribute
* @param <A> the type of the attribute
* @return the attribute or {@code null} otherwise
*/
@Nullable default <A> List<A> getAttribute(String name) {
return (List<A>) getAttributes().get(name);
}
Map<String, List<Object>> getAttributes();
String getResponseValue();
}

View File

@ -27,6 +27,7 @@ import org.springframework.security.saml2.core.OpenSamlInitializationService;
import org.springframework.security.saml2.core.Saml2Error; import org.springframework.security.saml2.core.Saml2Error;
import org.springframework.security.saml2.core.Saml2ErrorCodes; import org.springframework.security.saml2.core.Saml2ErrorCodes;
import org.springframework.security.saml2.core.Saml2X509Credential; 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;
import org.springframework.security.saml2.provider.service.authentication.logout.OpenSamlOperations.VerificationConfigurer.RedirectParameters; import org.springframework.security.saml2.provider.service.authentication.logout.OpenSamlOperations.VerificationConfigurer.RedirectParameters;
import org.springframework.security.saml2.provider.service.registration.AssertingPartyMetadata; 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<Saml2Error> errors) { private void validateNameId(NameID nameId, Authentication authentication, Collection<Saml2Error> errors) {
String name = nameId.getValue(); String name = (authentication.getCredentials() instanceof Saml2ResponseAssertionAccessor assertion)
if (!name.equals(authentication.getName())) { ? assertion.getNameId() : authentication.getName();
if (!nameId.getValue().equals(name)) {
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_REQUEST, errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_REQUEST,
"Failed to match subject in LogoutRequest with currently logged in user")); "Failed to match subject in LogoutRequest with currently logged in user"));
} }

View File

@ -42,7 +42,9 @@ import org.springframework.core.convert.converter.Converter;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.saml2.core.OpenSamlInitializationService; import org.springframework.security.saml2.core.OpenSamlInitializationService;
import org.springframework.security.saml2.core.Saml2ParameterNames; 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.authentication.logout.Saml2LogoutRequest;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
@ -148,17 +150,25 @@ final class BaseOpenSamlLogoutRequestResolver implements Saml2LogoutRequestResol
logoutRequest.setIssuer(issuer); logoutRequest.setIssuer(issuer);
NameID nameId = this.nameIdBuilder.buildObject(); NameID nameId = this.nameIdBuilder.buildObject();
logoutRequest.setNameID(nameId); logoutRequest.setNameID(nameId);
Saml2AuthenticationInfo info = Saml2AuthenticationInfo.fromAuthentication(authentication); if (authentication.getCredentials() instanceof Saml2ResponseAssertionAccessor info) {
if (info != null) {
nameId.setValue(info.getNameId()); nameId.setValue(info.getNameId());
}
else {
nameId.setValue(authentication.getName());
}
if (authentication.getCredentials() instanceof Saml2ResponseAssertionAccessor info) {
for (String index : info.getSessionIndexes()) { for (String index : info.getSessionIndexes()) {
SessionIndex sessionIndex = this.sessionIndexBuilder.buildObject(); SessionIndex sessionIndex = this.sessionIndexBuilder.buildObject();
sessionIndex.setValue(index); sessionIndex.setValue(index);
logoutRequest.getSessionIndexes().add(sessionIndex); logoutRequest.getSessionIndexes().add(sessionIndex);
} }
} }
else { else if (authentication.getPrincipal() instanceof Saml2AuthenticatedPrincipal info) {
nameId.setValue(authentication.getName()); for (String index : info.getSessionIndexes()) {
SessionIndex sessionIndex = this.sessionIndexBuilder.buildObject();
sessionIndex.setValue(index);
logoutRequest.getSessionIndexes().add(sessionIndex);
}
} }
logoutRequest.setIssueInstant(Instant.now(this.clock)); logoutRequest.setIssueInstant(Instant.now(this.clock));
this.parametersConsumer this.parametersConsumer
@ -194,9 +204,14 @@ final class BaseOpenSamlLogoutRequestResolver implements Saml2LogoutRequestResol
if (this.logger.isTraceEnabled()) { if (this.logger.isTraceEnabled()) {
this.logger.trace("Attempting to resolve registrationId from " + authentication); this.logger.trace("Attempting to resolve registrationId from " + authentication);
} }
Saml2AuthenticationInfo info = Saml2AuthenticationInfo.fromAuthentication(authentication); if (authentication == null) {
if (info != null) { return null;
return info.getRelyingPartyRegistrationId(); }
if (authentication instanceof Saml2AssertionAuthentication response) {
return response.getRelyingPartyRegistrationId();
}
if (authentication.getPrincipal() instanceof Saml2AuthenticatedPrincipal principal) {
return principal.getRelyingPartyRegistrationId();
} }
return null; return null;
} }

View File

@ -24,8 +24,9 @@ import org.springframework.security.core.Authentication;
import org.springframework.security.saml2.core.OpenSamlInitializationService; import org.springframework.security.saml2.core.OpenSamlInitializationService;
import org.springframework.security.saml2.core.Saml2Error; import org.springframework.security.saml2.core.Saml2Error;
import org.springframework.security.saml2.core.Saml2ParameterNames; 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.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.Saml2LogoutRequest;
import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequestValidatorParameters; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequestValidatorParameters;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
@ -130,9 +131,14 @@ final class BaseOpenSamlLogoutRequestValidatorParametersResolver
if (registrationId != null) { if (registrationId != null) {
return registrationId; return registrationId;
} }
Saml2AuthenticationInfo info = Saml2AuthenticationInfo.fromAuthentication(authentication); if (authentication == null) {
if (info != null) { return null;
return info.getRelyingPartyRegistrationId(); }
if (authentication instanceof Saml2AssertionAuthentication saml2) {
return saml2.getRelyingPartyRegistrationId();
}
if (authentication.getPrincipal() instanceof Saml2AuthenticatedPrincipal saml2) {
return saml2.getRelyingPartyRegistrationId();
} }
return null; return null;
} }

View File

@ -46,8 +46,9 @@ import org.springframework.security.saml2.core.OpenSamlInitializationService;
import org.springframework.security.saml2.core.Saml2Error; import org.springframework.security.saml2.core.Saml2Error;
import org.springframework.security.saml2.core.Saml2ErrorCodes; import org.springframework.security.saml2.core.Saml2ErrorCodes;
import org.springframework.security.saml2.core.Saml2ParameterNames; 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.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.authentication.logout.Saml2LogoutResponse;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
@ -217,9 +218,14 @@ final class BaseOpenSamlLogoutResponseResolver implements Saml2LogoutResponseRes
if (this.logger.isTraceEnabled()) { if (this.logger.isTraceEnabled()) {
this.logger.trace("Attempting to resolve registrationId from " + authentication); this.logger.trace("Attempting to resolve registrationId from " + authentication);
} }
Saml2AuthenticationInfo info = Saml2AuthenticationInfo.fromAuthentication(authentication); if (authentication == null) {
if (info != null) { return null;
return info.getRelyingPartyRegistrationId(); }
if (authentication instanceof Saml2AssertionAuthentication saml2) {
return saml2.getRelyingPartyRegistrationId();
}
if (authentication.getPrincipal() instanceof Saml2AuthenticatedPrincipal saml2) {
return saml2.getRelyingPartyRegistrationId();
} }
return null; return null;
} }

View File

@ -33,8 +33,9 @@ import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.saml2.core.Saml2Error; import org.springframework.security.saml2.core.Saml2Error;
import org.springframework.security.saml2.core.Saml2ErrorCodes; import org.springframework.security.saml2.core.Saml2ErrorCodes;
import org.springframework.security.saml2.core.Saml2ParameterNames; 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.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.Saml2LogoutRequest;
import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequestValidator; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequestValidator;
import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequestValidatorParameters; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequestValidatorParameters;
@ -329,9 +330,14 @@ public final class Saml2LogoutRequestFilter extends OncePerRequestFilter {
if (registrationId != null) { if (registrationId != null) {
return registrationId; return registrationId;
} }
Saml2AuthenticationInfo info = Saml2AuthenticationInfo.fromAuthentication(authentication); if (authentication == null) {
if (info != null) { return null;
return info.getRelyingPartyRegistrationId(); }
if (authentication instanceof Saml2AssertionAuthentication saml2) {
return saml2.getRelyingPartyRegistrationId();
}
if (authentication.getPrincipal() instanceof Saml2AuthenticatedPrincipal saml2) {
return saml2.getRelyingPartyRegistrationId();
} }
return null; return null;
} }

View File

@ -28,8 +28,8 @@ import org.springframework.security.core.Authentication;
import org.springframework.security.saml2.core.OpenSamlInitializationService; import org.springframework.security.saml2.core.OpenSamlInitializationService;
import org.springframework.security.saml2.core.Saml2Error; import org.springframework.security.saml2.core.Saml2Error;
import org.springframework.security.saml2.core.Saml2ParameterNames; 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.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.Saml2LogoutRequest;
import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequestValidatorParameters; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequestValidatorParameters;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
@ -144,9 +144,11 @@ public final class OpenSamlLogoutRequestValidatorParametersResolver
if (registrationId != null) { if (registrationId != null) {
return registrationId; return registrationId;
} }
Saml2AuthenticationInfo info = Saml2AuthenticationInfo.fromAuthentication(authentication); if (authentication == null) {
if (info != null) { return null;
return info.getRelyingPartyRegistrationId(); }
if (authentication.getPrincipal() instanceof Saml2AuthenticatedPrincipal principal) {
return principal.getRelyingPartyRegistrationId();
} }
return null; return null;
} }

View File

@ -893,14 +893,15 @@ public final class OpenSaml5AuthenticationProvider implements AuthenticationProv
Saml2AuthenticationToken token = responseToken.token; Saml2AuthenticationToken token = responseToken.token;
Assertion assertion = CollectionUtils.firstElement(response.getAssertions()); Assertion assertion = CollectionUtils.firstElement(response.getAssertions());
String username = this.principalNameConverter.convert(assertion); String username = this.principalNameConverter.convert(assertion);
Map<String, List<Object>> attributes = BaseOpenSamlAuthenticationProvider.getAssertionAttributes(assertion);
List<String> sessionIndexes = BaseOpenSamlAuthenticationProvider.getSessionIndexes(assertion);
DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal(username, attributes,
sessionIndexes);
String registrationId = responseToken.token.getRelyingPartyRegistration().getRegistrationId(); String registrationId = responseToken.token.getRelyingPartyRegistration().getRegistrationId();
principal.setRelyingPartyRegistrationId(registrationId); Saml2ResponseAssertionAccessor accessor = Saml2ResponseAssertion.withResponseValue(token.getSaml2Response())
return new Saml2Authentication(principal, token.getSaml2Response(), .nameId(authenticatedPrincipal(assertion))
this.grantedAuthoritiesConverter.convert(assertion)); .sessionIndexes(BaseOpenSamlAuthenticationProvider.getSessionIndexes(assertion))
.attributes(BaseOpenSamlAuthenticationProvider.getAssertionAttributes(assertion))
.build();
Saml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal(username, accessor);
Collection<GrantedAuthority> authorities = this.grantedAuthoritiesConverter.convert(assertion);
return new Saml2AssertionAuthentication(principal, accessor, authorities, registrationId);
} }
/** /**

View File

@ -48,7 +48,8 @@ public class DefaultSaml2AuthenticatedPrincipalTests {
@Test @Test
public void createDefaultSaml2AuthenticatedPrincipalWhenAttributesNullThenException() { public void createDefaultSaml2AuthenticatedPrincipalWhenAttributesNullThenException() {
assertThatIllegalArgumentException().isThrownBy(() -> new DefaultSaml2AuthenticatedPrincipal("user", null)) assertThatIllegalArgumentException()
.isThrownBy(() -> new DefaultSaml2AuthenticatedPrincipal("user", (Map<String, List<Object>>) null))
.withMessageContaining("attributes cannot be null"); .withMessageContaining("attributes cannot be null");
} }