├── .gitignore ├── .pairs ├── .travis.yml ├── LICENSE ├── README.md ├── build.gradle ├── core ├── README.md ├── build.gradle └── src │ ├── main │ └── java │ │ └── org │ │ └── springframework │ │ └── security │ │ └── extensions │ │ └── saml2 │ │ └── config │ │ ├── SAMLConfigurer.java │ │ └── SAMLDslEntryPoint.java │ └── test │ ├── java │ ├── acceptance │ │ ├── HttpMetadataLoginTest.java │ │ ├── IntegrationTest.java │ │ └── LoginTest.java │ ├── com │ │ └── example │ │ │ ├── ColombiaApplication.java │ │ │ ├── MvcConfig.java │ │ │ └── SecurityConfiguration.java │ ├── helper │ │ ├── Credentials.java │ │ └── LoginHelper.java │ └── org │ │ └── springframework │ │ └── security │ │ └── extensions │ │ └── saml2 │ │ └── config │ │ ├── SAMLConfigurerCsrfDisabledTests.java │ │ ├── SAMLConfigurerProfileConsumerTests.java │ │ └── SAMLConfigurerTests.java │ └── resources │ ├── application-http-metadata.yml │ ├── application.yml │ ├── credentials.example.yml │ ├── saml │ ├── SAMLResponse.xml │ ├── keystore.jks │ └── metadata.xml │ └── templates │ └── index.html ├── gradle.properties ├── gradle ├── publish-maven.gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── samples └── spring-security-saml-dsl-sample │ ├── .gitkeep │ ├── README.md │ ├── build.gradle │ ├── okta-config-page.png │ └── src │ └── main │ ├── java │ └── com │ │ └── example │ │ ├── ColombiaApplication.java │ │ ├── MvcConfig.java │ │ └── SecurityConfiguration.java │ └── resources │ ├── application.yml │ ├── saml │ └── keystore.jks │ └── templates │ ├── index.html │ └── login.html └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | .idea 3 | *.iml 4 | *build/ 5 | .DS_Store 6 | .gradle 7 | .settings 8 | .project 9 | .classpath 10 | bin 11 | .springBeans 12 | out/ 13 | credentials.yml -------------------------------------------------------------------------------- /.pairs: -------------------------------------------------------------------------------- 1 | # .pairs - configuration for 'git pair' 2 | pairs: 3 | jdk: Jean de Klerk; jadekler 4 | md: Mark Douglass; mark.douglass 5 | mk: Mark Kollasch; mkollasch 6 | email: 7 | prefix: pair 8 | domain: gmail.com 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | env: 2 | global: 3 | - secure: pQ4obfUOvjERJwAbkRczNMEUbYTunOFAtXjxC5iCJ3kiO+HVBdt3gfQRHYOjZgLzN0AM699HZkE5VbKRs/cM7kopk0YR17QxL456CuC/MQjaAgc301Fyt/rV3EBqgTzyyzyARvaPvGtlM0dFkSv2B+556vCL8R+pOSGqHD6U6aNKKTHbwojvM3tWykswAb6oYyu/wlUOxiEDsvuj5HhviTQRW9exnSiu+14xoBXFBE4lJtqHTooCCY7wYq3gq5pl6gCD6MYAxrAsbpkIUH6GkJs3VgSUbMFLx78G7oyA4Ji9eM/+w/bmLtF2DFiquEMsf7TUKSoFdBEXBk5G1N5+rUmEdefWjFWfhFbVSWcNk9MhgDAWcd/pVKTsonMdxicKH71ZHawKLTas1r6gPDIWr01NbeyDMmqfGmxgV9jGS9T1nmGQVCD4Db5GyurLDccTkNerBEatJLczFHLM0t/fVq7ASLF/0ggD/V/mPNze8UR/QuqUEJIsuKzgqAlXG7JkjJXoJI5UfepautGRC9KTS305uq2HY4MiyLKZsTyjWZ7rQvSQHnKwOuh9EUPydQcJvJhC345tVLI6khiDSbOkSZeTekM/71SaB+OIsKFJmp3S1HQkTXo/VpucSeS+XfBK4MldQ5P+Qh2XzzVUsi46NYX4RCbmsYLw8BN/fmLzt/w= 4 | - secure: hIo1qv1KhN33zQLQdcd6drGSjIPEvBQptU9zcib0C6Fn2X458a14fvd74BpaplQqCGcKQI/W7565IeS7gKx2AeCG0xUHkw8738cd0w+CrV75hn4QdenGSSQZomal/DrWYKPAEZRZqqjzSee02p1URRN1nz3KoRGXxW1ZQxzr/EhgztKUz/NFAo7xuze+dyN10lK0xhTXommpPvmzMgRetS2xrW7T5HkjlXWR6ia0q57Vplklm/aG0qtu0skZoyRnV6BpEjD8fETT0OyAuPFRPVlnb1rrPedbZR7QdvvP21xztw2F905vml/qBP3Rc/wVoLxydOU695g1GMaJo4dzwj/LDB6p9Lp8n6KLm1sU0eM2YxGxi27mk6vSKCCWsRjZAZJjIk56mJhsRIg9DTR3W7jSCrui3whEZVjGOpH4hcmsgbt3uVfGjZqLjzJRaP6U0bUNk033Ogkd8IzUOoVVyhLwb8yt9cU7d7g4sYS9vmEvat72k/JCZLOlGLEpg9q7wkIBeLPHrpFQAYSu+TzLyqcjS1lOT+PaEIV/AaiKFXgkT/2c2z+M12WKOziOhD+PkWjO+JGGNEqGyGNF8EESwwOD0sa1AwRnFqZ3iaTyqzVFF1Mj7bZO+pzbYegl679IlRDPysf6ilZY5AH7eZwA1cvMA4b/csFK9jCG+mD8bTY= 5 | 6 | language: java 7 | 8 | jdk: 9 | - oraclejdk8 10 | 11 | addons: 12 | firefox: '59.0.1' 13 | 14 | before_script: 15 | - export DISPLAY=:99.0 16 | - sh -e /etc/init.d/xvfb start 17 | - sleep 3 18 | - wget https://github.com/mozilla/geckodriver/releases/download/v0.20.0/geckodriver-v0.20.0-linux64.tar.gz 19 | - mkdir geckodriver 20 | - tar -xzf geckodriver-v0.20.0-linux64.tar.gz -C geckodriver 21 | - export PATH=$PATH:$PWD/geckodriver 22 | 23 | script: 24 | - "./gradlew -Dtest.sec.saml.dsl.integration=true assemble test" 25 | 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Jean de Klerk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This project is no longer supported in favor of the support build into Spring Security 5.2.+. See https://docs.spring.io/spring-security/site/docs/5.2.x/reference/htmlsingle/#saml2 2 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | maven { url 'https://repo.spring.io/plugins-release' } 4 | } 5 | dependencies { 6 | classpath("org.springframework.build.gradle:propdeps-plugin:0.0.7") 7 | classpath group: "org.springframework.boot", name: "spring-boot-gradle-plugin", version: '1.5.10.RELEASE' 8 | } 9 | } 10 | 11 | def coreProjects = subprojects.findAll { !it.name.contains("samples") } 12 | 13 | configure(allprojects) { project -> 14 | group = "org.springframework.security.extensions" 15 | 16 | 17 | ext { 18 | gradleScriptDir = "${rootProject.projectDir}/gradle" 19 | springBootVersion = '1.5.10.RELEASE' 20 | springSecurityVersion = '4.2.4.RELEASE' 21 | springSecuritySamlVersion = '1.0.4.RELEASE' 22 | } 23 | 24 | apply plugin: 'java' 25 | apply plugin: 'maven' 26 | apply plugin: 'propdeps' 27 | 28 | 29 | sourceCompatibility = 1.8 30 | targetCompatibility = 1.8 31 | 32 | repositories { 33 | mavenCentral() 34 | maven { url 'https://build.shibboleth.net/nexus/content/repositories/releases/' } 35 | maven { url 'https://repository.mulesoft.org/releases/'} 36 | } 37 | } 38 | 39 | configure(subprojects) { subproject -> 40 | apply from: "${gradleScriptDir}/publish-maven.gradle" 41 | } 42 | 43 | -------------------------------------------------------------------------------- /core/README.md: -------------------------------------------------------------------------------- 1 | ## Running tests 2 | 3 | 1. Copy `test/resources/credentials.example.yml` to `test/resources/credentials.yml` and fill in with the correct test credentials. 4 | 1. Replace your saml metadata.xml with your test IDP metadata. 5 | 1. Update application-http-metadata.yml with the path or url of metadata.xml 6 | 1. `./gradlew clean test` -------------------------------------------------------------------------------- /core/build.gradle: -------------------------------------------------------------------------------- 1 | description = "Spring Security SAML DSL Library" 2 | 3 | dependencies { 4 | 5 | compile( 6 | "org.springframework.security:spring-security-web:$springSecurityVersion", 7 | "org.springframework.security:spring-security-config:$springSecurityVersion", 8 | 'org.springframework.security.extensions:spring-security-saml2-core:1.0.4.RELEASE', 9 | ) 10 | 11 | provided( 12 | "javax.servlet:javax.servlet-api:3.1.0", 13 | ) 14 | 15 | testCompile( 16 | "org.springframework.boot:spring-boot-starter-test:$springBootVersion", 17 | "org.springframework.security:spring-security-test:$springSecurityVersion", 18 | 'org.assertj:assertj-core:3.3.0', 19 | 'org.seleniumhq.selenium:selenium-java:3.11.0', 20 | "org.springframework.boot:spring-boot-starter-thymeleaf:$springBootVersion", 21 | "org.springframework.boot:spring-boot-starter-web:$springBootVersion", 22 | "org.springframework.boot:spring-boot-starter-tomcat:$springBootVersion", 23 | ) 24 | 25 | configurations { 26 | compile.exclude module: 'spring-boot-starter-jetty' 27 | } 28 | } 29 | 30 | test { 31 | testLogging { 32 | showStandardStreams = true 33 | } 34 | } 35 | 36 | task packageSources(type: Jar) { 37 | classifier = 'sources' 38 | from sourceSets.main.allSource 39 | } 40 | 41 | task generateDocs() { 42 | } 43 | 44 | javadoc { 45 | logging.captureStandardError LogLevel.INFO 46 | logging.captureStandardOutput LogLevel.INFO // suppress "## warnings" message 47 | } 48 | 49 | task javadocJar(type: Jar, dependsOn: javadoc) { 50 | classifier 'javadoc' 51 | from javadoc.destinationDir 52 | } 53 | 54 | artifacts { 55 | archives packageSources 56 | archives javadocJar 57 | } -------------------------------------------------------------------------------- /core/src/main/java/org/springframework/security/extensions/saml2/config/SAMLConfigurer.java: -------------------------------------------------------------------------------- 1 | package org.springframework.security.extensions.saml2.config; 2 | 3 | import javax.servlet.http.HttpServletRequest; 4 | import java.io.File; 5 | import java.io.IOException; 6 | import java.util.ArrayList; 7 | import java.util.Collection; 8 | import java.util.HashMap; 9 | import java.util.List; 10 | import java.util.Map; 11 | import java.util.Timer; 12 | import java.util.regex.Pattern; 13 | 14 | import org.apache.commons.httpclient.HttpClient; 15 | import org.opensaml.Configuration; 16 | import org.opensaml.PaosBootstrap; 17 | import org.opensaml.saml2.metadata.provider.FilesystemMetadataProvider; 18 | import org.opensaml.saml2.metadata.provider.HTTPMetadataProvider; 19 | import org.opensaml.saml2.metadata.provider.MetadataProvider; 20 | import org.opensaml.saml2.metadata.provider.MetadataProviderException; 21 | import org.opensaml.xml.ConfigurationException; 22 | import org.opensaml.xml.parse.ParserPool; 23 | import org.opensaml.xml.parse.StaticBasicParserPool; 24 | import org.opensaml.xml.parse.XMLParserException; 25 | import org.opensaml.xml.security.keyinfo.NamedKeyInfoGeneratorManager; 26 | import org.opensaml.xml.security.x509.CertPathPKIXTrustEvaluator; 27 | import org.opensaml.xml.security.x509.PKIXTrustEvaluator; 28 | import org.opensaml.xml.security.x509.X509KeyInfoGeneratorFactory; 29 | import org.springframework.core.io.DefaultResourceLoader; 30 | import org.springframework.core.io.FileSystemResource; 31 | import org.springframework.core.io.Resource; 32 | import org.springframework.security.config.annotation.ObjectPostProcessor; 33 | import org.springframework.security.config.annotation.SecurityConfigurerAdapter; 34 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 35 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 36 | import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer; 37 | import org.springframework.security.saml.SAMLAuthenticationProvider; 38 | import org.springframework.security.saml.SAMLConstants; 39 | import org.springframework.security.saml.SAMLDiscovery; 40 | import org.springframework.security.saml.SAMLEntryPoint; 41 | import org.springframework.security.saml.SAMLLogoutFilter; 42 | import org.springframework.security.saml.SAMLLogoutProcessingFilter; 43 | import org.springframework.security.saml.SAMLProcessingFilter; 44 | import org.springframework.security.saml.context.SAMLContextProvider; 45 | import org.springframework.security.saml.context.SAMLContextProviderLB; 46 | import org.springframework.security.saml.key.JKSKeyManager; 47 | import org.springframework.security.saml.key.KeyManager; 48 | import org.springframework.security.saml.log.SAMLDefaultLogger; 49 | import org.springframework.security.saml.metadata.CachingMetadataManager; 50 | import org.springframework.security.saml.metadata.ExtendedMetadata; 51 | import org.springframework.security.saml.metadata.ExtendedMetadataDelegate; 52 | import org.springframework.security.saml.metadata.MetadataDisplayFilter; 53 | import org.springframework.security.saml.metadata.MetadataGenerator; 54 | import org.springframework.security.saml.metadata.MetadataGeneratorFilter; 55 | import org.springframework.security.saml.processor.HTTPPostBinding; 56 | import org.springframework.security.saml.processor.HTTPRedirectDeflateBinding; 57 | import org.springframework.security.saml.processor.SAMLBinding; 58 | import org.springframework.security.saml.processor.SAMLProcessor; 59 | import org.springframework.security.saml.processor.SAMLProcessorImpl; 60 | import org.springframework.security.saml.storage.SAMLMessageStorageFactory; 61 | import org.springframework.security.saml.trust.MetadataCredentialResolver; 62 | import org.springframework.security.saml.trust.PKIXInformationResolver; 63 | import org.springframework.security.saml.userdetails.SAMLUserDetailsService; 64 | import org.springframework.security.saml.util.VelocityFactory; 65 | import org.springframework.security.saml.websso.SingleLogoutProfile; 66 | import org.springframework.security.saml.websso.SingleLogoutProfileImpl; 67 | import org.springframework.security.saml.websso.WebSSOProfile; 68 | import org.springframework.security.saml.websso.WebSSOProfileConsumerImpl; 69 | import org.springframework.security.saml.websso.WebSSOProfileImpl; 70 | import org.springframework.security.saml.websso.WebSSOProfileOptions; 71 | import org.springframework.security.web.DefaultSecurityFilterChain; 72 | import org.springframework.security.web.FilterChainProxy; 73 | import org.springframework.security.web.SecurityFilterChain; 74 | import org.springframework.security.web.access.channel.ChannelProcessingFilter; 75 | import org.springframework.security.web.authentication.logout.LogoutHandler; 76 | import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; 77 | import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler; 78 | import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; 79 | import org.springframework.security.web.util.matcher.AndRequestMatcher; 80 | import org.springframework.security.web.util.matcher.AntPathRequestMatcher; 81 | import org.springframework.security.web.util.matcher.NegatedRequestMatcher; 82 | import org.springframework.security.web.util.matcher.RequestMatcher; 83 | 84 | /* 85 | Spring security configurer for okta. 86 | @Author Mark Douglass 87 | @Author Jean de Klerk 88 | */ 89 | public class SAMLConfigurer extends SecurityConfigurerAdapter { 90 | private IdentityProvider identityProvider = new IdentityProvider(); 91 | private ServiceProvider serviceProvider = new ServiceProvider(); 92 | 93 | private WebSSOProfileOptions webSSOProfileOptions = webSSOProfileOptions(); 94 | private StaticBasicParserPool parserPool = staticBasicParserPool(); 95 | private SAMLProcessor samlProcessor = samlProcessor(); 96 | private SAMLDefaultLogger samlLogger = new SAMLDefaultLogger(); 97 | private WebSSOProfileConsumerImpl webSSOProfileConsumer; 98 | private SAMLAuthenticationProvider samlAuthenticationProvider; 99 | private MetadataProvider metadataProvider; 100 | private ExtendedMetadataDelegate extendedMetadataDelegate; 101 | private CachingMetadataManager cachingMetadataManager; 102 | private WebSSOProfile webSSOProfile; 103 | private SingleLogoutProfile singleLogoutProfile; 104 | private SAMLUserDetailsService samlUserDetailsService; 105 | private boolean forcePrincipalAsString = false; 106 | 107 | private ObjectPostProcessor objectPostProcessor = new ObjectPostProcessor() { 108 | public T postProcess(T object) { 109 | return object; 110 | } 111 | }; 112 | 113 | 114 | private SAMLConfigurer() { 115 | } 116 | 117 | @Override 118 | public void init(HttpSecurity http) { 119 | 120 | metadataProvider = identityProvider.metadataProvider(); 121 | ExtendedMetadata extendedMetadata = extendedMetadata(identityProvider.discoveryEnabled); 122 | extendedMetadataDelegate = extendedMetadataDelegate(extendedMetadata); 123 | serviceProvider.keyManager = serviceProvider.keyManager(); 124 | cachingMetadataManager = cachingMetadataManager(); 125 | webSSOProfile = webSSOProfile(); 126 | singleLogoutProfile = singleLogoutProfile(); 127 | 128 | if (webSSOProfileConsumer == null) { 129 | webSSOProfileConsumer = new WebSSOProfileConsumerImpl(samlProcessor, cachingMetadataManager); 130 | webSSOProfileConsumer.setMaxAuthenticationAge(serviceProvider.maxAuthenticationAge); 131 | } 132 | 133 | samlAuthenticationProvider = samlAuthenticationProvider(webSSOProfileConsumer); 134 | 135 | bootstrap(); 136 | 137 | SAMLContextProvider contextProvider = contextProvider(); 138 | SAMLEntryPoint samlEntryPoint = samlEntryPoint(contextProvider); 139 | SAMLLogoutFilter samlLogoutFilter = samlLogoutFilter(contextProvider); 140 | SAMLLogoutProcessingFilter samlLogoutProcessingFilter = samlLogoutProcessingFilter(contextProvider); 141 | 142 | try { 143 | http 144 | .httpBasic() 145 | .authenticationEntryPoint(samlEntryPoint); 146 | 147 | CsrfConfigurer csrfConfigurer = http.getConfigurer(CsrfConfigurer.class); 148 | if(csrfConfigurer != null) { 149 | // Workaround to get working with Spring Security 3.2. 150 | RequestMatcher ignored = new AntPathRequestMatcher("/saml/SSO"); 151 | RequestMatcher notIgnored = new NegatedRequestMatcher(ignored); 152 | RequestMatcher matcher = new AndRequestMatcher(new DefaultRequiresCsrfMatcher(), notIgnored); 153 | 154 | csrfConfigurer.requireCsrfProtectionMatcher(matcher); 155 | } 156 | } catch (Exception e) { 157 | e.printStackTrace(); 158 | } 159 | 160 | http 161 | .addFilterBefore(metadataGeneratorFilter(samlEntryPoint, extendedMetadata), ChannelProcessingFilter.class) 162 | .addFilterAfter(samlFilter(samlEntryPoint, samlLogoutFilter, samlLogoutProcessingFilter, contextProvider), 163 | BasicAuthenticationFilter.class) 164 | .authenticationProvider(samlAuthenticationProvider); 165 | } 166 | 167 | public static SAMLConfigurer saml() { 168 | return new SAMLConfigurer(); 169 | } 170 | 171 | public SAMLConfigurer userDetailsService(SAMLUserDetailsService samlUserDetailsService) { 172 | this.samlUserDetailsService = samlUserDetailsService; 173 | return this; 174 | } 175 | 176 | public SAMLConfigurer forcePrincipalAsString() { 177 | this.forcePrincipalAsString = true; 178 | return this; 179 | } 180 | 181 | public SAMLConfigurer webSSOProfileConsumer(WebSSOProfileConsumerImpl webSSOProfileConsumer) { 182 | this.webSSOProfileConsumer = webSSOProfileConsumer; 183 | return this; 184 | } 185 | 186 | public IdentityProvider identityProvider() { 187 | return identityProvider; 188 | } 189 | 190 | public ServiceProvider serviceProvider() { 191 | return serviceProvider; 192 | } 193 | 194 | private String entityBaseURL() { 195 | String entityBaseURL = serviceProvider.hostName + "/" + serviceProvider.basePath; 196 | entityBaseURL = entityBaseURL.replaceAll("//", "/").replaceAll("/$", ""); 197 | entityBaseURL = serviceProvider.protocol + "://" + entityBaseURL; 198 | return entityBaseURL; 199 | } 200 | 201 | private SAMLEntryPoint samlEntryPoint(SAMLContextProvider contextProvider) { 202 | SAMLEntryPoint samlEntryPoint = new SAMLDslEntryPoint(); 203 | samlEntryPoint.setDefaultProfileOptions(webSSOProfileOptions); 204 | samlEntryPoint.setWebSSOprofile(webSSOProfile); 205 | samlEntryPoint.setContextProvider(contextProvider); 206 | samlEntryPoint.setMetadata(cachingMetadataManager); 207 | samlEntryPoint.setSamlLogger(samlLogger); 208 | return samlEntryPoint; 209 | } 210 | 211 | private SimpleUrlLogoutSuccessHandler successLogoutHandler() { 212 | SimpleUrlLogoutSuccessHandler logoutSuccessHandler = new SimpleUrlLogoutSuccessHandler(); 213 | logoutSuccessHandler.setDefaultTargetUrl("/"); 214 | return logoutSuccessHandler; 215 | } 216 | 217 | private SecurityContextLogoutHandler logoutHandler() { 218 | SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler(); 219 | logoutHandler.setInvalidateHttpSession(true); 220 | logoutHandler.setClearAuthentication(true); 221 | return logoutHandler; 222 | } 223 | 224 | private SAMLLogoutFilter samlLogoutFilter(SAMLContextProvider contextProvider) { 225 | SAMLLogoutFilter samlLogoutFilter = new SAMLLogoutFilter(successLogoutHandler(), 226 | new LogoutHandler[]{logoutHandler()}, 227 | new LogoutHandler[]{logoutHandler()}); 228 | samlLogoutFilter.setProfile(singleLogoutProfile); 229 | samlLogoutFilter.setContextProvider(contextProvider); 230 | samlLogoutFilter.setSamlLogger(samlLogger); 231 | return samlLogoutFilter; 232 | } 233 | 234 | private SAMLLogoutProcessingFilter samlLogoutProcessingFilter(SAMLContextProvider contextProvider) { 235 | SAMLLogoutProcessingFilter samlLogoutProcessingFilter = 236 | new SAMLLogoutProcessingFilter(successLogoutHandler(), logoutHandler()); 237 | samlLogoutProcessingFilter.setLogoutProfile(singleLogoutProfile); 238 | samlLogoutProcessingFilter.setContextProvider(contextProvider); 239 | samlLogoutProcessingFilter.setSamlLogger(samlLogger); 240 | samlLogoutProcessingFilter.setSAMLProcessor(samlProcessor); 241 | return samlLogoutProcessingFilter; 242 | } 243 | 244 | private SAMLProcessor samlProcessor() { 245 | Collection bindings = new ArrayList<>(); 246 | bindings.add(httpRedirectDeflateBinding(parserPool)); 247 | bindings.add(httpPostBinding(parserPool)); 248 | return new SAMLProcessorImpl(bindings); 249 | } 250 | 251 | private CachingMetadataManager cachingMetadataManager() { 252 | List providers = new ArrayList<>(); 253 | providers.add(extendedMetadataDelegate); 254 | 255 | CachingMetadataManager cachingMetadataManager = null; 256 | try { 257 | cachingMetadataManager = new CachingMetadataManager(providers); 258 | } catch (MetadataProviderException e) { 259 | e.printStackTrace(); 260 | } 261 | 262 | cachingMetadataManager.setKeyManager(serviceProvider.keyManager); 263 | return cachingMetadataManager; 264 | } 265 | 266 | private StaticBasicParserPool staticBasicParserPool() { 267 | StaticBasicParserPool parserPool = new StaticBasicParserPool(); 268 | try { 269 | parserPool.initialize(); 270 | } catch (XMLParserException e) { 271 | e.printStackTrace(); 272 | } 273 | return parserPool; 274 | } 275 | 276 | private ExtendedMetadataDelegate extendedMetadataDelegate(ExtendedMetadata extendedMetadata) { 277 | ExtendedMetadataDelegate extendedMetadataDelegate = new ExtendedMetadataDelegate(metadataProvider, extendedMetadata); 278 | extendedMetadataDelegate.setMetadataTrustCheck(false); 279 | extendedMetadataDelegate.setMetadataRequireSignature(false); 280 | return extendedMetadataDelegate; 281 | } 282 | 283 | private ExtendedMetadata extendedMetadata(boolean discoveryEnabled) { 284 | ExtendedMetadata extendedMetadata = new ExtendedMetadata(); 285 | extendedMetadata.setIdpDiscoveryEnabled(discoveryEnabled); 286 | extendedMetadata.setSignMetadata(true); 287 | return extendedMetadata; 288 | } 289 | 290 | private WebSSOProfileOptions webSSOProfileOptions() { 291 | WebSSOProfileOptions webSSOProfileOptions = new WebSSOProfileOptions(); 292 | webSSOProfileOptions.setIncludeScoping(false); 293 | return webSSOProfileOptions; 294 | } 295 | 296 | private void bootstrap() { 297 | try { 298 | PaosBootstrap.bootstrap(); 299 | } catch (ConfigurationException e) { 300 | e.printStackTrace(); 301 | } 302 | 303 | NamedKeyInfoGeneratorManager manager = Configuration.getGlobalSecurityConfiguration().getKeyInfoGeneratorManager(); 304 | X509KeyInfoGeneratorFactory generator = new X509KeyInfoGeneratorFactory(); 305 | generator.setEmitEntityCertificate(true); 306 | generator.setEmitEntityCertificateChain(true); 307 | manager.registerFactory(SAMLConstants.SAML_METADATA_KEY_INFO_GENERATOR, generator); 308 | } 309 | 310 | private HTTPPostBinding httpPostBinding(ParserPool parserPool) { 311 | return new HTTPPostBinding(parserPool, VelocityFactory.getEngine()); 312 | } 313 | 314 | private HTTPRedirectDeflateBinding httpRedirectDeflateBinding(ParserPool parserPool) { 315 | return new HTTPRedirectDeflateBinding(parserPool); 316 | } 317 | 318 | private SAMLProcessingFilter samlWebSSOProcessingFilter(SAMLAuthenticationProvider samlAuthenticationProvider, SAMLContextProvider contextProvider, SAMLProcessor samlProcessor) throws Exception { 319 | SAMLProcessingFilter samlWebSSOProcessingFilter = new SAMLProcessingFilter(); 320 | 321 | AuthenticationManagerBuilder authenticationManagerBuilder = new AuthenticationManagerBuilder(objectPostProcessor); 322 | authenticationManagerBuilder.authenticationProvider(samlAuthenticationProvider); 323 | samlWebSSOProcessingFilter.setAuthenticationManager(authenticationManagerBuilder.build()); 324 | samlWebSSOProcessingFilter.setContextProvider(contextProvider); 325 | samlWebSSOProcessingFilter.setSAMLProcessor(samlProcessor); 326 | return samlWebSSOProcessingFilter; 327 | } 328 | 329 | private MetadataGeneratorFilter metadataGeneratorFilter(SAMLEntryPoint samlEntryPoint, ExtendedMetadata extendedMetadata) { 330 | MetadataGeneratorFilter metadataGeneratorFilter = new MetadataGeneratorFilter(getMetadataGenerator(samlEntryPoint, extendedMetadata)); 331 | metadataGeneratorFilter.setManager(cachingMetadataManager); 332 | return metadataGeneratorFilter; 333 | } 334 | 335 | private FilterChainProxy samlFilter(SAMLEntryPoint samlEntryPoint, SAMLLogoutFilter samlLogoutFilter, 336 | SAMLLogoutProcessingFilter samlLogoutProcessingFilter, SAMLContextProvider contextProvider) { 337 | List chains = new ArrayList<>(); 338 | chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/login/**"), 339 | samlEntryPoint)); 340 | chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/logout/**"), 341 | samlLogoutFilter)); 342 | chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/metadata/**"), 343 | metadataDisplayFilter(contextProvider))); 344 | try { 345 | chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SSO/**"), 346 | samlWebSSOProcessingFilter(samlAuthenticationProvider, contextProvider, samlProcessor))); 347 | } catch (Exception e) { 348 | e.printStackTrace(); 349 | } 350 | chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SingleLogout/**"), 351 | samlLogoutProcessingFilter)); 352 | SAMLDiscovery samlDiscovery = new SAMLDiscovery(); 353 | samlDiscovery.setMetadata(cachingMetadataManager); 354 | samlDiscovery.setContextProvider(contextProvider); 355 | chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/discovery/**"), 356 | samlDiscovery)); 357 | return new FilterChainProxy(chains); 358 | } 359 | 360 | private WebSSOProfile webSSOProfile() { 361 | WebSSOProfileImpl webSSOProfile = new WebSSOProfileImpl(samlProcessor, cachingMetadataManager); 362 | webSSOProfile.setResponseSkew(serviceProvider.responseSkew); 363 | return webSSOProfile; 364 | } 365 | 366 | private SingleLogoutProfile singleLogoutProfile() { 367 | SingleLogoutProfileImpl singleLogoutProfile = new SingleLogoutProfileImpl(); 368 | singleLogoutProfile.setMetadata(cachingMetadataManager); 369 | singleLogoutProfile.setProcessor(samlProcessor); 370 | singleLogoutProfile.setResponseSkew(serviceProvider.responseSkew); 371 | return singleLogoutProfile; 372 | } 373 | 374 | private SAMLAuthenticationProvider samlAuthenticationProvider(WebSSOProfileConsumerImpl webSSOProfileConsumer) { 375 | SAMLAuthenticationProvider samlAuthenticationProvider = new SAMLAuthenticationProvider(); 376 | samlAuthenticationProvider.setForcePrincipalAsString(forcePrincipalAsString); 377 | samlAuthenticationProvider.setSamlLogger(samlLogger); 378 | samlAuthenticationProvider.setConsumer(webSSOProfileConsumer); 379 | samlAuthenticationProvider.setUserDetails(this.samlUserDetailsService); 380 | return samlAuthenticationProvider; 381 | } 382 | 383 | private SAMLContextProvider contextProvider() { 384 | SAMLContextProviderLB contextProvider = new SAMLContextProviderLB(); 385 | contextProvider.setMetadata(cachingMetadataManager); 386 | contextProvider.setScheme(serviceProvider.protocol); 387 | contextProvider.setServerName(serviceProvider.hostName); 388 | contextProvider.setContextPath(serviceProvider.basePath); 389 | contextProvider.setKeyManager(serviceProvider.keyManager); 390 | 391 | MetadataCredentialResolver resolver = new MetadataCredentialResolver(cachingMetadataManager, serviceProvider.keyManager); 392 | PKIXTrustEvaluator pkixTrustEvaluator = new CertPathPKIXTrustEvaluator(); 393 | PKIXInformationResolver pkixInformationResolver = new PKIXInformationResolver(resolver, cachingMetadataManager, serviceProvider.keyManager); 394 | 395 | contextProvider.setPkixResolver(pkixInformationResolver); 396 | contextProvider.setPkixTrustEvaluator(pkixTrustEvaluator); 397 | contextProvider.setMetadataResolver(resolver); 398 | 399 | if (serviceProvider.storageFactory != null) { 400 | contextProvider.setStorageFactory(serviceProvider.storageFactory); 401 | } 402 | 403 | return contextProvider; 404 | } 405 | 406 | private MetadataGenerator getMetadataGenerator(SAMLEntryPoint samlEntryPoint, ExtendedMetadata extendedMetadata) { 407 | MetadataGenerator metadataGenerator = new MetadataGenerator(); 408 | 409 | metadataGenerator.setSamlEntryPoint(samlEntryPoint); 410 | metadataGenerator.setEntityBaseURL(entityBaseURL()); 411 | metadataGenerator.setKeyManager(serviceProvider.keyManager); 412 | metadataGenerator.setEntityId(serviceProvider.entityId); 413 | metadataGenerator.setIncludeDiscoveryExtension(false); 414 | metadataGenerator.setExtendedMetadata(extendedMetadata); 415 | 416 | return metadataGenerator; 417 | } 418 | 419 | public class IdentityProvider { 420 | 421 | private String metadataFilePath; 422 | private boolean discoveryEnabled = true; 423 | 424 | public IdentityProvider metadataFilePath(String metadataFilePath) { 425 | this.metadataFilePath = metadataFilePath; 426 | return this; 427 | } 428 | 429 | public IdentityProvider discoveryEnabled(boolean discoveryEnabled) { 430 | this.discoveryEnabled = discoveryEnabled; 431 | return this; 432 | } 433 | 434 | private MetadataProvider metadataProvider() { 435 | if (metadataFilePath.startsWith("http")) { 436 | return httpMetadataProvider(); 437 | } else { 438 | return fileSystemMetadataProvider(); 439 | } 440 | } 441 | 442 | private HTTPMetadataProvider httpMetadataProvider() { 443 | try { 444 | HTTPMetadataProvider httpMetadataProvider = new HTTPMetadataProvider(new Timer(), new HttpClient(), metadataFilePath); 445 | httpMetadataProvider.setParserPool(parserPool); 446 | return httpMetadataProvider; 447 | } catch (MetadataProviderException e) { 448 | e.printStackTrace(); 449 | return null; 450 | } 451 | } 452 | 453 | private FilesystemMetadataProvider fileSystemMetadataProvider() { 454 | DefaultResourceLoader loader = new DefaultResourceLoader(); 455 | Resource metadataResource = loader.getResource(metadataFilePath); 456 | 457 | File samlMetadata = null; 458 | try { 459 | samlMetadata = metadataResource.getFile(); 460 | } catch (IOException e) { 461 | e.printStackTrace(); 462 | } 463 | 464 | FilesystemMetadataProvider filesystemMetadataProvider = null; 465 | try { 466 | filesystemMetadataProvider = new FilesystemMetadataProvider(samlMetadata); 467 | } catch (MetadataProviderException e) { 468 | e.printStackTrace(); 469 | } 470 | filesystemMetadataProvider.setParserPool(parserPool); 471 | 472 | return filesystemMetadataProvider; 473 | } 474 | 475 | public SAMLConfigurer and () { return SAMLConfigurer.this; } 476 | } 477 | 478 | private MetadataDisplayFilter metadataDisplayFilter(SAMLContextProvider contextProvider) { 479 | MetadataDisplayFilter metadataDisplayFilter = new MetadataDisplayFilter(); 480 | metadataDisplayFilter.setContextProvider(contextProvider); 481 | metadataDisplayFilter.setKeyManager(serviceProvider.keyManager); 482 | metadataDisplayFilter.setManager(cachingMetadataManager); 483 | return metadataDisplayFilter; 484 | } 485 | 486 | public class ServiceProvider { 487 | 488 | private KeyStore keyStore = new KeyStore(); 489 | private KeyManager keyManager; 490 | private String protocol; 491 | private String hostName; 492 | private String basePath; 493 | private String entityId; 494 | private SAMLMessageStorageFactory storageFactory; 495 | private int responseSkew = 60; 496 | private long maxAuthenticationAge = 7200; 497 | 498 | public ServiceProvider protocol(String protocol) { 499 | this.protocol = protocol; 500 | return this; 501 | } 502 | 503 | public ServiceProvider hostname(String hostname) { 504 | this.hostName = hostname; 505 | return this; 506 | } 507 | 508 | public ServiceProvider basePath(String basePath) { 509 | this.basePath = basePath; 510 | return this; 511 | } 512 | 513 | public ServiceProvider entityId(String entityId) { 514 | this.entityId = entityId; 515 | return this; 516 | } 517 | 518 | public ServiceProvider storageFactory(SAMLMessageStorageFactory storageFactory) { 519 | this.storageFactory = storageFactory; 520 | return this; 521 | } 522 | 523 | public ServiceProvider responseSkew(int responseSkew) { 524 | this.responseSkew = responseSkew; 525 | return this; 526 | } 527 | 528 | public ServiceProvider maxAuthenticationAge(long maxAuthenticationAge) { 529 | this.maxAuthenticationAge = maxAuthenticationAge; 530 | return this; 531 | } 532 | 533 | public KeyStore keyStore() { 534 | return keyStore; 535 | } 536 | 537 | public SAMLConfigurer and () { return SAMLConfigurer.this; } 538 | 539 | private KeyManager keyManager() { 540 | DefaultResourceLoader loader = new DefaultResourceLoader(); 541 | Resource storeFile = loader.getResource(keyStore.getStoreFilePath()); 542 | if (keyStore.getStoreFilePath().startsWith("file://")) { 543 | try { 544 | storeFile = new FileSystemResource(storeFile.getFile()); 545 | } catch (IOException e) { 546 | e.printStackTrace(); 547 | throw new RuntimeException("Cannot load file system resource: " + keyStore.getStoreFilePath(), e); 548 | } 549 | } 550 | Map passwords = new HashMap<>(); 551 | passwords.put(keyStore.getKeyname(), keyStore.getKeyPassword()); 552 | return new JKSKeyManager(storeFile, keyStore.getPassword(), passwords, keyStore.getKeyname()); 553 | } 554 | 555 | public class KeyStore { 556 | private String storeFilePath; 557 | private String password; 558 | private String keyname; 559 | private String keyPassword; 560 | 561 | public KeyStore storeFilePath(String storeFilePath) { 562 | this.storeFilePath = storeFilePath; 563 | return this; 564 | } 565 | 566 | public KeyStore password(String password) { 567 | this.password = password; 568 | return this; 569 | } 570 | 571 | public KeyStore keyname(String keyname) { 572 | this.keyname = keyname; 573 | return this; 574 | } 575 | 576 | public KeyStore keyPassword(String keyPasswordword) { 577 | this.keyPassword = keyPasswordword; 578 | return this; 579 | } 580 | 581 | public ServiceProvider and() { 582 | return ServiceProvider.this; 583 | } 584 | 585 | public String getStoreFilePath() { 586 | return storeFilePath; 587 | } 588 | 589 | public String getPassword() { 590 | return password; 591 | } 592 | 593 | public String getKeyname() { 594 | return keyname; 595 | } 596 | 597 | public String getKeyPassword() { 598 | return keyPassword; 599 | } 600 | 601 | @Override 602 | public String toString() { 603 | return "KeyStore{" + 604 | "storeFilePath='" + storeFilePath + '\'' + 605 | ", password='" + password + '\'' + 606 | ", keyname='" + keyname + '\'' + 607 | ", keyPassword='" + keyPassword + '\'' + 608 | '}'; 609 | } 610 | 611 | @Override 612 | public boolean equals(Object o) { 613 | if (this == o) return true; 614 | if (o == null || getClass() != o.getClass()) return false; 615 | 616 | KeyStore keyStore = (KeyStore) o; 617 | 618 | if (storeFilePath != null ? !storeFilePath.equals(keyStore.storeFilePath) : keyStore.storeFilePath != null) 619 | return false; 620 | if (password != null ? !password.equals(keyStore.password) : keyStore.password != null) return false; 621 | if (keyname != null ? !keyname.equals(keyStore.keyname) : keyStore.keyname != null) return false; 622 | return keyPassword != null ? keyPassword.equals(keyStore.keyPassword) : keyStore.keyPassword == null; 623 | 624 | } 625 | 626 | @Override 627 | public int hashCode() { 628 | int result = storeFilePath != null ? storeFilePath.hashCode() : 0; 629 | result = 31 * result + (password != null ? password.hashCode() : 0); 630 | result = 31 * result + (keyname != null ? keyname.hashCode() : 0); 631 | result = 31 * result + (keyPassword != null ? keyPassword.hashCode() : 0); 632 | return result; 633 | } 634 | } 635 | } 636 | 637 | final class DefaultRequiresCsrfMatcher implements RequestMatcher { 638 | private Pattern allowedMethods = Pattern.compile("^(GET|HEAD|TRACE|OPTIONS)$"); 639 | 640 | /* (non-Javadoc) 641 | * @see org.springframework.security.web.util.matcher.RequestMatcher#matches(javax.servlet.http.HttpServletRequest) 642 | */ 643 | public boolean matches(HttpServletRequest request) { 644 | return !allowedMethods.matcher(request.getMethod()).matches(); 645 | } 646 | } 647 | } 648 | -------------------------------------------------------------------------------- /core/src/main/java/org/springframework/security/extensions/saml2/config/SAMLDslEntryPoint.java: -------------------------------------------------------------------------------- 1 | package org.springframework.security.extensions.saml2.config; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.beans.factory.annotation.Qualifier; 5 | import org.springframework.security.saml.context.SAMLContextProvider; 6 | import org.springframework.security.saml.log.SAMLLogger; 7 | import org.springframework.security.saml.metadata.MetadataManager; 8 | import org.springframework.security.saml.websso.WebSSOProfile; 9 | 10 | /* 11 | @Author Mark Douglass 12 | @Author Jean de Klerk 13 | */ 14 | public class SAMLDslEntryPoint extends org.springframework.security.saml.SAMLEntryPoint { 15 | /** 16 | * Metadata manager, cannot be null, must be set. 17 | * It is set directly in the custom config, so can be optional here. 18 | * User could override it if desired. 19 | * 20 | * @param metadata manager 21 | */ 22 | @Autowired(required = false) 23 | @Override 24 | public void setMetadata(MetadataManager metadata) { 25 | super.setMetadata(metadata); 26 | } 27 | 28 | /** 29 | * Logger for SAML events, cannot be null, must be set. 30 | * 31 | * @param samlLogger logger 32 | * It is set in the custom config, so can be optional here. 33 | * User could override it if desired. 34 | */ 35 | @Autowired(required = false) 36 | @Override 37 | public void setSamlLogger(SAMLLogger samlLogger) { 38 | super.setSamlLogger(samlLogger); 39 | } 40 | 41 | /** 42 | * Profile for consumption of processed messages, cannot be null, must be set. 43 | * It is set in the custom config, so can be optional here. 44 | * User could override it if desired. 45 | * 46 | * @param webSSOprofile profile 47 | */ 48 | @Autowired(required = false) 49 | @Qualifier("webSSOprofile") 50 | @Override 51 | public void setWebSSOprofile(WebSSOProfile webSSOprofile) { 52 | super.setWebSSOprofile(webSSOprofile); 53 | } 54 | 55 | /** 56 | * Sets entity responsible for populating local entity context data. 57 | * It is set in the custom config, so can be optional here. 58 | * User could override it if desired. 59 | * 60 | * @param contextProvider provider implementation 61 | */ 62 | @Autowired(required = false) 63 | @Override 64 | public void setContextProvider(SAMLContextProvider contextProvider) { 65 | super.setContextProvider(contextProvider); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /core/src/test/java/acceptance/HttpMetadataLoginTest.java: -------------------------------------------------------------------------------- 1 | package acceptance; 2 | 3 | import org.junit.Test; 4 | import org.openqa.selenium.By; 5 | import org.springframework.test.context.ActiveProfiles; 6 | 7 | import static java.lang.Thread.sleep; 8 | import static org.assertj.core.api.Assertions.assertThat; 9 | 10 | @ActiveProfiles("http-metadata") 11 | public class HttpMetadataLoginTest extends IntegrationTest { 12 | 13 | @Test 14 | public void canLogin() throws InterruptedException { 15 | driver.findElement(By.name("username")).sendKeys(username); 16 | driver.findElement(By.name("password")).sendKeys(password); 17 | driver.findElement(By.id("okta-signin-submit")).submit(); 18 | sleep(1000); 19 | System.err.println("Asserting body:"+driver.findElement(By.tagName("body"))); 20 | assertThat(driver.findElement(By.tagName("body")).getText()).contains("Hello world"); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /core/src/test/java/acceptance/IntegrationTest.java: -------------------------------------------------------------------------------- 1 | package acceptance; 2 | 3 | import com.example.ColombiaApplication; 4 | import helper.Credentials; 5 | import helper.LoginHelper; 6 | import org.junit.*; 7 | import org.junit.rules.TestRule; 8 | import org.junit.runner.Description; 9 | import org.junit.runner.RunWith; 10 | import org.junit.runners.model.Statement; 11 | import org.openqa.selenium.WebDriver; 12 | import org.openqa.selenium.firefox.FirefoxDriver; 13 | import org.springframework.boot.test.context.SpringBootTest; 14 | import org.springframework.test.annotation.DirtiesContext; 15 | import org.springframework.test.context.ContextConfiguration; 16 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 17 | 18 | import java.io.IOException; 19 | import java.util.concurrent.TimeUnit; 20 | 21 | import static org.hamcrest.Matchers.equalTo; 22 | 23 | 24 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) 25 | @ContextConfiguration(classes = ColombiaApplication.class) 26 | @RunWith(SpringJUnit4ClassRunner.class) 27 | @DirtiesContext(classMode= DirtiesContext.ClassMode.AFTER_CLASS) 28 | public abstract class IntegrationTest { 29 | 30 | @ClassRule 31 | public static IntegrationTestEnabled integrationTestEnabled = new IntegrationTestEnabled(); 32 | 33 | protected WebDriver driver = null; 34 | 35 | protected int port = 8443; 36 | protected String baseUrl; 37 | protected static String username; 38 | protected static String password; 39 | 40 | @BeforeClass 41 | public static void setupClass() throws IOException { 42 | Credentials credentials = LoginHelper.loadCredentials(); 43 | username = credentials.getUsername(); 44 | password = credentials.getPassword(); 45 | } 46 | 47 | @Before 48 | public void setup() { 49 | driver = new FirefoxDriver(); 50 | driver.manage().deleteAllCookies(); 51 | driver.manage().timeouts().implicitlyWait(5, TimeUnit.SECONDS); 52 | 53 | baseUrl = String.format("https://localhost:%d", port); 54 | driver.get(baseUrl); 55 | } 56 | 57 | @After 58 | public void teardown() { 59 | driver.close(); 60 | } 61 | 62 | public static class IntegrationTestEnabled implements TestRule { 63 | @Override 64 | public Statement apply(Statement base, Description description) { 65 | return new Statement() { 66 | @Override 67 | public void evaluate() throws Throwable { 68 | Assume.assumeThat( 69 | "the property `-Dtest.sec.saml.dsl.integration` property must be set to true", 70 | System.getProperty("test.sec.saml.dsl.integration", "false"), 71 | equalTo("true") 72 | ); 73 | base.evaluate(); 74 | } 75 | }; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /core/src/test/java/acceptance/LoginTest.java: -------------------------------------------------------------------------------- 1 | package acceptance; 2 | 3 | import org.junit.Test; 4 | import org.openqa.selenium.By; 5 | 6 | import static java.lang.Thread.sleep; 7 | import static org.assertj.core.api.Assertions.assertThat; 8 | 9 | public class LoginTest extends IntegrationTest { 10 | 11 | @Test 12 | public void canLogin() throws InterruptedException { 13 | driver.findElement(By.name("username")).sendKeys(username); 14 | driver.findElement(By.name("password")).sendKeys(password); 15 | driver.findElement(By.id("okta-signin-submit")).submit(); 16 | sleep(1000); 17 | 18 | assertThat(driver.findElement(By.tagName("body")).getText()).contains("Hello world"); 19 | } 20 | 21 | @Test 22 | public void cantLoginWithBadCreds() throws InterruptedException { 23 | driver.findElement(By.name("username")).sendKeys("someguy"); 24 | driver.findElement(By.name("password")).sendKeys("somepass"); 25 | driver.findElement(By.id("okta-signin-submit")).submit(); 26 | sleep(1000); 27 | assertThat(driver.findElement(By.tagName("body")).getText()).contains("Sign in failed!"); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /core/src/test/java/com/example/ColombiaApplication.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class ColombiaApplication { 8 | public static void main(String[] args) throws Throwable { 9 | SpringApplication.run(ColombiaApplication.class, args); 10 | } 11 | } -------------------------------------------------------------------------------- /core/src/test/java/com/example/MvcConfig.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; 5 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; 6 | 7 | @Configuration 8 | public class MvcConfig extends WebMvcConfigurerAdapter { 9 | @Override 10 | public void addViewControllers(ViewControllerRegistry registry) { 11 | registry.addViewController("/").setViewName("index"); 12 | registry.addViewController("/hello").setViewName("index"); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /core/src/test/java/com/example/SecurityConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | import org.springframework.beans.factory.annotation.Value; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.security.config.annotation.SecurityConfigurer; 6 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 7 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 8 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 9 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 10 | 11 | import static org.springframework.security.extensions.saml2.config.SAMLConfigurer.saml; 12 | 13 | @EnableWebSecurity 14 | @Configuration 15 | @EnableGlobalMethodSecurity(securedEnabled = true) 16 | class SecurityConfiguration extends WebSecurityConfigurerAdapter { 17 | 18 | @Value("${saml.metadata.path}") 19 | private String metadataPath; 20 | 21 | @Override 22 | protected void configure(HttpSecurity http) throws Exception { 23 | SecurityConfigurer securityConfigurerAdapter = 24 | saml() 25 | .identityProvider() 26 | .metadataFilePath(metadataPath) 27 | .and() 28 | .serviceProvider() 29 | .keyStore() 30 | .storeFilePath("saml/keystore.jks") 31 | .password("secret") 32 | .keyname("spring") 33 | .keyPassword("secret") 34 | .and() 35 | .protocol("https") 36 | .hostname("localhost:8443") 37 | .basePath("/") 38 | .entityId("com:example") 39 | .and(); 40 | 41 | http.apply(securityConfigurerAdapter); 42 | 43 | http 44 | .requiresChannel() 45 | .anyRequest().requiresSecure(); 46 | 47 | http 48 | .authorizeRequests() 49 | .antMatchers("/saml/**").permitAll() 50 | .antMatchers("/health").permitAll() 51 | .antMatchers("/error").permitAll() 52 | .anyRequest().authenticated(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /core/src/test/java/helper/Credentials.java: -------------------------------------------------------------------------------- 1 | package helper; 2 | 3 | public class Credentials { 4 | 5 | private String username; 6 | private String password; 7 | 8 | public Credentials(String username, String password) { 9 | this.username = username; 10 | this.password = password; 11 | } 12 | 13 | public String getPassword() { 14 | return password; 15 | } 16 | 17 | public String getUsername() { 18 | return username; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /core/src/test/java/helper/LoginHelper.java: -------------------------------------------------------------------------------- 1 | package helper; 2 | 3 | import org.springframework.core.io.ClassPathResource; 4 | import org.springframework.core.io.Resource; 5 | 6 | import java.io.IOException; 7 | import java.util.Properties; 8 | 9 | public class LoginHelper { 10 | 11 | private static final String CREDENTIALS_FILE = "credentials.yml"; 12 | 13 | private static final String USERNAME_PROPERTY = "username"; 14 | private static final String PASSWORD_PROPERTY = "password"; 15 | 16 | private static final String ENVVAR_USERNAME = "saml_username"; 17 | private static final String ENVVAR_PASSWORD = "saml_password"; 18 | 19 | public static Credentials loadCredentials() throws IOException { 20 | Resource resource = new ClassPathResource(CREDENTIALS_FILE); 21 | 22 | String username; 23 | String password; 24 | 25 | if (resource.exists()) { 26 | Properties props = new Properties(); 27 | props.load(resource.getInputStream()); 28 | username = props.getProperty(USERNAME_PROPERTY); 29 | password = props.getProperty(PASSWORD_PROPERTY); 30 | } else { 31 | username = System.getenv(ENVVAR_USERNAME); 32 | password = System.getenv(ENVVAR_PASSWORD); 33 | } 34 | 35 | return new Credentials(username, password); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /core/src/test/java/org/springframework/security/extensions/saml2/config/SAMLConfigurerCsrfDisabledTests.java: -------------------------------------------------------------------------------- 1 | package org.springframework.security.extensions.saml2.config; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 9 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 10 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 11 | import org.springframework.test.context.ContextConfiguration; 12 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 13 | import org.springframework.test.context.web.WebAppConfiguration; 14 | import org.springframework.test.web.servlet.MockMvc; 15 | import org.springframework.test.web.servlet.setup.MockMvcBuilders; 16 | import org.springframework.web.context.WebApplicationContext; 17 | 18 | import static org.springframework.security.extensions.saml2.config.SAMLConfigurer.saml; 19 | import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; 20 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 21 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 22 | 23 | @RunWith(SpringJUnit4ClassRunner.class) 24 | @ContextConfiguration 25 | @WebAppConfiguration 26 | public class SAMLConfigurerCsrfDisabledTests { 27 | @Autowired 28 | WebApplicationContext wac; 29 | 30 | MockMvc mockMvc; 31 | 32 | @Before 33 | public void setup() { 34 | mockMvc = MockMvcBuilders 35 | .webAppContextSetup(wac) 36 | .apply(springSecurity()) 37 | .build(); 38 | } 39 | 40 | @Test 41 | public void disableCsrfWorks() throws Exception { 42 | mockMvc.perform(post("/public/")) 43 | // ensure not 403 44 | .andExpect(status().isNotFound()); 45 | } 46 | 47 | @Configuration 48 | @EnableWebSecurity 49 | static class Config extends WebSecurityConfigurerAdapter { 50 | 51 | @Override 52 | protected void configure(HttpSecurity http) throws Exception { 53 | http 54 | .authorizeRequests() 55 | .anyRequest().permitAll() 56 | .and() 57 | .csrf().disable() 58 | .apply(saml()) 59 | .identityProvider() 60 | .metadataFilePath("https://dev-348145.oktapreview.com/app/exk5id72igJRNtH5M0h7/sso/saml/metadata") 61 | .and() 62 | .serviceProvider() 63 | .keyStore() 64 | .storeFilePath("saml/keystore.jks") 65 | .password("secret") 66 | .keyname("spring") 67 | .keyPassword("secret") 68 | .and() 69 | .protocol("https") 70 | .hostname("localhost:8443") 71 | .basePath("/"); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /core/src/test/java/org/springframework/security/extensions/saml2/config/SAMLConfigurerProfileConsumerTests.java: -------------------------------------------------------------------------------- 1 | package org.springframework.security.extensions.saml2.config; 2 | 3 | 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.mockito.ArgumentCaptor; 8 | import org.opensaml.saml2.core.Assertion; 9 | import org.opensaml.saml2.core.NameID; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.context.annotation.Bean; 12 | import org.springframework.context.annotation.Configuration; 13 | import org.springframework.core.io.ClassPathResource; 14 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 15 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 16 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 17 | import org.springframework.security.saml.SAMLCredential; 18 | import org.springframework.security.saml.context.SAMLMessageContext; 19 | import org.springframework.security.saml.websso.WebSSOProfileConsumerImpl; 20 | import org.springframework.test.context.ContextConfiguration; 21 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 22 | import org.springframework.test.context.web.WebAppConfiguration; 23 | import org.springframework.test.web.servlet.MockMvc; 24 | import org.springframework.test.web.servlet.setup.MockMvcBuilders; 25 | import org.springframework.util.StreamUtils; 26 | import org.springframework.web.context.WebApplicationContext; 27 | 28 | import java.nio.charset.StandardCharsets; 29 | 30 | import static org.assertj.core.api.Assertions.assertThat; 31 | import static org.mockito.Mockito.*; 32 | import static org.springframework.security.extensions.saml2.config.SAMLConfigurer.saml; 33 | import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; 34 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 35 | 36 | 37 | @RunWith(SpringJUnit4ClassRunner.class) 38 | @ContextConfiguration 39 | @WebAppConfiguration 40 | public class SAMLConfigurerProfileConsumerTests { 41 | 42 | @Autowired 43 | private WebSSOProfileConsumerImpl webSSOProfileConsumer; 44 | 45 | @Autowired 46 | WebApplicationContext wac; 47 | 48 | MockMvc mockMvc; 49 | 50 | @Before 51 | public void setup() { 52 | mockMvc = MockMvcBuilders 53 | .webAppContextSetup(wac) 54 | .apply(springSecurity()) 55 | .build(); 56 | } 57 | 58 | @Test 59 | public void webSSOProfileConsumerIsInjectedViaDSL() throws Exception { 60 | ArgumentCaptor samlMessageContextCaptor = ArgumentCaptor.forClass(SAMLMessageContext.class); 61 | 62 | when(webSSOProfileConsumer.processAuthenticationResponse(samlMessageContextCaptor.capture())) 63 | .thenReturn(stubSAMLCredential()); 64 | 65 | String samlResponse = StreamUtils.copyToString( 66 | new ClassPathResource("saml/SAMLResponse.xml").getInputStream(), 67 | StandardCharsets.UTF_8); 68 | 69 | mockMvc.perform(post("/saml/SSO").param("SAMLResponse", samlResponse)); 70 | 71 | verify(webSSOProfileConsumer).processAuthenticationResponse(samlMessageContextCaptor.capture()); 72 | 73 | SAMLMessageContext samlMessageContext = samlMessageContextCaptor.getValue(); 74 | 75 | assertThat(samlMessageContext).isNotNull(); 76 | assertThat(samlMessageContext.getInboundSAMLMessageId()).isEqualTo("id61844979402263501352984461"); 77 | assertThat(samlMessageContext.getPeerEntityId()).isEqualTo("http://www.okta.com/exkb5v2p0pp35JFKa0h7"); 78 | } 79 | 80 | private SAMLCredential stubSAMLCredential() { 81 | return new SAMLCredential( 82 | mock(NameID.class), 83 | mock(Assertion.class), 84 | "entity", 85 | "local"); 86 | } 87 | 88 | @Configuration 89 | @EnableWebSecurity 90 | static class Config extends WebSecurityConfigurerAdapter { 91 | 92 | @Bean 93 | public WebSSOProfileConsumerImpl webSSOProfileConsumer() { 94 | return mock(WebSSOProfileConsumerImpl.class); 95 | } 96 | 97 | @Override 98 | protected void configure(HttpSecurity http) throws Exception { 99 | http 100 | .authorizeRequests() 101 | .antMatchers("/saml/**").permitAll() 102 | .anyRequest().authenticated() 103 | .and() 104 | .apply(saml()) 105 | .identityProvider() 106 | .metadataFilePath("https://dev-547916.oktapreview.com/app/exkb5v2p0pp35JFKa0h7/sso/saml/metadata") 107 | .and() 108 | .webSSOProfileConsumer(webSSOProfileConsumer()) 109 | .serviceProvider() 110 | .keyStore() 111 | .storeFilePath("saml/keystore.jks") 112 | .password("secret") 113 | .keyname("spring") 114 | .keyPassword("secret") 115 | .and() 116 | .protocol("https") 117 | .hostname("localhost:8443") 118 | .basePath("/"); 119 | } 120 | } 121 | 122 | } 123 | -------------------------------------------------------------------------------- /core/src/test/java/org/springframework/security/extensions/saml2/config/SAMLConfigurerTests.java: -------------------------------------------------------------------------------- 1 | package org.springframework.security.extensions.saml2.config; 2 | 3 | import static org.hamcrest.Matchers.containsString; 4 | import static org.springframework.security.extensions.saml2.config.SAMLConfigurer.saml; 5 | import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; 6 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 7 | import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; 8 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; 9 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; 10 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 11 | 12 | import org.junit.Before; 13 | import org.junit.Test; 14 | import org.junit.runner.RunWith; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.context.annotation.Configuration; 17 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 18 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 19 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 20 | import org.springframework.test.context.ContextConfiguration; 21 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 22 | import org.springframework.test.context.web.WebAppConfiguration; 23 | import org.springframework.test.web.servlet.MockMvc; 24 | import org.springframework.test.web.servlet.setup.MockMvcBuilders; 25 | import org.springframework.web.context.WebApplicationContext; 26 | 27 | @RunWith(SpringJUnit4ClassRunner.class) 28 | @ContextConfiguration 29 | @WebAppConfiguration 30 | public class SAMLConfigurerTests { 31 | @Autowired 32 | WebApplicationContext wac; 33 | 34 | MockMvc mockMvc; 35 | 36 | @Before 37 | public void setup() { 38 | mockMvc = MockMvcBuilders 39 | .webAppContextSetup(wac) 40 | .apply(springSecurity()) 41 | .build(); 42 | } 43 | 44 | @Test 45 | public void protectedUrlRedirectsToDiscovery() throws Exception { 46 | mockMvc.perform(get("/protected/")) 47 | .andExpect(status().is3xxRedirection()) 48 | .andExpect(redirectedUrl("https://localhost:8443/saml/discovery?entityID=https%3A%2F%2Flocalhost%3A8443%2Fsaml%2Fmetadata&returnIDParam=idp")); 49 | } 50 | 51 | @Test 52 | public void discoveryRedirectsLogin() throws Exception { 53 | mockMvc.perform(get("/saml/discovery").param("entityID","https://localhost:8443/saml/metadata").param("returnIDParam","idp")) 54 | .andExpect(status().is3xxRedirection()) 55 | .andExpect(redirectedUrl("https://localhost:8443/saml/login?disco=true&idp=http%3A%2F%2Fwww.okta.com%2Fexk5id72igJRNtH5M0h7")); 56 | } 57 | 58 | @Test 59 | public void loginRendersSAMLRequest() throws Exception { 60 | mockMvc.perform(get("/saml/login").param("disco", "true").param("idp","http://www.okta.com/exk5id72igJRNtH5M0h7")) 61 | .andExpect(status().isOk()) 62 | .andExpect(content().string(containsString(" 2 | 4 | 6 | 7 | 8 | 9 | MIIDpDCCAoygAwIBAgIGAVNmAiVZMA0GCSqGSIb3DQEBBQUAMIGSMQswCQYDVQQGEwJVUzETMBEG 10 | A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU 11 | MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi05NTIzOTAxHDAaBgkqhkiG9w0BCQEW 12 | DWluZm9Ab2t0YS5jb20wHhcNMTYwMzExMTQwNzUwWhcNMjYwMzExMTQwODUwWjCBkjELMAkGA1UE 13 | BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV 14 | BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtOTUyMzkwMRwwGgYJ 15 | KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA 16 | m1aMQL8nx665UUy0ruleLCrAST7UgsREbg3CALqGVH+lnER7vpGr1Kh9LOvLzM1OmxTazgex+DfA 17 | 84BhpBIM88nz3CNdcoZsK6+kFdiL215nFT/NhCeBNEj1sb7ZwxBfsxTuV7FhWVHUwDI5VIMpcvkt 18 | a6ucaDTM55epIvc2Ts4/sIwbGDyShR7Jja7ztgUFFdq+4fzGjao/+3RZHcfw3GhRkKASfepne4MV 19 | IB9GYTGAwoSdStkR0gTvuwgPBvo1AUZfBBUMTvqwLi6I84f3CGoRmkG/In6RjlO5Rw+wYTjh7ZHh 20 | gOGzQAtb6Cb/n+fCN/EYWWECEXwN76rz7fSn3wIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQArMXe8 21 | /3t1P7bny7Y3WWJSDqHn/jkE4QXQSmqq4O8xYXGYcALJbD02pk/HrmaZQRIyPb6il3FcLrrgRKIn 22 | q7OM3syY+3wbVvZjtVYeqJ1nwCDW5BmFYh6fJqjsjkM2dDQ5m1qwbx8l6+2ho13YrPWT0NwxV1Eo 23 | Hw78/1AD3zSsSIdBsMyskOAIXb92D5bIptdDKcY7Rhs7WPR8NJcAEOS62MvuSPfSih7iqHfbXd5v 24 | TP60wBKmiBLK8IT9G0KS50pO3A9SIKg6cDOyoYPSecEmAW/EOjrPn8sfS8a00uP1ygSnj4EwYfMs 25 | +9uDwohAHyNi1zyKqe5ZXABkbW19gg62 26 | 27 | 28 | 29 | 30 | urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress 31 | urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified 32 | 34 | 36 | 37 | -------------------------------------------------------------------------------- /core/src/test/resources/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Spring Security Example 5 | 6 | 7 | Hello world 8 | 9 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | version=1.0.6.BUILD-SNAPSHOT 2 | group=org.springframework.security.extensions 3 | -------------------------------------------------------------------------------- /gradle/publish-maven.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "propdeps-maven" 2 | 3 | install { 4 | repositories.mavenInstaller { 5 | customizePom(pom, project) 6 | } 7 | } 8 | 9 | task generatePom { 10 | group = 'Build' 11 | description = 'Generates a Maven pom.xml' 12 | 13 | ext.generatedPomFileName = "pom.xml" 14 | onlyIf { install.enabled } 15 | 16 | inputs.files(fileTree(project.rootProject.rootDir).include("**/*.gradle").files) 17 | inputs.files(new File(project.rootProject.rootDir, Project.GRADLE_PROPERTIES)) 18 | outputs.files(generatedPomFileName) 19 | 20 | doLast() { 21 | def p = pom {} 22 | customizePom(p, project) 23 | p.writeTo(generatedPomFileName) 24 | } 25 | } 26 | 27 | build.dependsOn generatePom 28 | 29 | def customizePom(pom, gradleProject) { 30 | pom.whenConfigured { generatedPom -> 31 | 32 | // sort to make pom dependencies order consistent to ease comparison of older poms 33 | generatedPom.dependencies = generatedPom.dependencies.sort { dep -> 34 | "$dep.scope:$dep.groupId:$dep.artifactId" 35 | } 36 | 37 | // add all items necessary for maven central publication 38 | generatedPom.project { 39 | name = gradleProject.description 40 | description = gradleProject.description 41 | url = "https://github.com/spring-projects/spring-security-saml-dsl" 42 | organization { 43 | name = "Pivotal Software Inc" 44 | url = "https://github.com/spring-projects/spring-security-saml-dsl" 45 | } 46 | licenses { 47 | license { 48 | name "The Apache Software License, Version 2.0" 49 | url "https://www.apache.org/licenses/LICENSE-2.0.txt" 50 | distribution "repo" 51 | } 52 | } 53 | scm { 54 | url = "https://github.com/spring-projects/spring-security-saml-dsl" 55 | connection = "scm:git:git://github.com/spring-projects/spring-security-saml-dsl" 56 | developerConnection = "scm:git:git://github.com/spring-projects/spring-security-saml-dsl" 57 | } 58 | developers { 59 | developer { 60 | id = "fhanik" 61 | name = "Filip Hanik" 62 | email = "fhanik at pivotal.io" 63 | timezone = "+2" 64 | } 65 | } 66 | contributors { 67 | contributor { 68 | name = "Jean de Klerk" 69 | } 70 | contributor { 71 | name = "Rob Winch" 72 | } 73 | contributor { 74 | name = "Mark Douglass" 75 | } 76 | contributor { 77 | name = "Travis Tomsu" 78 | } 79 | contributor { 80 | name = "Matt Raible" 81 | } 82 | } 83 | issueManagement { 84 | system = "jira" 85 | url = "https://jira.springsource.org/browse/SES" 86 | } 87 | mailingLists { 88 | mailingList { 89 | name = "Spring Security SAML Forum" 90 | post = "https://stackoverflow.com/questions/tagged/spring-security" 91 | archive = "https://stackoverflow.com/questions/tagged/spring-security" 92 | } 93 | } 94 | } 95 | } 96 | } 97 | 98 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/spring-security-saml-dsl/3c06b4c5daa5ed926a5ed2160afdf56bbec1bd24/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >&- 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >&- 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /samples/spring-security-saml-dsl-sample/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/spring-security-saml-dsl/3c06b4c5daa5ed926a5ed2160afdf56bbec1bd24/samples/spring-security-saml-dsl-sample/.gitkeep -------------------------------------------------------------------------------- /samples/spring-security-saml-dsl-sample/README.md: -------------------------------------------------------------------------------- 1 | ## Set up a test okta 2 | 3 | #### Basic setup 4 | 1. Navigate to [http://developer.okta.com/](http://developer.okta.com/) 5 | 1. Click on *Sign Up* 6 | 1. Fill in your own name and email address to register, or login if you already have 7 | 1. Okta will send you a confirmation email, including your temporary password and a link to your new developer Okta instance 8 | 1. Navigate to the link in the email, and input your email address and the temporary password provided to you 9 | 1. Fill in the form presented to complete your registration, and click on *Create My Account* 10 | 1. You should now have an empty Okta instance with no apps, and only one user. 11 | 1. Click on **< > Developer** in the top-left corner and switch to the Classic UI. 12 | 13 | #### Create a test application 14 | 1. Click on *Admin*, then *Add Applications* 15 | 1. Click on *Create New App* 16 | 1. Click *Create New App*, select your platform (web, native, or SPA) and choose *SAML 2.0* 17 | 1. Give your app a name and click *Next* 18 | 1. Fill in *Single sign on URL* with `https://localhost:8443/saml/SSO` 19 | 1. Fill in *Audience URI* with `https://localhost:8443/saml/metadata` 20 | 1. Your config should look like this ![okta config](okta-config-page.png) 21 | 1. The rest of the fields can be left as they began, click *Next* 22 | 1. Select *I'm an Okta customer adding an internal app* 23 | 1. Check *This is an internal app that we have created* 24 | 1. Click *Finish* 25 | 1. Setup the metadata via url 26 | 1. Copy and paste the Identity Provider metadata url into the application.yml under `security.saml2.metadata-url` 27 | 1. OR setup the metadata via xml 28 | 1. Click *View Setup Instructions* 29 | 1. Copy the xml IDP Metadata from the Optional section into a file in your project: `src/main/resources/saml/metadata.xml` 30 | 1. Set metadataFilePath in SecurityConfiguration to the xml file (i.e. `metadataFilePath("saml/metadata.xml")` 31 | 32 | #### Assign the test application 33 | 1. Return to your Okta home screen and click *Admin* 34 | 1. Click *Assign Applications* 35 | 1. Select the application you just created, yourself, then click *Next* 36 | 1. Click *Confirm Assignments* 37 | 38 | ## One time application setup 39 | 40 | 1. Generate a keystore and key in `src/main/resources/saml` with password `secret`: 41 | 42 | `keytool -genkey -v -keystore keystore.jks -alias spring -keyalg RSA -keysize 2048 -validity 10000` 43 | 44 | ## Running the app 45 | 46 | 1. `./gradlew clean bootRun` 47 | 1. Navigate to `http://localhost:8443` 48 | -------------------------------------------------------------------------------- /samples/spring-security-saml-dsl-sample/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'org.springframework.boot' 2 | 3 | tasks.findByPath("artifactoryPublish")?.enabled = false 4 | 5 | dependencies { 6 | 7 | compile( 8 | "org.springframework.boot:spring-boot-starter-tomcat:$springBootVersion", 9 | "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion", 10 | "org.springframework.boot:spring-boot-starter-security:$springBootVersion", 11 | "org.springframework.boot:spring-boot-starter-thymeleaf:$springBootVersion", 12 | "org.springframework.boot:spring-boot-starter-web:$springBootVersion", 13 | "org.springframework.security.extensions:spring-security-saml2-core:$springSecuritySamlVersion", 14 | project(':spring-security-saml-dsl-core'), 15 | ) 16 | 17 | configurations { 18 | compile.exclude module: 'spring-boot-starter-jetty' 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /samples/spring-security-saml-dsl-sample/okta-config-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/spring-security-saml-dsl/3c06b4c5daa5ed926a5ed2160afdf56bbec1bd24/samples/spring-security-saml-dsl-sample/okta-config-page.png -------------------------------------------------------------------------------- /samples/spring-security-saml-dsl-sample/src/main/java/com/example/ColombiaApplication.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class ColombiaApplication { 8 | 9 | public static void main(String[] args) throws Throwable { 10 | SpringApplication.run(ColombiaApplication.class, args); 11 | } 12 | 13 | } -------------------------------------------------------------------------------- /samples/spring-security-saml-dsl-sample/src/main/java/com/example/MvcConfig.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; 5 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; 6 | 7 | @Configuration 8 | public class MvcConfig extends WebMvcConfigurerAdapter { 9 | @Override 10 | public void addViewControllers(ViewControllerRegistry registry) { 11 | registry.addViewController("/").setViewName("index"); 12 | registry.addViewController("/hello").setViewName("index"); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /samples/spring-security-saml-dsl-sample/src/main/java/com/example/SecurityConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | import org.springframework.beans.factory.annotation.Value; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 6 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 7 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 8 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 9 | 10 | import static org.springframework.security.extensions.saml2.config.SAMLConfigurer.saml; 11 | 12 | @EnableWebSecurity 13 | @Configuration 14 | @EnableGlobalMethodSecurity(securedEnabled = true) 15 | public class SecurityConfiguration extends WebSecurityConfigurerAdapter { 16 | @Value("${security.saml2.metadata-url}") 17 | String metadataUrl; 18 | 19 | @Override 20 | protected void configure(HttpSecurity http) throws Exception { 21 | http 22 | .authorizeRequests() 23 | .antMatchers("/saml/**").permitAll() 24 | .anyRequest().authenticated() 25 | .and() 26 | .apply(saml()) 27 | .serviceProvider() 28 | .keyStore() 29 | .storeFilePath("saml/keystore.jks") 30 | .password("secret") 31 | .keyname("spring") 32 | .keyPassword("secret") 33 | .and() 34 | .protocol("https") 35 | .hostname("localhost:8443") 36 | .basePath("/") 37 | .and() 38 | .identityProvider() 39 | .metadataFilePath(metadataUrl) 40 | .and(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /samples/spring-security-saml-dsl-sample/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8443 3 | ssl: 4 | enabled: true 5 | key-alias: spring 6 | key-store: src/main/resources/saml/keystore.jks 7 | key-store-password: secret 8 | 9 | security: 10 | saml2: 11 | metadata-url: https://dev-348145.oktapreview.com/app/exk6bmtlqpOBRo4a20h7/sso/saml/metadata -------------------------------------------------------------------------------- /samples/spring-security-saml-dsl-sample/src/main/resources/saml/keystore.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/spring-security-saml-dsl/3c06b4c5daa5ed926a5ed2160afdf56bbec1bd24/samples/spring-security-saml-dsl-sample/src/main/resources/saml/keystore.jks -------------------------------------------------------------------------------- /samples/spring-security-saml-dsl-sample/src/main/resources/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Spring Security Example 5 | 6 | 7 | Hello world 8 | 9 | -------------------------------------------------------------------------------- /samples/spring-security-saml-dsl-sample/src/main/resources/templates/login.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | Spring Security Example 6 | 7 | 8 |
9 | Invalid username and password. 10 |
11 |
12 | You have been logged out. 13 |
14 |
15 |
16 |
17 |
18 |
19 | 20 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include 'core' 2 | include 'samples/spring-security-saml-dsl-sample' 3 | 4 | rootProject.name = "spring-security-saml-dsl" 5 | 6 | rootProject.children.each { p -> 7 | p.name = "spring-security-saml-dsl-${p.name}" 8 | } 9 | --------------------------------------------------------------------------------