13 Commits

Author SHA1 Message Date
Marcus Da Coregio 32c8db1c3e Add Gradle Enterprise plugin
Issue gh-94
2022-09-16 08:55:29 -03:00
Marcus Da Coregio d7a34c849a Accept gradle Terms of Service
Issue gh-94
2022-09-16 08:55:29 -03:00
Marcus Da Coregio 67d1bb921d Add new task that runs all subproject's tests 2022-09-16 08:55:29 -03:00
Marcus Da Coregio 1fc20d346e Fix command to run Spring Boot SAML2 samples 2022-07-28 16:17:46 -03:00
Rob Winch b2310d91fe jcenter() -> mavenCentral()
jcenter is intermittently producing circular redirects. It is deprecated and we should use
Maven Central anyway.
2022-05-18 10:39:49 -05:00
Marcus Da Coregio b67e18fb82 Add init script to be used in Spring Security CI
Issue https://github.com/spring-projects/spring-security/issues/10344
2022-05-11 16:05:03 -03:00
Steve Riesenberg dbf3fbb635 Update to Spring Authorization Server 0.2.3 2022-03-28 11:22:43 -05:00
Marcus Da Coregio a4c998ed77 Update README for SAML 2.0 samples 2022-03-28 11:00:25 -03:00
Marcus Da Coregio da6fa7a565 Re-enable SAML 2.0 samples with Okta IdP
Closes gh-55
2022-03-17 09:19:45 -03:00
Marcus Da Coregio 802311ac70 SAML 2.0 Login & Logout XML Sample
Issue gh-57
2022-03-10 12:17:21 -03:00
Eleftheria Stein 2ddf0a2fa9 Update LDAP samples to use LdapBindAuthenticationManagerFactory
Closes gh-61
2022-01-31 12:37:36 +01:00
Steve Riesenberg a19471b510 Update Spring Authorization Server to 0.2.1 2022-01-20 11:41:45 -06:00
Steve Riesenberg 73fbaa9950 Add milestone repository
Closes gh-58
2022-01-14 13:40:19 -06:00
101 changed files with 8527 additions and 419 deletions
+1 -1
View File
@@ -4,5 +4,5 @@
<component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="temurin-11" project-jdk-type="JavaSDK" />
</project>
+23 -1
View File
@@ -20,4 +20,26 @@ allprojects {
}
}
}
}
}
if (hasProperty('buildScan')) {
buildScan {
termsOfServiceUrl = 'https://gradle.com/terms-of-service'
termsOfServiceAgree = 'yes'
}
}
repositories {
mavenCentral()
}
tasks.register('runAllTests') {
var allTasks = rootProject.getAllTasks(true)
var allTestsTasks = allTasks.values().collect { t ->
t.findAll { it.name == 'test' || it.name == 'integrationTest' }
}.flatten()
it.dependsOn {
allTestsTasks
}
}
+2 -1
View File
@@ -6,7 +6,8 @@ plugins {
}
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
}
@@ -6,7 +6,8 @@ plugins {
}
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
}
+2 -1
View File
@@ -6,7 +6,8 @@ plugins {
}
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
}
@@ -6,7 +6,8 @@ plugins {
}
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
}
@@ -6,7 +6,8 @@ plugins {
}
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
}
@@ -6,7 +6,8 @@ plugins {
}
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
}
@@ -6,7 +6,8 @@ plugins {
}
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
}
+2 -1
View File
@@ -6,7 +6,8 @@ plugins {
}
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
}
+2 -1
View File
@@ -6,7 +6,8 @@ plugins {
}
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
}
@@ -6,7 +6,8 @@ plugins {
}
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
}
@@ -6,7 +6,8 @@ plugins {
}
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
}
@@ -6,7 +6,8 @@ plugins {
}
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
}
@@ -4,7 +4,8 @@ plugins {
}
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
}
@@ -8,7 +8,8 @@ plugins {
apply from: "gradle/gretty.gradle"
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
}
@@ -8,7 +8,8 @@ plugins {
apply from: "gradle/gretty.gradle"
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
}
@@ -8,7 +8,8 @@ plugins {
apply from: "gradle/gretty.gradle"
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
}
@@ -8,7 +8,8 @@ plugins {
apply from: "gradle/gretty.gradle"
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
}
@@ -8,7 +8,8 @@ plugins {
apply from: "gradle/gretty.gradle"
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
}
@@ -8,7 +8,8 @@ plugins {
apply from: "gradle/gretty.gradle"
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 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.
@@ -17,39 +17,29 @@ package example;
import org.springframework.context.annotation.Bean;
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
import org.springframework.security.ldap.authentication.BindAuthenticator;
import org.springframework.security.ldap.authentication.LdapAuthenticationProvider;
import org.springframework.security.ldap.authentication.LdapAuthenticator;
import org.springframework.security.ldap.server.UnboundIdContainer;
import org.springframework.security.config.ldap.EmbeddedLdapServerContextSourceFactoryBean;
import org.springframework.security.config.ldap.LdapBindAuthenticationManagerFactory;
import org.springframework.security.ldap.userdetails.PersonContextMapper;
@EnableWebSecurity
public class SecurityConfiguration {
@Bean
UnboundIdContainer ldapContainer() {
UnboundIdContainer result = new UnboundIdContainer("dc=springframework,dc=org", "classpath:users.ldif");
result.setPort(0);
return result;
public EmbeddedLdapServerContextSourceFactoryBean contextSourceFactoryBean() {
EmbeddedLdapServerContextSourceFactoryBean contextSourceFactoryBean = EmbeddedLdapServerContextSourceFactoryBean
.fromEmbeddedLdapServer();
contextSourceFactoryBean.setPort(0);
return contextSourceFactoryBean;
}
@Bean
DefaultSpringSecurityContextSource contextSource(UnboundIdContainer container) {
return new DefaultSpringSecurityContextSource(
"ldap://localhost:" + container.getPort() + "/dc=springframework,dc=org");
}
@Bean
BindAuthenticator authenticator(BaseLdapPathContextSource contextSource) {
BindAuthenticator authenticator = new BindAuthenticator(contextSource);
authenticator.setUserDnPatterns(new String[] { "uid={0},ou=people" });
return authenticator;
}
@Bean
LdapAuthenticationProvider authenticationProvider(LdapAuthenticator authenticator) {
return new LdapAuthenticationProvider(authenticator);
AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource) {
LdapBindAuthenticationManagerFactory factory = new LdapBindAuthenticationManagerFactory(contextSource);
factory.setUserDnPatterns("uid={0},ou=people");
factory.setUserDetailsContextMapper(new PersonContextMapper());
return factory.createAuthenticationManager();
}
}
@@ -8,7 +8,8 @@ plugins {
//apply from: "gradle/gretty.gradle"
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
}
+2 -1
View File
@@ -4,7 +4,8 @@ plugins {
}
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
}
@@ -8,7 +8,8 @@ plugins {
apply from: "gradle/gretty.gradle"
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
}
@@ -8,7 +8,8 @@ plugins {
apply from: "gradle/gretty.gradle"
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
}
@@ -8,7 +8,8 @@ plugins {
apply from: "gradle/gretty.gradle"
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
}
@@ -8,7 +8,8 @@ plugins {
apply from: "gradle/gretty.gradle"
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
}
@@ -12,18 +12,18 @@ The https://docs.spring.io/spring-security/reference/servlet/saml2/logout.html[S
=== 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 https://developer.okta.com/docs/guides/build-sso-integration/saml2/main/[Okta SAML 2.0 IDP] reference implementation.
The following features are implemented in the MVP:
1. Receive and validate a SAML 2.0 Response containing an assertion, and create a corresponding authentication in Spring Security
2. Send a SAML 2.0 AuthNRequest to an Identity Provider
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 Okta SAML 2.0 IDP 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.
`saml2Logout()` supports RP- and AP-initiated SAML 2.0 Single Logout via the HTTP-POST and HTTP-REDIRECT bindings against the https://developer.okta.com/docs/guides/build-sso-integration/saml2/main/[Okta SAML 2.0 IDP] reference implementation.
On this sample, the SAML 2.0 Logout is using the HTTP-POST binding.
@@ -31,20 +31,21 @@ You can refer to the https://docs.spring.io/spring-security/reference/servlet/sa
== Run the Sample
=== Start up the application
You should run the application war in a servlet container like Tomcat
=== Start up the Sample Boot Application
```
./gradlew :spring-security-samples-boot-saml2login:bootRun
```
=== Open a Browser
http://localhost:8080/
You will be redirect to the SimpleSAMLphp IDP
You will be redirect to the Okta SAML 2.0 IDP
=== Type in your credentials
```
User: user
Password: password
User: testuser@spring.security.saml
Password: 12345678
```
@@ -24,7 +24,8 @@ plugins {
apply from: "gradle/gretty.gradle"
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
maven { url "https://build.shibboleth.net/nexus/content/repositories/releases/" }
}
@@ -16,15 +16,14 @@
package example;
import java.io.IOException;
import com.gargoylesoftware.htmlunit.ElementNotFoundException;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.HtmlElement;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlInput;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.html.HtmlPasswordInput;
import com.gargoylesoftware.htmlunit.html.HtmlSubmitInput;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -40,6 +39,8 @@ import org.springframework.test.web.servlet.htmlunit.MockMvcWebClientBuilder;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = ApplicationConfiguration.class)
@WebAppConfiguration
@@ -66,35 +67,45 @@ public class Saml2JavaConfigurationITests {
@Test
void authenticationAttemptWhenValidThenShowsUserEmailAddress() throws Exception {
HtmlPage relyingParty = performLogin();
Assertions.assertThat(relyingParty.asText()).contains("You're email address is testuser@spring.security.saml");
performLogin();
HtmlPage home = (HtmlPage) this.webClient.getCurrentWindow().getEnclosedPage();
assertThat(home.asText()).contains("You're email address is testuser@spring.security.saml");
}
@Test
void logoutWhenRelyingPartyInitiatedLogoutThenLoginPageWithLogoutParam() throws Exception {
HtmlPage relyingParty = performLogin();
HtmlElement rpLogoutButton = relyingParty.getHtmlElementById("rp_logout_button");
performLogin();
HtmlPage home = (HtmlPage) this.webClient.getCurrentWindow().getEnclosedPage();
HtmlElement rpLogoutButton = home.getHtmlElementById("rp_logout_button");
HtmlPage loginPage = rpLogoutButton.click();
Assertions.assertThat(loginPage.getUrl().getFile()).isEqualTo("/login?logout");
assertThat(loginPage.getUrl().getFile()).isEqualTo("/login?logout");
}
@Test
void logoutWhenAssertingPartyInitiatedLogoutThenLoginPageWithLogoutParam() throws Exception {
HtmlPage relyingParty = performLogin();
HtmlElement apLogoutButton = relyingParty.getHtmlElementById("ap_logout_button");
HtmlPage loginPage = apLogoutButton.click();
Assertions.assertThat(loginPage.getUrl().getFile()).isEqualTo("/login?logout");
}
private HtmlPage performLogin() throws IOException {
private void performLogin() throws Exception {
HtmlPage login = this.webClient.getPage("/");
HtmlForm form = login.getFormByName("f");
this.webClient.waitForBackgroundJavaScript(10000);
HtmlForm form = findForm(login);
HtmlInput username = form.getInputByName("username");
HtmlInput password = form.getInputByName("password");
HtmlSubmitInput submit = login.getHtmlElementById("submit_button");
username.setValueAttribute("user");
password.setValueAttribute("password");
return submit.click();
HtmlPasswordInput password = form.getInputByName("password");
HtmlSubmitInput submit = login.getHtmlElementById("okta-signin-submit");
username.type("testuser@spring.security.saml");
password.type("12345678");
submit.click();
this.webClient.waitForBackgroundJavaScript(10000);
}
private HtmlForm findForm(HtmlPage login) {
for (HtmlForm form : login.getForms()) {
try {
if (form.getId().equals("form19")) {
return form;
}
}
catch (ElementNotFoundException ex) {
// Continue
}
}
throw new IllegalStateException("Could not resolve login form");
}
}
@@ -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";
@@ -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);
}
@@ -36,11 +36,6 @@
</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">
@@ -6,7 +6,8 @@ plugins {
}
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
}
@@ -6,7 +6,8 @@ plugins {
}
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
}
@@ -6,7 +6,8 @@ plugins {
}
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
}
@@ -6,7 +6,8 @@ plugins {
}
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
}
+2 -1
View File
@@ -6,7 +6,8 @@ plugins {
}
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
}
@@ -5,7 +5,8 @@ plugins {
}
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
}
+2 -1
View File
@@ -5,7 +5,8 @@ plugins {
}
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 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.
@@ -18,13 +18,10 @@ package example;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
import org.springframework.security.ldap.authentication.BindAuthenticator;
import org.springframework.security.ldap.authentication.LdapAuthenticationProvider;
import org.springframework.security.ldap.authentication.LdapAuthenticator;
import org.springframework.security.ldap.server.UnboundIdContainer;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.ldap.EmbeddedLdapServerContextSourceFactoryBean;
import org.springframework.security.config.ldap.LdapBindAuthenticationManagerFactory;
import org.springframework.security.ldap.userdetails.PersonContextMapper;
/**
@@ -36,30 +33,19 @@ import org.springframework.security.ldap.userdetails.PersonContextMapper;
public class SecurityConfig {
@Bean
UnboundIdContainer ldapContainer() {
UnboundIdContainer container = new UnboundIdContainer("dc=springframework,dc=org", "classpath:users.ldif");
container.setPort(0);
return container;
public EmbeddedLdapServerContextSourceFactoryBean contextSourceFactoryBean() {
EmbeddedLdapServerContextSourceFactoryBean contextSourceFactoryBean = EmbeddedLdapServerContextSourceFactoryBean
.fromEmbeddedLdapServer();
contextSourceFactoryBean.setPort(0);
return contextSourceFactoryBean;
}
@Bean
ContextSource contextSource(UnboundIdContainer container) {
int port = container.getPort();
return new DefaultSpringSecurityContextSource("ldap://localhost:" + port + "/dc=springframework,dc=org");
}
@Bean
BindAuthenticator authenticator(BaseLdapPathContextSource contextSource) {
BindAuthenticator authenticator = new BindAuthenticator(contextSource);
authenticator.setUserDnPatterns(new String[] { "uid={0},ou=people" });
return authenticator;
}
@Bean
LdapAuthenticationProvider authenticationProvider(LdapAuthenticator authenticator) {
LdapAuthenticationProvider provider = new LdapAuthenticationProvider(authenticator);
provider.setUserDetailsContextMapper(new PersonContextMapper());
return provider;
AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource) {
LdapBindAuthenticationManagerFactory factory = new LdapBindAuthenticationManagerFactory(contextSource);
factory.setUserDnPatterns("uid={0},ou=people");
factory.setUserDetailsContextMapper(new PersonContextMapper());
return factory.createAuthenticationManager();
}
}
@@ -6,13 +6,14 @@ plugins {
}
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.security:spring-security-oauth2-authorization-server:0.2.0'
implementation 'org.springframework.security:spring-security-oauth2-authorization-server:0.2.3'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
@@ -6,7 +6,8 @@ plugins {
}
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
}
@@ -6,7 +6,8 @@ plugins {
}
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
}
@@ -22,7 +22,8 @@ plugins {
}
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
}
@@ -22,7 +22,8 @@ plugins {
}
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
}
@@ -22,7 +22,8 @@ plugins {
}
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
}
@@ -22,7 +22,8 @@ plugins {
}
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
}
@@ -22,7 +22,8 @@ plugins {
}
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
}
@@ -12,18 +12,18 @@ The https://docs.spring.io/spring-security/reference/servlet/saml2/logout.html[S
=== 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 https://developer.okta.com/docs/guides/build-sso-integration/saml2/main/[Okta SAML 2.0 IDP] reference implementation.
The following features are implemented in the MVP:
1. Receive and validate a SAML 2.0 Response containing an assertion, and create a corresponding authentication in Spring Security
2. Send a SAML 2.0 AuthNRequest to an Identity Provider
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 Okta SAML 2.0 IDP 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.
`saml2Logout()` supports RP- and AP-initiated SAML 2.0 Single Logout via the HTTP-POST and HTTP-REDIRECT bindings against the https://developer.okta.com/docs/guides/build-sso-integration/saml2/main/[Okta SAML 2.0 IDP] reference implementation.
On this sample, the SAML 2.0 Logout is using the HTTP-POST binding.
@@ -33,19 +33,20 @@ You can refer to the https://docs.spring.io/spring-security/reference/servlet/sa
=== Start up the Sample Boot Application
```
./gradlew :spring-security-samples-boot-saml2login:bootRun
./gradlew :servlet:spring-boot:java:saml2:login-single-tenant:bootRun
```
=== Open a Browser
http://localhost:8080/
You will be redirect to the SimpleSAMLphp IDP
You will be redirect to the Okta SAML 2.0 IDP
=== Type in your credentials
```
User: user
Password: password
User: testuser@spring.security.saml
Password: 12345678
```
@@ -6,7 +6,8 @@ plugins {
}
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
maven { url "https://build.shibboleth.net/nexus/content/repositories/releases/" }
}
@@ -23,7 +24,7 @@ dependencies {
implementation 'org.springframework.security:spring-security-saml2-service-provider'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
testImplementation 'net.sourceforge.htmlunit:htmlunit'
testImplementation 'net.sourceforge.htmlunit:htmlunit:2.44.0'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
}
@@ -31,4 +32,4 @@ dependencies {
tasks.withType(Test).configureEach {
useJUnitPlatform()
outputs.upToDateWhen { false }
}
}
File diff suppressed because one or more lines are too long
@@ -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";
@@ -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);
}
}
}
@@ -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
@@ -36,11 +36,6 @@
</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">
@@ -12,18 +12,18 @@ The https://docs.spring.io/spring-security/reference/servlet/saml2/logout.html[S
=== 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 https://developer.okta.com/docs/guides/build-sso-integration/saml2/main/[Okta SAML 2.0 IDP] reference implementation.
The following features are implemented in the MVP:
1. Receive and validate a SAML 2.0 Response containing an assertion, and create a corresponding authentication in Spring Security
2. Send a SAML 2.0 AuthNRequest to an Identity Provider
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 Okta SAML 2.0 IDP 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.
`saml2Logout()` supports RP- and AP-initiated SAML 2.0 Single Logout via the HTTP-POST and HTTP-REDIRECT bindings against the https://developer.okta.com/docs/guides/build-sso-integration/saml2/main/[Okta SAML 2.0 IDP] reference implementation.
On this sample, the SAML 2.0 Logout is using the HTTP-POST binding.
@@ -33,19 +33,19 @@ You can refer to the https://docs.spring.io/spring-security/reference/servlet/sa
=== Start up the Sample Boot Application
```
./gradlew :spring-security-samples-boot-saml2login:bootRun
./gradlew :servlet:spring-boot:java:saml2:login:bootRun
```
=== Open a Browser
http://localhost:8080/
You will be redirect to the SimpleSAMLphp IDP
You will be redirect to the Okta SAML 2.0 IDP
=== Type in your credentials
```
User: user
Password: password
User: testuser@spring.security.saml
Password: 12345678
```
@@ -6,7 +6,8 @@ plugins {
}
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
maven { url "https://build.shibboleth.net/nexus/content/repositories/releases/" }
}
@@ -23,7 +24,7 @@ dependencies {
implementation 'org.springframework.security:spring-security-saml2-service-provider'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
testImplementation 'net.sourceforge.htmlunit:htmlunit'
testImplementation 'net.sourceforge.htmlunit:htmlunit:2.44.0'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
}
@@ -31,4 +32,4 @@ dependencies {
tasks.withType(Test).configureEach {
useJUnitPlatform()
outputs.upToDateWhen { false }
}
}
@@ -1,2 +1,3 @@
version=5.7.0-SNAPSHOT
spring-security.version=5.7.0-SNAPSHOT
selenium-htmlunit.version=2.44.0
File diff suppressed because one or more lines are too long
@@ -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";
@@ -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);
}
}
}
@@ -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
@@ -36,11 +36,6 @@
</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">
@@ -1,4 +1,4 @@
= SAML 2.0 Login & Logout Sample
= SAML 2.0 Refreshable Metadata
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.
@@ -12,18 +12,18 @@ The https://docs.spring.io/spring-security/reference/servlet/saml2/logout.html[S
=== 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 https://developer.okta.com/docs/guides/build-sso-integration/saml2/main/[Okta SAML 2.0 IDP] reference implementation.
The following features are implemented in the MVP:
1. Receive and validate a SAML 2.0 Response containing an assertion, and create a corresponding authentication in Spring Security
2. Send a SAML 2.0 AuthNRequest to an Identity Provider
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 Okta SAML 2.0 IDP 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.
`saml2Logout()` supports RP- and AP-initiated SAML 2.0 Single Logout via the HTTP-POST and HTTP-REDIRECT bindings against the https://developer.okta.com/docs/guides/build-sso-integration/saml2/main/[Okta SAML 2.0 IDP] reference implementation.
On this sample, the SAML 2.0 Logout is using the HTTP-POST binding.
@@ -45,12 +45,14 @@ This particular implementation uses a `@Scheduled` annotation to update its meta
http://localhost:8080/
You will be redirect to the SimpleSAMLphp IDP
You will be redirect to the Okta SAML 2.0 IDP
=== Type in your credentials
```
User: user
Password: password
User: testuser@spring.security.saml
Password: 12345678
```
@@ -6,7 +6,8 @@ plugins {
}
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
maven { url "https://build.shibboleth.net/nexus/content/repositories/releases/" }
}
@@ -23,7 +24,7 @@ dependencies {
implementation 'org.springframework.security:spring-security-saml2-service-provider'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
testImplementation 'net.sourceforge.htmlunit:htmlunit'
testImplementation 'net.sourceforge.htmlunit:htmlunit:2.44.0'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
}
@@ -31,4 +32,4 @@ dependencies {
tasks.withType(Test).configureEach {
useJUnitPlatform()
outputs.upToDateWhen { false }
}
}
File diff suppressed because one or more lines are too long
@@ -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";
@@ -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
@@ -36,11 +36,6 @@
</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">
+2 -1
View File
@@ -8,7 +8,8 @@ plugins {
apply from: "gradle/gretty.gradle"
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
}
+2 -1
View File
@@ -4,7 +4,8 @@ plugins {
}
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
}
+2 -1
View File
@@ -8,7 +8,8 @@ plugins {
apply from: "gradle/gretty.gradle"
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
}
+2 -1
View File
@@ -4,7 +4,8 @@ plugins {
}
repositories {
jcenter()
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
}
@@ -0,0 +1,46 @@
plugins {
id "java"
id "nebula.integtest" version "8.2.0"
id "org.gretty" version "3.0.6"
id "war"
}
apply from: "gradle/gretty.gradle"
repositories {
mavenCentral()
maven { url "https://repo.spring.io/snapshot" }
maven { url "https://build.shibboleth.net/nexus/content/repositories/releases/" }
}
dependencies {
constraints {
implementation "org.opensaml:opensaml-core:4.1.1"
implementation "org.opensaml:opensaml-saml-api:4.1.1"
implementation "org.opensaml:opensaml-saml-impl:4.1.1"
}
implementation platform("org.springframework:spring-framework-bom:5.3.13")
implementation platform("org.springframework.security:spring-security-bom:5.7.0-SNAPSHOT")
implementation platform("org.junit:junit-bom:5.7.0")
implementation "org.springframework.security:spring-security-config"
implementation "org.springframework.security:spring-security-web"
implementation "org.springframework.security:spring-security-saml2-service-provider"
implementation "org.springframework:spring-webmvc"
implementation "org.thymeleaf:thymeleaf-spring5:3.0.11.RELEASE"
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.0.4.RELEASE'
testImplementation "org.assertj:assertj-core:3.18.0"
testImplementation "org.springframework:spring-test"
testImplementation "org.springframework.security:spring-security-test"
testImplementation "org.junit.jupiter:junit-jupiter-api"
testImplementation 'net.sourceforge.htmlunit:htmlunit:2.44.0'
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
}
tasks.withType(Test).configureEach {
useJUnitPlatform()
outputs.upToDateWhen { false }
}
@@ -0,0 +1 @@
version=5.7.0-SNAPSHOT
@@ -0,0 +1,41 @@
gretty {
servletContainer = "tomcat9"
contextPath = "/"
fileLogEnabled = false
integrationTestTask = 'integrationTest'
}
Task prepareAppServerForIntegrationTests = project.tasks.create('prepareAppServerForIntegrationTests') {
group = 'Verification'
description = 'Prepares the app server for integration tests'
doFirst {
project.gretty {
httpPort = -1
}
}
}
project.tasks.matching { it.name == "appBeforeIntegrationTest" }.all { task ->
task.dependsOn prepareAppServerForIntegrationTests
}
project.tasks.matching { it.name == "integrationTest" }.all {
task -> task.doFirst {
def gretty = project.gretty
String host = project.gretty.host ?: 'localhost'
boolean isHttps = gretty.httpsEnabled
Integer httpPort = integrationTest.systemProperties['gretty.httpPort']
Integer httpsPort = integrationTest.systemProperties['gretty.httpsPort']
int port = isHttps ? httpsPort : httpPort
String contextPath = project.gretty.contextPath
String httpBaseUrl = "http://${host}:${httpPort}${contextPath}"
String httpsBaseUrl = "https://${host}:${httpsPort}${contextPath}"
String baseUrl = isHttps ? httpsBaseUrl : httpBaseUrl
integrationTest.systemProperty 'app.port', port
integrationTest.systemProperty 'app.httpPort', httpPort
integrationTest.systemProperty 'app.httpsPort', httpsPort
integrationTest.systemProperty 'app.baseURI', baseUrl
integrationTest.systemProperty 'app.httpBaseURI', httpBaseUrl
integrationTest.systemProperty 'app.httpsBaseURI', httpsBaseUrl
}
}
@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
+185
View File
@@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 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.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"
+104
View File
@@ -0,0 +1,104 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
@@ -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
* <code>localhost:$&#123;local.server.port&#125;</code>.
*
* @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 extends Page> 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);
}
}
@@ -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");
}
}
@@ -0,0 +1,43 @@
/*
* Copyright 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.
* 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.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class IndexController {
private final RelyingPartyRegistrationRepository repository;
public IndexController(RelyingPartyRegistrationRepository repository) {
this.repository = repository;
}
@GetMapping("/")
public String index(Model model, @AuthenticationPrincipal Saml2AuthenticatedPrincipal principal) {
String emailAddress = principal.getFirstAttribute("email");
model.addAttribute("emailAddress", emailAddress);
model.addAttribute("userAttributes", principal.getAttributes());
return "index";
}
}
@@ -0,0 +1,84 @@
/*
* Copyright 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.
* 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.util.List;
import org.thymeleaf.extras.springsecurity5.dialect.SpringSecurityDialect;
import org.thymeleaf.spring5.SpringTemplateEngine;
import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring5.view.ThymeleafViewResolver;
import org.thymeleaf.templatemode.TemplateMode;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@EnableWebMvc
public class WebConfiguration implements WebMvcConfigurer, ApplicationContextAware {
private ApplicationContext context;
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
AuthenticationPrincipalArgumentResolver principalArgumentResolver = new AuthenticationPrincipalArgumentResolver();
principalArgumentResolver
.setBeanResolver(new BeanFactoryResolver(this.context.getAutowireCapableBeanFactory()));
resolvers.add(principalArgumentResolver);
}
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
this.context = context;
}
@Bean
public SpringResourceTemplateResolver templateResolver() {
SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
templateResolver.setApplicationContext(this.context);
templateResolver.setPrefix("/WEB-INF/templates/");
templateResolver.setSuffix(".html");
templateResolver.setTemplateMode(TemplateMode.HTML);
templateResolver.setCacheable(false);
return templateResolver;
}
@Bean
public SpringTemplateEngine templateEngine(SpringResourceTemplateResolver templateResolver) {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
templateEngine.setEnableSpringELCompiler(true);
templateEngine.addDialect(new SpringSecurityDialect());
return templateEngine;
}
@Bean
public ThymeleafViewResolver viewResolver(SpringTemplateEngine templateEngine) {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine);
return viewResolver;
}
}
@@ -0,0 +1,24 @@
-----BEGIN CERTIFICATE-----
MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYD
VQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYD
VQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwX
c2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0Bw
aXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJ
BgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAa
BgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQD
DBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlr
QHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62
E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz
2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWW
RDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQ
nX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5
cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gph
iJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5
ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTAD
AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduO
nRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+v
ZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLu
xbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6z
V9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3
lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk
-----END CERTIFICATE-----
@@ -0,0 +1,16 @@
-----BEGIN CERTIFICATE-----
MIICgTCCAeoCCQCuVzyqFgMSyDANBgkqhkiG9w0BAQsFADCBhDELMAkGA1UEBhMC
VVMxEzARBgNVBAgMCldhc2hpbmd0b24xEjAQBgNVBAcMCVZhbmNvdXZlcjEdMBsG
A1UECgwUU3ByaW5nIFNlY3VyaXR5IFNBTUwxCzAJBgNVBAsMAnNwMSAwHgYDVQQD
DBdzcC5zcHJpbmcuc2VjdXJpdHkuc2FtbDAeFw0xODA1MTQxNDMwNDRaFw0yODA1
MTExNDMwNDRaMIGEMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2FzaGluZ3RvbjES
MBAGA1UEBwwJVmFuY291dmVyMR0wGwYDVQQKDBRTcHJpbmcgU2VjdXJpdHkgU0FN
TDELMAkGA1UECwwCc3AxIDAeBgNVBAMMF3NwLnNwcmluZy5zZWN1cml0eS5zYW1s
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDRu7/EI0BlNzMEBFVAcbx+lLos
vzIWU+01dGTY8gBdhMQNYKZ92lMceo2CuVJ66cUURPym3i7nGGzoSnAxAre+0YIM
+U0razrWtAUE735bkcqELZkOTZLelaoOztmWqRbe5OuEmpewH7cx+kNgcVjdctOG
y3Q6x+I4qakY/9qhBQIDAQABMA0GCSqGSIb3DQEBCwUAA4GBAAeViTvHOyQopWEi
XOfI2Z9eukwrSknDwq/zscR0YxwwqDBMt/QdAODfSwAfnciiYLkmEjlozWRtOeN+
qK7UFgP1bRl5qksrYX5S0z2iGJh0GvonLUt3e20Ssfl5tTEDDnAEUMLfBkyaxEHD
RZ/nbTJ7VTeZOSyRoVn5XHhpuJ0B
-----END CERTIFICATE-----
@@ -0,0 +1,16 @@
-----BEGIN PRIVATE KEY-----
MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBANG7v8QjQGU3MwQE
VUBxvH6Uuiy/MhZT7TV0ZNjyAF2ExA1gpn3aUxx6jYK5UnrpxRRE/KbeLucYbOhK
cDECt77Rggz5TStrOta0BQTvfluRyoQtmQ5Nkt6Vqg7O2ZapFt7k64Sal7AftzH6
Q2BxWN1y04bLdDrH4jipqRj/2qEFAgMBAAECgYEAj4ExY1jjdN3iEDuOwXuRB+Nn
x7pC4TgntE2huzdKvLJdGvIouTArce8A6JM5NlTBvm69mMepvAHgcsiMH1zGr5J5
wJz23mGOyhM1veON41/DJTVG+cxq4soUZhdYy3bpOuXGMAaJ8QLMbQQoivllNihd
vwH0rNSK8LTYWWPZYIECQQDxct+TFX1VsQ1eo41K0T4fu2rWUaxlvjUGhK6HxTmY
8OMJptunGRJL1CUjIb45Uz7SP8TPz5FwhXWsLfS182kRAkEA3l+Qd9C9gdpUh1uX
oPSNIxn5hFUrSTW1EwP9QH9vhwb5Vr8Jrd5ei678WYDLjUcx648RjkjhU9jSMzIx
EGvYtQJBAMm/i9NR7IVyyNIgZUpz5q4LI21rl1r4gUQuD8vA36zM81i4ROeuCly0
KkfdxR4PUfnKcQCX11YnHjk9uTFj75ECQEFY/gBnxDjzqyF35hAzrYIiMPQVfznt
YX/sDTE2AdVBVGaMj1Cb51bPHnNC6Q5kXKQnj/YrLqRQND09Q7ParX0CQQC5NxZr
9jKqhHj8yQD6PlXTsY4Occ7DH6/IoDenfdEVD5qlet0zmd50HatN2Jiqm5ubN7CM
INrtuLp4YHbgk1mi
-----END PRIVATE KEY-----
@@ -0,0 +1,14 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="org.springframework.security" level="TRACE"/>
<root level="TRACE">
<appender-ref ref="STDOUT" />
</root>
</configuration>
@@ -0,0 +1,2 @@
Manifest-Version: 1.0
@@ -0,0 +1,24 @@
<!--
~ Copyright 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.
~ 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.
-->
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="example"/>
</beans>
@@ -0,0 +1,28 @@
<b:beans xmlns="http://www.springframework.org/schema/security"
xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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">
<http auto-config="true">
<intercept-url pattern="/**" access="authenticated"/>
<saml2-login />
<saml2-logout />
</http>
<user-service>
<user name="user" password="{noop}password" authorities="ROLE_USER" />
</user-service>
<relying-party-registrations>
<relying-party-registration registration-id="one"
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">
<signing-credential certificate-location="classpath:credentials/rp-certificate.crt"
private-key-location="classpath:credentials/rp-private.key"/>
</relying-party-registration>
</relying-party-registrations>
</b:beans>
@@ -0,0 +1,55 @@
<!--
~ Copyright 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.
~ 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.
-->
<!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 & 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 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>
</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>
</body>
</html>
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
https://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<!--
- Location of the XML file that defines the root application context
- Applied by ContextLoaderListener.
-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/spring/*.xml
</param-value>
</context-param>
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

+10 -4
View File
@@ -13,6 +13,11 @@ pluginManagement {
}
}
plugins {
id "com.gradle.enterprise" version "3.10.2"
id "io.spring.ge.conventions" version "0.0.7"
}
include ":reactive:rsocket:hello-security"
include ":reactive:webflux-fn:hello"
include ":reactive:webflux-fn:hello-security"
@@ -39,7 +44,7 @@ include ":servlet:java-configuration:hello-mvc-security"
include ":servlet:java-configuration:hello-security"
include ":servlet:java-configuration:hello-security-explicit"
include ":servlet:java-configuration:max-sessions"
//include ":servlet:java-configuration:saml2:login"
include ":servlet:java-configuration:saml2:login"
include ":servlet:spring-boot:java:authentication:username-password:user-details-service:custom-user"
include ":servlet:spring-boot:java:authentication:username-password:mfa"
include ":servlet:spring-boot:java:hello"
@@ -55,12 +60,13 @@ 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"
include ":servlet:xml:java:contacts"
include ":servlet:xml:java:dms"
include ":servlet:xml:java:saml2:login-logout"

Some files were not shown because too many files have changed in this diff Show More