Sfoglia il codice sorgente

Update custom-urls Sample to use Docker-based IdP

Issue gh-127
Josh Cummings 9 mesi fa
parent
commit
8eb06469e2

+ 8 - 0
servlet/spring-boot/java/saml2/custom-urls/.idea/.gitignore

@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml

+ 15 - 0
servlet/spring-boot/java/saml2/custom-urls/.idea/checkstyle-idea.xml

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="CheckStyle-IDEA" serialisationVersion="2">
+    <checkstyleVersion>10.18.1</checkstyleVersion>
+    <scanScope>JavaOnly</scanScope>
+    <option name="thirdPartyClasspath" />
+    <option name="activeLocationIds" />
+    <option name="locations">
+      <list>
+        <ConfigurationLocation id="bundled-sun-checks" type="BUNDLED" scope="All" description="Sun Checks">(bundled)</ConfigurationLocation>
+        <ConfigurationLocation id="bundled-google-checks" type="BUNDLED" scope="All" description="Google Checks">(bundled)</ConfigurationLocation>
+      </list>
+    </option>
+  </component>
+</project>

+ 31 - 0
servlet/spring-boot/java/saml2/custom-urls/.idea/codeStyles

@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectCodeStyleConfiguration">
+    <code_scheme name="Project" version="173">
+      <DBN-PSQL>
+        <case-options enabled="false">
+          <option name="KEYWORD_CASE" value="lower" />
+          <option name="FUNCTION_CASE" value="lower" />
+          <option name="PARAMETER_CASE" value="lower" />
+          <option name="DATATYPE_CASE" value="lower" />
+          <option name="OBJECT_CASE" value="preserve" />
+        </case-options>
+        <formatting-settings enabled="false" />
+      </DBN-PSQL>
+      <DBN-SQL>
+        <case-options enabled="false">
+          <option name="KEYWORD_CASE" value="lower" />
+          <option name="FUNCTION_CASE" value="lower" />
+          <option name="PARAMETER_CASE" value="lower" />
+          <option name="DATATYPE_CASE" value="lower" />
+          <option name="OBJECT_CASE" value="preserve" />
+        </case-options>
+        <formatting-settings enabled="false">
+          <option name="STATEMENT_SPACING" value="one_line" />
+          <option name="CLAUSE_CHOP_DOWN" value="chop_down_if_statement_long" />
+          <option name="ITERATION_ELEMENTS_WRAPPING" value="chop_down_if_not_single" />
+        </formatting-settings>
+      </DBN-SQL>
+    </code_scheme>
+  </component>
+</project>

+ 6 - 0
servlet/spring-boot/java/saml2/custom-urls/.idea/compiler.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="CompilerConfiguration">
+    <bytecodeTargetLevel target="21" />
+  </component>
+</project>

+ 15 - 0
servlet/spring-boot/java/saml2/custom-urls/.idea/gradle.xml

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="GradleSettings">
+    <option name="linkedExternalProjectsSettings">
+      <GradleProjectSettings>
+        <option name="externalProjectPath" value="$PROJECT_DIR$" />
+        <option name="modules">
+          <set>
+            <option value="$PROJECT_DIR$" />
+          </set>
+        </option>
+      </GradleProjectSettings>
+    </option>
+  </component>
+</project>

+ 35 - 0
servlet/spring-boot/java/saml2/custom-urls/.idea/jarRepositories.xml

@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="RemoteRepositoriesConfiguration">
+    <remote-repository>
+      <option name="id" value="central" />
+      <option name="name" value="Maven Central repository" />
+      <option name="url" value="https://repo1.maven.org/maven2" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="jboss.community" />
+      <option name="name" value="JBoss Community repository" />
+      <option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="MavenRepo" />
+      <option name="name" value="MavenRepo" />
+      <option name="url" value="https://repo.maven.apache.org/maven2/" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="maven" />
+      <option name="name" value="maven" />
+      <option name="url" value="https://repo.spring.io/milestone" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="maven2" />
+      <option name="name" value="maven2" />
+      <option name="url" value="https://repo.spring.io/snapshot" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="maven3" />
+      <option name="name" value="maven3" />
+      <option name="url" value="https://build.shibboleth.net/nexus/content/repositories/releases/" />
+    </remote-repository>
+  </component>
+</project>

+ 8 - 0
servlet/spring-boot/java/saml2/custom-urls/.idea/misc.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ExternalStorageConfigurationManager" enabled="true" />
+  <component name="FrameworkDetectionExcludesConfiguration">
+    <file type="web" url="file://$PROJECT_DIR$" />
+  </component>
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="21" project-jdk-type="JavaSDK" />
+</project>

+ 6 - 0
servlet/spring-boot/java/saml2/custom-urls/.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$/../../../../.." vcs="Git" />
+  </component>
+</project>

+ 10 - 0
servlet/spring-boot/java/saml2/custom-urls/build.gradle

@@ -12,6 +12,15 @@ repositories {
 	maven { url "https://build.shibboleth.net/nexus/content/repositories/releases/" }
 }
 
+sourceSets.main.java.srcDirs += "$projectDir/../identity-provider/src/main/java"
+sourceSets.main.resources.srcDirs += "$projectDir/../identity-provider/src/main/resources"
+
+if (plugins.hasPlugin("io.spring.javaformat")) {
+	tasks.formatMain {
+		dependsOn(":servlet:spring-boot:java:saml2:identity-provider:formatMain")
+	}
+}
+
 dependencies {
 	constraints {
 		implementation "org.opensaml:opensaml-saml-api:5.1.3"
@@ -26,6 +35,7 @@ dependencies {
 	testImplementation 'org.htmlunit:htmlunit'
 	testImplementation 'org.springframework.boot:spring-boot-starter-test'
 	testImplementation 'org.springframework.security:spring-security-test'
+	runtimeOnly "org.springframework.boot:spring-boot-docker-compose"
 }
 
 tasks.withType(Test).configureEach {

+ 12 - 8
servlet/spring-boot/java/saml2/custom-urls/src/integTest/java/example/CustomUrlsApplicationITests.java

@@ -21,18 +21,19 @@ import java.util.List;
 
 import org.htmlunit.ElementNotFoundException;
 import org.htmlunit.WebClient;
+import org.htmlunit.html.HtmlButton;
 import org.htmlunit.html.HtmlElement;
 import org.htmlunit.html.HtmlForm;
 import org.htmlunit.html.HtmlInput;
 import org.htmlunit.html.HtmlPage;
 import org.htmlunit.html.HtmlPasswordInput;
-import org.htmlunit.html.HtmlSubmitInput;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
 import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.web.server.LocalServerPort;
 import org.springframework.test.web.servlet.MockMvc;
 
 import static org.assertj.core.api.Assertions.assertThat;
@@ -40,10 +41,13 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrl;
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
 
-@SpringBootTest
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
 @AutoConfigureMockMvc
 public class CustomUrlsApplicationITests {
 
+	@LocalServerPort
+	int port;
+
 	@Autowired
 	MockMvc mvc;
 
@@ -59,7 +63,7 @@ public class CustomUrlsApplicationITests {
 	void authenticationAttemptWhenValidThenShowsUserEmailAddress() throws Exception {
 		performLogin();
 		HtmlPage home = (HtmlPage) this.webClient.getCurrentWindow().getEnclosedPage();
-		assertThat(home.asNormalizedText()).contains("You're email address is testuser2@spring.security.saml");
+		assertThat(home.asNormalizedText()).contains("You're email address is user1@example.org");
 	}
 
 	@Test
@@ -83,14 +87,14 @@ public class CustomUrlsApplicationITests {
 	}
 
 	private void performLogin() throws Exception {
-		HtmlPage login = this.webClient.getPage("/");
+		HtmlPage login = this.webClient.getPage("http://localhost:" + this.port + "/saml/login");
 		this.webClient.waitForBackgroundJavaScript(10000);
 		HtmlForm form = findForm(login);
 		HtmlInput username = form.getInputByName("username");
 		HtmlPasswordInput password = form.getInputByName("password");
-		HtmlSubmitInput submit = login.getHtmlElementById("okta-signin-submit");
-		username.type("testuser2@spring.security.saml");
-		password.type("12345678");
+		HtmlButton submit = (HtmlButton) form.getElementsByTagName("button").iterator().next();
+		username.type("user1");
+		password.type("user1pass");
 		submit.click();
 		this.webClient.waitForBackgroundJavaScript(10000);
 	}
@@ -98,7 +102,7 @@ public class CustomUrlsApplicationITests {
 	private HtmlForm findForm(HtmlPage login) {
 		for (HtmlForm form : login.getForms()) {
 			try {
-				if (form.getId().equals("form19")) {
+				if (form.getNameAttribute().equals("f")) {
 					return form;
 				}
 			}

+ 70 - 0
servlet/spring-boot/java/saml2/custom-urls/src/integTest/java/example/PreDockerComposeServerPortInitializer.java

@@ -0,0 +1,70 @@
+/*
+ * 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.
+ */
+
+package example;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.env.EnvironmentPostProcessor;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.core.env.PropertySource;
+
+/**
+ * Spring Boot doesn't determine the port before the docker containers are loaded, so
+ * we'll decide the test port here and override the associated properties.
+ *
+ * @author Josh Cummings
+ */
+public class PreDockerComposeServerPortInitializer implements EnvironmentPostProcessor {
+
+	private static final Integer port = getPort();
+
+	@Override
+	public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
+		environment.getPropertySources().addFirst(new ServerPortPropertySource(port));
+	}
+
+	private static Integer getPort() {
+		try (ServerSocket serverSocket = new ServerSocket(0)) {
+			return serverSocket.getLocalPort();
+		}
+		catch (IOException ex) {
+			throw new RuntimeException(ex);
+		}
+	}
+
+	private static class ServerPortPropertySource extends PropertySource<Integer> {
+
+		ServerPortPropertySource(Integer port) {
+			super("server.port.override", port);
+		}
+
+		@Override
+		public Object getProperty(String name) {
+			if ("server.port".equals(name)) {
+				return getSource();
+			}
+			if ("SERVER_PORT".equals(name)) {
+				return getSource();
+			}
+			return null;
+		}
+
+	}
+
+}

+ 1 - 0
servlet/spring-boot/java/saml2/custom-urls/src/integTest/resources/META-INF/spring.factories

@@ -0,0 +1 @@
+org.springframework.boot.env.EnvironmentPostProcessor=example.PreDockerComposeServerPortInitializer

+ 2 - 13
servlet/spring-boot/java/saml2/custom-urls/src/main/java/example/SecurityConfiguration.java

@@ -21,12 +21,6 @@ import org.springframework.context.annotation.Configuration;
 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.provider.service.metadata.OpenSamlMetadataResolver;
-import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
-import org.springframework.security.saml2.provider.service.web.DefaultRelyingPartyRegistrationResolver;
-import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver;
-import org.springframework.security.saml2.provider.service.web.Saml2MetadataFilter;
-import org.springframework.security.saml2.provider.service.web.authentication.Saml2WebSsoAuthenticationFilter;
 import org.springframework.security.web.SecurityFilterChain;
 
 @Configuration
@@ -34,12 +28,7 @@ import org.springframework.security.web.SecurityFilterChain;
 public class SecurityConfiguration {
 
 	@Bean
-	SecurityFilterChain securityFilterChain(HttpSecurity http,
-			RelyingPartyRegistrationRepository relyingPartyRegistrationRepository) throws Exception {
-		RelyingPartyRegistrationResolver relyingPartyRegistrationResolver = new DefaultRelyingPartyRegistrationResolver(
-				relyingPartyRegistrationRepository);
-		Saml2MetadataFilter metadataFilter = new Saml2MetadataFilter(relyingPartyRegistrationResolver,
-				new OpenSamlMetadataResolver());
+	SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
 		// @formatter:off
 		http
 			.authorizeHttpRequests((authorize) -> authorize
@@ -48,7 +37,7 @@ public class SecurityConfiguration {
 			)
 			.saml2Login(Customizer.withDefaults())
 			.saml2Logout(Customizer.withDefaults())
-			.addFilterBefore(metadataFilter, Saml2WebSsoAuthenticationFilter.class);
+			.saml2Metadata(Customizer.withDefaults());
 		// @formatter:on
 		return http.build();
 	}

+ 13 - 2
servlet/spring-boot/java/saml2/custom-urls/src/main/resources/application.yml

@@ -1,4 +1,14 @@
+logging.level:
+  org.springframework.security: TRACE
+
 spring:
+  docker:
+    compose:
+      file: docker:docker/compose.yml
+      readiness:
+        wait: never
+      skip:
+        in-tests: false
   security:
     filter:
       dispatcher-types: async, error, request, forward
@@ -6,12 +16,13 @@ spring:
       relyingparty:
         registration:
           one:
+            entity-id: "{baseUrl}/saml/metadata"
             signing.credentials:
               - private-key-location: classpath:credentials/rp-private.key
                 certificate-location: classpath:credentials/rp-certificate.crt
-            assertingparty.metadata-uri: https://dev-05937739.okta.com/app/exk598vc9bHhwoTXM5d7/sso/saml/metadata
+            assertingparty.metadata-uri: http://idp-one.7f000001.nip.io/simplesaml/saml2/idp/metadata.php
             singlelogout:
-              binding: POST
+              binding: REDIRECT
               url: "{baseUrl}/saml/logout"
               responseUrl: "{baseUrl}/saml/SingleLogout"
             acs:

+ 22 - 0
servlet/spring-boot/java/saml2/identity-provider/src/main/resources/docker/metadata/one-relyingparties.php

@@ -1,5 +1,8 @@
 <?php
 $port = getenv("PORT");
+
+// Spring Security SP
+
 $metadata["http://localhost:$port/saml2/metadata"] = array(
     'AssertionConsumerService' => "https://localhost:$port/login/saml2/sso",
     'SingleLogoutService' => "https://localhost:$port/logout/saml2/slo",
@@ -10,4 +13,23 @@ $metadata["http://localhost:$port/saml2/metadata"] = array(
     'validate.authnrequest' => FALSE,
     'redirect.sign' => TRUE,
 );
+
+// Spring Security SAML SP
+
+$metadata["http://localhost:$port/saml/metadata"] = array(
+    'AssertionConsumerService' => "http://localhost:$port/saml/SSO",
+    'SingleLogoutService' => [
+        [
+            'Binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
+            'Location' => "http://localhost:$port/saml/logout",
+            'ResponseLocation' => "http://localhost:$port/saml/SingleLogout"
+        ]
+    ],
+    'NameIDFormat' => 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',
+    'simplesaml.nameidattribute' => 'emailAddress',
+    'assertion.encryption' => FALSE,
+    'nameid.encryption' => FALSE,
+    'validate.authnrequest' => FALSE,
+    'redirect.sign' => TRUE,
+);
 ?>

+ 22 - 0
servlet/spring-boot/java/saml2/identity-provider/src/main/resources/docker/metadata/two-relyingparties.php

@@ -1,5 +1,8 @@
 <?php
 $port = getenv("PORT");
+
+// Spring Security SP
+
 $metadata["http://localhost:$port/saml2/metadata"] = array(
     'AssertionConsumerService' => "http://localhost:$port/login/saml2/sso",
     'SingleLogoutService' => "http://localhost:$port/logout/saml2/slo",
@@ -10,4 +13,23 @@ $metadata["http://localhost:$port/saml2/metadata"] = array(
     'validate.authnrequest' => FALSE,
     'redirect.sign' => TRUE,
 );
+
+// Spring Security SAML SP
+
+$metadata["http://localhost:$port/saml/metadata"] = array(
+    'AssertionConsumerService' => "http://localhost:$port/saml/SSO",
+    'SingleLogoutService' => [
+        [
+            'Binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
+            'Location' => "http://localhost:$port/saml/logout",
+            'ResponseLocation' => "http://localhost:$port/saml/SingleLogout"
+        ]
+    ],
+    'NameIDFormat' => 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',
+    'simplesaml.nameidattribute' => 'emailAddress',
+    'assertion.encryption' => FALSE,
+    'nameid.encryption' => FALSE,
+    'validate.authnrequest' => FALSE,
+    'redirect.sign' => TRUE,
+);
 ?>