From 2f8a63dbe3f13b22ce42b2cc3ca058d1430dbefe Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Fri, 3 Mar 2023 14:46:52 -0700 Subject: [PATCH] Use Federation Support Closes gh-132 --- .../gradle.properties | 4 +- ...ityIdRelyingPartyRegistrationResolver.java | 101 ------------------ .../SamlExtensionUrlForwardingFilter.java | 71 ------------ .../java/example/SecurityConfiguration.java | 28 +---- .../src/main/resources/application.yml | 10 +- 5 files changed, 11 insertions(+), 203 deletions(-) delete mode 100644 servlet/spring-boot/java/saml2/saml-extension-federation/src/main/java/example/EntityIdRelyingPartyRegistrationResolver.java delete mode 100644 servlet/spring-boot/java/saml2/saml-extension-federation/src/main/java/example/SamlExtensionUrlForwardingFilter.java diff --git a/servlet/spring-boot/java/saml2/saml-extension-federation/gradle.properties b/servlet/spring-boot/java/saml2/saml-extension-federation/gradle.properties index ce1417e..3ba0e2a 100644 --- a/servlet/spring-boot/java/saml2/saml-extension-federation/gradle.properties +++ b/servlet/spring-boot/java/saml2/saml-extension-federation/gradle.properties @@ -1,2 +1,2 @@ -version=6.0.0-SNAPSHOT -spring-security.version=6.0.0-SNAPSHOT +version=6.1.0-SNAPSHOT +spring-security.version=6.1.0-SNAPSHOT diff --git a/servlet/spring-boot/java/saml2/saml-extension-federation/src/main/java/example/EntityIdRelyingPartyRegistrationResolver.java b/servlet/spring-boot/java/saml2/saml-extension-federation/src/main/java/example/EntityIdRelyingPartyRegistrationResolver.java deleted file mode 100644 index d3e4fb6..0000000 --- a/servlet/spring-boot/java/saml2/saml-extension-federation/src/main/java/example/EntityIdRelyingPartyRegistrationResolver.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2023 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 example; - -import java.io.ByteArrayInputStream; -import java.nio.charset.StandardCharsets; -import java.util.Base64; - -import jakarta.servlet.http.HttpServletRequest; -import net.shibboleth.utilities.java.support.xml.ParserPool; -import org.opensaml.core.config.ConfigurationService; -import org.opensaml.core.xml.config.XMLObjectProviderRegistry; -import org.opensaml.saml.saml2.core.Response; -import org.opensaml.saml.saml2.core.impl.ResponseUnmarshaller; -import org.w3c.dom.Document; -import org.w3c.dom.Element; - -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.Saml2AuthenticationException; -import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; -import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver; - -public class EntityIdRelyingPartyRegistrationResolver implements RelyingPartyRegistrationResolver { - - static { - OpenSamlInitializationService.initialize(); - } - - private final ResponseUnmarshaller responseUnmarshaller; - - private final ParserPool parserPool; - - private final InMemoryRelyingPartyRegistrationRepository registrations; - - public EntityIdRelyingPartyRegistrationResolver(InMemoryRelyingPartyRegistrationRepository registrations) { - XMLObjectProviderRegistry registry = ConfigurationService.get(XMLObjectProviderRegistry.class); - this.responseUnmarshaller = (ResponseUnmarshaller) registry.getUnmarshallerFactory() - .getUnmarshaller(Response.DEFAULT_ELEMENT_NAME); - this.parserPool = registry.getParserPool(); - this.registrations = registrations; - } - - @Override - public RelyingPartyRegistration resolve(HttpServletRequest request, String relyingPartyRegistrationId) { - if (relyingPartyRegistrationId != null) { - return this.registrations.findByRegistrationId(relyingPartyRegistrationId); - } - String entityId = resolveEntityId(request); - if (entityId == null) { - return null; - } - for (RelyingPartyRegistration registration : this.registrations) { - if (entityId.equals(registration.getAssertingPartyDetails().getEntityId())) { - return registration; - } - } - return null; - } - - private String resolveEntityId(HttpServletRequest request) { - String saml2Response = request.getParameter(Saml2ParameterNames.SAML_RESPONSE); - if (saml2Response == null) { - return null; - } - byte[] decoded = Base64.getMimeDecoder().decode(saml2Response); - String serialized = new String(decoded, StandardCharsets.UTF_8); - return parseResponse(serialized).getIssuer().getValue(); - } - - private Response parseResponse(String serialized) { - try { - Document document = this.parserPool - .parse(new ByteArrayInputStream(serialized.getBytes(StandardCharsets.UTF_8))); - Element element = document.getDocumentElement(); - return (Response) this.responseUnmarshaller.unmarshall(element); - } - catch (Exception ex) { - Saml2Error error = new Saml2Error(Saml2ErrorCodes.MALFORMED_RESPONSE_DATA, ex.getMessage()); - throw new Saml2AuthenticationException(error, ex.getMessage()); - } - } - -} diff --git a/servlet/spring-boot/java/saml2/saml-extension-federation/src/main/java/example/SamlExtensionUrlForwardingFilter.java b/servlet/spring-boot/java/saml2/saml-extension-federation/src/main/java/example/SamlExtensionUrlForwardingFilter.java deleted file mode 100644 index 3461805..0000000 --- a/servlet/spring-boot/java/saml2/saml-extension-federation/src/main/java/example/SamlExtensionUrlForwardingFilter.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2002-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package example; - -import java.io.IOException; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.RequestDispatcher; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import org.springframework.core.annotation.Order; -import org.springframework.security.web.util.matcher.AntPathRequestMatcher; -import org.springframework.security.web.util.matcher.OrRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.stereotype.Component; -import org.springframework.web.filter.OncePerRequestFilter; - -@Component -@Order(-102) // To run before FilterChainProxy -public class SamlExtensionUrlForwardingFilter extends OncePerRequestFilter { - - // @formatter:off - private static final Map urlMapping = Map.of("/saml/SSO", "/login/saml2/sso/sp", - "/saml/logout", "/logout/saml2/slo", - "/saml/SingleLogout", "/logout/saml2/slo", - "/saml/metadata", "/saml2/service-provider-metadata/sp"); - // @formatter:on - - private final RequestMatcher matcher = createRequestMatcher(); - - private RequestMatcher createRequestMatcher() { - Set urls = urlMapping.keySet(); - List matchers = new LinkedList<>(); - urls.forEach((url) -> matchers.add(new AntPathRequestMatcher(url))); - return new OrRequestMatcher(matchers); - } - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { - boolean match = this.matcher.matches(request); - if (!match) { - filterChain.doFilter(request, response); - return; - } - String forwardUrl = urlMapping.get(request.getRequestURI()); - RequestDispatcher dispatcher = request.getRequestDispatcher(forwardUrl); - dispatcher.forward(request, response); - } - -} diff --git a/servlet/spring-boot/java/saml2/saml-extension-federation/src/main/java/example/SecurityConfiguration.java b/servlet/spring-boot/java/saml2/saml-extension-federation/src/main/java/example/SecurityConfiguration.java index a81fa8c..e1a652a 100644 --- a/servlet/spring-boot/java/saml2/saml-extension-federation/src/main/java/example/SecurityConfiguration.java +++ b/servlet/spring-boot/java/saml2/saml-extension-federation/src/main/java/example/SecurityConfiguration.java @@ -30,21 +30,13 @@ import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyProperties; import org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyProperties.Registration; -import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.annotation.Order; -import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.saml2.core.Saml2X509Credential; -import org.springframework.security.saml2.provider.service.metadata.OpenSamlMetadataResolver; import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrations; -import org.springframework.security.saml2.provider.service.web.DefaultRelyingPartyRegistrationResolver; -import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationTokenConverter; -import org.springframework.security.saml2.provider.service.web.Saml2MetadataFilter; import org.springframework.security.web.SecurityFilterChain; @Configuration @@ -59,26 +51,14 @@ public class SecurityConfiguration { .requestMatchers("/error").permitAll() .anyRequest().authenticated() ) - .saml2Login(Customizer.withDefaults()) - .saml2Logout(Customizer.withDefaults()); + .saml2Login((saml2) -> saml2.loginProcessingUrl("/saml/SSO")) + .saml2Logout((saml2) -> saml2.logoutRequest((request) -> request.logoutUrl("/saml/logout"))) + .saml2Logout((saml2) -> saml2.logoutResponse((response) -> response.logoutUrl("/saml/SingleLogout"))) + .saml2Metadata((saml2) -> saml2.metadataUrl("/saml/metadata")); // @formatter:on return http.build(); } - @Bean - Saml2AuthenticationTokenConverter usingEntityId(InMemoryRelyingPartyRegistrationRepository repository) { - var registrations = new EntityIdRelyingPartyRegistrationResolver(repository); - return new Saml2AuthenticationTokenConverter(registrations); - } - - @Bean - @Order(-101) - FilterRegistrationBean metadata(Iterable repository) { - var registrations = new DefaultRelyingPartyRegistrationResolver((id) -> repository.iterator().next()); - var filter = new Saml2MetadataFilter(registrations, new OpenSamlMetadataResolver()); - return new FilterRegistrationBean<>(filter); - } - @Bean InMemoryRelyingPartyRegistrationRepository repository(Saml2RelyingPartyProperties properties, @Value("classpath:credentials/rp-private.key") RSAPrivateKey key, diff --git a/servlet/spring-boot/java/saml2/saml-extension-federation/src/main/resources/application.yml b/servlet/spring-boot/java/saml2/saml-extension-federation/src/main/resources/application.yml index 41d286a..9e9f774 100644 --- a/servlet/spring-boot/java/saml2/saml-extension-federation/src/main/resources/application.yml +++ b/servlet/spring-boot/java/saml2/saml-extension-federation/src/main/resources/application.yml @@ -8,12 +8,12 @@ spring: saml2: relyingparty: registration: - sp: - entity-id: "http://localhost:8080/saml2/service-provider-metadata/one" + metadata: + entity-id: "{baseUrl}/saml2/service-provider-metadata/one" singlelogout: binding: POST - url: "http://localhost:8080/saml/logout" - responseUrl: "http://localhost:8080/saml/SingleLogout" + url: "{baseUrl}/saml/logout" + responseUrl: "{baseUrl}/saml/SingleLogout" acs: - location: "http://localhost:8080/saml/SSO" + location: "{baseUrl}/saml/SSO" assertingparty.metadata-uri: https://dev-05937739.okta.com/app/exk598vc9bHhwoTXM5d7/sso/saml/metadata