Update SAML 2.0 Login sample to use SAML 2.0 Logout
Closes gh-36
This commit is contained in:
parent
f3348eec4a
commit
cbd87c4e04
@ -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`
|
The sample application uses Spring Boot and the `spring-security-saml2-service-provider`
|
||||||
module which is new in Spring Security 5.2.
|
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
|
== 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.
|
`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:
|
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
|
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
|
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
|
== Run the Sample
|
||||||
|
|
||||||
=== Start up the Sample Boot Application
|
=== Start up the Sample Boot Application
|
||||||
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package example;
|
package example;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -24,10 +25,12 @@ import java.util.Map;
|
|||||||
import javax.servlet.http.HttpSession;
|
import javax.servlet.http.HttpSession;
|
||||||
|
|
||||||
import com.gargoylesoftware.htmlunit.WebClient;
|
import com.gargoylesoftware.htmlunit.WebClient;
|
||||||
|
import com.gargoylesoftware.htmlunit.html.HtmlElement;
|
||||||
import com.gargoylesoftware.htmlunit.html.HtmlForm;
|
import com.gargoylesoftware.htmlunit.html.HtmlForm;
|
||||||
import com.gargoylesoftware.htmlunit.html.HtmlInput;
|
import com.gargoylesoftware.htmlunit.html.HtmlInput;
|
||||||
import com.gargoylesoftware.htmlunit.html.HtmlPage;
|
import com.gargoylesoftware.htmlunit.html.HtmlPage;
|
||||||
import com.gargoylesoftware.htmlunit.html.HtmlSubmitInput;
|
import com.gargoylesoftware.htmlunit.html.HtmlSubmitInput;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@ -63,6 +66,11 @@ public class Saml2LoginApplicationITests {
|
|||||||
@Autowired
|
@Autowired
|
||||||
WebClient webClient;
|
WebClient webClient;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setup() {
|
||||||
|
this.webClient.getCookieManager().clearCookies();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void indexWhenSamlResponseThenShowsUserInformation() throws Exception {
|
void indexWhenSamlResponseThenShowsUserInformation() throws Exception {
|
||||||
HttpSession session = this.mvc.perform(get("http://localhost:8080/")).andExpect(status().is3xxRedirection())
|
HttpSession session = this.mvc.perform(get("http://localhost:8080/")).andExpect(status().is3xxRedirection())
|
||||||
@ -79,6 +87,27 @@ public class Saml2LoginApplicationITests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void authenticationAttemptWhenValidThenShowsUserEmailAddress() throws Exception {
|
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("/");
|
HtmlPage assertingParty = this.webClient.getPage("/");
|
||||||
HtmlForm form = assertingParty.getFormByName("f");
|
HtmlForm form = assertingParty.getFormByName("f");
|
||||||
HtmlInput username = form.getInputByName("username");
|
HtmlInput username = form.getInputByName("username");
|
||||||
@ -86,8 +115,7 @@ public class Saml2LoginApplicationITests {
|
|||||||
HtmlSubmitInput submit = assertingParty.getHtmlElementById("submit_button");
|
HtmlSubmitInput submit = assertingParty.getHtmlElementById("submit_button");
|
||||||
username.setValueAttribute("user");
|
username.setValueAttribute("user");
|
||||||
password.setValueAttribute("password");
|
password.setValueAttribute("password");
|
||||||
HtmlPage relyingParty = submit.click();
|
return submit.click();
|
||||||
assertThat(relyingParty.asText()).contains("You're email address is testuser@spring.security.saml");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package example;
|
package example;
|
||||||
|
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -4,5 +4,12 @@ spring:
|
|||||||
relyingparty:
|
relyingparty:
|
||||||
registration:
|
registration:
|
||||||
one:
|
one:
|
||||||
|
signing.credentials:
|
||||||
|
- private-key-location: classpath:credentials/rp-private.key
|
||||||
|
certificate-location: classpath:credentials/rp-certificate.crt
|
||||||
identityprovider:
|
identityprovider:
|
||||||
metadata-uri: https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/metadata.php
|
metadata-uri: https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/metadata.php
|
||||||
|
|
||||||
|
|
||||||
|
logging.level:
|
||||||
|
org.springframework.security: TRACE
|
||||||
|
@ -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");
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
~ you may not use this file except in compliance with the License.
|
~ you may not use this file except in compliance with the License.
|
||||||
@ -17,30 +17,44 @@
|
|||||||
<!doctype html>
|
<!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">
|
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org" xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
|
||||||
<head>
|
<head>
|
||||||
<title>Spring Security - SAML 2.0 Login</title>
|
<title>Spring Security - SAML 2.0 Login & Logout</title>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<style>
|
<style>
|
||||||
span, dt {
|
span, dt {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
</style>
|
</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>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div>
|
<div class="container">
|
||||||
<form th:action="@{/logout}" method="post">
|
<ul class="nav">
|
||||||
<input type="submit" value="Logout" />
|
<li class="nav-item">
|
||||||
</form>
|
<form th:action="@{/logout}" method="post">
|
||||||
<a href="https://simplesaml-for-spring-saml.cfapps.io/module.php/core/authenticate.php?as=example-userpass&logout">
|
<button class="btn btn-primary" id="rp_logout_button" type="submit">
|
||||||
Log out of SimpleSAMLphp
|
RP-initiated Logout
|
||||||
</a>
|
</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>
|
</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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user