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
This commit is contained in:
Christian Schuster 2022-06-06 19:09:42 +02:00 committed by Josh Cummings
parent ffd6e3c0f7
commit 36c7b91fb9
9 changed files with 108 additions and 46 deletions

View File

@ -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<H extends HttpSecurityBuilder<H>>
@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;
}
}

View File

@ -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) {

View File

@ -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<String> getSessionIndexes() {
return Collections.emptyList();
}

View File

@ -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
*
* <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 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

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

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.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;
}