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:
+59
@@ -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) {
|
||||
}
|
||||
|
||||
}
|
||||
+4
@@ -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);
|
||||
|
||||
+56
@@ -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) {
|
||||
}
|
||||
|
||||
}
|
||||
+8
@@ -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;
|
||||
|
||||
+65
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+5
-8
@@ -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<String> getSessionIndexes() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
+9
-1
@@ -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<? extends GrantedAuthority> authorities) {
|
||||
super(authorities);
|
||||
this.principal = principal;
|
||||
this.saml2Response = saml2Response;
|
||||
setAuthenticated(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getPrincipal() {
|
||||
return this.principal;
|
||||
|
||||
-85
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+115
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+65
@@ -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();
|
||||
|
||||
}
|
||||
+4
-2
@@ -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<Saml2Error> 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"));
|
||||
}
|
||||
|
||||
+23
-8
@@ -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;
|
||||
}
|
||||
|
||||
+10
-4
@@ -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;
|
||||
}
|
||||
|
||||
+10
-4
@@ -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;
|
||||
}
|
||||
|
||||
+10
-4
@@ -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;
|
||||
}
|
||||
|
||||
+6
-4
@@ -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;
|
||||
}
|
||||
|
||||
+8
-7
@@ -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<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();
|
||||
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<GrantedAuthority> authorities = this.grantedAuthoritiesConverter.convert(assertion);
|
||||
return new Saml2AssertionAuthentication(principal, accessor, authorities, registrationId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+2
-1
@@ -48,7 +48,8 @@ public class DefaultSaml2AuthenticatedPrincipalTests {
|
||||
|
||||
@Test
|
||||
public void createDefaultSaml2AuthenticatedPrincipalWhenAttributesNullThenException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new DefaultSaml2AuthenticatedPrincipal("user", null))
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new DefaultSaml2AuthenticatedPrincipal("user", (Map<String, List<Object>>) null))
|
||||
.withMessageContaining("attributes cannot be null");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user