Update SAML 2.0 Login sample to use SAML 2.0 Logout

Closes gh-36
This commit is contained in:
Josh Cummings 2021-09-08 21:33:13 -06:00 committed by Marcus Hert Da Coregio
parent f3348eec4a
commit cbd87c4e04
6 changed files with 128 additions and 23 deletions

View File

@ -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

View File

@ -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();
}
}

View File

@ -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;

View File

@ -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();
}
}

View File

@ -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

View File

@ -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.
@ -17,30 +17,44 @@
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org" xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
<title>Spring Security - SAML 2.0 Login</title>
<title>Spring Security - SAML 2.0 Login & Logout</title>
<meta charset="utf-8" />
<style>
span, dt {
font-weight: bold;
}
</style>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
</head>
<body>
<div>
<form th:action="@{/logout}" method="post">
<input type="submit" value="Logout" />
</form>
<a href="https://simplesaml-for-spring-saml.cfapps.io/module.php/core/authenticate.php?as=example-userpass&logout">
Log out of SimpleSAMLphp
</a>
<div class="container">
<ul class="nav">
<li class="nav-item">
<form th:action="@{/logout}" method="post">
<button class="btn btn-primary" id="rp_logout_button" type="submit">
RP-initiated Logout
</button>
</form>
</li>
<li class="nav-item">
<a id="ap_logout_button" class="nav-link" href="https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/SingleLogoutService.php?ReturnTo=http://localhost:8080/login?logout">
AP-initiated Logout
</a>
</li>
</ul>
</div>
<main role="main" class="container">
<h1 class="mt-5">SAML 2.0 Login & Single Logout with Spring Security</h1>
<p class="lead">You are successfully logged in as <span sec:authentication="name"></span></p>
<p class="lead">You're email address is <span th:text="${emailAddress}"></span></p>
<h2 class="mt-2">All Your Attributes</h2>
<dl th:each="userAttribute : ${userAttributes}">
<dt th:text="${userAttribute.key}"></dt>
<dd th:text="${userAttribute.value}"></dd>
</dl>
<h6>Visit the <a href="https://docs.spring.io/spring-security/site/docs/current/reference/html5/#servlet-saml2" target="_blank">SAML 2.0 Login & Logout</a> documentation for more details.</h6>
</main>
</div>
<h1>SAML 2.0 Login with Spring Security</h1>
<p>You are successfully logged in as <span sec:authentication="name"></span></p>
<p>You're email address is <span th:text="${emailAddress}"></span></p>
<h2>All Your Attributes</h2>
<dl th:each="userAttribute : ${userAttributes}">
<dt th:text="${userAttribute.key}"></dt>
<dd th:text="${userAttribute.value}"></dd>
</dl>
</body>
</html>