diff --git a/.idea/misc.xml b/.idea/misc.xml
index 5821b2f..37183b0 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -4,5 +4,5 @@
-
+
\ No newline at end of file
diff --git a/servlet/java-configuration/saml2/login/src/integTest/java/example/Saml2JavaConfigurationITests.java b/servlet/java-configuration/saml2/login/src/integTest/java/example/Saml2JavaConfigurationITests.java
index 3cd5ac1..fbf0cc9 100644
--- a/servlet/java-configuration/saml2/login/src/integTest/java/example/Saml2JavaConfigurationITests.java
+++ b/servlet/java-configuration/saml2/login/src/integTest/java/example/Saml2JavaConfigurationITests.java
@@ -16,15 +16,14 @@
package example;
-import java.io.IOException;
-
+import com.gargoylesoftware.htmlunit.ElementNotFoundException;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.HtmlElement;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlInput;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
+import com.gargoylesoftware.htmlunit.html.HtmlPasswordInput;
import com.gargoylesoftware.htmlunit.html.HtmlSubmitInput;
-import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -40,6 +39,8 @@ import org.springframework.test.web.servlet.htmlunit.MockMvcWebClientBuilder;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
+import static org.assertj.core.api.Assertions.assertThat;
+
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = ApplicationConfiguration.class)
@WebAppConfiguration
@@ -66,35 +67,45 @@ public class Saml2JavaConfigurationITests {
@Test
void authenticationAttemptWhenValidThenShowsUserEmailAddress() throws Exception {
- HtmlPage relyingParty = performLogin();
- Assertions.assertThat(relyingParty.asText()).contains("You're email address is testuser@spring.security.saml");
+ performLogin();
+ HtmlPage home = (HtmlPage) this.webClient.getCurrentWindow().getEnclosedPage();
+ assertThat(home.asText()).contains("You're email address is testuser@spring.security.saml");
}
@Test
void logoutWhenRelyingPartyInitiatedLogoutThenLoginPageWithLogoutParam() throws Exception {
- HtmlPage relyingParty = performLogin();
- HtmlElement rpLogoutButton = relyingParty.getHtmlElementById("rp_logout_button");
+ performLogin();
+ HtmlPage home = (HtmlPage) this.webClient.getCurrentWindow().getEnclosedPage();
+ HtmlElement rpLogoutButton = home.getHtmlElementById("rp_logout_button");
HtmlPage loginPage = rpLogoutButton.click();
- Assertions.assertThat(loginPage.getUrl().getFile()).isEqualTo("/login?logout");
+ assertThat(loginPage.getUrl().getFile()).isEqualTo("/login?logout");
}
- @Test
- void logoutWhenAssertingPartyInitiatedLogoutThenLoginPageWithLogoutParam() throws Exception {
- HtmlPage relyingParty = performLogin();
- HtmlElement apLogoutButton = relyingParty.getHtmlElementById("ap_logout_button");
- HtmlPage loginPage = apLogoutButton.click();
- Assertions.assertThat(loginPage.getUrl().getFile()).isEqualTo("/login?logout");
- }
-
- private HtmlPage performLogin() throws IOException {
+ private void performLogin() throws Exception {
HtmlPage login = this.webClient.getPage("/");
- HtmlForm form = login.getFormByName("f");
+ this.webClient.waitForBackgroundJavaScript(10000);
+ HtmlForm form = findForm(login);
HtmlInput username = form.getInputByName("username");
- HtmlInput password = form.getInputByName("password");
- HtmlSubmitInput submit = login.getHtmlElementById("submit_button");
- username.setValueAttribute("user");
- password.setValueAttribute("password");
- return submit.click();
+ HtmlPasswordInput password = form.getInputByName("password");
+ HtmlSubmitInput submit = login.getHtmlElementById("okta-signin-submit");
+ username.type("testuser@spring.security.saml");
+ password.type("12345678");
+ submit.click();
+ this.webClient.waitForBackgroundJavaScript(10000);
+ }
+
+ private HtmlForm findForm(HtmlPage login) {
+ for (HtmlForm form : login.getForms()) {
+ try {
+ if (form.getId().equals("form19")) {
+ return form;
+ }
+ }
+ catch (ElementNotFoundException ex) {
+ // Continue
+ }
+ }
+ throw new IllegalStateException("Could not resolve login form");
}
}
diff --git a/servlet/java-configuration/saml2/login/src/main/java/example/IndexController.java b/servlet/java-configuration/saml2/login/src/main/java/example/IndexController.java
index 7c6cdce..424d3f0 100644
--- a/servlet/java-configuration/saml2/login/src/main/java/example/IndexController.java
+++ b/servlet/java-configuration/saml2/login/src/main/java/example/IndexController.java
@@ -31,7 +31,7 @@ public class IndexController {
@GetMapping("/")
public String index(Model model, @AuthenticationPrincipal Saml2AuthenticatedPrincipal principal) {
- String emailAddress = principal.getFirstAttribute("emailAddress");
+ String emailAddress = principal.getFirstAttribute("email");
model.addAttribute("emailAddress", emailAddress);
model.addAttribute("userAttributes", principal.getAttributes());
return "index";
diff --git a/servlet/java-configuration/saml2/login/src/main/java/example/SecurityConfiguration.java b/servlet/java-configuration/saml2/login/src/main/java/example/SecurityConfiguration.java
index ab2311c..b5a9abe 100644
--- a/servlet/java-configuration/saml2/login/src/main/java/example/SecurityConfiguration.java
+++ b/servlet/java-configuration/saml2/login/src/main/java/example/SecurityConfiguration.java
@@ -32,6 +32,7 @@ import org.springframework.security.saml2.provider.service.registration.InMemory
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.registration.RelyingPartyRegistrations;
+import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
import org.springframework.security.web.SecurityFilterChain;
@EnableWebSecurity
@@ -57,13 +58,16 @@ public class SecurityConfiguration {
@Bean
RelyingPartyRegistrationRepository relyingPartyRegistrationRepository() {
RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistrations
- .fromMetadataLocation("https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/metadata.php")
+ .fromMetadataLocation("https://dev-05937739.okta.com/app/exk46xofd8NZvFCpS5d7/sso/saml/metadata")
.registrationId("one")
.decryptionX509Credentials(
(c) -> c.add(Saml2X509Credential.decryption(this.privateKey, relyingPartyCertificate())))
.signingX509Credentials(
(c) -> c.add(Saml2X509Credential.signing(this.privateKey, relyingPartyCertificate())))
- .build();
+ .singleLogoutServiceLocation(
+ "https://dev-05937739.okta.com/app/dev-05937739_springgsecuritysaml2idp_1/exk46xofd8NZvFCpS5d7/slo/saml")
+ .singleLogoutServiceResponseLocation("http://localhost:8080/logout/saml2/slo")
+ .singleLogoutServiceBinding(Saml2MessageBinding.POST).build();
return new InMemoryRelyingPartyRegistrationRepository(relyingPartyRegistration);
}
diff --git a/servlet/java-configuration/saml2/login/src/main/resources/templates/index.html b/servlet/java-configuration/saml2/login/src/main/resources/templates/index.html
index 2970e85..2e970b2 100644
--- a/servlet/java-configuration/saml2/login/src/main/resources/templates/index.html
+++ b/servlet/java-configuration/saml2/login/src/main/resources/templates/index.html
@@ -36,11 +36,6 @@
-
-
- AP-initiated Logout
-
-
diff --git a/servlet/spring-boot/java/saml2/login-single-tenant/build.gradle b/servlet/spring-boot/java/saml2/login-single-tenant/build.gradle
index 305d20e..0769f02 100644
--- a/servlet/spring-boot/java/saml2/login-single-tenant/build.gradle
+++ b/servlet/spring-boot/java/saml2/login-single-tenant/build.gradle
@@ -24,7 +24,7 @@ dependencies {
implementation 'org.springframework.security:spring-security-saml2-service-provider'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
- testImplementation 'net.sourceforge.htmlunit:htmlunit'
+ testImplementation 'net.sourceforge.htmlunit:htmlunit:2.44.0'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
}
@@ -32,4 +32,4 @@ dependencies {
tasks.withType(Test).configureEach {
useJUnitPlatform()
outputs.upToDateWhen { false }
-}
\ No newline at end of file
+}
diff --git a/servlet/spring-boot/java/saml2/login-single-tenant/src/integTest/java/example/Saml2LoginApplicationITests.java b/servlet/spring-boot/java/saml2/login-single-tenant/src/integTest/java/example/Saml2LoginApplicationITests.java
index ca034ad..5382a08 100644
--- a/servlet/spring-boot/java/saml2/login-single-tenant/src/integTest/java/example/Saml2LoginApplicationITests.java
+++ b/servlet/spring-boot/java/saml2/login-single-tenant/src/integTest/java/example/Saml2LoginApplicationITests.java
@@ -16,19 +16,13 @@
package example;
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-
-import javax.servlet.http.HttpSession;
-
+import com.gargoylesoftware.htmlunit.ElementNotFoundException;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.HtmlElement;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlInput;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
+import com.gargoylesoftware.htmlunit.html.HtmlPasswordInput;
import com.gargoylesoftware.htmlunit.html.HtmlSubmitInput;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -36,30 +30,14 @@ 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.mock.web.MockHttpSession;
import org.springframework.test.web.servlet.MockMvc;
import static org.assertj.core.api.Assertions.assertThat;
-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.result.MockMvcResultMatchers.model;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest
@AutoConfigureMockMvc
public class Saml2LoginApplicationITests {
- static final String SIGNED_RESPONSE = "PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIElEPSJfNWJhOGEyMGJlMDE3NzIxZTFiYjhmYmIwNGUxNzRhNDNjN2FiOTFjYTkxIiBWZXJzaW9uPSIyLjAiIElzc3VlSW5zdGFudD0iMjAyMS0xMC0xMlQxNDozMzowOFoiIERlc3RpbmF0aW9uPSJodHRwOi8vbG9jYWxob3N0OjgwODAvbG9naW4vc2FtbDIvc3NvIiBJblJlc3BvbnNlVG89IkFSUTI3OTZiZGMtOWJiOC00OTNmLWI3ZTMtMWE2MjI5ZjFiN2U0Ij48c2FtbDpJc3N1ZXI+aHR0cHM6Ly9zaW1wbGVzYW1scGhwLmFwcHMucGNmb25lLmlvL3NhbWwyL2lkcC9tZXRhZGF0YS5waHA8L3NhbWw6SXNzdWVyPjxkczpTaWduYXR1cmUgeG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPgogIDxkczpTaWduZWRJbmZvPjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+CiAgICA8ZHM6U2lnbmF0dXJlTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxkc2lnLW1vcmUjcnNhLXNoYTI1NiIvPgogIDxkczpSZWZlcmVuY2UgVVJJPSIjXzViYThhMjBiZTAxNzcyMWUxYmI4ZmJiMDRlMTc0YTQzYzdhYjkxY2E5MSI+PGRzOlRyYW5zZm9ybXM+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNlbnZlbG9wZWQtc2lnbmF0dXJlIi8+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjwvZHM6VHJhbnNmb3Jtcz48ZHM6RGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjc2hhMjU2Ii8+PGRzOkRpZ2VzdFZhbHVlPkNzM3U4UXRsSXlaMFZrRXRaU0FhL1VyUFJPbnRxT2syYVhaUmM0cVJSM2M9PC9kczpEaWdlc3RWYWx1ZT48L2RzOlJlZmVyZW5jZT48L2RzOlNpZ25lZEluZm8+PGRzOlNpZ25hdHVyZVZhbHVlPm9ZdW1idWQxYmU0ZWZJTUNGVGdqVG4ydGdOSUJpZlc4L3BRZGJtcUVCVzNaM29VVVdTWnEraDQvd3VWdlBEQ2l5R2J4d28yQ3lhS3JzdW1sUG5rWk9sR3lrdUYzbkFPRkJSaEd1OEdobWdObktiZkVuWXVzQldlOFdiZkFpYXJ6UUQwWHBOYmdsSHpsaE96Umk5ZnR1YkNjbTVQbGFEQytpVW1BbXYxT2ZSYnA5MncrVGhUbEtwaTQzeDNsQW1vRkk0ZlJQbUN2R1FGd0JYSEM5bG1KNGU1MUgwMm1nS2NTTTlnaGFLSk9DeTRxRVVjMVk3RE5xOTM4UHllcEZzVzRzc0I1VmJRalpxTjFBcmZOa0RTOTRKOXBvZFNsSktBcnlyKy9GZml6a0VndmJneVhwajgwclVLcThtNHN3SkE1KzV4NUZMY21xZ2UxR2FRRTVUQUxoUT09PC9kczpTaWduYXR1cmVWYWx1ZT4KPGRzOktleUluZm8+PGRzOlg1MDlEYXRhPjxkczpYNTA5Q2VydGlmaWNhdGU+TUlJRUV6Q0NBdnVnQXdJQkFnSUpBSWMxcXpMcnYrNW5NQTBHQ1NxR1NJYjNEUUVCQ3dVQU1JR2ZNUXN3Q1FZRFZRUUdFd0pWVXpFTE1Ba0dBMVVFQ0F3Q1EwOHhGREFTQmdOVkJBY01DME5oYzNSc1pTQlNiMk5yTVJ3d0dnWURWUVFLREJOVFlXMXNJRlJsYzNScGJtY2dVMlZ5ZG1WeU1Rc3dDUVlEVlFRTERBSkpWREVnTUI0R0ExVUVBd3dYYzJsdGNHeGxjMkZ0YkhCb2NDNWpabUZ3Y0hNdWFXOHhJREFlQmdrcWhraUc5dzBCQ1FFV0VXWm9ZVzVwYTBCd2FYWnZkR0ZzTG1sdk1CNFhEVEUxTURJeU16SXlORFV3TTFvWERUSTFNREl5TWpJeU5EVXdNMW93Z1o4eEN6QUpCZ05WQkFZVEFsVlRNUXN3Q1FZRFZRUUlEQUpEVHpFVU1CSUdBMVVFQnd3TFEyRnpkR3hsSUZKdlkyc3hIREFhQmdOVkJBb01FMU5oYld3Z1ZHVnpkR2x1WnlCVFpYSjJaWEl4Q3pBSkJnTlZCQXNNQWtsVU1TQXdIZ1lEVlFRRERCZHphVzF3YkdWellXMXNjR2h3TG1ObVlYQndjeTVwYnpFZ01CNEdDU3FHU0liM0RRRUpBUllSWm1oaGJtbHJRSEJwZG05MFlXd3VhVzh3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRQzRjbjYyRTF4THFwTjM0UG1icktCYmtPWEZqeldnSjliK3BYdWFSZnQ2QTMzOXV1SVFlb2VINXFlU0tSVlRsMzJMMGdkejJaaXZMd1pYVytjcXZmdFZXMXR2RUh2ekpGeXhlVFczZkNVZUNRc2ViTG5BMnFSYTA3Umt4VG82TmYyNDRtV1dSRG9kY29IRWZEVVNieGZUWjZJRXhTb2pTSVUyUm5ENldsbFlXRmREMUdGcEJKT21RQjhyQWM4d0pJQmRIRmRRblg4VHRsN2haNnJ0Z3FFWU16WVZNdUoyRjJyMUhTVTF6U0F2d3BkWVA2clJHRlJKRWZkQTltbTNXS2ZOTFNjNWNsanowWC9UWHkwdlZsQVY5NWw5cWNmRnpQbXJrTklzdDlGWlN3cHZCNDlMeUFWa2UwNEZRUFB3TGdWSDRncGhpSkgzanZaN0krSjVsUzhWQWdNQkFBR2pVREJPTUIwR0ExVWREZ1FXQkJUVHlQNkNjNUhsQko1K3VjVkN3R2M1b2dLTkd6QWZCZ05WSFNNRUdEQVdnQlRUeVA2Q2M1SGxCSjUrdWNWQ3dHYzVvZ0tOR3pBTUJnTlZIUk1FQlRBREFRSC9NQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUJBUUF2TVM0RVFlUC9pcFY0ak9HNWxPNi90WUNiL2lKZUFkdU9uUmhrSmswRGJYMzI5bERMWmhUVEwveC93LzltdUNWY3ZMcnpFcDZQTitWV2Z3NUU1Rld0Wk4weWhHdFA5Uit2Wm5yVitvYzJ6R0Qrbm8xL3lTRk9lM0VpSkNPNWRlaHhLallFbUJSdjVzVS9MWkZLWnBvektOL0JNRWE2Q3FMdXhiemI3eWt4VnI3RVZGWHdsdFB4ekU5VG1MOU9BQ05OeUY1ZUpIV01STWxsYXJVdmtjWGxoNHB1eDRrczllNnpWOURRQnkyemRzOWYxSTNxeGcwZVg2Sm5HclhpL1ppQ1QrbEpnVmUzWkZYaWVqaUxBaUtCMDRzWFczdGkwTFczbHgxM1kxWWxRNC90bHBnVGdmSUp4S1Y2bnlQaUxvSzBueXdiTWQrdnBBaXJEdDJPYytoazwvZHM6WDUwOUNlcnRpZmljYXRlPjwvZHM6WDUwOURhdGE+PC9kczpLZXlJbmZvPjwvZHM6U2lnbmF0dXJlPjxzYW1scDpTdGF0dXM+PHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPjwvc2FtbHA6U3RhdHVzPjxzYW1sOkFzc2VydGlvbiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIElEPSJfYTU2NmRiYzE1Yjg0ZDUxNTFmOWJjODgwOTYzOWUyMWI3ZTMwNjg4MGFmIiBWZXJzaW9uPSIyLjAiIElzc3VlSW5zdGFudD0iMjAyMS0xMC0xMlQxNDozMzowOFoiPjxzYW1sOklzc3Vlcj5odHRwczovL3NpbXBsZXNhbWxwaHAuYXBwcy5wY2ZvbmUuaW8vc2FtbDIvaWRwL21ldGFkYXRhLnBocDwvc2FtbDpJc3N1ZXI+PGRzOlNpZ25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+CiAgPGRzOlNpZ25lZEluZm8+PGRzOkNhbm9uaWNhbGl6YXRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz4KICAgIDxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNyc2Etc2hhMjU2Ii8+CiAgPGRzOlJlZmVyZW5jZSBVUkk9IiNfYTU2NmRiYzE1Yjg0ZDUxNTFmOWJjODgwOTYzOWUyMWI3ZTMwNjg4MGFmIj48ZHM6VHJhbnNmb3Jtcz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI2VudmVsb3BlZC1zaWduYXR1cmUiLz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+PC9kczpUcmFuc2Zvcm1zPjxkczpEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNzaGEyNTYiLz48ZHM6RGlnZXN0VmFsdWU+STdLWlpiY0V2R2xrb2NWSWtuT0tOQ1MwbXVvbmlDU1ViYzBwQ2NOR3phRT08L2RzOkRpZ2VzdFZhbHVlPjwvZHM6UmVmZXJlbmNlPjwvZHM6U2lnbmVkSW5mbz48ZHM6U2lnbmF0dXJlVmFsdWU+cjdkMzJtUzB0MXNJaXNEZzcway9xdGVQNWVFb1BtV2tGT3pGQytjaTdPZmNPUmZ3WmZpd0pwSlVKb2RjRE1TZjlRRjVZOFQ1ckp2VXp3UFZwZHBnVFVzUmpQaUQ4K0JxWS9QMnhQc1didE5wYks3cEhjZmlDQWU5aTAvc2o4MktZajVUZlozMlBGV1RGSmVOSVdtUkhxNnlLRVRySmpmM0UvNVBsb0g1WmMyMFljZUMxRkloR0M1akpjSXl3cXVST01WZ1NGdWhVZ0VVaHJMOVpHVUtYdE5KL2dva0JobjloTjFMM1lCdnRYYm9kUExxN0NlUXpvT1pTWDV6bllRbTR4elhiSnN1OHVKOWEva3haUnNPd2JnUXZOcU5sL2t5akgvVTBxVk9HVjJ3ZzhDemhXMy9PN2RRb3VPQVNPV1pxVitpU3dYdFIweE95Zm1kQnlJZXRBPT08L2RzOlNpZ25hdHVyZVZhbHVlPgo8ZHM6S2V5SW5mbz48ZHM6WDUwOURhdGE+PGRzOlg1MDlDZXJ0aWZpY2F0ZT5NSUlFRXpDQ0F2dWdBd0lCQWdJSkFJYzFxekxydis1bk1BMEdDU3FHU0liM0RRRUJDd1VBTUlHZk1Rc3dDUVlEVlFRR0V3SlZVekVMTUFrR0ExVUVDQXdDUTA4eEZEQVNCZ05WQkFjTUMwTmhjM1JzWlNCU2IyTnJNUnd3R2dZRFZRUUtEQk5UWVcxc0lGUmxjM1JwYm1jZ1UyVnlkbVZ5TVFzd0NRWURWUVFMREFKSlZERWdNQjRHQTFVRUF3d1hjMmx0Y0d4bGMyRnRiSEJvY0M1alptRndjSE11YVc4eElEQWVCZ2txaGtpRzl3MEJDUUVXRVdab1lXNXBhMEJ3YVhadmRHRnNMbWx2TUI0WERURTFNREl5TXpJeU5EVXdNMW9YRFRJMU1ESXlNakl5TkRVd00xb3dnWjh4Q3pBSkJnTlZCQVlUQWxWVE1Rc3dDUVlEVlFRSURBSkRUekVVTUJJR0ExVUVCd3dMUTJGemRHeGxJRkp2WTJzeEhEQWFCZ05WQkFvTUUxTmhiV3dnVkdWemRHbHVaeUJUWlhKMlpYSXhDekFKQmdOVkJBc01Ba2xVTVNBd0hnWURWUVFEREJkemFXMXdiR1Z6WVcxc2NHaHdMbU5tWVhCd2N5NXBiekVnTUI0R0NTcUdTSWIzRFFFSkFSWVJabWhoYm1sclFIQnBkbTkwWVd3dWFXOHdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFDNGNuNjJFMXhMcXBOMzRQbWJyS0Jia09YRmp6V2dKOWIrcFh1YVJmdDZBMzM5dXVJUWVvZUg1cWVTS1JWVGwzMkwwZ2R6Mlppdkx3WlhXK2NxdmZ0VlcxdHZFSHZ6SkZ5eGVUVzNmQ1VlQ1FzZWJMbkEycVJhMDdSa3hUbzZOZjI0NG1XV1JEb2Rjb0hFZkRVU2J4ZlRaNklFeFNvalNJVTJSbkQ2V2xsWVdGZEQxR0ZwQkpPbVFCOHJBYzh3SklCZEhGZFFuWDhUdGw3aFo2cnRncUVZTXpZVk11SjJGMnIxSFNVMXpTQXZ3cGRZUDZyUkdGUkpFZmRBOW1tM1dLZk5MU2M1Y2xqejBYL1RYeTB2VmxBVjk1bDlxY2ZGelBtcmtOSXN0OUZaU3dwdkI0OUx5QVZrZTA0RlFQUHdMZ1ZINGdwaGlKSDNqdlo3SStKNWxTOFZBZ01CQUFHalVEQk9NQjBHQTFVZERnUVdCQlRUeVA2Q2M1SGxCSjUrdWNWQ3dHYzVvZ0tOR3pBZkJnTlZIU01FR0RBV2dCVFR5UDZDYzVIbEJKNSt1Y1ZDd0djNW9nS05HekFNQmdOVkhSTUVCVEFEQVFIL01BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQXZNUzRFUWVQL2lwVjRqT0c1bE82L3RZQ2IvaUplQWR1T25SaGtKazBEYlgzMjlsRExaaFRUTC94L3cvOW11Q1ZjdkxyekVwNlBOK1ZXZnc1RTVGV3RaTjB5aEd0UDlSK3ZabnJWK29jMnpHRCtubzEveVNGT2UzRWlKQ081ZGVoeEtqWUVtQlJ2NXNVL0xaRktacG96S04vQk1FYTZDcUx1eGJ6Yjd5a3hWcjdFVkZYd2x0UHh6RTlUbUw5T0FDTk55RjVlSkhXTVJNbGxhclV2a2NYbGg0cHV4NGtzOWU2elY5RFFCeTJ6ZHM5ZjFJM3F4ZzBlWDZKbkdyWGkvWmlDVCtsSmdWZTNaRlhpZWppTEFpS0IwNHNYVzN0aTBMVzNseDEzWTFZbFE0L3RscGdUZ2ZJSnhLVjZueVBpTG9LMG55d2JNZCt2cEFpckR0Mk9jK2hrPC9kczpYNTA5Q2VydGlmaWNhdGU+PC9kczpYNTA5RGF0YT48L2RzOktleUluZm8+PC9kczpTaWduYXR1cmU+PHNhbWw6U3ViamVjdD48c2FtbDpOYW1lSUQgU1BOYW1lUXVhbGlmaWVyPSJodHRwOi8vbG9jYWxob3N0OjgwODAvc2FtbDIvbWV0YWRhdGEiIEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6MS4xOm5hbWVpZC1mb3JtYXQ6ZW1haWxBZGRyZXNzIj50ZXN0dXNlckBzcHJpbmcuc2VjdXJpdHkuc2FtbDwvc2FtbDpOYW1lSUQ+PHNhbWw6U3ViamVjdENvbmZpcm1hdGlvbiBNZXRob2Q9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpjbTpiZWFyZXIiPjxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj0iMjA1My0wNi0yMFQxNjoxOTo0OFoiIFJlY2lwaWVudD0iaHR0cDovL2xvY2FsaG9zdDo4MDgwL2xvZ2luL3NhbWwyL3NzbyIgSW5SZXNwb25zZVRvPSJBUlEyNzk2YmRjLTliYjgtNDkzZi1iN2UzLTFhNjIyOWYxYjdlNCIvPjwvc2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uPjwvc2FtbDpTdWJqZWN0PjxzYW1sOkNvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDIxLTEwLTEyVDE0OjMyOjM4WiIgTm90T25PckFmdGVyPSIyMDUzLTA2LTIwVDE2OjE5OjQ4WiI+PHNhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj48c2FtbDpBdWRpZW5jZT5odHRwOi8vbG9jYWxob3N0OjgwODAvc2FtbDIvbWV0YWRhdGE8L3NhbWw6QXVkaWVuY2U+PC9zYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+PC9zYW1sOkNvbmRpdGlvbnM+PHNhbWw6QXV0aG5TdGF0ZW1lbnQgQXV0aG5JbnN0YW50PSIyMDIxLTEwLTEyVDE0OjMzOjA4WiIgU2Vzc2lvbk5vdE9uT3JBZnRlcj0iMjAyMS0xMC0xMlQyMjozMzowOFoiIFNlc3Npb25JbmRleD0iXzlkNTg3ODM2YWFmMTBjNmM2ODhmMTc0YTZkYzYyNDZlZjEyNTQ4Mzk2MyI+PHNhbWw6QXV0aG5Db250ZXh0PjxzYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlBhc3N3b3JkUHJvdGVjdGVkVHJhbnNwb3J0PC9zYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPjwvc2FtbDpBdXRobkNvbnRleHQ+PC9zYW1sOkF1dGhuU3RhdGVtZW50PjxzYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD48c2FtbDpBdHRyaWJ1dGUgTmFtZT0idWlkIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj50ZXN0dXNlckBzcHJpbmcuc2VjdXJpdHkuc2FtbDwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJlZHVQZXJzb25BZmZpbGlhdGlvbiIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+bWVtYmVyPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPnVzZXI8L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48c2FtbDpBdHRyaWJ1dGUgTmFtZT0iZW1haWxBZGRyZXNzIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj50ZXN0dXNlckBzcHJpbmcuc2VjdXJpdHkuc2FtbDwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjwvc2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+PC9zYW1sOkFzc2VydGlvbj48L3NhbWxwOlJlc3BvbnNlPg==";
-
- static final Map> USER_ATTRIBUTES = new LinkedHashMap<>();
-
- static {
- USER_ATTRIBUTES.put("uid", Arrays.asList("testuser@spring.security.saml"));
- USER_ATTRIBUTES.put("eduPersonAffiliation", Arrays.asList("member", "user"));
- USER_ATTRIBUTES.put("emailAddress", Arrays.asList("testuser@spring.security.saml"));
- }
-
@Autowired
MockMvc mvc;
@@ -71,51 +49,47 @@ public class Saml2LoginApplicationITests {
this.webClient.getCookieManager().clearCookies();
}
- @Test
- void indexWhenSamlResponseThenShowsUserInformation() throws Exception {
- HttpSession session = this.mvc.perform(get("http://localhost:8080/")).andExpect(status().is3xxRedirection())
- .andExpect(redirectedUrl("http://localhost:8080/saml2/authenticate/metadata")).andReturn().getRequest()
- .getSession();
-
- this.mvc.perform(post("http://localhost:8080/login/saml2/sso").param("SAMLResponse", SIGNED_RESPONSE)
- .session((MockHttpSession) session)).andExpect(redirectedUrl("http://localhost:8080/"));
-
- this.mvc.perform(get("http://localhost:8080/").session((MockHttpSession) session))
- .andExpect(model().attribute("emailAddress", "testuser@spring.security.saml"))
- .andExpect(model().attribute("userAttributes", USER_ATTRIBUTES));
- }
-
@Test
void authenticationAttemptWhenValidThenShowsUserEmailAddress() throws Exception {
- HtmlPage relyingParty = performLogin();
- assertThat(relyingParty.asNormalizedText()).contains("You're email address is testuser@spring.security.saml");
+ performLogin();
+ HtmlPage home = (HtmlPage) this.webClient.getCurrentWindow().getEnclosedPage();
+ assertThat(home.asText()).contains("You're email address is testuser@spring.security.saml");
}
@Test
void logoutWhenRelyingPartyInitiatedLogoutThenLoginPageWithLogoutParam() throws Exception {
- HtmlPage relyingParty = performLogin();
- HtmlElement rpLogoutButton = relyingParty.getHtmlElementById("rp_logout_button");
+ performLogin();
+ HtmlPage home = (HtmlPage) this.webClient.getCurrentWindow().getEnclosedPage();
+ HtmlElement rpLogoutButton = home.getHtmlElementById("rp_logout_button");
HtmlPage loginPage = rpLogoutButton.click();
assertThat(loginPage.getUrl().getFile()).isEqualTo("/login?logout");
}
- @Test
- void logoutWhenAssertingPartyInitiatedLogoutThenLoginPageWithLogoutParam() throws Exception {
- HtmlPage relyingParty = performLogin();
- HtmlElement apLogoutButton = relyingParty.getHtmlElementById("ap_logout_button");
- HtmlPage loginPage = apLogoutButton.click();
- assertThat(loginPage.getUrl().getFile()).isEqualTo("/login?logout");
+ private void performLogin() throws Exception {
+ HtmlPage login = this.webClient.getPage("/");
+ 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("testuser@spring.security.saml");
+ password.type("12345678");
+ submit.click();
+ this.webClient.waitForBackgroundJavaScript(10000);
}
- private HtmlPage performLogin() throws IOException {
- HtmlPage assertingParty = this.webClient.getPage("/");
- HtmlForm form = assertingParty.getFormByName("f");
- HtmlInput username = form.getInputByName("username");
- HtmlInput password = form.getInputByName("password");
- HtmlSubmitInput submit = assertingParty.getHtmlElementById("submit_button");
- username.setValueAttribute("user");
- password.setValueAttribute("password");
- return submit.click();
+ private HtmlForm findForm(HtmlPage login) {
+ for (HtmlForm form : login.getForms()) {
+ try {
+ if (form.getId().equals("form19")) {
+ return form;
+ }
+ }
+ catch (ElementNotFoundException ex) {
+ // Continue
+ }
+ }
+ throw new IllegalStateException("Could not resolve login form");
}
}
diff --git a/servlet/spring-boot/java/saml2/login-single-tenant/src/main/java/example/IndexController.java b/servlet/spring-boot/java/saml2/login-single-tenant/src/main/java/example/IndexController.java
index 81c4c9f..4d22891 100644
--- a/servlet/spring-boot/java/saml2/login-single-tenant/src/main/java/example/IndexController.java
+++ b/servlet/spring-boot/java/saml2/login-single-tenant/src/main/java/example/IndexController.java
@@ -27,7 +27,7 @@ public class IndexController {
@GetMapping("/")
public String index(Model model, @AuthenticationPrincipal Saml2AuthenticatedPrincipal principal) {
- String emailAddress = principal.getFirstAttribute("emailAddress");
+ String emailAddress = principal.getFirstAttribute("email");
model.addAttribute("emailAddress", emailAddress);
model.addAttribute("userAttributes", principal.getAttributes());
return "index";
diff --git a/servlet/spring-boot/java/saml2/login-single-tenant/src/main/java/example/SecurityConfiguration.java b/servlet/spring-boot/java/saml2/login-single-tenant/src/main/java/example/SecurityConfiguration.java
index 7898c30..804ab8e 100644
--- a/servlet/spring-boot/java/saml2/login-single-tenant/src/main/java/example/SecurityConfiguration.java
+++ b/servlet/spring-boot/java/saml2/login-single-tenant/src/main/java/example/SecurityConfiguration.java
@@ -16,13 +16,26 @@
package example;
+import java.io.InputStream;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.interfaces.RSAPrivateKey;
+
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+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.RelyingPartyRegistrationRepository;
+import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrations;
+import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
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.Saml2AuthenticationTokenConverter;
@@ -39,7 +52,7 @@ public class SecurityConfiguration {
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
- .saml2Login((saml2) -> saml2.loginProcessingUrl("/login/saml2/sso"))
+ .saml2Login(Customizer.withDefaults())
.saml2Logout(Customizer.withDefaults());
// @formatter:on
@@ -49,7 +62,7 @@ public class SecurityConfiguration {
@Bean
RelyingPartyRegistrationResolver relyingPartyRegistrationResolver(
RelyingPartyRegistrationRepository registrations) {
- return new DefaultRelyingPartyRegistrationResolver((id) -> registrations.findByRegistrationId("metadata"));
+ return new DefaultRelyingPartyRegistrationResolver((id) -> registrations.findByRegistrationId("two"));
}
@Bean
@@ -65,4 +78,29 @@ public class SecurityConfiguration {
return filter;
}
+ @Bean
+ RelyingPartyRegistrationRepository repository(
+ @Value("classpath:credentials/rp-private.key") RSAPrivateKey privateKey) {
+ RelyingPartyRegistration two = RelyingPartyRegistrations
+ .fromMetadataLocation("https://dev-05937739.okta.com/app/exk4842vmapcMkohr5d7/sso/saml/metadata")
+ .registrationId("two")
+ .signingX509Credentials(
+ (c) -> c.add(Saml2X509Credential.signing(privateKey, relyingPartyCertificate())))
+ .singleLogoutServiceLocation(
+ "https://dev-05937739.okta.com/app/dev-05937739_springsecuritysaml2idptwo_1/exk4842vmapcMkohr5d7/slo/saml")
+ .singleLogoutServiceResponseLocation("http://localhost:8080/logout/saml2/slo")
+ .singleLogoutServiceBinding(Saml2MessageBinding.POST).build();
+ return new InMemoryRelyingPartyRegistrationRepository(two);
+ }
+
+ X509Certificate relyingPartyCertificate() {
+ Resource resource = new ClassPathResource("credentials/rp-certificate.crt");
+ try (InputStream is = resource.getInputStream()) {
+ return (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(is);
+ }
+ catch (Exception ex) {
+ throw new UnsupportedOperationException(ex);
+ }
+ }
+
}
diff --git a/servlet/spring-boot/java/saml2/login-single-tenant/src/main/resources/application.yml b/servlet/spring-boot/java/saml2/login-single-tenant/src/main/resources/application.yml
index 333d229..e112d19 100644
--- a/servlet/spring-boot/java/saml2/login-single-tenant/src/main/resources/application.yml
+++ b/servlet/spring-boot/java/saml2/login-single-tenant/src/main/resources/application.yml
@@ -1,17 +1,2 @@
-spring:
- security:
- saml2:
- relyingparty:
- registration:
- metadata:
- 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
- identityprovider:
- metadata-uri: https://simplesamlphp.apps.pcfone.io/saml2/idp/metadata.php
-
-
logging.level:
org.springframework.security: TRACE
diff --git a/servlet/spring-boot/java/saml2/login-single-tenant/src/main/resources/templates/index.html b/servlet/spring-boot/java/saml2/login-single-tenant/src/main/resources/templates/index.html
index 2970e85..2e970b2 100644
--- a/servlet/spring-boot/java/saml2/login-single-tenant/src/main/resources/templates/index.html
+++ b/servlet/spring-boot/java/saml2/login-single-tenant/src/main/resources/templates/index.html
@@ -36,11 +36,6 @@
-
-
- AP-initiated Logout
-
-
diff --git a/servlet/spring-boot/java/saml2/login/build.gradle b/servlet/spring-boot/java/saml2/login/build.gradle
index 305d20e..0769f02 100644
--- a/servlet/spring-boot/java/saml2/login/build.gradle
+++ b/servlet/spring-boot/java/saml2/login/build.gradle
@@ -24,7 +24,7 @@ dependencies {
implementation 'org.springframework.security:spring-security-saml2-service-provider'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
- testImplementation 'net.sourceforge.htmlunit:htmlunit'
+ testImplementation 'net.sourceforge.htmlunit:htmlunit:2.44.0'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
}
@@ -32,4 +32,4 @@ dependencies {
tasks.withType(Test).configureEach {
useJUnitPlatform()
outputs.upToDateWhen { false }
-}
\ No newline at end of file
+}
diff --git a/servlet/spring-boot/java/saml2/login/gradle.properties b/servlet/spring-boot/java/saml2/login/gradle.properties
index e19a862..0e2ed66 100644
--- a/servlet/spring-boot/java/saml2/login/gradle.properties
+++ b/servlet/spring-boot/java/saml2/login/gradle.properties
@@ -1,2 +1,3 @@
version=5.7.0-SNAPSHOT
spring-security.version=5.7.0-SNAPSHOT
+selenium-htmlunit.version=2.44.0
diff --git a/servlet/spring-boot/java/saml2/login/src/integTest/java/example/Saml2LoginApplicationITests.java b/servlet/spring-boot/java/saml2/login/src/integTest/java/example/Saml2LoginApplicationITests.java
index 2ddcfaf..00c98b9 100644
--- a/servlet/spring-boot/java/saml2/login/src/integTest/java/example/Saml2LoginApplicationITests.java
+++ b/servlet/spring-boot/java/saml2/login/src/integTest/java/example/Saml2LoginApplicationITests.java
@@ -16,50 +16,30 @@
package example;
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-
-import javax.servlet.http.HttpSession;
-
+import com.gargoylesoftware.htmlunit.ElementNotFoundException;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.HtmlElement;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlInput;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
+import com.gargoylesoftware.htmlunit.html.HtmlPasswordInput;
import com.gargoylesoftware.htmlunit.html.HtmlSubmitInput;
import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
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.mock.web.MockHttpSession;
import org.springframework.test.web.servlet.MockMvc;
import static org.assertj.core.api.Assertions.assertThat;
-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.result.MockMvcResultMatchers.model;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest
@AutoConfigureMockMvc
public class Saml2LoginApplicationITests {
- static final String SIGNED_RESPONSE = "PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIElEPSJfY2UyYjU4NTVjZjU5YmEyNDc4OTUyOGRkOGQzZDcyOGRiMGViZjNlNzNiIiBWZXJzaW9uPSIyLjAiIElzc3VlSW5zdGFudD0iMjAyMS0wMS0yMFQwMTowMzoyNFoiIERlc3RpbmF0aW9uPSJodHRwOi8vbG9jYWxob3N0OjgwODAvbG9naW4vc2FtbDIvc3NvL29uZSIgSW5SZXNwb25zZVRvPSJBUlFjOThmMjAwLWRjZjctNGRmNC1hNTIyLTA3MjA2MjA4YjA3ZCI+PHNhbWw6SXNzdWVyPmh0dHBzOi8vc2ltcGxlc2FtbC1mb3Itc3ByaW5nLXNhbWwuYXBwcy5wY2ZvbmUuaW8vc2FtbDIvaWRwL21ldGFkYXRhLnBocDwvc2FtbDpJc3N1ZXI+PGRzOlNpZ25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+CiAgPGRzOlNpZ25lZEluZm8+PGRzOkNhbm9uaWNhbGl6YXRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz4KICAgIDxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNyc2Etc2hhMjU2Ii8+CiAgPGRzOlJlZmVyZW5jZSBVUkk9IiNfY2UyYjU4NTVjZjU5YmEyNDc4OTUyOGRkOGQzZDcyOGRiMGViZjNlNzNiIj48ZHM6VHJhbnNmb3Jtcz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI2VudmVsb3BlZC1zaWduYXR1cmUiLz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+PC9kczpUcmFuc2Zvcm1zPjxkczpEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNzaGEyNTYiLz48ZHM6RGlnZXN0VmFsdWU+TXJUNS8wdTRScTl3QmMvejFQd2FrNURXZm1xOGlOVk52NldHZWFnZUVzUT08L2RzOkRpZ2VzdFZhbHVlPjwvZHM6UmVmZXJlbmNlPjwvZHM6U2lnbmVkSW5mbz48ZHM6U2lnbmF0dXJlVmFsdWU+WWg1UFE5cFVBbkkvOW5tNzgxZ040bThTS0U4T1VRTDVOMlI3ZklPZmtHVmMzRjhzNlJlR3hRNWFneUFXYnQzUDRwQ3RWeGtqbk4rMk5KeUw4QmhRMHN0dEovb2JFTHJGUldLemYyYUJaS2NCN0JHTFNtRXdoUFE3N3BHL0psMjBhaDQyaGRyWFc3TE9Ob0VZOHMyY093dm16NkQ2bW9YQWprMHV2UEVTNjhUVndxU2VmT3JwNXV0QmRSQUt6cUJRQ2NQWFJCdnB5NWJ3QkpDL2RKNE5QLzJpalE3N2I3eWhvVDQ0R21hSUduSGo0YVFaeG9kY1JuNU9oQ2hYRk4ydUk2YW1mT0ZYOThjUXZ5KzhDWm9YYUZRMnJmT2dPbGdzbmNGWGMwaXhYK05MSjlvSlJWT2hxRVpjY2JoZ3hPM2hpQ2UwemRuZHloVWxpaGMwdFU2OVlBPT08L2RzOlNpZ25hdHVyZVZhbHVlPgo8ZHM6S2V5SW5mbz48ZHM6WDUwOURhdGE+PGRzOlg1MDlDZXJ0aWZpY2F0ZT5NSUlFRXpDQ0F2dWdBd0lCQWdJSkFJYzFxekxydis1bk1BMEdDU3FHU0liM0RRRUJDd1VBTUlHZk1Rc3dDUVlEVlFRR0V3SlZVekVMTUFrR0ExVUVDQXdDUTA4eEZEQVNCZ05WQkFjTUMwTmhjM1JzWlNCU2IyTnJNUnd3R2dZRFZRUUtEQk5UWVcxc0lGUmxjM1JwYm1jZ1UyVnlkbVZ5TVFzd0NRWURWUVFMREFKSlZERWdNQjRHQTFVRUF3d1hjMmx0Y0d4bGMyRnRiSEJvY0M1alptRndjSE11YVc4eElEQWVCZ2txaGtpRzl3MEJDUUVXRVdab1lXNXBhMEJ3YVhadmRHRnNMbWx2TUI0WERURTFNREl5TXpJeU5EVXdNMW9YRFRJMU1ESXlNakl5TkRVd00xb3dnWjh4Q3pBSkJnTlZCQVlUQWxWVE1Rc3dDUVlEVlFRSURBSkRUekVVTUJJR0ExVUVCd3dMUTJGemRHeGxJRkp2WTJzeEhEQWFCZ05WQkFvTUUxTmhiV3dnVkdWemRHbHVaeUJUWlhKMlpYSXhDekFKQmdOVkJBc01Ba2xVTVNBd0hnWURWUVFEREJkemFXMXdiR1Z6WVcxc2NHaHdMbU5tWVhCd2N5NXBiekVnTUI0R0NTcUdTSWIzRFFFSkFSWVJabWhoYm1sclFIQnBkbTkwWVd3dWFXOHdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFDNGNuNjJFMXhMcXBOMzRQbWJyS0Jia09YRmp6V2dKOWIrcFh1YVJmdDZBMzM5dXVJUWVvZUg1cWVTS1JWVGwzMkwwZ2R6Mlppdkx3WlhXK2NxdmZ0VlcxdHZFSHZ6SkZ5eGVUVzNmQ1VlQ1FzZWJMbkEycVJhMDdSa3hUbzZOZjI0NG1XV1JEb2Rjb0hFZkRVU2J4ZlRaNklFeFNvalNJVTJSbkQ2V2xsWVdGZEQxR0ZwQkpPbVFCOHJBYzh3SklCZEhGZFFuWDhUdGw3aFo2cnRncUVZTXpZVk11SjJGMnIxSFNVMXpTQXZ3cGRZUDZyUkdGUkpFZmRBOW1tM1dLZk5MU2M1Y2xqejBYL1RYeTB2VmxBVjk1bDlxY2ZGelBtcmtOSXN0OUZaU3dwdkI0OUx5QVZrZTA0RlFQUHdMZ1ZINGdwaGlKSDNqdlo3SStKNWxTOFZBZ01CQUFHalVEQk9NQjBHQTFVZERnUVdCQlRUeVA2Q2M1SGxCSjUrdWNWQ3dHYzVvZ0tOR3pBZkJnTlZIU01FR0RBV2dCVFR5UDZDYzVIbEJKNSt1Y1ZDd0djNW9nS05HekFNQmdOVkhSTUVCVEFEQVFIL01BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQXZNUzRFUWVQL2lwVjRqT0c1bE82L3RZQ2IvaUplQWR1T25SaGtKazBEYlgzMjlsRExaaFRUTC94L3cvOW11Q1ZjdkxyekVwNlBOK1ZXZnc1RTVGV3RaTjB5aEd0UDlSK3ZabnJWK29jMnpHRCtubzEveVNGT2UzRWlKQ081ZGVoeEtqWUVtQlJ2NXNVL0xaRktacG96S04vQk1FYTZDcUx1eGJ6Yjd5a3hWcjdFVkZYd2x0UHh6RTlUbUw5T0FDTk55RjVlSkhXTVJNbGxhclV2a2NYbGg0cHV4NGtzOWU2elY5RFFCeTJ6ZHM5ZjFJM3F4ZzBlWDZKbkdyWGkvWmlDVCtsSmdWZTNaRlhpZWppTEFpS0IwNHNYVzN0aTBMVzNseDEzWTFZbFE0L3RscGdUZ2ZJSnhLVjZueVBpTG9LMG55d2JNZCt2cEFpckR0Mk9jK2hrPC9kczpYNTA5Q2VydGlmaWNhdGU+PC9kczpYNTA5RGF0YT48L2RzOktleUluZm8+PC9kczpTaWduYXR1cmU+PHNhbWxwOlN0YXR1cz48c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+PC9zYW1scDpTdGF0dXM+PHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9Il9jYjYzYmMzNmMyYzAzYjRlMWJjZDViMWIwY2MyZTE2NWQwNDQ1NDZlODgiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDIxLTAxLTIwVDAxOjAzOjI0WiI+PHNhbWw6SXNzdWVyPmh0dHBzOi8vc2ltcGxlc2FtbC1mb3Itc3ByaW5nLXNhbWwuYXBwcy5wY2ZvbmUuaW8vc2FtbDIvaWRwL21ldGFkYXRhLnBocDwvc2FtbDpJc3N1ZXI+PGRzOlNpZ25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+CiAgPGRzOlNpZ25lZEluZm8+PGRzOkNhbm9uaWNhbGl6YXRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz4KICAgIDxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNyc2Etc2hhMjU2Ii8+CiAgPGRzOlJlZmVyZW5jZSBVUkk9IiNfY2I2M2JjMzZjMmMwM2I0ZTFiY2Q1YjFiMGNjMmUxNjVkMDQ0NTQ2ZTg4Ij48ZHM6VHJhbnNmb3Jtcz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI2VudmVsb3BlZC1zaWduYXR1cmUiLz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+PC9kczpUcmFuc2Zvcm1zPjxkczpEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNzaGEyNTYiLz48ZHM6RGlnZXN0VmFsdWU+VWFtN2NHVGlCd2xuRDBJdGd5aU5KVjN2Z0NPNytZZkRxSWJrWERkR3hrQT08L2RzOkRpZ2VzdFZhbHVlPjwvZHM6UmVmZXJlbmNlPjwvZHM6U2lnbmVkSW5mbz48ZHM6U2lnbmF0dXJlVmFsdWU+RkhpWUpETDlKTXM1Y2V5WXhUVVgrUndEQm45RFYzVE81dDFham4raGFtb1c2MUpBY0JaNjEwUHpYMzN3alA3Mk1kYmdDWnR5ZmNrSktZUUpPT0szRkxLTkJLQkphOTNsSS9rZWZjTXRTUGxBU2hESm9ydmU0U0tWa29WbzZLVnB0eC9OTnowRkhJNURFZTZiUUVjZWFiNERVNDFVdEpQMHUyWm16ejVjNC83VzhLdmt6MkxMbXhWZlE3Q2todmgvNzBhWHlkWVBVRml3bE4vV1lTV3JYVU9oOXNFTDFiZGVlQzFkYnpaeVdNNldnSkdRMUpJblBnSGd0YTlxMU96eGliOFlLRXpQSUMzVEZldkU1Y0phMFQvd1NzOVIxN0JSR09OclhTTWQvRCt4YkY0Z3lIYW5EZFlOYVN2TzdIS2p4bzRwYk1aY05peDhMTkVYZGtiZEx3PT08L2RzOlNpZ25hdHVyZVZhbHVlPgo8ZHM6S2V5SW5mbz48ZHM6WDUwOURhdGE+PGRzOlg1MDlDZXJ0aWZpY2F0ZT5NSUlFRXpDQ0F2dWdBd0lCQWdJSkFJYzFxekxydis1bk1BMEdDU3FHU0liM0RRRUJDd1VBTUlHZk1Rc3dDUVlEVlFRR0V3SlZVekVMTUFrR0ExVUVDQXdDUTA4eEZEQVNCZ05WQkFjTUMwTmhjM1JzWlNCU2IyTnJNUnd3R2dZRFZRUUtEQk5UWVcxc0lGUmxjM1JwYm1jZ1UyVnlkbVZ5TVFzd0NRWURWUVFMREFKSlZERWdNQjRHQTFVRUF3d1hjMmx0Y0d4bGMyRnRiSEJvY0M1alptRndjSE11YVc4eElEQWVCZ2txaGtpRzl3MEJDUUVXRVdab1lXNXBhMEJ3YVhadmRHRnNMbWx2TUI0WERURTFNREl5TXpJeU5EVXdNMW9YRFRJMU1ESXlNakl5TkRVd00xb3dnWjh4Q3pBSkJnTlZCQVlUQWxWVE1Rc3dDUVlEVlFRSURBSkRUekVVTUJJR0ExVUVCd3dMUTJGemRHeGxJRkp2WTJzeEhEQWFCZ05WQkFvTUUxTmhiV3dnVkdWemRHbHVaeUJUWlhKMlpYSXhDekFKQmdOVkJBc01Ba2xVTVNBd0hnWURWUVFEREJkemFXMXdiR1Z6WVcxc2NHaHdMbU5tWVhCd2N5NXBiekVnTUI0R0NTcUdTSWIzRFFFSkFSWVJabWhoYm1sclFIQnBkbTkwWVd3dWFXOHdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFDNGNuNjJFMXhMcXBOMzRQbWJyS0Jia09YRmp6V2dKOWIrcFh1YVJmdDZBMzM5dXVJUWVvZUg1cWVTS1JWVGwzMkwwZ2R6Mlppdkx3WlhXK2NxdmZ0VlcxdHZFSHZ6SkZ5eGVUVzNmQ1VlQ1FzZWJMbkEycVJhMDdSa3hUbzZOZjI0NG1XV1JEb2Rjb0hFZkRVU2J4ZlRaNklFeFNvalNJVTJSbkQ2V2xsWVdGZEQxR0ZwQkpPbVFCOHJBYzh3SklCZEhGZFFuWDhUdGw3aFo2cnRncUVZTXpZVk11SjJGMnIxSFNVMXpTQXZ3cGRZUDZyUkdGUkpFZmRBOW1tM1dLZk5MU2M1Y2xqejBYL1RYeTB2VmxBVjk1bDlxY2ZGelBtcmtOSXN0OUZaU3dwdkI0OUx5QVZrZTA0RlFQUHdMZ1ZINGdwaGlKSDNqdlo3SStKNWxTOFZBZ01CQUFHalVEQk9NQjBHQTFVZERnUVdCQlRUeVA2Q2M1SGxCSjUrdWNWQ3dHYzVvZ0tOR3pBZkJnTlZIU01FR0RBV2dCVFR5UDZDYzVIbEJKNSt1Y1ZDd0djNW9nS05HekFNQmdOVkhSTUVCVEFEQVFIL01BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQXZNUzRFUWVQL2lwVjRqT0c1bE82L3RZQ2IvaUplQWR1T25SaGtKazBEYlgzMjlsRExaaFRUTC94L3cvOW11Q1ZjdkxyekVwNlBOK1ZXZnc1RTVGV3RaTjB5aEd0UDlSK3ZabnJWK29jMnpHRCtubzEveVNGT2UzRWlKQ081ZGVoeEtqWUVtQlJ2NXNVL0xaRktacG96S04vQk1FYTZDcUx1eGJ6Yjd5a3hWcjdFVkZYd2x0UHh6RTlUbUw5T0FDTk55RjVlSkhXTVJNbGxhclV2a2NYbGg0cHV4NGtzOWU2elY5RFFCeTJ6ZHM5ZjFJM3F4ZzBlWDZKbkdyWGkvWmlDVCtsSmdWZTNaRlhpZWppTEFpS0IwNHNYVzN0aTBMVzNseDEzWTFZbFE0L3RscGdUZ2ZJSnhLVjZueVBpTG9LMG55d2JNZCt2cEFpckR0Mk9jK2hrPC9kczpYNTA5Q2VydGlmaWNhdGU+PC9kczpYNTA5RGF0YT48L2RzOktleUluZm8+PC9kczpTaWduYXR1cmU+PHNhbWw6U3ViamVjdD48c2FtbDpOYW1lSUQgU1BOYW1lUXVhbGlmaWVyPSJodHRwOi8vbG9jYWxob3N0OjgwODAvc2FtbDIvc2VydmljZS1wcm92aWRlci1tZXRhZGF0YS9vbmUiIEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6MS4xOm5hbWVpZC1mb3JtYXQ6ZW1haWxBZGRyZXNzIj50ZXN0dXNlckBzcHJpbmcuc2VjdXJpdHkuc2FtbDwvc2FtbDpOYW1lSUQ+PHNhbWw6U3ViamVjdENvbmZpcm1hdGlvbiBNZXRob2Q9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpjbTpiZWFyZXIiPjxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj0iMjA1Mi0wOS0yOFQwMjo1MDowNFoiIFJlY2lwaWVudD0iaHR0cDovL2xvY2FsaG9zdDo4MDgwL2xvZ2luL3NhbWwyL3Nzby9vbmUiIEluUmVzcG9uc2VUbz0iQVJRYzk4ZjIwMC1kY2Y3LTRkZjQtYTUyMi0wNzIwNjIwOGIwN2QiLz48L3NhbWw6U3ViamVjdENvbmZpcm1hdGlvbj48L3NhbWw6U3ViamVjdD48c2FtbDpDb25kaXRpb25zIE5vdEJlZm9yZT0iMjAyMS0wMS0yMFQwMTowMjo1NFoiIE5vdE9uT3JBZnRlcj0iMjA1Mi0wOS0yOFQwMjo1MDowNFoiPjxzYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+PHNhbWw6QXVkaWVuY2U+aHR0cDovL2xvY2FsaG9zdDo4MDgwL3NhbWwyL3NlcnZpY2UtcHJvdmlkZXItbWV0YWRhdGEvb25lPC9zYW1sOkF1ZGllbmNlPjwvc2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPjwvc2FtbDpDb25kaXRpb25zPjxzYW1sOkF1dGhuU3RhdGVtZW50IEF1dGhuSW5zdGFudD0iMjAyMS0wMS0yMFQwMDo0ODoyOVoiIFNlc3Npb25Ob3RPbk9yQWZ0ZXI9IjIwMjEtMDEtMjBUMDg6NDg6MjlaIiBTZXNzaW9uSW5kZXg9Il9lN2ExYTllNDk1YmZlMjI2NjQ5ZThkY2MzN2UxNDE1NDQ5NTIxNWQ2ZWIiPjxzYW1sOkF1dGhuQ29udGV4dD48c2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj51cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YWM6Y2xhc3NlczpQYXNzd29yZFByb3RlY3RlZFRyYW5zcG9ydDwvc2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj48L3NhbWw6QXV0aG5Db250ZXh0Pjwvc2FtbDpBdXRoblN0YXRlbWVudD48c2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+PHNhbWw6QXR0cmlidXRlIE5hbWU9InVpZCIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+dGVzdHVzZXJAc3ByaW5nLnNlY3VyaXR5LnNhbWw8L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48c2FtbDpBdHRyaWJ1dGUgTmFtZT0iZWR1UGVyc29uQWZmaWxpYXRpb24iIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPm1lbWJlcjwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj51c2VyPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU+PHNhbWw6QXR0cmlidXRlIE5hbWU9ImVtYWlsQWRkcmVzcyIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+dGVzdHVzZXJAc3ByaW5nLnNlY3VyaXR5LnNhbWw8L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48L3NhbWw6QXR0cmlidXRlU3RhdGVtZW50Pjwvc2FtbDpBc3NlcnRpb24+PC9zYW1scDpSZXNwb25zZT4=";
-
- static final Map> USER_ATTRIBUTES = new LinkedHashMap<>();
-
- static {
- USER_ATTRIBUTES.put("uid", Arrays.asList("testuser@spring.security.saml"));
- USER_ATTRIBUTES.put("eduPersonAffiliation", Arrays.asList("member", "user"));
- USER_ATTRIBUTES.put("emailAddress", Arrays.asList("testuser@spring.security.saml"));
- }
-
@Autowired
MockMvc mvc;
@@ -71,51 +51,78 @@ public class Saml2LoginApplicationITests {
this.webClient.getCookieManager().clearCookies();
}
- @Test
- void indexWhenSamlResponseThenShowsUserInformation() throws Exception {
- HttpSession session = this.mvc.perform(get("http://localhost:8080/")).andExpect(status().is3xxRedirection())
- .andExpect(redirectedUrl("http://localhost:8080/login")).andReturn().getRequest().getSession();
-
- this.mvc.perform(post("http://localhost:8080/login/saml2/sso/one").param("SAMLResponse", SIGNED_RESPONSE)
- .session((MockHttpSession) session)).andExpect(redirectedUrl("http://localhost:8080/"));
-
- this.mvc.perform(get("http://localhost:8080/").session((MockHttpSession) session))
- .andExpect(model().attribute("emailAddress", "testuser@spring.security.saml"))
- .andExpect(model().attribute("userAttributes", USER_ATTRIBUTES));
- }
-
- @Test
- void authenticationAttemptWhenValidThenShowsUserEmailAddress() throws Exception {
- HtmlPage relyingParty = performLogin();
- assertThat(relyingParty.asNormalizedText()).contains("You're email address is testuser@spring.security.saml");
- }
-
- @Test
- void logoutWhenRelyingPartyInitiatedLogoutThenLoginPageWithLogoutParam() throws Exception {
- HtmlPage relyingParty = performLogin();
- HtmlElement rpLogoutButton = relyingParty.getHtmlElementById("rp_logout_button");
- HtmlPage loginPage = rpLogoutButton.click();
- assertThat(loginPage.getUrl().getFile()).isEqualTo("/login?logout");
- }
-
- @Test
- void logoutWhenAssertingPartyInitiatedLogoutThenLoginPageWithLogoutParam() throws Exception {
- HtmlPage relyingParty = performLogin();
- HtmlElement apLogoutButton = relyingParty.getHtmlElementById("ap_logout_button");
- HtmlPage loginPage = apLogoutButton.click();
- assertThat(loginPage.getUrl().getFile()).isEqualTo("/login?logout");
- }
-
- private HtmlPage performLogin() throws IOException {
+ private void performLogin(String registrationId) throws Exception {
HtmlPage login = this.webClient.getPage("/");
- HtmlPage assertingParty = login.getAnchorByHref("/saml2/authenticate/one").click();
- HtmlForm form = assertingParty.getFormByName("f");
+ login.getAnchorByHref("/saml2/authenticate/" + registrationId).click();
+ this.webClient.waitForBackgroundJavaScript(10000);
+ HtmlPage okta = (HtmlPage) this.webClient.getCurrentWindow().getEnclosedPage();
+ this.webClient.waitForBackgroundJavaScript(10000);
+ HtmlForm form = findForm(okta);
HtmlInput username = form.getInputByName("username");
- HtmlInput password = form.getInputByName("password");
- HtmlSubmitInput submit = assertingParty.getHtmlElementById("submit_button");
- username.setValueAttribute("user");
- password.setValueAttribute("password");
- return submit.click();
+ HtmlPasswordInput password = form.getInputByName("password");
+ HtmlSubmitInput submit = okta.getHtmlElementById("okta-signin-submit");
+ username.type("testuser@spring.security.saml");
+ password.type("12345678");
+ submit.click();
+ this.webClient.waitForBackgroundJavaScript(10000);
+ }
+
+ private HtmlForm findForm(HtmlPage login) {
+ for (HtmlForm form : login.getForms()) {
+ try {
+ if (form.getId().equals("form19")) {
+ return form;
+ }
+ }
+ catch (ElementNotFoundException ex) {
+ // Continue
+ }
+ }
+ throw new IllegalStateException("Could not resolve login form");
+ }
+
+ @DisplayName("Tenant one tests")
+ @Nested
+ class TenantOneTests {
+
+ @Test
+ void authenticationAttemptWhenValidThenShowsUserEmailAddress() throws Exception {
+ performLogin("one");
+ HtmlPage home = (HtmlPage) Saml2LoginApplicationITests.this.webClient.getCurrentWindow().getEnclosedPage();
+ assertThat(home.asText()).contains("You're email address is testuser@spring.security.saml");
+ }
+
+ @Test
+ void logoutWhenRelyingPartyInitiatedLogoutThenLoginPageWithLogoutParam() throws Exception {
+ performLogin("one");
+ HtmlPage home = (HtmlPage) Saml2LoginApplicationITests.this.webClient.getCurrentWindow().getEnclosedPage();
+ HtmlElement rpLogoutButton = home.getHtmlElementById("rp_logout_button");
+ HtmlPage loginPage = rpLogoutButton.click();
+ assertThat(loginPage.getUrl().getFile()).isEqualTo("/login?logout");
+ }
+
+ }
+
+ @DisplayName("Tenant two tests")
+ @Nested
+ class TenantTwoTests {
+
+ @Test
+ void authenticationAttemptWhenValidThenShowsUserEmailAddress() throws Exception {
+ performLogin("two");
+ HtmlPage home = (HtmlPage) Saml2LoginApplicationITests.this.webClient.getCurrentWindow().getEnclosedPage();
+ assertThat(home.asText()).contains("You're email address is testuser@spring.security.saml");
+ }
+
+ @Test
+ void logoutWhenRelyingPartyInitiatedLogoutThenLoginPageWithLogoutParam() throws Exception {
+ performLogin("two");
+ HtmlPage home = (HtmlPage) Saml2LoginApplicationITests.this.webClient.getCurrentWindow().getEnclosedPage();
+ HtmlElement rpLogoutButton = home.getHtmlElementById("rp_logout_button");
+ HtmlPage loginPage = rpLogoutButton.click();
+ assertThat(loginPage.getUrl().getFile()).isEqualTo("/login?logout");
+ }
+
}
}
diff --git a/servlet/spring-boot/java/saml2/login/src/main/java/example/IndexController.java b/servlet/spring-boot/java/saml2/login/src/main/java/example/IndexController.java
index 81c4c9f..4d22891 100644
--- a/servlet/spring-boot/java/saml2/login/src/main/java/example/IndexController.java
+++ b/servlet/spring-boot/java/saml2/login/src/main/java/example/IndexController.java
@@ -27,7 +27,7 @@ public class IndexController {
@GetMapping("/")
public String index(Model model, @AuthenticationPrincipal Saml2AuthenticatedPrincipal principal) {
- String emailAddress = principal.getFirstAttribute("emailAddress");
+ String emailAddress = principal.getFirstAttribute("email");
model.addAttribute("emailAddress", emailAddress);
model.addAttribute("userAttributes", principal.getAttributes());
return "index";
diff --git a/servlet/spring-boot/java/saml2/login/src/main/java/example/SecurityConfiguration.java b/servlet/spring-boot/java/saml2/login/src/main/java/example/SecurityConfiguration.java
index 1214b95..a58d88f 100644
--- a/servlet/spring-boot/java/saml2/login/src/main/java/example/SecurityConfiguration.java
+++ b/servlet/spring-boot/java/saml2/login/src/main/java/example/SecurityConfiguration.java
@@ -16,13 +16,26 @@
package example;
+import java.io.InputStream;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.interfaces.RSAPrivateKey;
+
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+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.RelyingPartyRegistrationRepository;
+import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrations;
+import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
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;
@@ -59,4 +72,38 @@ public class SecurityConfiguration {
return filter;
}
+ @Bean
+ RelyingPartyRegistrationRepository repository(
+ @Value("classpath:credentials/rp-private.key") RSAPrivateKey privateKey) {
+ RelyingPartyRegistration one = RelyingPartyRegistrations
+ .fromMetadataLocation("https://dev-05937739.okta.com/app/exk46xofd8NZvFCpS5d7/sso/saml/metadata")
+ .registrationId("one")
+ .signingX509Credentials(
+ (c) -> c.add(Saml2X509Credential.signing(privateKey, relyingPartyCertificate())))
+ .singleLogoutServiceLocation(
+ "https://dev-05937739.okta.com/app/dev-05937739_springgsecuritysaml2idp_1/exk46xofd8NZvFCpS5d7/slo/saml")
+ .singleLogoutServiceResponseLocation("http://localhost:8080/logout/saml2/slo")
+ .singleLogoutServiceBinding(Saml2MessageBinding.POST).build();
+ RelyingPartyRegistration two = RelyingPartyRegistrations
+ .fromMetadataLocation("https://dev-05937739.okta.com/app/exk4842vmapcMkohr5d7/sso/saml/metadata")
+ .registrationId("two")
+ .signingX509Credentials(
+ (c) -> c.add(Saml2X509Credential.signing(privateKey, relyingPartyCertificate())))
+ .singleLogoutServiceLocation(
+ "https://dev-05937739.okta.com/app/dev-05937739_springsecuritysaml2idptwo_1/exk4842vmapcMkohr5d7/slo/saml")
+ .singleLogoutServiceResponseLocation("http://localhost:8080/logout/saml2/slo")
+ .singleLogoutServiceBinding(Saml2MessageBinding.POST).build();
+ return new InMemoryRelyingPartyRegistrationRepository(one, two);
+ }
+
+ X509Certificate relyingPartyCertificate() {
+ Resource resource = new ClassPathResource("credentials/rp-certificate.crt");
+ try (InputStream is = resource.getInputStream()) {
+ return (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(is);
+ }
+ catch (Exception ex) {
+ throw new UnsupportedOperationException(ex);
+ }
+ }
+
}
diff --git a/servlet/spring-boot/java/saml2/login/src/main/resources/application.yml b/servlet/spring-boot/java/saml2/login/src/main/resources/application.yml
index bb0f783..e112d19 100644
--- a/servlet/spring-boot/java/saml2/login/src/main/resources/application.yml
+++ b/servlet/spring-boot/java/saml2/login/src/main/resources/application.yml
@@ -1,20 +1,2 @@
-spring:
- security:
- saml2:
- relyingparty:
- registration:
- one:
- signing.credentials: &rp-metadata
- - private-key-location: classpath:credentials/rp-private.key
- certificate-location: classpath:credentials/rp-certificate.crt
- identityprovider:
- metadata-uri: https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/metadata.php
- two:
- signing.credentials: *rp-metadata
- decryption.credentials: *rp-metadata
- identityprovider:
- metadata-uri: https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/metadata.php
-
-
logging.level:
org.springframework.security: TRACE
diff --git a/servlet/spring-boot/java/saml2/login/src/main/resources/templates/index.html b/servlet/spring-boot/java/saml2/login/src/main/resources/templates/index.html
index 2970e85..2e970b2 100644
--- a/servlet/spring-boot/java/saml2/login/src/main/resources/templates/index.html
+++ b/servlet/spring-boot/java/saml2/login/src/main/resources/templates/index.html
@@ -36,11 +36,6 @@
-
-
- AP-initiated Logout
-
-
diff --git a/servlet/spring-boot/java/saml2/refreshable-metadata/build.gradle b/servlet/spring-boot/java/saml2/refreshable-metadata/build.gradle
index 4c0427d..b933682 100644
--- a/servlet/spring-boot/java/saml2/refreshable-metadata/build.gradle
+++ b/servlet/spring-boot/java/saml2/refreshable-metadata/build.gradle
@@ -24,7 +24,7 @@ dependencies {
implementation 'org.springframework.security:spring-security-saml2-service-provider'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
- testImplementation 'net.sourceforge.htmlunit:htmlunit'
+ testImplementation 'net.sourceforge.htmlunit:htmlunit:2.44.0'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
}
@@ -32,4 +32,4 @@ dependencies {
tasks.withType(Test).configureEach {
useJUnitPlatform()
outputs.upToDateWhen { false }
-}
\ No newline at end of file
+}
diff --git a/servlet/spring-boot/java/saml2/refreshable-metadata/src/integTest/java/example/Saml2LoginApplicationITests.java b/servlet/spring-boot/java/saml2/refreshable-metadata/src/integTest/java/example/Saml2LoginApplicationITests.java
index 00334e8..b73e69c 100644
--- a/servlet/spring-boot/java/saml2/refreshable-metadata/src/integTest/java/example/Saml2LoginApplicationITests.java
+++ b/servlet/spring-boot/java/saml2/refreshable-metadata/src/integTest/java/example/Saml2LoginApplicationITests.java
@@ -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.
@@ -16,19 +16,12 @@
package example;
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-
-import javax.servlet.http.HttpSession;
-
+import com.gargoylesoftware.htmlunit.ElementNotFoundException;
import com.gargoylesoftware.htmlunit.WebClient;
-import com.gargoylesoftware.htmlunit.html.HtmlElement;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlInput;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
+import com.gargoylesoftware.htmlunit.html.HtmlPasswordInput;
import com.gargoylesoftware.htmlunit.html.HtmlSubmitInput;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -36,30 +29,14 @@ 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.mock.web.MockHttpSession;
import org.springframework.test.web.servlet.MockMvc;
import static org.assertj.core.api.Assertions.assertThat;
-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.result.MockMvcResultMatchers.model;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest
@AutoConfigureMockMvc
public class Saml2LoginApplicationITests {
- static final String SIGNED_RESPONSE = "PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIElEPSJfY2UyYjU4NTVjZjU5YmEyNDc4OTUyOGRkOGQzZDcyOGRiMGViZjNlNzNiIiBWZXJzaW9uPSIyLjAiIElzc3VlSW5zdGFudD0iMjAyMS0wMS0yMFQwMTowMzoyNFoiIERlc3RpbmF0aW9uPSJodHRwOi8vbG9jYWxob3N0OjgwODAvbG9naW4vc2FtbDIvc3NvL29uZSIgSW5SZXNwb25zZVRvPSJBUlFjOThmMjAwLWRjZjctNGRmNC1hNTIyLTA3MjA2MjA4YjA3ZCI+PHNhbWw6SXNzdWVyPmh0dHBzOi8vc2ltcGxlc2FtbC1mb3Itc3ByaW5nLXNhbWwuYXBwcy5wY2ZvbmUuaW8vc2FtbDIvaWRwL21ldGFkYXRhLnBocDwvc2FtbDpJc3N1ZXI+PGRzOlNpZ25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+CiAgPGRzOlNpZ25lZEluZm8+PGRzOkNhbm9uaWNhbGl6YXRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz4KICAgIDxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNyc2Etc2hhMjU2Ii8+CiAgPGRzOlJlZmVyZW5jZSBVUkk9IiNfY2UyYjU4NTVjZjU5YmEyNDc4OTUyOGRkOGQzZDcyOGRiMGViZjNlNzNiIj48ZHM6VHJhbnNmb3Jtcz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI2VudmVsb3BlZC1zaWduYXR1cmUiLz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+PC9kczpUcmFuc2Zvcm1zPjxkczpEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNzaGEyNTYiLz48ZHM6RGlnZXN0VmFsdWU+TXJUNS8wdTRScTl3QmMvejFQd2FrNURXZm1xOGlOVk52NldHZWFnZUVzUT08L2RzOkRpZ2VzdFZhbHVlPjwvZHM6UmVmZXJlbmNlPjwvZHM6U2lnbmVkSW5mbz48ZHM6U2lnbmF0dXJlVmFsdWU+WWg1UFE5cFVBbkkvOW5tNzgxZ040bThTS0U4T1VRTDVOMlI3ZklPZmtHVmMzRjhzNlJlR3hRNWFneUFXYnQzUDRwQ3RWeGtqbk4rMk5KeUw4QmhRMHN0dEovb2JFTHJGUldLemYyYUJaS2NCN0JHTFNtRXdoUFE3N3BHL0psMjBhaDQyaGRyWFc3TE9Ob0VZOHMyY093dm16NkQ2bW9YQWprMHV2UEVTNjhUVndxU2VmT3JwNXV0QmRSQUt6cUJRQ2NQWFJCdnB5NWJ3QkpDL2RKNE5QLzJpalE3N2I3eWhvVDQ0R21hSUduSGo0YVFaeG9kY1JuNU9oQ2hYRk4ydUk2YW1mT0ZYOThjUXZ5KzhDWm9YYUZRMnJmT2dPbGdzbmNGWGMwaXhYK05MSjlvSlJWT2hxRVpjY2JoZ3hPM2hpQ2UwemRuZHloVWxpaGMwdFU2OVlBPT08L2RzOlNpZ25hdHVyZVZhbHVlPgo8ZHM6S2V5SW5mbz48ZHM6WDUwOURhdGE+PGRzOlg1MDlDZXJ0aWZpY2F0ZT5NSUlFRXpDQ0F2dWdBd0lCQWdJSkFJYzFxekxydis1bk1BMEdDU3FHU0liM0RRRUJDd1VBTUlHZk1Rc3dDUVlEVlFRR0V3SlZVekVMTUFrR0ExVUVDQXdDUTA4eEZEQVNCZ05WQkFjTUMwTmhjM1JzWlNCU2IyTnJNUnd3R2dZRFZRUUtEQk5UWVcxc0lGUmxjM1JwYm1jZ1UyVnlkbVZ5TVFzd0NRWURWUVFMREFKSlZERWdNQjRHQTFVRUF3d1hjMmx0Y0d4bGMyRnRiSEJvY0M1alptRndjSE11YVc4eElEQWVCZ2txaGtpRzl3MEJDUUVXRVdab1lXNXBhMEJ3YVhadmRHRnNMbWx2TUI0WERURTFNREl5TXpJeU5EVXdNMW9YRFRJMU1ESXlNakl5TkRVd00xb3dnWjh4Q3pBSkJnTlZCQVlUQWxWVE1Rc3dDUVlEVlFRSURBSkRUekVVTUJJR0ExVUVCd3dMUTJGemRHeGxJRkp2WTJzeEhEQWFCZ05WQkFvTUUxTmhiV3dnVkdWemRHbHVaeUJUWlhKMlpYSXhDekFKQmdOVkJBc01Ba2xVTVNBd0hnWURWUVFEREJkemFXMXdiR1Z6WVcxc2NHaHdMbU5tWVhCd2N5NXBiekVnTUI0R0NTcUdTSWIzRFFFSkFSWVJabWhoYm1sclFIQnBkbTkwWVd3dWFXOHdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFDNGNuNjJFMXhMcXBOMzRQbWJyS0Jia09YRmp6V2dKOWIrcFh1YVJmdDZBMzM5dXVJUWVvZUg1cWVTS1JWVGwzMkwwZ2R6Mlppdkx3WlhXK2NxdmZ0VlcxdHZFSHZ6SkZ5eGVUVzNmQ1VlQ1FzZWJMbkEycVJhMDdSa3hUbzZOZjI0NG1XV1JEb2Rjb0hFZkRVU2J4ZlRaNklFeFNvalNJVTJSbkQ2V2xsWVdGZEQxR0ZwQkpPbVFCOHJBYzh3SklCZEhGZFFuWDhUdGw3aFo2cnRncUVZTXpZVk11SjJGMnIxSFNVMXpTQXZ3cGRZUDZyUkdGUkpFZmRBOW1tM1dLZk5MU2M1Y2xqejBYL1RYeTB2VmxBVjk1bDlxY2ZGelBtcmtOSXN0OUZaU3dwdkI0OUx5QVZrZTA0RlFQUHdMZ1ZINGdwaGlKSDNqdlo3SStKNWxTOFZBZ01CQUFHalVEQk9NQjBHQTFVZERnUVdCQlRUeVA2Q2M1SGxCSjUrdWNWQ3dHYzVvZ0tOR3pBZkJnTlZIU01FR0RBV2dCVFR5UDZDYzVIbEJKNSt1Y1ZDd0djNW9nS05HekFNQmdOVkhSTUVCVEFEQVFIL01BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQXZNUzRFUWVQL2lwVjRqT0c1bE82L3RZQ2IvaUplQWR1T25SaGtKazBEYlgzMjlsRExaaFRUTC94L3cvOW11Q1ZjdkxyekVwNlBOK1ZXZnc1RTVGV3RaTjB5aEd0UDlSK3ZabnJWK29jMnpHRCtubzEveVNGT2UzRWlKQ081ZGVoeEtqWUVtQlJ2NXNVL0xaRktacG96S04vQk1FYTZDcUx1eGJ6Yjd5a3hWcjdFVkZYd2x0UHh6RTlUbUw5T0FDTk55RjVlSkhXTVJNbGxhclV2a2NYbGg0cHV4NGtzOWU2elY5RFFCeTJ6ZHM5ZjFJM3F4ZzBlWDZKbkdyWGkvWmlDVCtsSmdWZTNaRlhpZWppTEFpS0IwNHNYVzN0aTBMVzNseDEzWTFZbFE0L3RscGdUZ2ZJSnhLVjZueVBpTG9LMG55d2JNZCt2cEFpckR0Mk9jK2hrPC9kczpYNTA5Q2VydGlmaWNhdGU+PC9kczpYNTA5RGF0YT48L2RzOktleUluZm8+PC9kczpTaWduYXR1cmU+PHNhbWxwOlN0YXR1cz48c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+PC9zYW1scDpTdGF0dXM+PHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9Il9jYjYzYmMzNmMyYzAzYjRlMWJjZDViMWIwY2MyZTE2NWQwNDQ1NDZlODgiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDIxLTAxLTIwVDAxOjAzOjI0WiI+PHNhbWw6SXNzdWVyPmh0dHBzOi8vc2ltcGxlc2FtbC1mb3Itc3ByaW5nLXNhbWwuYXBwcy5wY2ZvbmUuaW8vc2FtbDIvaWRwL21ldGFkYXRhLnBocDwvc2FtbDpJc3N1ZXI+PGRzOlNpZ25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+CiAgPGRzOlNpZ25lZEluZm8+PGRzOkNhbm9uaWNhbGl6YXRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz4KICAgIDxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNyc2Etc2hhMjU2Ii8+CiAgPGRzOlJlZmVyZW5jZSBVUkk9IiNfY2I2M2JjMzZjMmMwM2I0ZTFiY2Q1YjFiMGNjMmUxNjVkMDQ0NTQ2ZTg4Ij48ZHM6VHJhbnNmb3Jtcz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI2VudmVsb3BlZC1zaWduYXR1cmUiLz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+PC9kczpUcmFuc2Zvcm1zPjxkczpEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNzaGEyNTYiLz48ZHM6RGlnZXN0VmFsdWU+VWFtN2NHVGlCd2xuRDBJdGd5aU5KVjN2Z0NPNytZZkRxSWJrWERkR3hrQT08L2RzOkRpZ2VzdFZhbHVlPjwvZHM6UmVmZXJlbmNlPjwvZHM6U2lnbmVkSW5mbz48ZHM6U2lnbmF0dXJlVmFsdWU+RkhpWUpETDlKTXM1Y2V5WXhUVVgrUndEQm45RFYzVE81dDFham4raGFtb1c2MUpBY0JaNjEwUHpYMzN3alA3Mk1kYmdDWnR5ZmNrSktZUUpPT0szRkxLTkJLQkphOTNsSS9rZWZjTXRTUGxBU2hESm9ydmU0U0tWa29WbzZLVnB0eC9OTnowRkhJNURFZTZiUUVjZWFiNERVNDFVdEpQMHUyWm16ejVjNC83VzhLdmt6MkxMbXhWZlE3Q2todmgvNzBhWHlkWVBVRml3bE4vV1lTV3JYVU9oOXNFTDFiZGVlQzFkYnpaeVdNNldnSkdRMUpJblBnSGd0YTlxMU96eGliOFlLRXpQSUMzVEZldkU1Y0phMFQvd1NzOVIxN0JSR09OclhTTWQvRCt4YkY0Z3lIYW5EZFlOYVN2TzdIS2p4bzRwYk1aY05peDhMTkVYZGtiZEx3PT08L2RzOlNpZ25hdHVyZVZhbHVlPgo8ZHM6S2V5SW5mbz48ZHM6WDUwOURhdGE+PGRzOlg1MDlDZXJ0aWZpY2F0ZT5NSUlFRXpDQ0F2dWdBd0lCQWdJSkFJYzFxekxydis1bk1BMEdDU3FHU0liM0RRRUJDd1VBTUlHZk1Rc3dDUVlEVlFRR0V3SlZVekVMTUFrR0ExVUVDQXdDUTA4eEZEQVNCZ05WQkFjTUMwTmhjM1JzWlNCU2IyTnJNUnd3R2dZRFZRUUtEQk5UWVcxc0lGUmxjM1JwYm1jZ1UyVnlkbVZ5TVFzd0NRWURWUVFMREFKSlZERWdNQjRHQTFVRUF3d1hjMmx0Y0d4bGMyRnRiSEJvY0M1alptRndjSE11YVc4eElEQWVCZ2txaGtpRzl3MEJDUUVXRVdab1lXNXBhMEJ3YVhadmRHRnNMbWx2TUI0WERURTFNREl5TXpJeU5EVXdNMW9YRFRJMU1ESXlNakl5TkRVd00xb3dnWjh4Q3pBSkJnTlZCQVlUQWxWVE1Rc3dDUVlEVlFRSURBSkRUekVVTUJJR0ExVUVCd3dMUTJGemRHeGxJRkp2WTJzeEhEQWFCZ05WQkFvTUUxTmhiV3dnVkdWemRHbHVaeUJUWlhKMlpYSXhDekFKQmdOVkJBc01Ba2xVTVNBd0hnWURWUVFEREJkemFXMXdiR1Z6WVcxc2NHaHdMbU5tWVhCd2N5NXBiekVnTUI0R0NTcUdTSWIzRFFFSkFSWVJabWhoYm1sclFIQnBkbTkwWVd3dWFXOHdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFDNGNuNjJFMXhMcXBOMzRQbWJyS0Jia09YRmp6V2dKOWIrcFh1YVJmdDZBMzM5dXVJUWVvZUg1cWVTS1JWVGwzMkwwZ2R6Mlppdkx3WlhXK2NxdmZ0VlcxdHZFSHZ6SkZ5eGVUVzNmQ1VlQ1FzZWJMbkEycVJhMDdSa3hUbzZOZjI0NG1XV1JEb2Rjb0hFZkRVU2J4ZlRaNklFeFNvalNJVTJSbkQ2V2xsWVdGZEQxR0ZwQkpPbVFCOHJBYzh3SklCZEhGZFFuWDhUdGw3aFo2cnRncUVZTXpZVk11SjJGMnIxSFNVMXpTQXZ3cGRZUDZyUkdGUkpFZmRBOW1tM1dLZk5MU2M1Y2xqejBYL1RYeTB2VmxBVjk1bDlxY2ZGelBtcmtOSXN0OUZaU3dwdkI0OUx5QVZrZTA0RlFQUHdMZ1ZINGdwaGlKSDNqdlo3SStKNWxTOFZBZ01CQUFHalVEQk9NQjBHQTFVZERnUVdCQlRUeVA2Q2M1SGxCSjUrdWNWQ3dHYzVvZ0tOR3pBZkJnTlZIU01FR0RBV2dCVFR5UDZDYzVIbEJKNSt1Y1ZDd0djNW9nS05HekFNQmdOVkhSTUVCVEFEQVFIL01BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQXZNUzRFUWVQL2lwVjRqT0c1bE82L3RZQ2IvaUplQWR1T25SaGtKazBEYlgzMjlsRExaaFRUTC94L3cvOW11Q1ZjdkxyekVwNlBOK1ZXZnc1RTVGV3RaTjB5aEd0UDlSK3ZabnJWK29jMnpHRCtubzEveVNGT2UzRWlKQ081ZGVoeEtqWUVtQlJ2NXNVL0xaRktacG96S04vQk1FYTZDcUx1eGJ6Yjd5a3hWcjdFVkZYd2x0UHh6RTlUbUw5T0FDTk55RjVlSkhXTVJNbGxhclV2a2NYbGg0cHV4NGtzOWU2elY5RFFCeTJ6ZHM5ZjFJM3F4ZzBlWDZKbkdyWGkvWmlDVCtsSmdWZTNaRlhpZWppTEFpS0IwNHNYVzN0aTBMVzNseDEzWTFZbFE0L3RscGdUZ2ZJSnhLVjZueVBpTG9LMG55d2JNZCt2cEFpckR0Mk9jK2hrPC9kczpYNTA5Q2VydGlmaWNhdGU+PC9kczpYNTA5RGF0YT48L2RzOktleUluZm8+PC9kczpTaWduYXR1cmU+PHNhbWw6U3ViamVjdD48c2FtbDpOYW1lSUQgU1BOYW1lUXVhbGlmaWVyPSJodHRwOi8vbG9jYWxob3N0OjgwODAvc2FtbDIvc2VydmljZS1wcm92aWRlci1tZXRhZGF0YS9vbmUiIEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6MS4xOm5hbWVpZC1mb3JtYXQ6ZW1haWxBZGRyZXNzIj50ZXN0dXNlckBzcHJpbmcuc2VjdXJpdHkuc2FtbDwvc2FtbDpOYW1lSUQ+PHNhbWw6U3ViamVjdENvbmZpcm1hdGlvbiBNZXRob2Q9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpjbTpiZWFyZXIiPjxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj0iMjA1Mi0wOS0yOFQwMjo1MDowNFoiIFJlY2lwaWVudD0iaHR0cDovL2xvY2FsaG9zdDo4MDgwL2xvZ2luL3NhbWwyL3Nzby9vbmUiIEluUmVzcG9uc2VUbz0iQVJRYzk4ZjIwMC1kY2Y3LTRkZjQtYTUyMi0wNzIwNjIwOGIwN2QiLz48L3NhbWw6U3ViamVjdENvbmZpcm1hdGlvbj48L3NhbWw6U3ViamVjdD48c2FtbDpDb25kaXRpb25zIE5vdEJlZm9yZT0iMjAyMS0wMS0yMFQwMTowMjo1NFoiIE5vdE9uT3JBZnRlcj0iMjA1Mi0wOS0yOFQwMjo1MDowNFoiPjxzYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+PHNhbWw6QXVkaWVuY2U+aHR0cDovL2xvY2FsaG9zdDo4MDgwL3NhbWwyL3NlcnZpY2UtcHJvdmlkZXItbWV0YWRhdGEvb25lPC9zYW1sOkF1ZGllbmNlPjwvc2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPjwvc2FtbDpDb25kaXRpb25zPjxzYW1sOkF1dGhuU3RhdGVtZW50IEF1dGhuSW5zdGFudD0iMjAyMS0wMS0yMFQwMDo0ODoyOVoiIFNlc3Npb25Ob3RPbk9yQWZ0ZXI9IjIwMjEtMDEtMjBUMDg6NDg6MjlaIiBTZXNzaW9uSW5kZXg9Il9lN2ExYTllNDk1YmZlMjI2NjQ5ZThkY2MzN2UxNDE1NDQ5NTIxNWQ2ZWIiPjxzYW1sOkF1dGhuQ29udGV4dD48c2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj51cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YWM6Y2xhc3NlczpQYXNzd29yZFByb3RlY3RlZFRyYW5zcG9ydDwvc2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj48L3NhbWw6QXV0aG5Db250ZXh0Pjwvc2FtbDpBdXRoblN0YXRlbWVudD48c2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+PHNhbWw6QXR0cmlidXRlIE5hbWU9InVpZCIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+dGVzdHVzZXJAc3ByaW5nLnNlY3VyaXR5LnNhbWw8L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48c2FtbDpBdHRyaWJ1dGUgTmFtZT0iZWR1UGVyc29uQWZmaWxpYXRpb24iIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPm1lbWJlcjwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj51c2VyPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU+PHNhbWw6QXR0cmlidXRlIE5hbWU9ImVtYWlsQWRkcmVzcyIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+dGVzdHVzZXJAc3ByaW5nLnNlY3VyaXR5LnNhbWw8L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48L3NhbWw6QXR0cmlidXRlU3RhdGVtZW50Pjwvc2FtbDpBc3NlcnRpb24+PC9zYW1scDpSZXNwb25zZT4=";
-
- static final Map> USER_ATTRIBUTES = new LinkedHashMap<>();
-
- static {
- USER_ATTRIBUTES.put("uid", Arrays.asList("testuser@spring.security.saml"));
- USER_ATTRIBUTES.put("eduPersonAffiliation", Arrays.asList("member", "user"));
- USER_ATTRIBUTES.put("emailAddress", Arrays.asList("testuser@spring.security.saml"));
- }
-
@Autowired
MockMvc mvc;
@@ -71,51 +48,38 @@ public class Saml2LoginApplicationITests {
this.webClient.getCookieManager().clearCookies();
}
- @Test
- void indexWhenSamlResponseThenShowsUserInformation() throws Exception {
- HttpSession session = this.mvc.perform(get("http://localhost:8080/")).andExpect(status().is3xxRedirection())
- .andExpect(redirectedUrl("http://localhost:8080/saml2/authenticate/one")).andReturn().getRequest()
- .getSession();
-
- this.mvc.perform(post("http://localhost:8080/login/saml2/sso/one").param("SAMLResponse", SIGNED_RESPONSE)
- .session((MockHttpSession) session)).andExpect(redirectedUrl("http://localhost:8080/"));
-
- this.mvc.perform(get("http://localhost:8080/").session((MockHttpSession) session))
- .andExpect(model().attribute("emailAddress", "testuser@spring.security.saml"))
- .andExpect(model().attribute("userAttributes", USER_ATTRIBUTES));
- }
-
@Test
void authenticationAttemptWhenValidThenShowsUserEmailAddress() throws Exception {
- HtmlPage relyingParty = performLogin();
- assertThat(relyingParty.asNormalizedText()).contains("You're email address is testuser@spring.security.saml");
+ performLogin();
+ HtmlPage home = (HtmlPage) this.webClient.getCurrentWindow().getEnclosedPage();
+ assertThat(home.asText()).contains("You're email address is testuser@spring.security.saml");
}
- @Test
- void logoutWhenRelyingPartyInitiatedLogoutThenLoginPageWithLogoutParam() throws Exception {
- HtmlPage relyingParty = performLogin();
- HtmlElement rpLogoutButton = relyingParty.getHtmlElementById("rp_logout_button");
- HtmlPage loginPage = rpLogoutButton.click();
- assertThat(loginPage.getUrl().getFile()).isEqualTo("/login?logout");
- }
-
- @Test
- void logoutWhenAssertingPartyInitiatedLogoutThenLoginPageWithLogoutParam() throws Exception {
- HtmlPage relyingParty = performLogin();
- HtmlElement apLogoutButton = relyingParty.getHtmlElementById("ap_logout_button");
- HtmlPage loginPage = apLogoutButton.click();
- assertThat(loginPage.getUrl().getFile()).isEqualTo("/login?logout");
- }
-
- private HtmlPage performLogin() throws IOException {
- HtmlPage assertingParty = this.webClient.getPage("/");
- HtmlForm form = assertingParty.getFormByName("f");
+ private void performLogin() throws Exception {
+ HtmlPage login = this.webClient.getPage("/");
+ this.webClient.waitForBackgroundJavaScript(10000);
+ HtmlForm form = findForm(login);
HtmlInput username = form.getInputByName("username");
- HtmlInput password = form.getInputByName("password");
- HtmlSubmitInput submit = assertingParty.getHtmlElementById("submit_button");
- username.setValueAttribute("user");
- password.setValueAttribute("password");
- return submit.click();
+ HtmlPasswordInput password = form.getInputByName("password");
+ HtmlSubmitInput submit = login.getHtmlElementById("okta-signin-submit");
+ username.type("testuser@spring.security.saml");
+ password.type("12345678");
+ submit.click();
+ this.webClient.waitForBackgroundJavaScript(10000);
+ }
+
+ private HtmlForm findForm(HtmlPage login) {
+ for (HtmlForm form : login.getForms()) {
+ try {
+ if (form.getId().equals("form19")) {
+ return form;
+ }
+ }
+ catch (ElementNotFoundException ex) {
+ // Continue
+ }
+ }
+ throw new IllegalStateException("Could not resolve login form");
}
}
diff --git a/servlet/spring-boot/java/saml2/refreshable-metadata/src/main/java/example/IndexController.java b/servlet/spring-boot/java/saml2/refreshable-metadata/src/main/java/example/IndexController.java
index 43d6606..1d83021 100644
--- a/servlet/spring-boot/java/saml2/refreshable-metadata/src/main/java/example/IndexController.java
+++ b/servlet/spring-boot/java/saml2/refreshable-metadata/src/main/java/example/IndexController.java
@@ -27,7 +27,7 @@ public class IndexController {
@GetMapping("/")
public String index(Model model, @AuthenticationPrincipal Saml2AuthenticatedPrincipal principal) {
- String emailAddress = principal.getFirstAttribute("emailAddress");
+ String emailAddress = principal.getFirstAttribute("email");
model.addAttribute("emailAddress", emailAddress);
model.addAttribute("userAttributes", principal.getAttributes());
return "index";
diff --git a/servlet/spring-boot/java/saml2/refreshable-metadata/src/main/resources/application.yml b/servlet/spring-boot/java/saml2/refreshable-metadata/src/main/resources/application.yml
index 157a113..297e69f 100644
--- a/servlet/spring-boot/java/saml2/refreshable-metadata/src/main/resources/application.yml
+++ b/servlet/spring-boot/java/saml2/refreshable-metadata/src/main/resources/application.yml
@@ -8,7 +8,7 @@ spring:
- private-key-location: classpath:credentials/rp-private.key
certificate-location: classpath:credentials/rp-certificate.crt
identityprovider:
- metadata-uri: https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/metadata.php
+ metadata-uri: https://dev-05937739.okta.com/app/exk46xofd8NZvFCpS5d7/sso/saml/metadata
logging.level:
org.springframework.security: TRACE
diff --git a/servlet/spring-boot/java/saml2/refreshable-metadata/src/main/resources/templates/index.html b/servlet/spring-boot/java/saml2/refreshable-metadata/src/main/resources/templates/index.html
index 2970e85..2e970b2 100644
--- a/servlet/spring-boot/java/saml2/refreshable-metadata/src/main/resources/templates/index.html
+++ b/servlet/spring-boot/java/saml2/refreshable-metadata/src/main/resources/templates/index.html
@@ -36,11 +36,6 @@
-
-
- AP-initiated Logout
-
-
diff --git a/servlet/xml/java/saml2/login-logout/build.gradle b/servlet/xml/java/saml2/login-logout/build.gradle
index 8f4f24c..12442ec 100644
--- a/servlet/xml/java/saml2/login-logout/build.gradle
+++ b/servlet/xml/java/saml2/login-logout/build.gradle
@@ -1,5 +1,6 @@
plugins {
id "java"
+ id "nebula.integtest" version "8.2.0"
id "org.gretty" version "3.0.6"
id "war"
}
@@ -33,7 +34,7 @@ dependencies {
testImplementation "org.springframework:spring-test"
testImplementation "org.springframework.security:spring-security-test"
testImplementation "org.junit.jupiter:junit-jupiter-api"
- testImplementation 'net.sourceforge.htmlunit:htmlunit:2.49.1'
+ testImplementation 'net.sourceforge.htmlunit:htmlunit:2.44.0'
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
diff --git a/servlet/xml/java/saml2/login-logout/src/integTest/java/example/LocalHostWebClient.java b/servlet/xml/java/saml2/login-logout/src/integTest/java/example/LocalHostWebClient.java
new file mode 100644
index 0000000..8cefa23
--- /dev/null
+++ b/servlet/xml/java/saml2/login-logout/src/integTest/java/example/LocalHostWebClient.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 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 com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
+import com.gargoylesoftware.htmlunit.Page;
+import com.gargoylesoftware.htmlunit.WebClient;
+
+import org.springframework.core.env.Environment;
+import org.springframework.util.Assert;
+
+/**
+ * {@link WebClient} will automatically prefix relative URLs with
+ * localhost:${local.server.port}.
+ *
+ * @author Phillip Webb
+ * @since 1.4.0
+ */
+public class LocalHostWebClient extends WebClient {
+
+ private final Environment environment;
+
+ public LocalHostWebClient(Environment environment) {
+ Assert.notNull(environment, "Environment must not be null");
+ this.environment = environment;
+ }
+
+ @Override
+ public P getPage(String url) throws IOException, FailingHttpStatusCodeException {
+ if (url.startsWith("/")) {
+ String port = this.environment.getProperty("local.server.port", "8080");
+ url = "http://localhost:" + port + url;
+ }
+ return super.getPage(url);
+ }
+
+}
diff --git a/servlet/xml/java/saml2/login-logout/src/integTest/java/example/Saml2XmlITests.java b/servlet/xml/java/saml2/login-logout/src/integTest/java/example/Saml2XmlITests.java
new file mode 100644
index 0000000..fb72938
--- /dev/null
+++ b/servlet/xml/java/saml2/login-logout/src/integTest/java/example/Saml2XmlITests.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 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 com.gargoylesoftware.htmlunit.ElementNotFoundException;
+import com.gargoylesoftware.htmlunit.WebClient;
+import com.gargoylesoftware.htmlunit.html.HtmlElement;
+import com.gargoylesoftware.htmlunit.html.HtmlForm;
+import com.gargoylesoftware.htmlunit.html.HtmlInput;
+import com.gargoylesoftware.htmlunit.html.HtmlPage;
+import com.gargoylesoftware.htmlunit.html.HtmlPasswordInput;
+import com.gargoylesoftware.htmlunit.html.HtmlSubmitInput;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.env.Environment;
+import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.htmlunit.MockMvcWebClientBuilder;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.web.context.WebApplicationContext;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration(locations = { "file:src/main/webapp/WEB-INF/spring-servlet.xml",
+ "file:src/main/webapp/WEB-INF/spring/security.xml" })
+@WebAppConfiguration
+public class Saml2XmlITests {
+
+ private MockMvc mvc;
+
+ private WebClient webClient;
+
+ @Autowired
+ WebApplicationContext webApplicationContext;
+
+ @Autowired
+ Environment environment;
+
+ @BeforeEach
+ void setup() {
+ this.mvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext)
+ .apply(SecurityMockMvcConfigurers.springSecurity()).build();
+ this.webClient = MockMvcWebClientBuilder.mockMvcSetup(this.mvc)
+ .withDelegate(new LocalHostWebClient(this.environment)).build();
+ this.webClient.getCookieManager().clearCookies();
+ }
+
+ @Test
+ void authenticationAttemptWhenValidThenShowsUserEmailAddress() throws Exception {
+ performLogin();
+ HtmlPage home = (HtmlPage) this.webClient.getCurrentWindow().getEnclosedPage();
+ assertThat(home.asText()).contains("You're email address is testuser@spring.security.saml");
+ }
+
+ @Test
+ void logoutWhenRelyingPartyInitiatedLogoutThenLoginPageWithLogoutParam() throws Exception {
+ performLogin();
+ HtmlPage home = (HtmlPage) this.webClient.getCurrentWindow().getEnclosedPage();
+ HtmlElement rpLogoutButton = home.getHtmlElementById("rp_logout_button");
+ HtmlPage loginPage = rpLogoutButton.click();
+ assertThat(loginPage.getUrl().getFile()).isEqualTo("/login?logout");
+ }
+
+ private void performLogin() throws Exception {
+ HtmlPage login = this.webClient.getPage("/");
+ 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("testuser@spring.security.saml");
+ password.type("12345678");
+ submit.click();
+ this.webClient.waitForBackgroundJavaScript(10000);
+ }
+
+ private HtmlForm findForm(HtmlPage login) {
+ for (HtmlForm form : login.getForms()) {
+ try {
+ if (form.getId().equals("form19")) {
+ return form;
+ }
+ }
+ catch (ElementNotFoundException ex) {
+ // Continue
+ }
+ }
+ throw new IllegalStateException("Could not resolve login form");
+ }
+
+}
diff --git a/servlet/xml/java/saml2/login-logout/src/main/java/example/IndexController.java b/servlet/xml/java/saml2/login-logout/src/main/java/example/IndexController.java
index 56c51f8..9644b60 100644
--- a/servlet/xml/java/saml2/login-logout/src/main/java/example/IndexController.java
+++ b/servlet/xml/java/saml2/login-logout/src/main/java/example/IndexController.java
@@ -34,7 +34,7 @@ public class IndexController {
@GetMapping("/")
public String index(Model model, @AuthenticationPrincipal Saml2AuthenticatedPrincipal principal) {
- String emailAddress = principal.getFirstAttribute("emailAddress");
+ String emailAddress = principal.getFirstAttribute("email");
model.addAttribute("emailAddress", emailAddress);
model.addAttribute("userAttributes", principal.getAttributes());
return "index";
diff --git a/servlet/xml/java/saml2/login-logout/src/main/webapp/WEB-INF/spring/security.xml b/servlet/xml/java/saml2/login-logout/src/main/webapp/WEB-INF/spring/security.xml
index 95b8ff4..dcb3466 100644
--- a/servlet/xml/java/saml2/login-logout/src/main/webapp/WEB-INF/spring/security.xml
+++ b/servlet/xml/java/saml2/login-logout/src/main/webapp/WEB-INF/spring/security.xml
@@ -4,7 +4,7 @@
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd">
-
+
@@ -14,40 +14,15 @@
-
-
-
-
-
-
-
-
+ metadata-location="https://dev-05937739.okta.com/app/exk46xofd8NZvFCpS5d7/sso/saml/metadata"
+ single-logout-service-location="https://dev-05937739.okta.com/app/dev-05937739_springgsecuritysaml2idp_1/exk46xofd8NZvFCpS5d7/slo/saml"
+ single-logout-service-response-location="{baseUrl}/logout/saml2/slo">
-
-
-
-
diff --git a/servlet/xml/java/saml2/login-logout/src/main/webapp/WEB-INF/templates/index.html b/servlet/xml/java/saml2/login-logout/src/main/webapp/WEB-INF/templates/index.html
index dd65ffd..7bba2e3 100644
--- a/servlet/xml/java/saml2/login-logout/src/main/webapp/WEB-INF/templates/index.html
+++ b/servlet/xml/java/saml2/login-logout/src/main/webapp/WEB-INF/templates/index.html
@@ -36,11 +36,6 @@
-
-
- AP-initiated Logout
-
-
diff --git a/settings.gradle b/settings.gradle
index fcbbf0f..7484ac5 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -39,7 +39,7 @@ include ":servlet:java-configuration:hello-mvc-security"
include ":servlet:java-configuration:hello-security"
include ":servlet:java-configuration:hello-security-explicit"
include ":servlet:java-configuration:max-sessions"
-//include ":servlet:java-configuration:saml2:login"
+include ":servlet:java-configuration:saml2:login"
include ":servlet:spring-boot:java:authentication:username-password:user-details-service:custom-user"
include ":servlet:spring-boot:java:authentication:username-password:mfa"
include ":servlet:spring-boot:java:hello"
@@ -55,9 +55,9 @@ include ":servlet:spring-boot:java:oauth2:resource-server:multi-tenancy"
include ":servlet:spring-boot:java:oauth2:resource-server:opaque"
include ":servlet:spring-boot:java:oauth2:resource-server:static"
include ":servlet:spring-boot:java:oauth2:webclient"
-//include ":servlet:spring-boot:java:saml2:login"
-//include ":servlet:spring-boot:java:saml2:login-single-tenant"
-//include ":servlet:spring-boot:java:saml2:refreshable-metadata"
+include ":servlet:spring-boot:java:saml2:login"
+include ":servlet:spring-boot:java:saml2:login-single-tenant"
+include ":servlet:spring-boot:java:saml2:refreshable-metadata"
include ":servlet:spring-boot:kotlin:hello-security"
include ":servlet:xml:java:helloworld"
include ":servlet:xml:java:preauth"