diff --git a/spring-security-modules/pom.xml b/spring-security-modules/pom.xml index 99dea4bc67..096ffb9c3f 100644 --- a/spring-security-modules/pom.xml +++ b/spring-security-modules/pom.xml @@ -35,6 +35,7 @@ spring-security-legacy-oidc spring-security-oidc spring-security-okta + spring-security-saml spring-security-web-react spring-security-web-rest spring-security-web-rest-basic-auth diff --git a/spring-security-modules/spring-security-saml/pom.xml b/spring-security-modules/spring-security-saml/pom.xml new file mode 100644 index 0000000000..561582045a --- /dev/null +++ b/spring-security-modules/spring-security-saml/pom.xml @@ -0,0 +1,73 @@ + + + 4.0.0 + spring-security-saml + 1.0-SNAPSHOT + spring-security-saml + war + + + com.baeldung + parent-boot-2 + 0.0.1-SNAPSHOT + ../../parent-boot-2 + + + + Shibboleth + Shibboleth + https://build.shibboleth.net/nexus/content/repositories/releases/ + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + org.springframework.security.extensions + spring-security-saml2-core + ${saml2-core.spring.version} + + + + + spring-security-saml + + + src/main/resources + + + + + org.springframework.boot + spring-boot-maven-plugin + + true + + + + + repackage + + + + + + + + + 1.0.10.RELEASE + + diff --git a/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/Application.java b/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/Application.java new file mode 100644 index 0000000000..39eaa46424 --- /dev/null +++ b/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/Application.java @@ -0,0 +1,11 @@ +package com.baeldung.saml; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + public static void main(String... args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/authentication/CustomSAMLAuthenticationProvider.java b/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/authentication/CustomSAMLAuthenticationProvider.java new file mode 100644 index 0000000000..b35a72763d --- /dev/null +++ b/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/authentication/CustomSAMLAuthenticationProvider.java @@ -0,0 +1,28 @@ +package com.baeldung.saml.authentication; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.providers.ExpiringUsernameAuthenticationToken; +import org.springframework.security.saml.SAMLAuthenticationProvider; +import org.springframework.security.saml.SAMLCredential; + +public class CustomSAMLAuthenticationProvider extends SAMLAuthenticationProvider { + + @Override + public Collection getEntitlements(SAMLCredential credential, Object userDetail) { + + if(userDetail instanceof ExpiringUsernameAuthenticationToken) { + List authorities = new ArrayList(); + authorities.addAll(((ExpiringUsernameAuthenticationToken) userDetail).getAuthorities()); + return authorities; + + } else { + return Collections.emptyList(); + } + } + +} diff --git a/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/config/SamlSecurityConfig.java b/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/config/SamlSecurityConfig.java new file mode 100644 index 0000000000..378db478cf --- /dev/null +++ b/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/config/SamlSecurityConfig.java @@ -0,0 +1,226 @@ +package com.baeldung.saml.config; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.opensaml.saml2.metadata.provider.FilesystemMetadataProvider; +import org.opensaml.saml2.metadata.provider.MetadataProvider; +import org.opensaml.saml2.metadata.provider.MetadataProviderException; +import org.opensaml.util.resource.ResourceException; +import org.opensaml.xml.parse.StaticBasicParserPool; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.Resource; +import org.springframework.security.saml.*; +import org.springframework.security.saml.context.SAMLContextProviderImpl; +import org.springframework.security.saml.key.JKSKeyManager; +import org.springframework.security.saml.key.KeyManager; +import org.springframework.security.saml.log.SAMLDefaultLogger; +import org.springframework.security.saml.metadata.CachingMetadataManager; +import org.springframework.security.saml.metadata.ExtendedMetadata; +import org.springframework.security.saml.metadata.ExtendedMetadataDelegate; +import org.springframework.security.saml.processor.*; +import org.springframework.security.saml.util.VelocityFactory; +import org.springframework.security.saml.websso.*; +import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; +import org.springframework.security.web.authentication.logout.LogoutHandler; +import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; +import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler; + +import com.baeldung.saml.authentication.CustomSAMLAuthenticationProvider; + +@Configuration +public class SamlSecurityConfig { + + @Value("${saml.keystore.location}") + private String samlKeystoreLocation; + + @Value("${saml.keystore.password}") + private String samlKeystorePassword; + + @Value("${saml.keystore.alias}") + private String samlKeystoreAlias; + + @Value("${saml.idp}") + private String defaultIdp; + + @Bean(initMethod = "initialize") + public StaticBasicParserPool parserPool() { + return new StaticBasicParserPool(); + } + + @Bean + public SAMLAuthenticationProvider samlAuthenticationProvider() { + return new CustomSAMLAuthenticationProvider(); + } + + @Bean + public SAMLContextProviderImpl contextProvider() { + return new SAMLContextProviderImpl(); + } + + @Bean + public static SAMLBootstrap samlBootstrap() { + return new SAMLBootstrap(); + } + + @Bean + public SAMLDefaultLogger samlLogger() { + return new SAMLDefaultLogger(); + } + + @Bean + public WebSSOProfileConsumer webSSOprofileConsumer() { + return new WebSSOProfileConsumerImpl(); + } + + @Bean + @Qualifier("hokWebSSOprofileConsumer") + public WebSSOProfileConsumerHoKImpl hokWebSSOProfileConsumer() { + return new WebSSOProfileConsumerHoKImpl(); + } + + @Bean + public WebSSOProfile webSSOprofile() { + return new WebSSOProfileImpl(); + } + + @Bean + public WebSSOProfileConsumerHoKImpl hokWebSSOProfile() { + return new WebSSOProfileConsumerHoKImpl(); + } + + @Bean + public WebSSOProfileECPImpl ecpProfile() { + return new WebSSOProfileECPImpl(); + } + + @Bean + public SingleLogoutProfile logoutProfile() { + return new SingleLogoutProfileImpl(); + } + + @Bean + public KeyManager keyManager() { + DefaultResourceLoader loader = new DefaultResourceLoader(); + Resource storeFile = loader.getResource(samlKeystoreLocation); + Map passwords = new HashMap<>(); + passwords.put(samlKeystoreAlias, samlKeystorePassword); + return new JKSKeyManager(storeFile, samlKeystorePassword, passwords, samlKeystoreAlias); + } + + @Bean + public WebSSOProfileOptions defaultWebSSOProfileOptions() { + WebSSOProfileOptions webSSOProfileOptions = new WebSSOProfileOptions(); + webSSOProfileOptions.setIncludeScoping(false); + return webSSOProfileOptions; + } + + @Bean + public SAMLEntryPoint samlEntryPoint() { + SAMLEntryPoint samlEntryPoint = new SAMLEntryPoint(); + samlEntryPoint.setDefaultProfileOptions(defaultWebSSOProfileOptions()); + return samlEntryPoint; + } + + @Bean + public ExtendedMetadata extendedMetadata() { + ExtendedMetadata extendedMetadata = new ExtendedMetadata(); + extendedMetadata.setIdpDiscoveryEnabled(false); + extendedMetadata.setSignMetadata(false); + return extendedMetadata; + } + + @Bean + @Qualifier("okta") + public ExtendedMetadataDelegate oktaExtendedMetadataProvider() throws MetadataProviderException { + File metadata = null; + try { + metadata = new File("./src/main/resources/saml/metadata/sso.xml"); + } catch (Exception e) { + e.printStackTrace(); + } + FilesystemMetadataProvider provider = new FilesystemMetadataProvider(metadata); + provider.setParserPool(parserPool()); + return new ExtendedMetadataDelegate(provider, extendedMetadata()); + } + + @Bean + @Qualifier("metadata") + public CachingMetadataManager metadata() throws MetadataProviderException, ResourceException { + List providers = new ArrayList<>(); + providers.add(oktaExtendedMetadataProvider()); + CachingMetadataManager metadataManager = new CachingMetadataManager(providers); + metadataManager.setDefaultIDP(defaultIdp); + return metadataManager; + } + + @Bean + @Qualifier("saml") + public SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler() { + SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler = new SavedRequestAwareAuthenticationSuccessHandler(); + successRedirectHandler.setDefaultTargetUrl("/home"); + return successRedirectHandler; + } + + @Bean + @Qualifier("saml") + public SimpleUrlAuthenticationFailureHandler authenticationFailureHandler() { + SimpleUrlAuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler(); + failureHandler.setUseForward(true); + failureHandler.setDefaultFailureUrl("/error"); + return failureHandler; + } + + @Bean + public SimpleUrlLogoutSuccessHandler successLogoutHandler() { + SimpleUrlLogoutSuccessHandler successLogoutHandler = new SimpleUrlLogoutSuccessHandler(); + successLogoutHandler.setDefaultTargetUrl("/"); + return successLogoutHandler; + } + + @Bean + public SecurityContextLogoutHandler logoutHandler() { + SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler(); + logoutHandler.setInvalidateHttpSession(true); + logoutHandler.setClearAuthentication(true); + return logoutHandler; + } + + @Bean + public SAMLLogoutProcessingFilter samlLogoutProcessingFilter() { + return new SAMLLogoutProcessingFilter(successLogoutHandler(), logoutHandler()); + } + + @Bean + public SAMLLogoutFilter samlLogoutFilter() { + return new SAMLLogoutFilter(successLogoutHandler(), + new LogoutHandler[] { logoutHandler() }, + new LogoutHandler[] { logoutHandler() }); + } + + @Bean + public HTTPPostBinding httpPostBinding() { + return new HTTPPostBinding(parserPool(), VelocityFactory.getEngine()); + } + + @Bean + public HTTPRedirectDeflateBinding httpRedirectDeflateBinding() { + return new HTTPRedirectDeflateBinding(parserPool()); + } + + @Bean + public SAMLProcessorImpl processor() { + ArrayList bindings = new ArrayList<>(); + bindings.add(httpRedirectDeflateBinding()); + bindings.add(httpPostBinding()); + return new SAMLProcessorImpl(bindings); + } +} diff --git a/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/config/WebSecurityConfig.java b/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/config/WebSecurityConfig.java new file mode 100644 index 0000000000..297c391823 --- /dev/null +++ b/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/config/WebSecurityConfig.java @@ -0,0 +1,152 @@ +package com.baeldung.saml.config; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.saml.*; +import org.springframework.security.saml.key.KeyManager; +import org.springframework.security.saml.metadata.*; +import org.springframework.security.web.DefaultSecurityFilterChain; +import org.springframework.security.web.FilterChainProxy; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.access.channel.ChannelProcessingFilter; +import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; +import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; +import org.springframework.security.web.csrf.CsrfFilter; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; + +@Configuration +@EnableWebSecurity +@EnableGlobalMethodSecurity(securedEnabled = true) +public class WebSecurityConfig extends WebSecurityConfigurerAdapter { + + @Value("${saml.sp}") + private String samlAudience; + + @Autowired + @Qualifier("saml") + private SavedRequestAwareAuthenticationSuccessHandler samlAuthSuccessHandler; + + @Autowired + @Qualifier("saml") + private SimpleUrlAuthenticationFailureHandler samlAuthFailureHandler; + + @Autowired + private SAMLEntryPoint samlEntryPoint; + + @Autowired + private SAMLLogoutFilter samlLogoutFilter; + + @Autowired + private SAMLLogoutProcessingFilter samlLogoutProcessingFilter; + + @Bean + public SAMLDiscovery samlDiscovery() { + SAMLDiscovery idpDiscovery = new SAMLDiscovery(); + return idpDiscovery; + } + + @Autowired + private SAMLAuthenticationProvider samlAuthenticationProvider; + + @Autowired + private ExtendedMetadata extendedMetadata; + + @Autowired + private KeyManager keyManager; + + public MetadataGenerator metadataGenerator() { + MetadataGenerator metadataGenerator = new MetadataGenerator(); + metadataGenerator.setEntityId(samlAudience); + metadataGenerator.setExtendedMetadata(extendedMetadata); + metadataGenerator.setIncludeDiscoveryExtension(false); + metadataGenerator.setKeyManager(keyManager); + return metadataGenerator; + } + + @Bean + public SAMLProcessingFilter samlWebSSOProcessingFilter() throws Exception { + SAMLProcessingFilter samlWebSSOProcessingFilter = new SAMLProcessingFilter(); + samlWebSSOProcessingFilter.setAuthenticationManager(authenticationManager()); + samlWebSSOProcessingFilter.setAuthenticationSuccessHandler(samlAuthSuccessHandler); + samlWebSSOProcessingFilter.setAuthenticationFailureHandler(samlAuthFailureHandler); + return samlWebSSOProcessingFilter; + } + + @Bean + public FilterChainProxy samlFilter() throws Exception { + List chains = new ArrayList<>(); + chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SSO/**"), + samlWebSSOProcessingFilter())); + chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/discovery/**"), + samlDiscovery())); + chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/login/**"), + samlEntryPoint)); + chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/logout/**"), + samlLogoutFilter)); + chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SingleLogout/**"), + samlLogoutProcessingFilter)); + return new FilterChainProxy(chains); + } + + @Bean + @Override + public AuthenticationManager authenticationManagerBean() throws Exception { + return super.authenticationManagerBean(); + } + + @Bean + public MetadataGeneratorFilter metadataGeneratorFilter() { + return new MetadataGeneratorFilter(metadataGenerator()); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .csrf() + .disable(); + + http + .httpBasic() + .authenticationEntryPoint(samlEntryPoint); + + http + .addFilterBefore(metadataGeneratorFilter(), ChannelProcessingFilter.class) + .addFilterAfter(samlFilter(), BasicAuthenticationFilter.class) + .addFilterBefore(samlFilter(), CsrfFilter.class); + + http + .authorizeRequests() + .antMatchers("/").permitAll() + .anyRequest().authenticated(); + + http + .logout() + .addLogoutHandler((request, response, authentication) -> { + try { + response.sendRedirect("/saml/logout"); + } catch (IOException e) { + e.printStackTrace(); + } + }); + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.authenticationProvider(samlAuthenticationProvider); + } + +} diff --git a/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/controller/HomeController.java b/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/controller/HomeController.java new file mode 100644 index 0000000000..e77933b8f3 --- /dev/null +++ b/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/controller/HomeController.java @@ -0,0 +1,35 @@ +package com.baeldung.saml.controller; + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +public class HomeController { + + @RequestMapping("/") + public String index() { + return "index"; + } + + @GetMapping(value = "/auth") + public String handleSamlAuth() { + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + if (auth != null) { + return "redirect:/home"; + } else { + return "/"; + } + } + + @RequestMapping("/home") + public String home(Model model) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + model.addAttribute("username", authentication.getPrincipal()); + return "home"; + } + +} diff --git a/spring-security-modules/spring-security-saml/src/main/resources/application.properties b/spring-security-modules/spring-security-saml/src/main/resources/application.properties new file mode 100644 index 0000000000..f9d6a5df3c --- /dev/null +++ b/spring-security-modules/spring-security-saml/src/main/resources/application.properties @@ -0,0 +1,6 @@ +saml.keystore.location=classpath:/saml/samlKeystore.jks +saml.keystore.password= +saml.keystore.alias= + +saml.idp= +saml.sp=http://localhost:8080/saml/metadata \ No newline at end of file diff --git a/spring-security-modules/spring-security-saml/src/main/resources/saml/metadata/sso.xml b/spring-security-modules/spring-security-saml/src/main/resources/saml/metadata/sso.xml new file mode 100644 index 0000000000..2d3258de12 --- /dev/null +++ b/spring-security-modules/spring-security-saml/src/main/resources/saml/metadata/sso.xml @@ -0,0 +1,44 @@ + + + + + + + MIIDpDCCAoygAwIBAgIGAXGiSQ7ZMA0GCSqGSIb3DQEBCwUAMIGSMQswCQYDVQQGEwJVUzETMBEG + A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU + MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi05MjY2NjYxHDAaBgkqhkiG9w0BCQEW + DWluZm9Ab2t0YS5jb20wHhcNMjAwNDIyMTQyNjA5WhcNMzAwNDIyMTQyNzA5WjCBkjELMAkGA1UE + BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV + BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtOTI2NjY2MRwwGgYJ + KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA + g1rQYYqeVx2gl/UUnLJzp5hrm06VOILJB9hIUmNqXgWV3UjzDq/zX0KW8MENjsO7+S8a+LLnYRkb + N5egH9FSt8AHtB1pmfXDtpUQmWe9yJbNxbCISoc6XzCmaRw3HRv9pK5SciIutciz9lvFaHMWAWtP + MmQSKdhMet52tuf6sTy4ODeXjyMnD9q5QOKww1SJ678wjHbGRRhNvCxvTSAH33sa4oNCf2RvP9hp + NiJRcYW9yLZXmZArPQOuAx5PIXfHhK2e4ac39YO4fgO7gwU5TZ+vL7o6iEmd9tk44PrND0ZV5yzZ + +Y33Hiun3fIiZu/nZZGUjm4k4exl8JJpwrVTHQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBcfHcL + 2DjTjZGoANF4dPpGXTYdVnL/XzGiLS+3LR/HDrEz/EqsHouF40RnzdZ7Ax7RReKBYCUUqHpSE+LU + ductz2ANguzyseGEn72I4Ym4ytQWnFyTXeW+xI9CoCLGfOUhT1hlKjsu/qNM8qwKFPWkzQp7mDN8 + S9MGhsnbiyeD/lceAEKw16Os73/sX2j7F+43WVCYRDCRB8pRIPfcqYLXUIUSstQlwEvCF7HyeO4+ + jxKHA1tp9Cpmj7/VD9TE3fyvrbVmfjTbKjF7/0wYQNfbHDDko0ratDMAizG5/d3i9wk9KbGCHSxT + ph5nl1pdjKgAYPK0iNDnGCZbGKzXOrqV + + + + + urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified + + urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress + + + + + \ No newline at end of file diff --git a/spring-security-modules/spring-security-saml/src/main/resources/saml/samlKeystore.jks b/spring-security-modules/spring-security-saml/src/main/resources/saml/samlKeystore.jks new file mode 100644 index 0000000000..7f3a5850d9 Binary files /dev/null and b/spring-security-modules/spring-security-saml/src/main/resources/saml/samlKeystore.jks differ diff --git a/spring-security-modules/spring-security-saml/src/main/resources/templates/error.html b/spring-security-modules/spring-security-saml/src/main/resources/templates/error.html new file mode 100644 index 0000000000..7223ee43fd --- /dev/null +++ b/spring-security-modules/spring-security-saml/src/main/resources/templates/error.html @@ -0,0 +1,13 @@ + + + +
Something went wrong
+
+ +

+ An error occurred +

+
+ + + \ No newline at end of file diff --git a/spring-security-modules/spring-security-saml/src/main/resources/templates/home.html b/spring-security-modules/spring-security-saml/src/main/resources/templates/home.html new file mode 100644 index 0000000000..c66e92c1f0 --- /dev/null +++ b/spring-security-modules/spring-security-saml/src/main/resources/templates/home.html @@ -0,0 +1,13 @@ + + + +Baeldung Spring Security SAML: Home + + +

Welcome!
You are successfully logged in!

+

You are logged as null.

+ + Logout + + + \ No newline at end of file diff --git a/spring-security-modules/spring-security-saml/src/main/resources/templates/index.html b/spring-security-modules/spring-security-saml/src/main/resources/templates/index.html new file mode 100644 index 0000000000..7999c2fded --- /dev/null +++ b/spring-security-modules/spring-security-saml/src/main/resources/templates/index.html @@ -0,0 +1,10 @@ + + + +Baeldung Spring Security SAML + + +

Welcome to Baeldung Spring Security SAML

+ Login + + \ No newline at end of file