From 36c7b91fb95de1c5e22184a3b259acbf8006e84f Mon Sep 17 00:00:00 2001 From: Christian Schuster Date: Mon, 6 Jun 2022 19:09:42 +0200 Subject: [PATCH] SAML 2.0 Single Logout Uses Saml2AuthenticationInfo This allows SLO to be triggered without the authentication principal needing to implement a given interface. Issue gh-10820 --- .../saml2/Saml2LogoutConfigurer.java | 7 +- .../http/Saml2LogoutBeanDefinitionParser.java | 7 +- .../Saml2AuthenticatedPrincipal.java | 4 +- .../Saml2AuthenticationInfo.java | 78 +++++++++++++++++++ .../BaseOpenSamlLogoutRequestResolver.java | 17 ++-- ...outRequestValidatorParametersResolver.java | 10 +-- .../BaseOpenSamlLogoutResponseResolver.java | 11 +-- .../logout/Saml2LogoutRequestFilter.java | 10 +-- ...outRequestValidatorParametersResolver.java | 10 +-- 9 files changed, 108 insertions(+), 46 deletions(-) create mode 100644 saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationInfo.java 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 e69f8c825b..f8303a81b9 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,7 @@ 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.Saml2AuthenticatedPrincipal; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationInfo; 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,10 +531,7 @@ public final class Saml2LogoutConfigurer> @Override public boolean matches(HttpServletRequest request) { Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication(); - if (authentication == null) { - return false; - } - return authentication.getPrincipal() instanceof Saml2AuthenticatedPrincipal; + return Saml2AuthenticationInfo.fromAuthentication(authentication) != null; } } 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 bef7a69d19..dfeed0d981 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,7 @@ 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.Saml2AuthenticatedPrincipal; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationInfo; 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,10 +236,7 @@ final class Saml2LogoutBeanDefinitionParser implements BeanDefinitionParser { @Override public boolean matches(HttpServletRequest request) { Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication(); - if (authentication == null) { - return false; - } - return authentication.getPrincipal() instanceof Saml2AuthenticatedPrincipal; + return Saml2AuthenticationInfo.fromAuthentication(authentication) != null; } public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) { 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 205c5f8941..69eb9d7628 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 @@ -31,7 +31,7 @@ import org.springframework.util.CollectionUtils; * @author Clement Stoquart * @since 5.2.2 */ -public interface Saml2AuthenticatedPrincipal extends AuthenticatedPrincipal { +public interface Saml2AuthenticatedPrincipal extends AuthenticatedPrincipal, Saml2AuthenticationInfo { /** * Get the first value of Saml2 token attribute by name @@ -72,10 +72,12 @@ public interface Saml2AuthenticatedPrincipal extends AuthenticatedPrincipal { * @return the {@link RelyingPartyRegistration} identifier * @since 5.6 */ + @Override default String getRelyingPartyRegistrationId() { return null; } + @Override default List getSessionIndexes() { return Collections.emptyList(); } 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 new file mode 100644 index 0000000000..309e2a909b --- /dev/null +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationInfo.java @@ -0,0 +1,78 @@ +/* + * 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.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 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/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 980cfa4ac6..c4550b632f 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,7 @@ 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.Saml2AuthenticatedPrincipal; +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.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; @@ -149,9 +149,9 @@ final class BaseOpenSamlLogoutRequestResolver implements Saml2LogoutRequestResol NameID nameId = this.nameIdBuilder.buildObject(); nameId.setValue(authentication.getName()); logoutRequest.setNameID(nameId); - if (authentication.getPrincipal() instanceof Saml2AuthenticatedPrincipal) { - Saml2AuthenticatedPrincipal principal = (Saml2AuthenticatedPrincipal) authentication.getPrincipal(); - for (String index : principal.getSessionIndexes()) { + Saml2AuthenticationInfo info = Saml2AuthenticationInfo.fromAuthentication(authentication); + if (info != null) { + for (String index : info.getSessionIndexes()) { SessionIndex sessionIndex = this.sessionIndexBuilder.buildObject(); sessionIndex.setValue(index); logoutRequest.getSessionIndexes().add(sessionIndex); @@ -191,12 +191,9 @@ final class BaseOpenSamlLogoutRequestResolver implements Saml2LogoutRequestResol if (this.logger.isTraceEnabled()) { this.logger.trace("Attempting to resolve registrationId from " + authentication); } - if (authentication == null) { - return null; - } - Object principal = authentication.getPrincipal(); - if (principal instanceof Saml2AuthenticatedPrincipal) { - return ((Saml2AuthenticatedPrincipal) principal).getRelyingPartyRegistrationId(); + Saml2AuthenticationInfo info = Saml2AuthenticationInfo.fromAuthentication(authentication); + if (info != null) { + return info.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 84a53005aa..975f83c930 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,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; @@ -130,11 +130,9 @@ final class BaseOpenSamlLogoutRequestValidatorParametersResolver if (registrationId != null) { return registrationId; } - if (authentication == null) { - return null; - } - if (authentication.getPrincipal() instanceof Saml2AuthenticatedPrincipal principal) { - return principal.getRelyingPartyRegistrationId(); + Saml2AuthenticationInfo info = Saml2AuthenticationInfo.fromAuthentication(authentication); + if (info != null) { + return info.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 4033e4d453..4fe929e372 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,8 @@ 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.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,12 +217,9 @@ final class BaseOpenSamlLogoutResponseResolver implements Saml2LogoutResponseRes if (this.logger.isTraceEnabled()) { this.logger.trace("Attempting to resolve registrationId from " + authentication); } - if (authentication == null) { - return null; - } - Object principal = authentication.getPrincipal(); - if (principal instanceof Saml2AuthenticatedPrincipal) { - return ((Saml2AuthenticatedPrincipal) principal).getRelyingPartyRegistrationId(); + Saml2AuthenticationInfo info = Saml2AuthenticationInfo.fromAuthentication(authentication); + if (info != null) { + return info.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 a3a22568df..18169605ca 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,8 @@ 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.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,11 +329,9 @@ public final class Saml2LogoutRequestFilter extends OncePerRequestFilter { if (registrationId != null) { return registrationId; } - if (authentication == null) { - return null; - } - if (authentication.getPrincipal() instanceof Saml2AuthenticatedPrincipal principal) { - return principal.getRelyingPartyRegistrationId(); + Saml2AuthenticationInfo info = Saml2AuthenticationInfo.fromAuthentication(authentication); + if (info != null) { + return info.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 aae95e28c7..ce8bc94300 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,11 +144,9 @@ public final class OpenSamlLogoutRequestValidatorParametersResolver if (registrationId != null) { return registrationId; } - if (authentication == null) { - return null; - } - if (authentication.getPrincipal() instanceof Saml2AuthenticatedPrincipal principal) { - return principal.getRelyingPartyRegistrationId(); + Saml2AuthenticationInfo info = Saml2AuthenticationInfo.fromAuthentication(authentication); + if (info != null) { + return info.getRelyingPartyRegistrationId(); } return null; }