Add SAML 2.0 Single Logout XML Support

Closes gh-10842
This commit is contained in:
Marcus Da Coregio 2022-02-17 15:52:41 -03:00 committed by Marcus Hert Da Coregio
parent 73f839312d
commit 93d4fd3559
18 changed files with 1336 additions and 6 deletions

View File

@ -142,4 +142,6 @@ public abstract class Elements {
public static final String SAML2_LOGIN = "saml2-login";
public static final String SAML2_LOGOUT = "saml2-logout";
}

View File

@ -164,6 +164,8 @@ final class AuthenticationConfigBuilder {
@SuppressWarnings("rawtypes")
private ManagedList logoutHandlers;
private BeanMetadataElement logoutSuccessHandler;
private BeanDefinition loginPageGenerationFilter;
private BeanDefinition logoutPageGenerationFilter;
@ -210,6 +212,12 @@ final class AuthenticationConfigBuilder {
private String saml2AuthenticationRequestFilterId;
private String saml2LogoutFilterId;
private String saml2LogoutRequestFilterId;
private String saml2LogoutResponseFilterId;
private boolean oauth2ClientEnabled;
private BeanDefinition authorizationRequestRedirectFilter;
@ -250,6 +258,7 @@ final class AuthenticationConfigBuilder {
createX509Filter(authenticationManager);
createJeeFilter(authenticationManager);
createLogoutFilter();
createSaml2LogoutFilter();
createLoginPageFilterIfNeeded();
createUserDetailsServiceFactory();
createExceptionTranslationFilter();
@ -720,9 +729,33 @@ final class AuthenticationConfigBuilder {
this.rememberMeServicesId, this.csrfLogoutHandler);
this.logoutFilter = logoutParser.parse(logoutElt, this.pc);
this.logoutHandlers = logoutParser.getLogoutHandlers();
this.logoutSuccessHandler = logoutParser.getLogoutSuccessHandler();
}
}
private void createSaml2LogoutFilter() {
Element saml2LogoutElt = DomUtils.getChildElementByTagName(this.httpElt, Elements.SAML2_LOGOUT);
if (saml2LogoutElt == null) {
return;
}
Saml2LogoutBeanDefinitionParser parser = new Saml2LogoutBeanDefinitionParser(this.logoutHandlers,
this.logoutSuccessHandler);
parser.parse(saml2LogoutElt, this.pc);
BeanDefinition saml2LogoutFilter = parser.getLogoutFilter();
BeanDefinition saml2LogoutRequestFilter = parser.getLogoutRequestFilter();
BeanDefinition saml2LogoutResponseFilter = parser.getLogoutResponseFilter();
this.saml2LogoutFilterId = this.pc.getReaderContext().generateBeanName(saml2LogoutFilter);
this.saml2LogoutRequestFilterId = this.pc.getReaderContext().generateBeanName(saml2LogoutRequestFilter);
this.saml2LogoutResponseFilterId = this.pc.getReaderContext().generateBeanName(saml2LogoutResponseFilter);
// register the component
this.pc.registerBeanComponent(new BeanComponentDefinition(saml2LogoutFilter, this.saml2LogoutFilterId));
this.pc.registerBeanComponent(
new BeanComponentDefinition(saml2LogoutRequestFilter, this.saml2LogoutRequestFilterId));
this.pc.registerBeanComponent(
new BeanComponentDefinition(saml2LogoutResponseFilter, this.saml2LogoutResponseFilterId));
}
@SuppressWarnings({ "rawtypes", "unchecked" })
ManagedList getLogoutHandlers() {
if (this.logoutHandlers == null && this.rememberMeProviderRef != null) {
@ -968,6 +1001,14 @@ final class AuthenticationConfigBuilder {
filters.add(new OrderDecorator(new RuntimeBeanReference(this.saml2AuthenticationRequestFilterId),
SecurityFilters.SAML2_AUTHENTICATION_REQUEST_FILTER));
}
if (this.saml2LogoutFilterId != null) {
filters.add(new OrderDecorator(new RuntimeBeanReference(this.saml2LogoutFilterId),
SecurityFilters.SAML2_LOGOUT_FILTER));
filters.add(new OrderDecorator(new RuntimeBeanReference(this.saml2LogoutRequestFilterId),
SecurityFilters.SAML2_LOGOUT_REQUEST_FILTER));
filters.add(new OrderDecorator(new RuntimeBeanReference(this.saml2LogoutResponseFilterId),
SecurityFilters.SAML2_LOGOUT_RESPONSE_FILTER));
}
filters.add(new OrderDecorator(this.etf, SecurityFilters.EXCEPTION_TRANSLATION_FILTER));
return filters;
}

View File

@ -59,6 +59,8 @@ class LogoutBeanDefinitionParser implements BeanDefinitionParser {
private boolean csrfEnabled;
private BeanMetadataElement logoutSuccessHandler;
LogoutBeanDefinitionParser(String loginPageUrl, String rememberMeServices, BeanMetadataElement csrfLogoutHandler) {
this.defaultLogoutUrl = loginPageUrl + "?logout";
this.rememberMeServices = rememberMeServices;
@ -98,6 +100,7 @@ class LogoutBeanDefinitionParser implements BeanDefinitionParser {
pc.extractSource(element));
}
builder.addConstructorArgReference(successHandlerRef);
this.logoutSuccessHandler = new RuntimeBeanReference(successHandlerRef);
}
else {
// Use the logout URL if no handler set
@ -137,4 +140,8 @@ class LogoutBeanDefinitionParser implements BeanDefinitionParser {
return this.logoutHandlers;
}
BeanMetadataElement getLogoutSuccessHandler() {
return this.logoutSuccessHandler;
}
}

View File

@ -0,0 +1,236 @@
/*
* 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.config.http;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;
import javax.servlet.http.HttpServletRequest;
import org.w3c.dom.Element;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal;
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;
import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2RelyingPartyInitiatedLogoutSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessEventPublishingLogoutHandler;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
import org.springframework.security.web.util.matcher.AndRequestMatcher;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
/**
* SAML 2.0 Single Logout {@link BeanDefinitionParser}
*
* @author Marcus da Coregio
* @since 5.7
*/
final class Saml2LogoutBeanDefinitionParser implements BeanDefinitionParser {
private static final String ATT_LOGOUT_REQUEST_URL = "logout-request-url";
private static final String ATT_LOGOUT_RESPONSE_URL = "logout-response-url";
private static final String ATT_LOGOUT_URL = "logout-url";
private List<BeanMetadataElement> logoutHandlers;
private String logoutUrl = "/logout";
private String logoutRequestUrl = "/logout/saml2/slo";
private String logoutResponseUrl = "/logout/saml2/slo";
private BeanMetadataElement logoutSuccessHandler;
private BeanDefinition logoutRequestFilter;
private BeanDefinition logoutResponseFilter;
private BeanDefinition logoutFilter;
Saml2LogoutBeanDefinitionParser(ManagedList<BeanMetadataElement> logoutHandlers,
BeanMetadataElement logoutSuccessHandler) {
this.logoutHandlers = logoutHandlers;
this.logoutSuccessHandler = logoutSuccessHandler;
}
@Override
public BeanDefinition parse(Element element, ParserContext pc) {
String logoutUrl = element.getAttribute(ATT_LOGOUT_URL);
if (StringUtils.hasText(logoutUrl)) {
this.logoutUrl = logoutUrl;
}
String logoutRequestUrl = element.getAttribute(ATT_LOGOUT_REQUEST_URL);
if (StringUtils.hasText(logoutRequestUrl)) {
this.logoutRequestUrl = logoutRequestUrl;
}
String logoutResponseUrl = element.getAttribute(ATT_LOGOUT_RESPONSE_URL);
if (StringUtils.hasText(logoutResponseUrl)) {
this.logoutResponseUrl = logoutResponseUrl;
}
WebConfigUtils.validateHttpRedirect(this.logoutUrl, pc, element);
WebConfigUtils.validateHttpRedirect(this.logoutRequestUrl, pc, element);
WebConfigUtils.validateHttpRedirect(this.logoutResponseUrl, pc, element);
if (CollectionUtils.isEmpty(this.logoutHandlers)) {
this.logoutHandlers = createDefaultLogoutHandlers();
}
if (this.logoutSuccessHandler == null) {
this.logoutSuccessHandler = createDefaultLogoutSuccessHandler();
}
BeanMetadataElement relyingPartyRegistrationRepository = Saml2LogoutBeanDefinitionParserUtils
.getRelyingPartyRegistrationRepository(element);
BeanMetadataElement registrations = BeanDefinitionBuilder
.rootBeanDefinition(DefaultRelyingPartyRegistrationResolver.class)
.addConstructorArgValue(relyingPartyRegistrationRepository).getBeanDefinition();
BeanMetadataElement logoutResponseResolver = Saml2LogoutBeanDefinitionParserUtils
.getLogoutResponseResolver(element, registrations);
BeanMetadataElement logoutRequestValidator = Saml2LogoutBeanDefinitionParserUtils
.getLogoutRequestValidator(element);
BeanMetadataElement logoutRequestMatcher = createSaml2LogoutRequestMatcher();
this.logoutRequestFilter = BeanDefinitionBuilder.rootBeanDefinition(Saml2LogoutRequestFilter.class)
.addConstructorArgValue(registrations).addConstructorArgValue(logoutRequestValidator)
.addConstructorArgValue(logoutResponseResolver).addConstructorArgValue(this.logoutHandlers)
.addPropertyValue("logoutRequestMatcher", logoutRequestMatcher).getBeanDefinition();
BeanMetadataElement logoutResponseValidator = Saml2LogoutBeanDefinitionParserUtils
.getLogoutResponseValidator(element);
BeanMetadataElement logoutRequestRepository = Saml2LogoutBeanDefinitionParserUtils
.getLogoutRequestRepository(element);
BeanMetadataElement logoutResponseMatcher = createSaml2LogoutResponseMatcher();
this.logoutResponseFilter = BeanDefinitionBuilder.rootBeanDefinition(Saml2LogoutResponseFilter.class)
.addConstructorArgValue(registrations).addConstructorArgValue(logoutResponseValidator)
.addConstructorArgValue(this.logoutSuccessHandler)
.addPropertyValue("logoutRequestMatcher", logoutResponseMatcher)
.addPropertyValue("logoutRequestRepository", logoutRequestRepository).getBeanDefinition();
BeanMetadataElement logoutRequestResolver = Saml2LogoutBeanDefinitionParserUtils
.getLogoutRequestResolver(element, registrations);
BeanMetadataElement saml2LogoutRequestSuccessHandler = BeanDefinitionBuilder
.rootBeanDefinition(Saml2RelyingPartyInitiatedLogoutSuccessHandler.class)
.addConstructorArgValue(logoutRequestResolver).getBeanDefinition();
this.logoutFilter = BeanDefinitionBuilder.rootBeanDefinition(LogoutFilter.class)
.addConstructorArgValue(saml2LogoutRequestSuccessHandler).addConstructorArgValue(this.logoutHandlers)
.addPropertyValue("logoutRequestMatcher", createLogoutRequestMatcher()).getBeanDefinition();
return null;
}
private static List<BeanMetadataElement> createDefaultLogoutHandlers() {
List<BeanMetadataElement> handlers = new ManagedList<>();
handlers.add(BeanDefinitionBuilder.rootBeanDefinition(SecurityContextLogoutHandler.class).getBeanDefinition());
handlers.add(BeanDefinitionBuilder.rootBeanDefinition(LogoutSuccessEventPublishingLogoutHandler.class)
.getBeanDefinition());
return handlers;
}
private static BeanMetadataElement createDefaultLogoutSuccessHandler() {
return BeanDefinitionBuilder.rootBeanDefinition(SimpleUrlLogoutSuccessHandler.class)
.addPropertyValue("defaultTargetUrl", "/login?logout").getBeanDefinition();
}
private BeanMetadataElement createLogoutRequestMatcher() {
BeanMetadataElement logoutMatcher = BeanDefinitionBuilder.rootBeanDefinition(AntPathRequestMatcher.class)
.addConstructorArgValue(this.logoutUrl).addConstructorArgValue("POST").getBeanDefinition();
BeanMetadataElement saml2Matcher = BeanDefinitionBuilder.rootBeanDefinition(Saml2RequestMatcher.class)
.getBeanDefinition();
return BeanDefinitionBuilder.rootBeanDefinition(AndRequestMatcher.class)
.addConstructorArgValue(toManagedList(logoutMatcher, saml2Matcher)).getBeanDefinition();
}
private BeanMetadataElement createSaml2LogoutRequestMatcher() {
BeanMetadataElement logoutRequestMatcher = BeanDefinitionBuilder.rootBeanDefinition(AntPathRequestMatcher.class)
.addConstructorArgValue(this.logoutRequestUrl).getBeanDefinition();
BeanMetadataElement saml2RequestMatcher = BeanDefinitionBuilder
.rootBeanDefinition(ParameterRequestMatcher.class).addConstructorArgValue("SAMLRequest")
.getBeanDefinition();
return BeanDefinitionBuilder.rootBeanDefinition(AndRequestMatcher.class)
.addConstructorArgValue(toManagedList(logoutRequestMatcher, saml2RequestMatcher)).getBeanDefinition();
}
private BeanMetadataElement createSaml2LogoutResponseMatcher() {
BeanMetadataElement logoutResponseMatcher = BeanDefinitionBuilder
.rootBeanDefinition(AntPathRequestMatcher.class).addConstructorArgValue(this.logoutResponseUrl)
.getBeanDefinition();
BeanMetadataElement saml2ResponseMatcher = BeanDefinitionBuilder
.rootBeanDefinition(ParameterRequestMatcher.class).addConstructorArgValue("SAMLResponse")
.getBeanDefinition();
return BeanDefinitionBuilder.rootBeanDefinition(AndRequestMatcher.class)
.addConstructorArgValue(toManagedList(logoutResponseMatcher, saml2ResponseMatcher)).getBeanDefinition();
}
private static List<BeanMetadataElement> toManagedList(BeanMetadataElement... elements) {
List<BeanMetadataElement> managedList = new ManagedList<>();
managedList.addAll(Arrays.asList(elements));
return managedList;
}
BeanDefinition getLogoutRequestFilter() {
return this.logoutRequestFilter;
}
BeanDefinition getLogoutResponseFilter() {
return this.logoutResponseFilter;
}
BeanDefinition getLogoutFilter() {
return this.logoutFilter;
}
private static class ParameterRequestMatcher implements RequestMatcher {
Predicate<String> test = Objects::nonNull;
String name;
ParameterRequestMatcher(String name) {
this.name = name;
}
@Override
public boolean matches(HttpServletRequest request) {
return this.test.test(request.getParameter(this.name));
}
}
private static class Saml2RequestMatcher implements RequestMatcher {
@Override
public boolean matches(HttpServletRequest request) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
return false;
}
return authentication.getPrincipal() instanceof Saml2AuthenticatedPrincipal;
}
}
}

View File

@ -0,0 +1,104 @@
/*
* 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.config.http;
import org.w3c.dom.Element;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.security.saml2.provider.service.authentication.logout.OpenSamlLogoutRequestValidator;
import org.springframework.security.saml2.provider.service.authentication.logout.OpenSamlLogoutResponseValidator;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import org.springframework.security.saml2.provider.service.web.authentication.logout.HttpSessionLogoutRequestRepository;
import org.springframework.util.StringUtils;
/**
* @author Marcus da Coregio
* @since 5.7
*/
final class Saml2LogoutBeanDefinitionParserUtils {
private static final String ATT_RELYING_PARTY_REGISTRATION_REPOSITORY_REF = "relying-party-registration-repository-ref";
private static final String ATT_LOGOUT_REQUEST_VALIDATOR_REF = "logout-request-validator-ref";
private static final String ATT_LOGOUT_REQUEST_REPOSITORY_REF = "logout-request-repository-ref";
private static final String ATT_LOGOUT_REQUEST_RESOLVER_REF = "logout-request-resolver-ref";
private static final String ATT_LOGOUT_RESPONSE_RESOLVER_REF = "logout-response-resolver-ref";
private static final String ATT_LOGOUT_RESPONSE_VALIDATOR_REF = "logout-response-validator-ref";
private Saml2LogoutBeanDefinitionParserUtils() {
}
static BeanMetadataElement getRelyingPartyRegistrationRepository(Element element) {
String relyingPartyRegistrationRepositoryRef = element
.getAttribute(ATT_RELYING_PARTY_REGISTRATION_REPOSITORY_REF);
if (StringUtils.hasText(relyingPartyRegistrationRepositoryRef)) {
return new RuntimeBeanReference(relyingPartyRegistrationRepositoryRef);
}
return new RuntimeBeanReference(RelyingPartyRegistrationRepository.class);
}
static BeanMetadataElement getLogoutResponseResolver(Element element, BeanMetadataElement registrations) {
String logoutResponseResolver = element.getAttribute(ATT_LOGOUT_RESPONSE_RESOLVER_REF);
if (StringUtils.hasText(logoutResponseResolver)) {
return new RuntimeBeanReference(logoutResponseResolver);
}
return BeanDefinitionBuilder.rootBeanDefinition(
"org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml4LogoutResponseResolver")
.addConstructorArgValue(registrations).getBeanDefinition();
}
static BeanMetadataElement getLogoutRequestValidator(Element element) {
String logoutRequestValidator = element.getAttribute(ATT_LOGOUT_REQUEST_VALIDATOR_REF);
if (StringUtils.hasText(logoutRequestValidator)) {
return new RuntimeBeanReference(logoutRequestValidator);
}
return BeanDefinitionBuilder.rootBeanDefinition(OpenSamlLogoutRequestValidator.class).getBeanDefinition();
}
static BeanMetadataElement getLogoutResponseValidator(Element element) {
String logoutResponseValidator = element.getAttribute(ATT_LOGOUT_RESPONSE_VALIDATOR_REF);
if (StringUtils.hasText(logoutResponseValidator)) {
return new RuntimeBeanReference(logoutResponseValidator);
}
return BeanDefinitionBuilder.rootBeanDefinition(OpenSamlLogoutResponseValidator.class).getBeanDefinition();
}
static BeanMetadataElement getLogoutRequestRepository(Element element) {
String logoutRequestRepository = element.getAttribute(ATT_LOGOUT_REQUEST_REPOSITORY_REF);
if (StringUtils.hasText(logoutRequestRepository)) {
return new RuntimeBeanReference(logoutRequestRepository);
}
return BeanDefinitionBuilder.rootBeanDefinition(HttpSessionLogoutRequestRepository.class).getBeanDefinition();
}
static BeanMetadataElement getLogoutRequestResolver(Element element, BeanMetadataElement registrations) {
String logoutRequestResolver = element.getAttribute(ATT_LOGOUT_REQUEST_RESOLVER_REF);
if (StringUtils.hasText(logoutRequestResolver)) {
return new RuntimeBeanReference(logoutRequestResolver);
}
return BeanDefinitionBuilder.rootBeanDefinition(
"org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml4LogoutRequestResolver")
.addConstructorArgValue(registrations).getBeanDefinition();
}
}

View File

@ -41,8 +41,14 @@ enum SecurityFilters {
CORS_FILTER,
SAML2_LOGOUT_REQUEST_FILTER,
SAML2_LOGOUT_RESPONSE_FILTER,
CSRF_FILTER,
SAML2_LOGOUT_FILTER,
LOGOUT_FILTER,
OAUTH2_AUTHORIZATION_REQUEST_FILTER,

View File

@ -88,6 +88,12 @@ public final class RelyingPartyRegistrationsBeanDefinitionParser implements Bean
private static final String ATT_SIGNING_ALGORITHMS = "signing-algorithms";
private static final String ATT_SINGLE_LOGOUT_SERVICE_LOCATION = "single-logout-service-location";
private static final String ATT_SINGLE_LOGOUT_SERVICE_RESPONSE_LOCATION = "single-logout-service-response-location";
private static final String ATT_SINGLE_LOGOUT_SERVICE_BINDING = "single-logout-service-binding";
private static final ResourceLoader resourceLoader = new DefaultResourceLoader();
@Override
@ -120,12 +126,19 @@ public final class RelyingPartyRegistrationsBeanDefinitionParser implements Bean
String singleSignOnServiceLocation = assertingPartyElt.getAttribute(ATT_SINGLE_SIGN_ON_SERVICE_LOCATION);
String singleSignOnServiceBinding = assertingPartyElt.getAttribute(ATT_SINGLE_SIGN_ON_SERVICE_BINDING);
String signingAlgorithms = assertingPartyElt.getAttribute(ATT_SIGNING_ALGORITHMS);
String singleLogoutServiceLocation = assertingPartyElt.getAttribute(ATT_SINGLE_LOGOUT_SERVICE_LOCATION);
String singleLogoutServiceResponseLocation = assertingPartyElt
.getAttribute(ATT_SINGLE_LOGOUT_SERVICE_RESPONSE_LOCATION);
String singleLogoutServiceBinding = assertingPartyElt.getAttribute(ATT_SINGLE_LOGOUT_SERVICE_BINDING);
assertingParty.put(ATT_ASSERTING_PARTY_ID, assertingPartyId);
assertingParty.put(ATT_ENTITY_ID, entityId);
assertingParty.put(ATT_WANT_AUTHN_REQUESTS_SIGNED, wantAuthnRequestsSigned);
assertingParty.put(ATT_SINGLE_SIGN_ON_SERVICE_LOCATION, singleSignOnServiceLocation);
assertingParty.put(ATT_SINGLE_SIGN_ON_SERVICE_BINDING, singleSignOnServiceBinding);
assertingParty.put(ATT_SIGNING_ALGORITHMS, signingAlgorithms);
assertingParty.put(ATT_SINGLE_LOGOUT_SERVICE_LOCATION, singleLogoutServiceLocation);
assertingParty.put(ATT_SINGLE_LOGOUT_SERVICE_RESPONSE_LOCATION, singleLogoutServiceResponseLocation);
assertingParty.put(ATT_SINGLE_LOGOUT_SERVICE_BINDING, singleLogoutServiceBinding);
addVerificationCredentials(assertingPartyElt, assertingParty);
addEncryptionCredentials(assertingPartyElt, assertingParty);
providers.put(assertingPartyId, assertingParty);
@ -195,8 +208,16 @@ public final class RelyingPartyRegistrationsBeanDefinitionParser implements Bean
ParserContext parserContext) {
String registrationId = relyingPartyRegistrationElt.getAttribute(ATT_REGISTRATION_ID);
String metadataLocation = relyingPartyRegistrationElt.getAttribute(ATT_METADATA_LOCATION);
String singleLogoutServiceLocation = relyingPartyRegistrationElt
.getAttribute(ATT_SINGLE_LOGOUT_SERVICE_LOCATION);
String singleLogoutServiceResponseLocation = relyingPartyRegistrationElt
.getAttribute(ATT_SINGLE_LOGOUT_SERVICE_RESPONSE_LOCATION);
Saml2MessageBinding singleLogoutServiceBinding = getSingleLogoutServiceBinding(relyingPartyRegistrationElt);
if (StringUtils.hasText(metadataLocation)) {
return RelyingPartyRegistrations.fromMetadataLocation(metadataLocation).registrationId(registrationId);
return RelyingPartyRegistrations.fromMetadataLocation(metadataLocation).registrationId(registrationId)
.singleLogoutServiceLocation(singleLogoutServiceLocation)
.singleLogoutServiceResponseLocation(singleLogoutServiceResponseLocation)
.singleLogoutServiceBinding(singleLogoutServiceBinding);
}
String entityId = relyingPartyRegistrationElt.getAttribute(ATT_ENTITY_ID);
String assertionConsumerServiceLocation = relyingPartyRegistrationElt
@ -206,6 +227,9 @@ public final class RelyingPartyRegistrationsBeanDefinitionParser implements Bean
return RelyingPartyRegistration.withRegistrationId(registrationId).entityId(entityId)
.assertionConsumerServiceLocation(assertionConsumerServiceLocation)
.assertionConsumerServiceBinding(assertionConsumerServiceBinding)
.singleLogoutServiceLocation(singleLogoutServiceLocation)
.singleLogoutServiceResponseLocation(singleLogoutServiceResponseLocation)
.singleLogoutServiceBinding(singleLogoutServiceBinding)
.assertingPartyDetails((builder) -> buildAssertingParty(relyingPartyRegistrationElt, assertingParties,
builder, parserContext));
}
@ -225,9 +249,18 @@ public final class RelyingPartyRegistrationsBeanDefinitionParser implements Bean
String singleSignOnServiceBinding = getAsString(assertingParty, ATT_SINGLE_SIGN_ON_SERVICE_BINDING);
Saml2MessageBinding saml2MessageBinding = StringUtils.hasText(singleSignOnServiceBinding)
? Saml2MessageBinding.valueOf(singleSignOnServiceBinding) : Saml2MessageBinding.REDIRECT;
String singleLogoutServiceLocation = getAsString(assertingParty, ATT_SINGLE_LOGOUT_SERVICE_LOCATION);
String singleLogoutServiceResponseLocation = getAsString(assertingParty,
ATT_SINGLE_LOGOUT_SERVICE_RESPONSE_LOCATION);
String singleLogoutServiceBinding = getAsString(assertingParty, ATT_SINGLE_LOGOUT_SERVICE_BINDING);
Saml2MessageBinding saml2LogoutMessageBinding = StringUtils.hasText(singleLogoutServiceBinding)
? Saml2MessageBinding.valueOf(singleLogoutServiceBinding) : Saml2MessageBinding.REDIRECT;
builder.entityId(entityId).wantAuthnRequestsSigned(Boolean.parseBoolean(wantAuthnRequestsSigned))
.singleSignOnServiceLocation(singleSignOnServiceLocation)
.singleSignOnServiceBinding(saml2MessageBinding);
.singleSignOnServiceBinding(saml2MessageBinding)
.singleLogoutServiceLocation(singleLogoutServiceLocation)
.singleLogoutServiceResponseLocation(singleLogoutServiceResponseLocation)
.singleLogoutServiceBinding(saml2LogoutMessageBinding);
addSigningAlgorithms(assertingParty, builder);
addVerificationCredentials(assertingParty, builder);
addEncryptionCredentials(assertingParty, builder);
@ -279,6 +312,14 @@ public final class RelyingPartyRegistrationsBeanDefinitionParser implements Bean
return Saml2MessageBinding.REDIRECT;
}
private static Saml2MessageBinding getSingleLogoutServiceBinding(Element relyingPartyRegistrationElt) {
String singleLogoutServiceBinding = relyingPartyRegistrationElt.getAttribute(ATT_SINGLE_LOGOUT_SERVICE_BINDING);
if (StringUtils.hasText(singleLogoutServiceBinding)) {
return Saml2MessageBinding.valueOf(singleLogoutServiceBinding);
}
return Saml2MessageBinding.POST;
}
private static Saml2X509Credential getSaml2VerificationCredential(String certificateLocation) {
return getSaml2Credential(certificateLocation, Saml2X509Credential.Saml2X509CredentialType.VERIFICATION);
}

View File

@ -312,7 +312,7 @@ http-firewall =
http =
## Container element for HTTP security configuration. Multiple elements can now be defined, each with a specific pattern to which the enclosed security configuration applies. A pattern can also be configured to bypass Spring Security's filters completely by setting the "security" attribute to "none".
element http {http.attlist, (intercept-url* & access-denied-handler? & form-login? & oauth2-login? & oauth2-client? & oauth2-resource-server? & openid-login? & saml2-login? & x509? & jee? & http-basic? & logout? & password-management? & session-management & remember-me? & anonymous? & port-mappings & custom-filter* & request-cache? & expression-handler? & headers? & csrf? & cors?) }
element http {http.attlist, (intercept-url* & access-denied-handler? & form-login? & oauth2-login? & oauth2-client? & oauth2-resource-server? & openid-login? & saml2-login? & saml2-logout? & x509? & jee? & http-basic? & logout? & password-management? & session-management & remember-me? & anonymous? & port-mappings & custom-filter* & request-cache? & expression-handler? & headers? & csrf? & cors?) }
http.attlist &=
## The request URL pattern which will be mapped to the filter chain created by this <http> element. If omitted, the filter chain will match all requests.
attribute pattern {xsd:token}?
@ -690,6 +690,37 @@ saml2-login.attlist &=
## Reference to the AuthenticationManager
attribute authentication-manager-ref {xsd:token}?
saml2-logout =
## Configures SAML 2.0 Single Logout support
element saml2-logout {saml2-logout.attlist}
saml2-logout.attlist &=
## The URL by which the relying or asserting party can trigger logout
attribute logout-url {xsd:token}?
saml2-logout.attlist &=
## The URL by which the asserting party can send a SAML 2.0 Logout Request
attribute logout-request-url {xsd:token}?
saml2-logout.attlist &=
## The URL by which the asserting party can send a SAML 2.0 Logout Response
attribute logout-response-url {xsd:token}?
saml2-logout.attlist &=
## Reference to the RelyingPartyRegistrationRepository
attribute relying-party-registration-repository-ref {xsd:token}?
saml2-logout.attlist &=
## Reference to the Saml2LogoutRequestValidator
attribute logout-request-validator-ref {xsd:token}?
saml2-logout.attlist &=
## Reference to the Saml2LogoutRequestResolver
attribute logout-request-resolver-ref {xsd:token}?
saml2-logout.attlist &=
## Reference to the Saml2LogoutRequestRepository
attribute logout-request-repository-ref {xsd:token}?
saml2-logout.attlist &=
## Reference to the Saml2LogoutResponseValidator
attribute logout-response-validator-ref {xsd:token}?
saml2-logout.attlist &=
## Reference to the Saml2LogoutResponseResolver
attribute logout-response-resolver-ref {xsd:token}?
relying-party-registrations =
## Container element for relying party(ies) registered with a SAML 2.0 identity provider
element relying-party-registrations {relying-party-registration+, asserting-party*}
@ -715,6 +746,15 @@ relying-party-registration.attlist &=
relying-party-registration.attlist &=
## A reference to the associated asserting party.
attribute asserting-party-id {xsd:token}?
relying-party-registration.attlist &=
## The relying party <a href="https://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf#page=7">SingleLogoutService Location</a>
attribute single-logout-service-location {xsd:token}?
relying-party-registration.attlist &=
## The relying party <a href="https://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf#page=7">SingleLogoutService Response Location</a>
attribute single-logout-service-response-location {xsd:token}?
relying-party-registration.attlist &=
## The relying party <a href="https://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf#page=7">SingleLogoutService Binding</a>
attribute single-logout-service-binding {xsd:token}?
signing-credential =
## The relying party's signing credential
@ -757,6 +797,15 @@ asserting-party.attlist &=
asserting-party.attlist &=
## A comma separated list of org.opensaml.saml.ext.saml2alg.SigningMethod Algorithms for this asserting party, in preference order.
attribute signing-algorithms {xsd:token}?
asserting-party.attlist &=
## The asserting party <a href="https://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf#page=7">SingleLogoutService Location</a>
attribute single-logout-service-location {xsd:token}?
asserting-party.attlist &=
## The asserting party <a href="https://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf#page=7">SingleLogoutService Response Location</a>
attribute single-logout-service-response-location {xsd:token}?
asserting-party.attlist &=
## The asserting party <a href="https://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf#page=7">SingleLogoutService Binding</a>
attribute single-logout-service-binding {xsd:token}?
verification-credential =
## The relying party's verification credential
@ -1266,4 +1315,4 @@ position =
## The explicit position at which the custom-filter should be placed in the chain. Use if you are replacing a standard filter.
attribute position {named-security-filter}
named-security-filter = "FIRST" | "CHANNEL_FILTER" | "SECURITY_CONTEXT_FILTER" | "CONCURRENT_SESSION_FILTER" | "WEB_ASYNC_MANAGER_FILTER" | "HEADERS_FILTER" | "CORS_FILTER" | "CSRF_FILTER" | "LOGOUT_FILTER" | "OAUTH2_AUTHORIZATION_REQUEST_FILTER" | "SAML2_AUTHENTICATION_REQUEST_FILTER" | "X509_FILTER" | "PRE_AUTH_FILTER" | "CAS_FILTER" | "OAUTH2_LOGIN_FILTER" | "SAML2_AUTHENTICATION_FILTER" | "FORM_LOGIN_FILTER" | "OPENID_FILTER" | "LOGIN_PAGE_FILTER" |"LOGOUT_PAGE_FILTER" | "DIGEST_AUTH_FILTER" | "BEARER_TOKEN_AUTH_FILTER" | "BASIC_AUTH_FILTER" | "REQUEST_CACHE_FILTER" | "SERVLET_API_SUPPORT_FILTER" | "JAAS_API_SUPPORT_FILTER" | "REMEMBER_ME_FILTER" | "ANONYMOUS_FILTER" | "OAUTH2_AUTHORIZATION_CODE_GRANT_FILTER" | "WELL_KNOWN_CHANGE_PASSWORD_REDIRECT_FILTER" | "SESSION_MANAGEMENT_FILTER" | "EXCEPTION_TRANSLATION_FILTER" | "FILTER_SECURITY_INTERCEPTOR" | "SWITCH_USER_FILTER" | "LAST"
named-security-filter = "FIRST" | "CHANNEL_FILTER" | "SECURITY_CONTEXT_FILTER" | "CONCURRENT_SESSION_FILTER" | "WEB_ASYNC_MANAGER_FILTER" | "HEADERS_FILTER" | "CORS_FILTER" | "SAML2_LOGOUT_REQUEST_FILTER" | "SAML2_LOGOUT_RESPONSE_FILTER" | "CSRF_FILTER" | "SAML2_LOGOUT_FILTER" | "LOGOUT_FILTER" | "OAUTH2_AUTHORIZATION_REQUEST_FILTER" | "SAML2_AUTHENTICATION_REQUEST_FILTER" | "X509_FILTER" | "PRE_AUTH_FILTER" | "CAS_FILTER" | "OAUTH2_LOGIN_FILTER" | "SAML2_AUTHENTICATION_FILTER" | "FORM_LOGIN_FILTER" | "OPENID_FILTER" | "LOGIN_PAGE_FILTER" |"LOGOUT_PAGE_FILTER" | "DIGEST_AUTH_FILTER" | "BEARER_TOKEN_AUTH_FILTER" | "BASIC_AUTH_FILTER" | "REQUEST_CACHE_FILTER" | "SERVLET_API_SUPPORT_FILTER" | "JAAS_API_SUPPORT_FILTER" | "REMEMBER_ME_FILTER" | "ANONYMOUS_FILTER" | "OAUTH2_AUTHORIZATION_CODE_GRANT_FILTER" | "WELL_KNOWN_CHANGE_PASSWORD_REDIRECT_FILTER" | "SESSION_MANAGEMENT_FILTER" | "EXCEPTION_TRANSLATION_FILTER" | "FILTER_SECURITY_INTERCEPTOR" | "SWITCH_USER_FILTER" | "LAST"

View File

@ -1046,6 +1046,15 @@
<xs:attributeGroup ref="security:saml2-login.attlist"/>
</xs:complexType>
</xs:element>
<xs:element name="saml2-logout">
<xs:annotation>
<xs:documentation>Configures SAML 2.0 Single Logout support
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attributeGroup ref="security:saml2-logout.attlist"/>
</xs:complexType>
</xs:element>
<xs:element name="x509">
<xs:annotation>
<xs:documentation>Adds support for X.509 client authentication.
@ -2075,6 +2084,63 @@
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:attributeGroup name="saml2-logout.attlist">
<xs:attribute name="logout-url" type="xs:token">
<xs:annotation>
<xs:documentation>The URL by which the relying or asserting party can trigger logout
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="logout-request-url" type="xs:token">
<xs:annotation>
<xs:documentation>The URL by which the asserting party can send a SAML 2.0 Logout Request
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="logout-response-url" type="xs:token">
<xs:annotation>
<xs:documentation>The URL by which the asserting party can send a SAML 2.0 Logout Response
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="relying-party-registration-repository-ref" type="xs:token">
<xs:annotation>
<xs:documentation>Reference to the RelyingPartyRegistrationRepository
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="logout-request-validator-ref" type="xs:token">
<xs:annotation>
<xs:documentation>Reference to the Saml2LogoutRequestValidator
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="logout-request-resolver-ref" type="xs:token">
<xs:annotation>
<xs:documentation>Reference to the Saml2LogoutRequestResolver
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="logout-request-repository-ref" type="xs:token">
<xs:annotation>
<xs:documentation>Reference to the Saml2LogoutRequestRepository
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="logout-response-validator-ref" type="xs:token">
<xs:annotation>
<xs:documentation>Reference to the Saml2LogoutResponseValidator
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="logout-response-resolver-ref" type="xs:token">
<xs:annotation>
<xs:documentation>Reference to the Saml2LogoutResponseResolver
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:element name="relying-party-registrations">
<xs:annotation>
<xs:documentation>Container element for relying party(ies) registered with a SAML 2.0 identity provider
@ -2137,6 +2203,30 @@
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="single-logout-service-location" type="xs:token">
<xs:annotation>
<xs:documentation>The relying party &lt;a
href="https://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf#page=7"&gt;SingleLogoutService
Location&lt;/a&gt;
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="single-logout-service-response-location" type="xs:token">
<xs:annotation>
<xs:documentation>The relying party &lt;a
href="https://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf#page=7"&gt;SingleLogoutService
Response Location&lt;/a&gt;
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="single-logout-service-binding" type="xs:token">
<xs:annotation>
<xs:documentation>The relying party &lt;a
href="https://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf#page=7"&gt;SingleLogoutService
Binding&lt;/a&gt;
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:element name="signing-credential">
<xs:annotation>
@ -2240,6 +2330,30 @@
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="single-logout-service-location" type="xs:token">
<xs:annotation>
<xs:documentation>The asserting party &lt;a
href="https://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf#page=7"&gt;SingleLogoutService
Location&lt;/a&gt;
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="single-logout-service-response-location" type="xs:token">
<xs:annotation>
<xs:documentation>The asserting party &lt;a
href="https://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf#page=7"&gt;SingleLogoutService
Response Location&lt;/a&gt;
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="single-logout-service-binding" type="xs:token">
<xs:annotation>
<xs:documentation>The asserting party &lt;a
href="https://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf#page=7"&gt;SingleLogoutService
Binding&lt;/a&gt;
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:element name="verification-credential">
<xs:annotation>
@ -3605,7 +3719,10 @@
<xs:enumeration value="WEB_ASYNC_MANAGER_FILTER"/>
<xs:enumeration value="HEADERS_FILTER"/>
<xs:enumeration value="CORS_FILTER"/>
<xs:enumeration value="SAML2_LOGOUT_REQUEST_FILTER"/>
<xs:enumeration value="SAML2_LOGOUT_RESPONSE_FILTER"/>
<xs:enumeration value="CSRF_FILTER"/>
<xs:enumeration value="SAML2_LOGOUT_FILTER"/>
<xs:enumeration value="LOGOUT_FILTER"/>
<xs:enumeration value="OAUTH2_AUTHORIZATION_REQUEST_FILTER"/>
<xs:enumeration value="SAML2_AUTHENTICATION_REQUEST_FILTER"/>

View File

@ -9,7 +9,7 @@
<xsl:output method="xml" indent="yes"/>
<xsl:variable name="elts-to-inline">
<xsl:text>,access-denied-handler,anonymous,session-management,concurrency-control,after-invocation-provider,authentication-provider,ldap-authentication-provider,user,port-mapping,openid-login,saml2-login,expression-handler,form-login,http-basic,intercept-url,logout,password-encoder,port-mappings,port-mapper,password-compare,protect,protect-pointcut,pre-post-annotation-handling,pre-invocation-advice,post-invocation-advice,invocation-attribute-factory,remember-me,salt-source,x509,add-headers,</xsl:text>
<xsl:text>,access-denied-handler,anonymous,session-management,concurrency-control,after-invocation-provider,authentication-provider,ldap-authentication-provider,user,port-mapping,openid-login,saml2-login,saml2-logout,expression-handler,form-login,http-basic,intercept-url,logout,password-encoder,port-mappings,port-mapper,password-compare,protect,protect-pointcut,pre-post-annotation-handling,pre-invocation-advice,post-invocation-advice,invocation-attribute-factory,remember-me,salt-source,x509,add-headers,</xsl:text>
</xsl:variable>
<xsl:template match="xs:element">

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* 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.

View File

@ -0,0 +1,327 @@
/*
* 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.config.http;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.Collections;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.opensaml.saml.saml2.core.LogoutRequest;
import org.opensaml.xmlsec.signature.support.SignatureConstants;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.saml2.core.Saml2Utils;
import org.springframework.security.saml2.provider.service.authentication.DefaultSaml2AuthenticatedPrincipal;
import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
import org.springframework.security.saml2.provider.service.authentication.TestOpenSamlObjects;
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.Saml2LogoutResponse;
import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutResponseValidator;
import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutValidatorResult;
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.web.authentication.logout.HttpSessionLogoutRequestRepository;
import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestRepository;
import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestResolver;
import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutResponseResolver;
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.verify;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests for {@link Saml2LogoutBeanDefinitionParser}
*
* @author Marcus da Coregio
*/
@ExtendWith({ SpringExtension.class, SpringTestContextExtension.class })
@SecurityTestExecutionListeners
public class Saml2LogoutBeanDefinitionParserTests {
public final SpringTestContext spring = new SpringTestContext(this);
private final Saml2LogoutRequestRepository logoutRequestRepository = new HttpSessionLogoutRequestRepository();
private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/Saml2LogoutBeanDefinitionParserTests";
String apLogoutRequest = "nZFBa4MwGIb/iuQeE2NTXFDLQAaC26Hrdtgt1dQFNMnyxdH9+zlboeyww275SN7nzcOX787jEH0qD9qaAiUxRZEyre206Qv0cnjAGdqVOchxYE40trdT2KuPSUGI5qQBcbkq0OSNsBI0CCNHBSK04vn+sREspsJ5G2xrBxRVc1AbGZa29xAcCEK8i9VZjm5QsfU9GZYWsoCJv5ShqK4K1Ow5p5LyU4aP6XaLN3cpw9mGctydjrxNaZt1XM5vASZVGwjShAIxyhJMU8z4gSWCM8GSmDH+hqLX1Xv+JLpaiiXsb+3+lpMAyv8IoVI6rEzQ4QvrLie3uBX+NMfr6l/waT6t0AumvI6/FlN+Aw==";
String apLogoutRequestSigAlg = SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256;
String apLogoutRequestRelayState = "33591874-b123-4f2c-ab0d-2d0d84aa8b56";
String apLogoutRequestSignature = "oKqdzrmn2YAqXcwkow2lzRXr5PNHm0s/gWsRnaZYhC+Oq5ekK5uIKQYvtmNR94HJjDe1VRs+vVQCYivgdoTzBV2ZlffTXZmYsCsY9q4jbCWR6R5CbhU73/MkKQsPcyVvMhNYxnDYapIlxDsfoZNTboDEz3GM+HRoGRfl9emCXY0lPRYwqC4kpu7oMDBkafR0A09jPIxFuNpqlLPwUxL9m+DGkvDK3mFDN1xJcgZaK73HcuJe7Qh4huOrKNFetwc5EvqfiwgiWF6sfq9A+rZBfCIYo10NNLY7fNQAR2IqwcKtawHgTGWbeshRyFrwVYMR64EnClfxUHsHKf5kiZ2dlw==";
String apLogoutResponse = "fZHRa4MwEMb/Fcl7jEadGqplrAwK3Uvb9WFvZ4ydoInk4uj++1nXbmWMvhwcd9/3Jb9bLE99530oi63RBQn9gHhKS1O3+liQ1/0zzciyXCD0HR/ExhzN6LYKB6NReZNUo/ieFWS0WhjAFoWGXqFwUuweXzaC+4EYrHFGmo54K4Wu1eDmuHfnBhSM2cFXJ+iHTvnGHlk3x7DZmNlLGvHWq4Jstk0GUSjjiIZJI2lcpQnNeRLTAOo4fwCeQg3Trr6+cm/OqmnWVHECVGWQ0jgCSatsKvXUxhFvZF7xSYU4qrVGB9oVhAc8pEFEebLnkeBc8NyPePpGvMOV1/Q3cqEjZrG9hXKfCSAqe+ZAShio0q51n7StF+zW7gf9zoEb8U/7ZGrlHaAb1f0onLfFbpRSIRJWXkJ+bdm/Fy6/AA==";
String apLogoutResponseSigAlg = SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256;
String apLogoutResponseRelayState = "8f63887a-ec7e-4149-b6a0-dd730017f315";
String apLogoutResponseSignature = "h2fDqSIBfmnkRHKDMY4IxkCXcI0w98ydNsnPmv1b7GTZCWLbJ+oxaP2yZNPw7wOWXTv86cTPwKLjx5halKy5C+hhWnT0haKhuMcUvHlsgAMBbJKLV+1afzL4O77cvAQJmMNRK7ugXGNV5PTEnd1U4voy134OgdD5XycYiFVRZOwP5H84eJ9xxlvqQwqDvZTcgiF/ZS4ioZgzgnIFcbagZQ12LWNh26OMaUpIW04kCeO6t2dUsxOL6nZWvNrX/Zx1sORIpu4doDUa1RYC8YnjZeQEzDqUVC/dBO/mbVJ/hbF9tD0jBUx7YIgoXpqsWK4TcCsvmlmhrJXvGxDyoAWu2Q==";
String rpLogoutRequest = "nZFBa4MwGIb/iuQeY6NlGtQykIHgdui6HXaLmrqAJlm+OLp/v0wrlB122CXkI3mfNw/JD5dpDD6FBalVgXZhhAKhOt1LNRTo5fSAU3Qoc+DTSA1r9KBndxQfswAX+KQCth4VaLaKaQ4SmOKTAOY69nz/2DAaRsxY7XSnRxRUPigVd0vbu3MGGCHchOLCJzOKUNuBjEsLWcDErmUoqKsCNcc+yc5tsudYpPwOJzHvcJv6pfdjEtNzl7XU3wWYRa3AceUKRCO6w1GM6f5EY0Ypo1lIk+gNBa+bt38kulqyJWxv7f6W4wDC/gih0hoslJPuC8s+J7e4Df7k43X1L/jsdxt0xZTX8dfHlN8=";
String rpLogoutRequestId = "LRd49fb45a-e8a7-43ac-b8ac-d8a7432fc9b2";
String rpLogoutRequestRelayState = "8f63887a-ec7e-4149-b6a0-dd730017f315";
String rpLogoutRequestSignature = "h2fDqSIBfmnkRHKDMY4IxkCXcI0w98ydNsnPmv1b7GTZCWLbJ+oxaP2yZNPw7wOWXTv86cTPwKLjx5halKy5C+hhWnT0haKhuMcUvHlsgAMBbJKLV+1afzL4O77cvAQJmMNRK7ugXGNV5PTEnd1U4voy134OgdD5XycYiFVRZOwP5H84eJ9xxlvqQwqDvZTcgiF/ZS4ioZgzgnIFcbagZQ12LWNh26OMaUpIW04kCeO6t2dUsxOL6nZWvNrX/Zx1sORIpu4doDUa1RYC8YnjZeQEzDqUVC/dBO/mbVJ/hbF9tD0jBUx7YIgoXpqsWK4TcCsvmlmhrJXvGxDyoAWu2Q==";
@Autowired(required = false)
private RelyingPartyRegistrationRepository repository;
@Autowired
private MockMvc mvc;
private Saml2Authentication saml2User;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
@BeforeEach
public void setup() {
DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal("user",
Collections.emptyMap());
principal.setRelyingPartyRegistrationId("registration-id");
this.saml2User = new Saml2Authentication(principal, "response",
AuthorityUtils.createAuthorityList("ROLE_USER"));
this.request = new MockHttpServletRequest("POST", "");
this.request.setServletPath("/login/saml2/sso/test-rp");
this.response = new MockHttpServletResponse();
}
@Test
public void logoutWhenLogoutSuccessHandlerAndNotSaml2LoginThenDefaultLogoutSuccessHandler() throws Exception {
this.spring.configLocations(this.xml("LogoutSuccessHandler")).autowire();
TestingAuthenticationToken user = new TestingAuthenticationToken("user", "password");
MvcResult result = this.mvc.perform(post("/logout").with(authentication(user)).with(csrf()))
.andExpect(status().isFound()).andReturn();
String location = result.getResponse().getHeader("Location");
assertThat(location).isEqualTo("/logoutSuccessEndpoint");
}
@Test
public void saml2LogoutWhenDefaultsThenLogsOutAndSendsLogoutRequest() throws Exception {
this.spring.configLocations(this.xml("Default")).autowire();
MvcResult result = this.mvc.perform(post("/logout").with(authentication(this.saml2User)).with(csrf()))
.andExpect(status().isFound()).andReturn();
String location = result.getResponse().getHeader("Location");
assertThat(location).startsWith("https://ap.example.org/logout/saml2/request");
}
@Test
public void saml2LogoutWhenUnauthenticatedThenEntryPoint() throws Exception {
this.spring.configLocations(this.xml("Default")).autowire();
this.mvc.perform(post("/logout").with(csrf())).andExpect(status().isFound())
.andExpect(redirectedUrl("/login?logout"));
}
@Test
public void saml2LogoutWhenMissingCsrfThen403() throws Exception {
this.spring.configLocations(this.xml("Default")).autowire();
this.mvc.perform(post("/logout").with(authentication(this.saml2User))).andExpect(status().isForbidden());
}
@Test
public void saml2LogoutWhenGetThenDefaultLogoutPage() throws Exception {
this.spring.configLocations(this.xml("Default")).autowire();
MvcResult result = this.mvc.perform(get("/logout").with(authentication(this.saml2User)))
.andExpect(status().isOk()).andReturn();
assertThat(result.getResponse().getContentAsString()).contains("Are you sure you want to log out?");
}
@Test
public void saml2LogoutWhenPutOrDeleteThen404() throws Exception {
this.spring.configLocations(this.xml("Default")).autowire();
this.mvc.perform(put("/logout").with(authentication(this.saml2User)).with(csrf()))
.andExpect(status().isNotFound());
this.mvc.perform(delete("/logout").with(authentication(this.saml2User)).with(csrf()))
.andExpect(status().isNotFound());
}
@Test
public void saml2LogoutWhenNoRegistrationThen401() throws Exception {
this.spring.configLocations(this.xml("Default")).autowire();
DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal("user",
Collections.emptyMap());
principal.setRelyingPartyRegistrationId("wrong");
Saml2Authentication authentication = new Saml2Authentication(principal, "response",
AuthorityUtils.createAuthorityList("ROLE_USER"));
this.mvc.perform(post("/logout").with(authentication(authentication)).with(csrf()))
.andExpect(status().isUnauthorized());
}
@Test
public void saml2LogoutWhenCsrfDisabledAndNoAuthenticationThenFinalRedirect() throws Exception {
this.spring.configLocations(this.xml("CsrfDisabled-MockLogoutSuccessHandler")).autowire();
this.mvc.perform(post("/logout"));
LogoutSuccessHandler logoutSuccessHandler = this.spring.getContext().getBean(LogoutSuccessHandler.class);
verify(logoutSuccessHandler).onLogoutSuccess(any(), any(), any());
}
@Test
public void saml2LogoutWhenCustomLogoutRequestResolverThenUses() throws Exception {
this.spring.configLocations(this.xml("CustomComponents")).autowire();
RelyingPartyRegistration registration = this.repository.findByRegistrationId("registration-id");
Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration)
.samlRequest(this.rpLogoutRequest).id(this.rpLogoutRequestId).relayState(this.rpLogoutRequestRelayState)
.parameters((params) -> params.put("Signature", this.rpLogoutRequestSignature)).build();
given(getBean(Saml2LogoutRequestResolver.class).resolve(any(), any())).willReturn(logoutRequest);
this.mvc.perform(post("/logout").with(authentication(this.saml2User)).with(csrf()));
verify(getBean(Saml2LogoutRequestResolver.class)).resolve(any(), any());
}
@Test
public void saml2LogoutRequestWhenDefaultsThenLogsOutAndSendsLogoutResponse() throws Exception {
this.spring.configLocations(this.xml("Default")).autowire();
DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal("user",
Collections.emptyMap());
principal.setRelyingPartyRegistrationId("get");
Saml2Authentication user = new Saml2Authentication(principal, "response",
AuthorityUtils.createAuthorityList("ROLE_USER"));
MvcResult result = this.mvc
.perform(get("/logout/saml2/slo").param("SAMLRequest", this.apLogoutRequest)
.param("RelayState", this.apLogoutRequestRelayState).param("SigAlg", this.apLogoutRequestSigAlg)
.param("Signature", this.apLogoutRequestSignature).with(authentication(user)))
.andExpect(status().isFound()).andReturn();
String location = result.getResponse().getHeader("Location");
assertThat(location).startsWith("https://ap.example.org/logout/saml2/response");
}
@Test
public void saml2LogoutRequestWhenNoRegistrationThen400() throws Exception {
this.spring.configLocations(this.xml("Default")).autowire();
DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal("user",
Collections.emptyMap());
principal.setRelyingPartyRegistrationId("wrong");
Saml2Authentication user = new Saml2Authentication(principal, "response",
AuthorityUtils.createAuthorityList("ROLE_USER"));
this.mvc.perform(get("/logout/saml2/slo").param("SAMLRequest", this.apLogoutRequest)
.param("RelayState", this.apLogoutRequestRelayState).param("SigAlg", this.apLogoutRequestSigAlg)
.param("Signature", this.apLogoutRequestSignature).with(authentication(user)))
.andExpect(status().isBadRequest());
}
@Test
public void saml2LogoutRequestWhenInvalidSamlRequestThen401() throws Exception {
this.spring.configLocations(this.xml("Default")).autowire();
this.mvc.perform(get("/logout/saml2/slo").param("SAMLRequest", this.apLogoutRequest)
.param("RelayState", this.apLogoutRequestRelayState).param("SigAlg", this.apLogoutRequestSigAlg)
.with(authentication(this.saml2User))).andExpect(status().isUnauthorized());
}
@Test
public void saml2LogoutRequestWhenCustomLogoutRequestHandlerThenUses() throws Exception {
this.spring.configLocations(this.xml("CustomComponents")).autowire();
RelyingPartyRegistration registration = this.repository.findByRegistrationId("registration-id");
LogoutRequest logoutRequest = TestOpenSamlObjects.assertingPartyLogoutRequest(registration);
logoutRequest.setIssueInstant(Instant.now());
given(getBean(Saml2LogoutRequestValidator.class).validate(any()))
.willReturn(Saml2LogoutValidatorResult.success());
Saml2LogoutResponse logoutResponse = Saml2LogoutResponse.withRelyingPartyRegistration(registration).build();
given(getBean(Saml2LogoutResponseResolver.class).resolve(any(), any())).willReturn(logoutResponse);
this.mvc.perform(
post("/logout/saml2/slo").param("SAMLRequest", "samlRequest").with(authentication(this.saml2User)))
.andReturn();
verify(getBean(Saml2LogoutRequestValidator.class)).validate(any());
verify(getBean(Saml2LogoutResponseResolver.class)).resolve(any(), any());
}
@Test
public void saml2LogoutResponseWhenDefaultsThenRedirects() throws Exception {
this.spring.configLocations(this.xml("Default")).autowire();
RelyingPartyRegistration registration = this.repository.findByRegistrationId("get");
Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration)
.samlRequest(this.rpLogoutRequest).id(this.rpLogoutRequestId).relayState(this.rpLogoutRequestRelayState)
.parameters((params) -> params.put("Signature", this.rpLogoutRequestSignature)).build();
this.logoutRequestRepository.saveLogoutRequest(logoutRequest, this.request, this.response);
this.request.setParameter("RelayState", logoutRequest.getRelayState());
assertThat(this.logoutRequestRepository.loadLogoutRequest(this.request)).isNotNull();
this.mvc.perform(get("/logout/saml2/slo").session(((MockHttpSession) this.request.getSession()))
.param("SAMLResponse", this.apLogoutResponse).param("RelayState", this.apLogoutResponseRelayState)
.param("SigAlg", this.apLogoutResponseSigAlg).param("Signature", this.apLogoutResponseSignature))
.andExpect(status().isFound()).andExpect(redirectedUrl("/login?logout"));
assertThat(this.logoutRequestRepository.loadLogoutRequest(this.request)).isNull();
}
@Test
public void saml2LogoutResponseWhenInvalidSamlResponseThen401() throws Exception {
this.spring.configLocations(this.xml("Default")).autowire();
RelyingPartyRegistration registration = this.repository.findByRegistrationId("registration-id");
Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration)
.samlRequest(this.rpLogoutRequest).id(this.rpLogoutRequestId).relayState(this.rpLogoutRequestRelayState)
.parameters((params) -> params.put("Signature", this.rpLogoutRequestSignature)).build();
this.logoutRequestRepository.saveLogoutRequest(logoutRequest, this.request, this.response);
String deflatedApLogoutResponse = Saml2Utils.samlEncode(
Saml2Utils.samlInflate(Saml2Utils.samlDecode(this.apLogoutResponse)).getBytes(StandardCharsets.UTF_8));
this.mvc.perform(post("/logout/saml2/slo").session((MockHttpSession) this.request.getSession())
.param("SAMLResponse", deflatedApLogoutResponse).param("RelayState", this.rpLogoutRequestRelayState)
.param("SigAlg", this.apLogoutRequestSigAlg).param("Signature", this.apLogoutResponseSignature))
.andExpect(status().reason(containsString("invalid_signature"))).andExpect(status().isUnauthorized());
}
@Test
public void saml2LogoutResponseWhenCustomLogoutResponseHandlerThenUses() throws Exception {
this.spring.configLocations(this.xml("CustomComponents")).autowire();
RelyingPartyRegistration registration = this.repository.findByRegistrationId("get");
Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration)
.samlRequest(this.rpLogoutRequest).id(this.rpLogoutRequestId).relayState(this.rpLogoutRequestRelayState)
.parameters((params) -> params.put("Signature", this.rpLogoutRequestSignature)).build();
given(getBean(Saml2LogoutRequestRepository.class).removeLogoutRequest(any(), any())).willReturn(logoutRequest);
given(getBean(Saml2LogoutResponseValidator.class).validate(any()))
.willReturn(Saml2LogoutValidatorResult.success());
this.mvc.perform(get("/logout/saml2/slo").param("SAMLResponse", "samlResponse")).andReturn();
verify(getBean(Saml2LogoutResponseValidator.class)).validate(any());
}
private <T> T getBean(Class<T> clazz) {
return this.spring.getContext().getBean(clazz);
}
private String xml(String configName) {
return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml";
}
}

View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
-->
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/security"
xsi:schemaLocation="
http://www.springframework.org/schema/security
https://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<http auto-config="true">
<csrf disabled="true"/>
<intercept-url pattern="/**" access="authenticated"/>
<logout success-handler-ref="logoutSuccessHandler"/>
<saml2-login/>
<saml2-logout/>
</http>
<b:bean id="logoutSuccessHandler" class="org.mockito.Mockito" factory-method="mock">
<b:constructor-arg value="org.springframework.security.web.authentication.logout.LogoutSuccessHandler"/>
</b:bean>
<b:import resource="userservice.xml"/>
<b:import resource="../saml2/logout-registrations.xml"/>
</b:beans>

View File

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
-->
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/security"
xsi:schemaLocation="
http://www.springframework.org/schema/security
https://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<http auto-config="true">
<intercept-url pattern="/**" access="authenticated"/>
<saml2-login/>
<saml2-logout logout-request-resolver-ref="logoutRequestResolver" logout-request-repository-ref="logoutRequestRepository"
logout-request-validator-ref="logoutRequestValidator" logout-response-validator-ref="logoutResponseValidator" logout-response-resolver-ref="logoutResponseResolver"/>
</http>
<b:bean id="logoutRequestResolver" class="org.mockito.Mockito" factory-method="mock">
<b:constructor-arg value="org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestResolver"/>
</b:bean>
<b:bean id="logoutRequestRepository" class="org.mockito.Mockito" factory-method="mock">
<b:constructor-arg value="org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestRepository"/>
</b:bean>
<b:bean id="logoutRequestValidator" class="org.mockito.Mockito" factory-method="mock">
<b:constructor-arg value="org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequestValidator"/>
</b:bean>
<b:bean id="logoutResponseValidator" class="org.mockito.Mockito" factory-method="mock">
<b:constructor-arg value="org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutResponseValidator"/>
</b:bean>
<b:bean id="logoutResponseResolver" class="org.mockito.Mockito" factory-method="mock">
<b:constructor-arg value="org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutResponseResolver"/>
</b:bean>
<b:import resource="userservice.xml"/>
<b:import resource="../saml2/logout-registrations.xml"/>
</b:beans>

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
-->
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/security"
xsi:schemaLocation="
http://www.springframework.org/schema/security
https://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<http auto-config="true">
<intercept-url pattern="/**" access="authenticated"/>
<saml2-login/>
<saml2-logout/>
</http>
<b:import resource="userservice.xml"/>
<b:import resource="../saml2/logout-registrations.xml"/>
</b:beans>

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
-->
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/security"
xsi:schemaLocation="
http://www.springframework.org/schema/security
https://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<http auto-config="true">
<intercept-url pattern="/**" access="authenticated"/>
<logout success-handler-ref="logoutSuccessEndpoint"/>
<saml2-login/>
<saml2-logout/>
</http>
<b:bean name="logoutSuccessEndpoint" class="org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler">
<b:property name="defaultTargetUrl" value="/logoutSuccessEndpoint"/>
</b:bean>
<b:import resource="userservice.xml"/>
<b:import resource="../saml2/logout-registrations.xml"/>
</b:beans>

View File

@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2002-2021 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.
-->
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/security"
xsi:schemaLocation="
http://www.springframework.org/schema/security
https://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<relying-party-registrations>
<relying-party-registration registration-id="registration-id"
entity-id="rp-entity-id"
assertion-consumer-service-location="https://rp.example.org/acs"
assertion-consumer-service-binding="REDIRECT"
single-logout-service-location="https://rp.example.org/logout/saml2/request"
single-logout-service-response-location="https://rp.example.org/logout/saml2/response"
asserting-party-id="ap">
<signing-credential certificate-location="classpath:org/springframework/security/config/saml2/rp-certificate.crt"
private-key-location="classpath:org/springframework/security/config/saml2/rp-private.key"/>
</relying-party-registration>
<relying-party-registration registration-id="get"
entity-id="rp-entity-id"
assertion-consumer-service-location="https://rp.example.org/acs"
assertion-consumer-service-binding="REDIRECT"
single-logout-service-location="https://rp.example.org/logout/saml2/request"
single-logout-service-response-location="https://rp.example.org/logout/saml2/response"
single-logout-service-binding="REDIRECT"
asserting-party-id="ap">
<signing-credential certificate-location="classpath:org/springframework/security/config/saml2/rp-certificate.crt"
private-key-location="classpath:org/springframework/security/config/saml2/rp-private.key"/>
</relying-party-registration>
<relying-party-registration registration-id="rp"
entity-id="ap-entity-id"
assertion-consumer-service-location="https://rp.example.org/acs"
assertion-consumer-service-binding="REDIRECT"
single-logout-service-location="https://rp.example.org/logout/saml2/request"
single-logout-service-response-location="https://rp.example.org/logout/saml2/response"
single-logout-service-binding="REDIRECT"
asserting-party-id="rp"/>
<asserting-party asserting-party-id="ap" entity-id="ap-entity-id"
single-sign-on-service-location="https://ap.example.org/sso"
single-logout-service-location="https://ap.example.org/logout/saml2/request"
single-logout-service-response-location="https://ap.example.org/logout/saml2/response">
<verification-credential
certificate-location="classpath:org/springframework/security/config/saml2/idp-certificate.crt"
private-key-location="classpath:org/springframework/security/config/saml2/rp-private.key"/>
<encryption-credential
certificate-location="classpath:org/springframework/security/config/saml2/idp-certificate.crt"
private-key-location="classpath:org/springframework/security/config/saml2/rp-private.key"/>
</asserting-party>
<asserting-party asserting-party-id="rp" entity-id="ap-entity-id"
single-sign-on-service-location="https://rp.example.org/sso"
single-logout-service-location="https://rp.example.org/logout/saml2/request"
single-logout-service-response-location="https://ap.example.org/logout/saml2/response">
<verification-credential
certificate-location="classpath:org/springframework/security/config/saml2/idp-certificate.crt"
private-key-location="classpath:org/springframework/security/config/saml2/rp-private.key"/>
<encryption-credential
certificate-location="classpath:org/springframework/security/config/saml2/idp-certificate.crt"
private-key-location="classpath:org/springframework/security/config/saml2/rp-private.key"/>
</asserting-party>
</relying-party-registrations>
</b:beans>

View File

@ -165,6 +165,7 @@ The default value is true.
* <<nsa-remember-me,remember-me>>
* <<nsa-request-cache,request-cache>>
* <<nsa-saml2-login,saml2-login>>
* <<nsa-saml2-logout,saml2-logout>>
* <<nsa-session-management,session-management>>
* <<nsa-x509,x509>>
@ -1342,11 +1343,78 @@ The AssertionConsumerService Location. Equivalent to the value found in `&lt;Ass
the AssertionConsumerService Binding. Equivalent to the value found in `&lt;AssertionConsumerService Binding="..."/&gt;` in the relying party's `&lt;SPSSODescriptor&gt;`.
The supported values are *POST* and *REDIRECT*.
[[nsa-relying-party-registration-single-logout-service-location]]
* **single-logout-service-location**
The SingleLogoutService Location. Equivalent to the value found in &lt;SingleLogoutService Location="..."/&gt; in the relying party's &lt;SPSSODescriptor&gt;.
[[nsa-relying-party-registration-single-logout-service-response-location]]
* **single-logout-service-response-location**
The SingleLogoutService ResponseLocation. Equivalent to the value found in &lt;SingleLogoutService ResponseLocation="..."/&gt; in the relying party's &lt;SPSSODescriptor&gt;.
[[nsa-relying-party-registration-single-logout-service-binding]]
* **single-logout-service-binding**
The SingleLogoutService Binding. Equivalent to the value found in &lt;SingleLogoutService Binding="..."/&gt; in the relying party's &lt;SPSSODescriptor&gt;.
The supported values are *POST* and *REDIRECT*.
[[nsa-relying-party-registration-asserting-party-id]]
* **asserting-party-id**
A reference to the associated asserting party. Must reference an `<asserting-party>` element.
[[nsa-relying-party-registration-children]]
=== Child Elements of <relying-party-registration>
* <<nsa-decryption-credential,decryption-credential>>
* <<nsa-signing-credential,signing-credential>>
[[nsa-decryption-credential]]
== <decryption-credential>
The decryption credentials associated with the relying party.
[[nsa-decryption-credential-parents]]
=== Parent Elements of <decryption-credential>
* <<nsa-relying-party-registration,relying-party-registration>>
[[nsa-decryption-credential-attributes]]
=== <decryption-credential> Attributes
[[nsa-decryption-credential-certificate-location]]
* **certificate-location**
The location to get the certificate
[[nsa-decryption-credential-private-key-location]]
* **private-key-location**
The location to get the Relying Party's private key
[[nsa-signing-credential]]
== <signing-credential>
The signing credentials associated with the relying party.
[[nsa-signing-credential-parents]]
=== Parent Elements of <verification-credential>
* <<nsa-relying-party-registration,relying-party-registration>>
[[nsa-signing-credential-attributes]]
=== <verification-credential> Attributes
[[nsa-signing-credential-certificate-location]]
* **certificate-location**
The location to get this certificate
[[nsa-signing-credential-private-key-location]]
* **private-key-location**
The location to get the Relying Party's private key
[[nsa-asserting-party]]
== <asserting-party>
@ -1394,6 +1462,22 @@ The supported values are *POST* and *REDIRECT*.
The list of `org.opensaml.saml.ext.saml2alg.SigningMethod` Algorithms for this asserting party, in preference order.
[[nsa-asserting-party-single-logout-service-location]]
* **single-logout-service-location**
The SingleLogoutService Location. Equivalent to the value found in &lt;SingleLogoutService Location="..."/&gt; in the asserting party's &lt;IDPSSODescriptor&gt;.
[[nsa-asserting-party-single-logout-service-response-location]]
* **single-logout-service-response-location**
The SingleLogoutService ResponseLocation. Equivalent to the value found in &lt;SingleLogoutService ResponseLocation="..."/&gt; in the asserting party's &lt;IDPSSODescriptor&gt;.
[[nsa-asserting-party-single-logout-service-binding]]
* **single-logout-service-binding**
The SingleLogoutService Binding. Equivalent to the value found in &lt;SingleLogoutService Binding="..."/&gt; in the asserting party's &lt;IDPSSODescriptor&gt;.
The supported values are *POST* and *REDIRECT*.
[[nsa-asserting-party-children]]
=== Child Elements of <asserting-party>
@ -1795,6 +1879,67 @@ Reference to the `AuthenticationFailureHandler`.
Reference to the `AuthenticationManager`.
[[nsa-saml2-logout]]
== <saml2-logout>
The xref:servlet/saml2/logout.adoc#servlet-saml2login-logout[SAML 2.0 Single Logout] feature configures support for RP- and AP-initiated SAML 2.0 Single Logout.
[[nsa-saml2-logout-parents]]
=== Parent Elements of <saml2-logout>
* <<nsa-http,http>>
[[nsa-saml2-logout-attributes]]
=== <saml2-logout> Attributes
[[nsa-saml2-logout-logout-url]]
* **logout-url**
The URL by which the relying or asserting party can trigger logout.
[[nsa-saml2-logout-logout-request-url]]
* **logout-request-url**
The URL by which the asserting party can send a SAML 2.0 Logout Request.
[[nsa-saml2-logout-logout-response-url]]
* **logout-response-url**
The URL by which the asserting party can send a SAML 2.0 Logout Response.
[[nsa-saml2-logout-relying-party-registration-repository-ref]]
* **relying-party-registration-repository-ref**
Reference to the `RelyingPartyRegistrationRepository`.
[[nsa-saml2-logout-logout-request-validator-ref]]
* **logout-request-validator-ref**
Reference to the `Saml2LogoutRequestValidator`.
[[nsa-saml2-logout-logout-request-resolver-ref]]
* **logout-request-resolver-ref**
Reference to the `Saml2LogoutRequestResolver`.
[[nsa-saml2-logout-logout-request-repository-ref]]
* **logout-request-repository-ref**
Reference to the `Saml2LogoutRequestRepository`.
[[nsa-saml2-logout-logout-response-validator-ref]]
* **logout-response-validator-ref**
Reference to the `Saml2LogoutResponseValidator`.
[[nsa-saml2-logout-logout-response-resolver-ref]]
* **logout-response-resolver-ref**
Reference to the `Saml2LogoutResponseResolver`.
[[nsa-attribute-exchange]]
== <attribute-exchange>
The `attribute-exchange` element defines the list of attributes which should be requested from the identity provider.