diff --git a/servlet/spring-boot/java/saml2-login/README.adoc b/servlet/spring-boot/java/saml2-login/README.adoc index d51b1bb..95bc623 100644 --- a/servlet/spring-boot/java/saml2-login/README.adoc +++ b/servlet/spring-boot/java/saml2-login/README.adoc @@ -1,12 +1,17 @@ -= SAML 2.0 Login Sample += SAML 2.0 Login & Logout Sample -This guide provides instructions on setting up this SAML 2.0 Login sample application. +This guide provides instructions on setting up this SAML 2.0 Login & Logout sample application. +It uses https://simplesamlphp.org/[SimpleSAMLphp] as its asserting party. The sample application uses Spring Boot and the `spring-security-saml2-service-provider` module which is new in Spring Security 5.2. +The https://docs.spring.io/spring-security/site/docs/5.6.0-SNAPSHOT/reference/html5/#servlet-saml2login-logout[SAML 2.0 Logout feature] is new in Spring Security 5.6. + == Goals +=== SAML 2.0 Login + `saml2Login()` provides a very simple implementation of a Service Provider that can receive a SAML 2.0 Response via the HTTP-POST and HTTP-REDIRECT bindings against the SimpleSAMLphp SAML 2.0 reference implementation. The following features are implemented in the MVP: @@ -16,6 +21,14 @@ The following features are implemented in the MVP: 3. Provide a framework for components used in SAML 2.0 authentication that can be swapped by configuration 4. Work against the SimpleSAMLphp reference implementation +=== SAML 2.0 Single Logout + +`saml2Logout()` supports RP- and AP-initiated SAML 2.0 Single Logout via the HTTP-POST and HTTP-REDIRECT bindings against the SimpleSAMLphp SAML 2.0 reference implementation. + +On this sample, the SAML 2.0 Logout is using the HTTP-POST binding. + +You can refer to the https://docs.spring.io/spring-security/site/docs/5.6.0-SNAPSHOT/reference/html5/#servlet-saml2login-logout[reference documentation] for more details about the RP- and AP-initiated SAML 2.0 Logout. + == Run the Sample === Start up the Sample Boot Application 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 4300922..00334e8 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package example; +import java.io.IOException; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; @@ -24,10 +25,12 @@ import java.util.Map; import javax.servlet.http.HttpSession; 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.HtmlSubmitInput; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -63,6 +66,11 @@ public class Saml2LoginApplicationITests { @Autowired WebClient webClient; + @BeforeEach + void setup() { + this.webClient.getCookieManager().clearCookies(); + } + @Test void indexWhenSamlResponseThenShowsUserInformation() throws Exception { HttpSession session = this.mvc.perform(get("http://localhost:8080/")).andExpect(status().is3xxRedirection()) @@ -79,6 +87,27 @@ public class Saml2LoginApplicationITests { @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 { HtmlPage assertingParty = this.webClient.getPage("/"); HtmlForm form = assertingParty.getFormByName("f"); HtmlInput username = form.getInputByName("username"); @@ -86,8 +115,7 @@ public class Saml2LoginApplicationITests { HtmlSubmitInput submit = assertingParty.getHtmlElementById("submit_button"); username.setValueAttribute("user"); password.setValueAttribute("password"); - HtmlPage relyingParty = submit.click(); - assertThat(relyingParty.asText()).contains("You're email address is testuser@spring.security.saml"); + return submit.click(); } } diff --git a/servlet/spring-boot/java/saml2-login/src/main/java/example/Saml2LoginApplication.java b/servlet/spring-boot/java/saml2-login/src/main/java/example/Saml2LoginApplication.java index c8cb6a5..d2f3629 100644 --- a/servlet/spring-boot/java/saml2-login/src/main/java/example/Saml2LoginApplication.java +++ b/servlet/spring-boot/java/saml2-login/src/main/java/example/Saml2LoginApplication.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package example; import org.springframework.boot.SpringApplication; 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 new file mode 100644 index 0000000..2e39bad --- /dev/null +++ b/servlet/spring-boot/java/saml2-login/src/main/java/example/SecurityConfiguration.java @@ -0,0 +1,42 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package example; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.web.SecurityFilterChain; + +@Configuration +public class SecurityConfiguration { + + @Bean + SecurityFilterChain app(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests((authorize) -> authorize + .anyRequest().authenticated() + ) + .saml2Login(Customizer.withDefaults()) + .saml2Logout(Customizer.withDefaults()); + // @formatter:on + + return http.build(); + } + +} 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 3be3057..05666f4 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 @@ -4,5 +4,12 @@ spring: relyingparty: registration: one: + signing.credentials: + - 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 + + +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 6073dfb..2970e85 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 @@ -1,5 +1,5 @@