소스 검색

Use Docker-based Identity Provider

Issue gh-127
Closes gh-64
Josh Cummings 9 달 전
부모
커밋
8bcfc647a7

+ 9 - 4
servlet/spring-boot/java/saml2/login/README.adoc

@@ -19,7 +19,7 @@ The following features are implemented in the MVP:
 1. Receive and validate a SAML 2.0 Response containing an assertion, and create a corresponding authentication in Spring Security
 2. Send a SAML 2.0 AuthNRequest to an Identity Provider
 3. Provide a framework for components used in SAML 2.0 authentication that can be swapped by configuration
-4. Work against the Okta SAML 2.0 IDP reference implementation
+4. Work against the SimpleSAMLPHP reference implementation
 
 === SAML 2.0 Single Logout
 
@@ -31,6 +31,11 @@ You can refer to the https://docs.spring.io/spring-security/reference/servlet/sa
 
 == Run the Sample
 
+=== Prerequisites
+
+This sample requires Docker in order to stand up the identity provider.
+If you don't have Docker, you can alternatively disable Docker in `application.yml` and stand up your own IdP.
+
 === Start up the Sample Boot Application
 ```
  ./gradlew :servlet:spring-boot:java:saml2:login:bootRun
@@ -40,12 +45,12 @@ You can refer to the https://docs.spring.io/spring-security/reference/servlet/sa
 
 http://localhost:8080/
 
-You will be redirect to the Okta SAML 2.0 IDP
+You will be redirected to a chooser page where you can pick between one of two identity providers.
 
 === Type in your credentials
 
 ```
-User: testuser2@spring.security.saml
-Password: 12345678
+User: user1
+Password: user1pass
 ```
 

+ 9 - 0
servlet/spring-boot/java/saml2/login/build.gradle

@@ -12,6 +12,14 @@ 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 {
@@ -27,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 {

+ 70 - 0
servlet/spring-boot/java/saml2/login/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;
+		}
+
+	}
+
+}

+ 11 - 11
servlet/spring-boot/java/saml2/login/src/integTest/java/example/Saml2LoginApplicationITests.java

@@ -21,28 +21,28 @@ 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.test.web.servlet.MockMvc;
+import org.springframework.boot.test.web.server.LocalServerPort;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
-@SpringBootTest
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
 @AutoConfigureMockMvc
 public class Saml2LoginApplicationITests {
 
-	@Autowired
-	MockMvc mvc;
+	@LocalServerPort
+	int port;
 
 	@Autowired
 	WebClient webClient;
@@ -56,7 +56,7 @@ public class Saml2LoginApplicationITests {
 	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
@@ -82,14 +82,14 @@ public class Saml2LoginApplicationITests {
 	}
 
 	private void performLogin() throws Exception {
-		HtmlPage login = this.webClient.getPage("/");
+		HtmlPage login = this.webClient.getPage("http://localhost:" + this.port + "/saml2/authenticate/one");
 		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);
 	}
@@ -97,7 +97,7 @@ public class Saml2LoginApplicationITests {
 	private HtmlForm findForm(HtmlPage login) {
 		for (HtmlForm form : login.getForms()) {
 			try {
-				if (form.getId().equals("form19")) {
+				if (form.getNameAttribute().equals("f")) {
 					return form;
 				}
 			}

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

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

+ 21 - 2
servlet/spring-boot/java/saml2/login/src/main/resources/application.yml

@@ -2,15 +2,34 @@ logging.level:
   org.springframework.security: TRACE
 
 spring:
+  docker:
+    compose:
+      file: docker:docker/compose.yml
+      readiness:
+        wait: never
+      skip:
+        in-tests: false
   security:
     saml2:
       relyingparty:
         registration:
           one:
+            entity-id: "{baseUrl}/saml2/metadata"
+            acs.location: "{baseUrl}/login/saml2/sso"
             signing.credentials:
               - private-key-location: classpath:credentials/rp-private.key
                 certificate-location: classpath:credentials/rp-certificate.crt
             singlelogout:
-              binding: POST
+              binding: REDIRECT
               url: "{baseUrl}/logout/saml2/slo"
-            assertingparty.metadata-uri: https://dev-05937739.okta.com/app/exk46xofd8NZvFCpS5d7/sso/saml/metadata
+            assertingparty.metadata-uri: http://idp-one.7f000001.nip.io/simplesaml/saml2/idp/metadata.php
+          two:
+            entity-id: "{baseUrl}/saml2/metadata"
+            acs.location: "{baseUrl}/login/saml2/sso"
+            signing.credentials:
+              - private-key-location: classpath:credentials/rp-private.key
+                certificate-location: classpath:credentials/rp-certificate.crt
+            singlelogout:
+              binding: REDIRECT
+              url: "{baseUrl}/logout/saml2/slo"
+            assertingparty.metadata-uri: http://idp-two.7f000001.nip.io/simplesaml/saml2/idp/metadata.php