From cb4bd098e3bcb85e22f2ca76fcc6c974bc0db09c Mon Sep 17 00:00:00 2001 From: Marcus Da Coregio Date: Thu, 17 Mar 2022 09:19:45 -0300 Subject: [PATCH] Re-enable SAML 2.0 samples with Okta IdP Closes gh-55 --- .idea/misc.xml | 2 +- .../main/java/example/IndexController.java | 2 +- .../java/example/SecurityConfiguration.java | 8 +- .../src/main/resources/templates/index.html | 5 - .../saml2/login-single-tenant/build.gradle | 2 +- .../example/Saml2LoginApplicationITests.java | 88 +++++--------- .../main/java/example/IndexController.java | 2 +- .../java/example/SecurityConfiguration.java | 42 ++++++- .../src/main/resources/application.yml | 15 --- .../src/main/resources/templates/index.html | 5 - .../spring-boot/java/saml2/login/build.gradle | 2 +- .../main/java/example/IndexController.java | 2 +- .../java/example/SecurityConfiguration.java | 47 ++++++++ .../login/src/main/resources/application.yml | 18 --- .../src/main/resources/templates/index.html | 5 - .../saml2/refreshable-metadata/build.gradle | 2 +- .../example/Saml2LoginApplicationITests.java | 96 +++++---------- .../main/java/example/IndexController.java | 2 +- .../src/main/resources/application.yml | 2 +- .../src/main/resources/templates/index.html | 5 - .../java/example/LocalHostWebClient.java | 53 +++++++++ .../java/example/Saml2XmlITests.java | 112 ++++++++++++++++++ .../main/java/example/IndexController.java | 2 +- .../main/webapp/WEB-INF/spring/security.xml | 33 +----- .../main/webapp/WEB-INF/templates/index.html | 5 - settings.gradle | 8 +- 26 files changed, 337 insertions(+), 228 deletions(-) create mode 100644 servlet/xml/java/saml2/login-logout/src/integTest/java/example/LocalHostWebClient.java create mode 100644 servlet/xml/java/saml2/login-logout/src/integTest/java/example/Saml2XmlITests.java diff --git a/.idea/misc.xml b/.idea/misc.xml index 7edc3a5..6eb4e09 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -5,4 +5,4 @@ - \ No newline at end of file + 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 @@ -
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 e5879af..7f88a7b 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-springsecurity6' - 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' } 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 @@ -
diff --git a/servlet/spring-boot/java/saml2/login/build.gradle b/servlet/spring-boot/java/saml2/login/build.gradle index ca79630..1045382 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-springsecurity6' - 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' testImplementation 'org.awaitility:awaitility:4.2.0' 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 @@ -
diff --git a/servlet/spring-boot/java/saml2/refreshable-metadata/build.gradle b/servlet/spring-boot/java/saml2/refreshable-metadata/build.gradle index e5879af..7f88a7b 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-springsecurity6' - 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' } 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 @@ -
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 @@ -

diff --git a/settings.gradle b/settings.gradle index 8a57a6c..2b11589 100644 --- a/settings.gradle +++ b/settings.gradle @@ -40,7 +40,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" @@ -56,9 +56,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"