├── .github └── workflows │ └── build.yml ├── .gitignore ├── .java-version ├── .mergify.yml ├── Dockerfile ├── LICENSE ├── README.md ├── build.gradle ├── docker-build.sh ├── docker-run.sh ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── run.sh ├── settings.gradle └── src └── main ├── java └── org │ └── springframework │ └── security │ └── saml │ └── web │ ├── ClassLoaderLeakPreventorListener.java │ ├── ConfigurableWebSsoProfile.java │ ├── CustomWebSSOProfileOptions.java │ ├── MetadataController.java │ ├── MetadataForm.java │ ├── MetadataValidator.java │ └── StatusServlet.java ├── resources ├── log4j.properties ├── metadata │ └── sp-metadata.xml └── security │ └── samlKeystore.jks └── webapp ├── WEB-INF ├── saml-servlet.xml ├── security │ ├── adminLogin.jsp │ ├── idpSelection.jsp │ ├── metadataGenerator.jsp │ ├── metadataList.jsp │ ├── metadataView.jsp │ └── providerView.jsp ├── securityContext.xml ├── sp.properties ├── templates │ ├── footer.jsp │ ├── head.jsp │ ├── navigation.jsp │ └── sidebar.jsp └── web.xml ├── css └── style.css ├── error.jsp ├── images ├── favicon.png ├── logo.png ├── main-two-columns-left.gif └── main-two-columns.gif ├── index.jsp └── logout.jsp /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | env: 4 | JAVA_OPTS: "-Xms512m -Xmx6048m -Xss128m -XX:ReservedCodeCacheSize=512m -server -XX:+UseG1GC" 5 | GRADLE_OPTS: "-Xms512m -Xmx6048m -Xss128m -XX:ReservedCodeCacheSize=512m -server -XX:+UseG1GC" 6 | TERM: xterm-256color 7 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 8 | JDK_CURRENT: 11 9 | 10 | ########################################################################## 11 | 12 | on: 13 | push: 14 | branches: [ master ] 15 | pull_request: 16 | branches: [ master ] 17 | 18 | ########################################################################## 19 | 20 | jobs: 21 | cancel-previous-runs: 22 | runs-on: ubuntu-latest 23 | timeout-minutes: 1 24 | steps: 25 | - uses: styfle/cancel-workflow-action@0.9.1 26 | with: 27 | access_token: ${{ github.token }} 28 | build: 29 | strategy: 30 | fail-fast: false 31 | matrix: 32 | os: [ubuntu-latest] 33 | runs-on: ${{ matrix.os }} 34 | needs: cancel-previous-runs 35 | steps: 36 | - uses: actions/checkout@v3 37 | # - name: Setup tmate session 38 | # uses: mxschmitt/action-tmate@v3 39 | - name: Set up JDK 40 | uses: actions/setup-java@v3 41 | with: 42 | java-version: ${{ env.JDK_CURRENT }} 43 | distribution: 'temurin' 44 | - name: Build 45 | run: ./gradlew clean build 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | .idea 4 | classes 5 | *.iml 6 | bin/ 7 | .classpath 8 | .project 9 | .settings/ 10 | .vscode/ 11 | .history 12 | -------------------------------------------------------------------------------- /.java-version: -------------------------------------------------------------------------------- 1 | 11.0 2 | -------------------------------------------------------------------------------- /.mergify.yml: -------------------------------------------------------------------------------- 1 | pull_request_rules: 2 | - name: automatic merge 3 | conditions: 4 | - status-success=build (ubuntu-latest) 5 | - status-success=WIP 6 | - "#changes-requested-reviews-by=0" 7 | - base=master 8 | - label=dependencies 9 | actions: 10 | merge: 11 | method: merge 12 | update: 13 | delete_head_branch: -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM eclipse-temurin:11-jdk AS war 2 | 3 | RUN mkdir -p sp-webapp 4 | COPY ./src sp-webapp/src/ 5 | COPY ./gradle/ sp-webapp/gradle/ 6 | COPY ./gradlew ./build.gradle ./gradle.properties /sp-webapp/ 7 | 8 | RUN cd / \ 9 | && mkdir -p /etc/cas/config/saml \ 10 | && mkdir -p /etc/cas/saml 11 | 12 | RUN mkdir -p ~/.gradle \ 13 | && echo "org.gradle.daemon=false" >> ~/.gradle/gradle.properties \ 14 | && echo "org.gradle.configureondemand=true" >> ~/.gradle/gradle.properties \ 15 | && cd sp-webapp \ 16 | && chmod 750 ./gradlew \ 17 | && ./gradlew --version; 18 | 19 | RUN cd sp-webapp && ./gradlew clean build --parallel --no-daemon; 20 | 21 | COPY ./docker-run.sh /sp-webapp/ 22 | 23 | EXPOSE 9876 8076 24 | 25 | ENV PATH $PATH:$JAVA_HOME/bin:. 26 | 27 | WORKDIR sp-webapp 28 | RUN ls -al && chmod +x *.sh 29 | 30 | ENTRYPOINT ["/bin/bash", "-c", "$PWD/docker-run.sh"] 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spring Security SAML2 - Sample Web Application 2 | 3 | A sample application protected by Spring Security Saml2. 4 | 5 | ## Configuration 6 | 7 | ### Context 8 | 9 | App is available by default at `https://hostname[:port]/sp`. 10 | 11 | ### SP Metadata 12 | 13 | The SP metadata is available under `src\main\resources\metadata`. Deploy the application, and 14 | use the `Metadata Administration` tab to generate new metadata for the SP. Copy the content 15 | and paste them into the `sp-metadata.xml` file. 16 | 17 | The newly generated SP metadata needs to be loaded by the IdP, in the `metadata-providers.xml` file. 18 | For example: 19 | 20 | ```xml 21 | 23 | ``` 24 | 25 | ### IdP Metadata 26 | 27 | The IdP metadata is referenced inside the `sp.properties` file. While you are reviewing this file, 28 | also adjust the `host.name` property. This should be pointed to FQDN of the server that hosts the sample 29 | application. 30 | 31 | ## Build 32 | 33 | ```bash 34 | gradlew build 35 | ``` 36 | 37 | ## Deploy 38 | 39 | 40 | ### Embedded 41 | 42 | The application can be run using an embedded Jetty instance: 43 | 44 | ```bash 45 | gradlew build jettyRunWar 46 | ``` 47 | 48 | - The sample application will be available at: `https://hostname[8081|9876]/sp` 49 | - Remote debugging via the embedded Jetty deployment option is available under port `5005` 50 | 51 | ### External 52 | 53 | The build script is able to automatically deploy the sample to `$CATALINA_HOME`, via: 54 | 55 | ```bash 56 | gradlew deploy 57 | ``` 58 | 59 | Note that `deploy` will automatically build the sample application as well. 60 | For this work, you must define the `$CATALINA_HOME` environment variable to point to your 61 | Tomcat installation directory. 62 | 63 | ## Test 64 | 65 | - The application has been tested with Shibboleth IdP 3.x, 4.x. 66 | - The application has been tested with Apereo CAS 5.x, 6.x. 67 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | defaultTasks "build", "jettyRunWar" 2 | 3 | allprojects { 4 | repositories { 5 | mavenLocal() 6 | mavenCentral() 7 | maven { url 'https://build.shibboleth.net/nexus/content/groups/public' } 8 | } 9 | } 10 | 11 | buildscript { 12 | repositories { 13 | mavenLocal() 14 | mavenCentral() 15 | } 16 | 17 | dependencies { 18 | classpath "org.gretty:gretty:3.0.5" 19 | } 20 | } 21 | 22 | apply plugin: 'war' 23 | apply plugin: 'java' 24 | apply plugin: 'idea' 25 | apply plugin: "org.gretty" 26 | 27 | war.archiveName("sp.war") 28 | 29 | description = "Spring Security SAML v2 sample webapp" 30 | 31 | 32 | dependencies { 33 | implementation "org.springframework.security.extensions:spring-security-saml2-core:${project.'spring-security-saml2.version'}", 34 | "org.springframework:spring-core:${project.'spring.version'}", 35 | "org.springframework:spring-beans:${project.'spring.version'}", 36 | "org.springframework:spring-context:${project.'spring.version'}", 37 | "org.springframework:spring-aop:${project.'spring.version'}", 38 | "org.springframework:spring-web:${project.'spring.version'}", 39 | "org.springframework:spring-webmvc:${project.'spring.version'}", 40 | "org.springframework.security:spring-security-config:${project.'spring-security.version'}", 41 | "org.slf4j:slf4j-log4j12:${project.'slf4j.version'}", 42 | "org.slf4j:jul-to-slf4j:${project.'slf4j.version'}", 43 | "commons-io:commons-io:2.11.0", 44 | "javax.servlet:jstl:1.2", 45 | "se.jiderhamn.classloader-leak-prevention:classloader-leak-prevention-servlet:2.7.0" 46 | 47 | providedCompile "javax.servlet:jsp-api:2.0" 48 | testImplementation "junit:junit:4.13.2" 49 | } 50 | 51 | configurations { 52 | grettyRunnerJetty9 { 53 | resolutionStrategy.eachDependency { DependencyResolveDetails details -> 54 | if (details.requested.group == 'org.ow2.asm') { 55 | details.useVersion('5.0.3') 56 | } 57 | if (details.requested.group == 'org.glassfish' 58 | && details.requested.name == 'javax.el') { 59 | details.useVersion('3.0.0') 60 | } 61 | } 62 | } 63 | } 64 | 65 | List programArgs = ["-Dspring.security.strategy=MODE_GLOBAL", 66 | "-Dorg.eclipse.jetty.annotations.maxWait=120"] 67 | gretty { 68 | System.properties.each { 69 | programArgs.add("-D${it.key}=${it.value}") 70 | } 71 | 72 | if (System.getProperty("debug") != null) { 73 | programArgs.add("-Xdebug") 74 | programArgs.add("-Xrunjdwp:transport=dt_socket,address=5008,server=y,suspend=n") 75 | } 76 | 77 | logger.info("Program arguments: {}", programArgs) 78 | jvmArgs = programArgs 79 | scanInterval = 5 80 | // servletContainer = 'jetty9.4' 81 | contextPath = '/sp' 82 | httpPort = 8076 83 | def path = System.getProperty("sp.sslKeystorePath", "/etc/cas/thekeystore") 84 | if (path != null && new java.io.File(path).exists()) { 85 | httpsEnabled = true 86 | httpsPort = 9876 87 | sslKeyStorePath = path 88 | sslKeyStorePassword = System.getProperty("sp.sslKeystorePassword", "changeit") 89 | sslTrustStorePath = path 90 | sslKeyStorePassword = System.getProperty("sp.sslKeystorePassword", "changeit") 91 | } 92 | } 93 | 94 | -------------------------------------------------------------------------------- /docker-build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | docker build --tag="apereo/saml2-sp" . \ 4 | && echo "Built image successfully." \ 5 | && docker images "apereo/saml2-sp" 6 | 7 | docker_user="$1" 8 | docker_psw="$2" 9 | 10 | if [ -n "${docker_user}" ]; then 11 | echo "$docker_psw" | docker login --username "$docker_user" --password-stdin 12 | echo "Pushing docker image: apereo/saml2-sp" 13 | docker push apereo/saml2-sp \ 14 | && echo "Pushed apereo/saml2-sp successfully."; 15 | fi 16 | 17 | # docker run -p 9876:9876 -it -d --rm --name=sp \ 18 | # -v /etc/cas/config/saml/idp-metadata.xml:/etc/cas/config/saml/idp-metadata.xml \ 19 | # -v /etc/cas/thekeystore:/etc/cas/thekeystore apereo/saml2-sp 20 | -------------------------------------------------------------------------------- /docker-run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ARGS="" 4 | if [ -n "${IDP_METADATA}" ]; then 5 | ARGS="${ARGS} -DidpMetadata="${IDP_METADATA}"" 6 | fi 7 | 8 | if [ -n "${IDP_METADATA_TYPE}" ]; then 9 | ARGS="${ARGS} -DidpMetadataType="${IDP_METADATA_TYPE}"" 10 | fi 11 | 12 | if [ -n "${METADATA_TYPE}" ]; then 13 | ARGS="${ARGS} -DmetadataType="${METADATA_TYPE}"" 14 | fi 15 | 16 | if [ -n "${INCLUDE_SCOPING}" ]; then 17 | ARGS="${ARGS} -DincludeScoping="${INCLUDE_SCOPING}"" 18 | fi 19 | 20 | if [ -n "${METADATA_LOCATION}" ]; then 21 | ARGS="${ARGS} -DmetadataLocation="${METADATA_LOCATION}"" 22 | fi 23 | 24 | if [ -n "${RELAY_STATE}" ]; then 25 | ARGS="${ARGS} -DrelayState="${RELAY_STATE}"" 26 | fi 27 | 28 | if [ -n "${NAMEID_FORMAT}" ]; then 29 | ARGS="${ARGS} -DnameIdFormat="${NAMEID_FORMAT}"" 30 | fi 31 | 32 | if [ -n "${ALLOWED_IDP1}" ]; then 33 | ARGS="${ARGS} -DallowedIdP1="${ALLOWED_IDP1}"" 34 | fi 35 | 36 | if [ -n "${ACS_URL}" ]; then 37 | ARGS="${ARGS} -DacsUrl="${ACS_URL}"" 38 | fi 39 | 40 | if [ -n "${SIGN_AUTHN_REQUESTS}" ]; then 41 | ARGS="${ARGS} -DsignAuthnRequests="${SIGN_AUTHN_REQUESTS}"" 42 | fi 43 | 44 | if [ -n "${FORCED_AUTHN}" ]; then 45 | ARGS="${ARGS} -DforcedAuthn="${FORCED_AUTHN}"" 46 | fi 47 | 48 | if [ -n "${AUTHN_REQUEST_BINDING_OPTIONAL}" ]; then 49 | ARGS="${ARGS} -DauthnRequestBindingOptional="${AUTHN_REQUEST_BINDING_OPTIONAL}"" 50 | fi 51 | 52 | if [ -n "${AUTHN_CONTEXT}" ]; then 53 | ARGS="${ARGS} -DauthnContext="${AUTHN_CONTEXT}"" 54 | fi 55 | 56 | if [ -n "${SP_KEYSTORE_PATH}" ]; then 57 | ARGS="${ARGS} -Dsp.sslKeystorePath="${SP_KEYSTORE_PATH}"" 58 | fi 59 | 60 | if [ -n "${SP_KEYSTORE_PASSWORD}" ]; then 61 | ARGS="${ARGS} -Dsp.sslKeystorePassword="${SP_KEYSTORE_PASSWORD}"" 62 | fi 63 | 64 | 65 | echo "Running SAML2 sample application with ${ARGS}" 66 | ./gradlew jettyRunWar --stacktrace ${ARGS} 67 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | spring.version=4.3.25.RELEASE 2 | spring-security.version=4.2.13.RELEASE 3 | spring-security-saml2.version=1.0.10.RELEASE 4 | slf4j.version=1.7.29 5 | jetty94Version=9.4.22.v20191022 6 | groupId=net.unicon 7 | version=1.0-SNAPSHOT 8 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apereo/saml2-sample-java-webapp/75a1bc380c5a8f89ea0b369888b39b5a09cec2f9/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Stop when "xargs" is not available. 209 | if ! command -v xargs >/dev/null 2>&1 210 | then 211 | die "xargs is not available" 212 | fi 213 | 214 | # Use "xargs" to parse quoted args. 215 | # 216 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 217 | # 218 | # In Bash we could simply go: 219 | # 220 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 221 | # set -- "${ARGS[@]}" "$@" 222 | # 223 | # but POSIX shell has neither arrays nor command substitution, so instead we 224 | # post-process each arg (as a line of input to sed) to backslash-escape any 225 | # character that might be a shell metacharacter, then use eval to reverse 226 | # that process (while maintaining the separation between arguments), and wrap 227 | # the whole thing up as a single "set" statement. 228 | # 229 | # This will of course break if any of these variables contains a newline or 230 | # an unmatched quote. 231 | # 232 | 233 | eval "set -- $( 234 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 235 | xargs -n1 | 236 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 237 | tr '\n' ' ' 238 | )" '"$@"' 239 | 240 | exec "$JAVACMD" "$@" 241 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if %ERRORLEVEL% equ 0 goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if %ERRORLEVEL% equ 0 goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | set EXIT_CODE=%ERRORLEVEL% 84 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 85 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 86 | exit /b %EXIT_CODE% 87 | 88 | :mainEnd 89 | if "%OS%"=="Windows_NT" endlocal 90 | 91 | :omega 92 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | docker run -p 9876:9876 -it --rm --name=sp \ 3 | -v /etc/cas/config/saml/idp-metadata.xml:/etc/cas/config/saml/idp-metadata.xml \ 4 | -v /etc/cas/thekeystore:/etc/cas/thekeystore apereo/saml2-sp 5 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name='sp' 2 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/security/saml/web/ClassLoaderLeakPreventorListener.java: -------------------------------------------------------------------------------- 1 | package org.springframework.security.saml.web; 2 | 3 | import org.slf4j.bridge.SLF4JBridgeHandler; 4 | 5 | /** 6 | * Extend ClassLoaderLeakPreventorListener to override standard logger (route it to slf4j) so we can control logging. 7 | * This might risk classloader leak but we just want threads stopped. 8 | */ 9 | public class ClassLoaderLeakPreventorListener 10 | extends se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventorListener { 11 | static { 12 | SLF4JBridgeHandler.removeHandlersForRootLogger(); 13 | SLF4JBridgeHandler.install(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/security/saml/web/ConfigurableWebSsoProfile.java: -------------------------------------------------------------------------------- 1 | package org.springframework.security.saml.web; 2 | 3 | import org.opensaml.common.SAMLException; 4 | import org.opensaml.saml2.core.AuthnRequest; 5 | import org.opensaml.saml2.metadata.AssertionConsumerService; 6 | import org.opensaml.saml2.metadata.SingleSignOnService; 7 | import org.opensaml.saml2.metadata.provider.MetadataProviderException; 8 | import org.opensaml.ws.message.encoder.MessageEncodingException; 9 | import org.springframework.security.saml.context.SAMLMessageContext; 10 | import org.springframework.security.saml.websso.WebSSOProfileImpl; 11 | import org.springframework.security.saml.websso.WebSSOProfileOptions; 12 | 13 | public class ConfigurableWebSsoProfile extends WebSSOProfileImpl { 14 | private CustomWebSSOProfileOptions options; 15 | 16 | public void setOptions(final CustomWebSSOProfileOptions options) { 17 | this.options = options; 18 | } 19 | 20 | @Override 21 | protected AuthnRequest getAuthnRequest(final SAMLMessageContext context, 22 | final WebSSOProfileOptions options, 23 | final AssertionConsumerService assertionConsumer, 24 | final SingleSignOnService bindingService) 25 | throws SAMLException, MetadataProviderException { 26 | 27 | CustomWebSSOProfileOptions opts = (CustomWebSSOProfileOptions) options; 28 | AuthnRequest request = super.getAuthnRequest(context, options, assertionConsumer, bindingService); 29 | request.setAssertionConsumerServiceURL(opts.getAcsUrl()); 30 | if (opts.isAuthnRequestBindingOptional()) { 31 | request.setProtocolBinding(null); 32 | } 33 | return request; 34 | } 35 | 36 | @Override 37 | protected void sendMessage(final SAMLMessageContext context, final boolean sign) 38 | throws MetadataProviderException, SAMLException, MessageEncodingException { 39 | super.sendMessage(context, sign || this.options.isSignAuthnRequests()); 40 | } 41 | 42 | @Override 43 | protected void sendMessage(final SAMLMessageContext context, final boolean sign, final String binding) 44 | throws MetadataProviderException, SAMLException, MessageEncodingException { 45 | super.sendMessage(context, sign || this.options.isSignAuthnRequests(), binding); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/security/saml/web/CustomWebSSOProfileOptions.java: -------------------------------------------------------------------------------- 1 | package org.springframework.security.saml.web; 2 | 3 | import org.springframework.security.saml.websso.WebSSOProfileOptions; 4 | 5 | public class CustomWebSSOProfileOptions extends WebSSOProfileOptions { 6 | private static final long serialVersionUID = -6802921791141304832L; 7 | 8 | private boolean signAuthnRequests; 9 | 10 | private String acsUrl; 11 | 12 | private boolean authnRequestBindingOptional; 13 | 14 | public boolean isAuthnRequestBindingOptional() { 15 | return authnRequestBindingOptional; 16 | } 17 | 18 | public void setAuthnRequestBindingOptional(final boolean authnRequestBindingOptional) { 19 | this.authnRequestBindingOptional = authnRequestBindingOptional; 20 | } 21 | 22 | public boolean isSignAuthnRequests() { 23 | return signAuthnRequests; 24 | } 25 | 26 | public void setSignAuthnRequests(final boolean signAuthnRequests) { 27 | this.signAuthnRequests = signAuthnRequests; 28 | } 29 | 30 | public String getAcsUrl() { 31 | return acsUrl; 32 | } 33 | 34 | public void setAcsUrl(final String acsUrl) { 35 | this.acsUrl = acsUrl; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/security/saml/web/MetadataController.java: -------------------------------------------------------------------------------- 1 | /* Copyright 2011 Vladimir Schafer 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | package org.springframework.security.saml.web; 16 | 17 | import org.opensaml.common.xml.SAMLConstants; 18 | import org.opensaml.saml2.metadata.EntityDescriptor; 19 | import org.opensaml.saml2.metadata.provider.MetadataProvider; 20 | import org.opensaml.saml2.metadata.provider.MetadataProviderException; 21 | import org.opensaml.xml.io.MarshallingException; 22 | import org.opensaml.xml.security.credential.Credential; 23 | import org.slf4j.Logger; 24 | import org.slf4j.LoggerFactory; 25 | import org.springframework.beans.factory.annotation.Autowired; 26 | import org.springframework.security.saml.key.KeyManager; 27 | import org.springframework.security.saml.metadata.*; 28 | import org.springframework.security.saml.util.SAMLUtil; 29 | import org.springframework.stereotype.Controller; 30 | import org.springframework.validation.BindingResult; 31 | import org.springframework.web.bind.annotation.ModelAttribute; 32 | import org.springframework.web.bind.annotation.RequestMapping; 33 | import org.springframework.web.bind.annotation.RequestParam; 34 | import org.springframework.web.servlet.ModelAndView; 35 | import org.springframework.web.servlet.view.InternalResourceView; 36 | 37 | import javax.servlet.http.HttpServletRequest; 38 | import java.security.KeyStoreException; 39 | import java.util.*; 40 | 41 | import static org.springframework.util.StringUtils.hasLength; 42 | 43 | /** 44 | * Class allows manipulation of metadata from web UI. 45 | */ 46 | @Controller 47 | @RequestMapping("/metadata") 48 | public class MetadataController { 49 | 50 | private final Logger log = LoggerFactory.getLogger(MetadataController.class); 51 | 52 | public static enum AllowedSSOBindings { 53 | SSO_POST, SSO_PAOS, SSO_ARTIFACT, HOKSSO_POST, HOKSSO_ARTIFACT 54 | } 55 | 56 | @Autowired 57 | MetadataManager metadataManager; 58 | 59 | @Autowired 60 | KeyManager keyManager; 61 | 62 | @RequestMapping 63 | public ModelAndView metadataList() throws MetadataProviderException { 64 | 65 | ModelAndView model = new ModelAndView(new InternalResourceView("/WEB-INF/security/metadataList.jsp", true)); 66 | 67 | model.addObject("hostedSP", metadataManager.getHostedSPName()); 68 | model.addObject("spList", metadataManager.getSPEntityNames()); 69 | model.addObject("idpList", metadataManager.getIDPEntityNames()); 70 | model.addObject("metadata", metadataManager.getAvailableProviders()); 71 | 72 | return model; 73 | 74 | } 75 | 76 | @RequestMapping(value = "/login") 77 | public ModelAndView adminLogin() { 78 | 79 | ModelAndView model = new ModelAndView(new InternalResourceView("/WEB-INF/security/adminLogin.jsp", true)); 80 | return model; 81 | 82 | } 83 | 84 | @RequestMapping(value = "/refresh") 85 | public ModelAndView refreshMetadata() throws MetadataProviderException { 86 | 87 | metadataManager.refreshMetadata(); 88 | return metadataList(); 89 | 90 | } 91 | 92 | @RequestMapping(value = "/provider") 93 | public ModelAndView displayProvider(@RequestParam("providerIndex") int providerIndex) { 94 | 95 | ModelAndView model = new ModelAndView(new InternalResourceView("/WEB-INF/security/providerView.jsp", true)); 96 | ExtendedMetadataDelegate delegate = metadataManager.getAvailableProviders().get(providerIndex); 97 | model.addObject("provider", delegate); 98 | model.addObject("providerIndex", providerIndex); 99 | return model; 100 | 101 | } 102 | 103 | @RequestMapping(value = "/removeProvider") 104 | public ModelAndView removeProvider(@RequestParam int providerIndex) throws MetadataProviderException { 105 | 106 | ExtendedMetadataDelegate delegate = metadataManager.getAvailableProviders().get(providerIndex); 107 | metadataManager.removeMetadataProvider(delegate); 108 | return metadataList(); 109 | 110 | } 111 | 112 | @RequestMapping(value = "/generate") 113 | public ModelAndView generateMetadata(HttpServletRequest request) throws KeyStoreException { 114 | 115 | ModelAndView model = new ModelAndView(new InternalResourceView("/WEB-INF/security/metadataGenerator.jsp", true)); 116 | MetadataForm defaultForm = new MetadataForm(); 117 | 118 | model.addObject("availableKeys", getAvailablePrivateKeys()); 119 | defaultForm.setBaseURL(getBaseURL(request)); 120 | defaultForm.setEntityId(getEntityId(request)); 121 | defaultForm.setNameID(MetadataGenerator.defaultNameID.toArray(new String[MetadataGenerator.defaultNameID.size()])); 122 | 123 | model.addObject("metadata", defaultForm); 124 | return model; 125 | 126 | } 127 | 128 | @RequestMapping(value = "/create") 129 | public ModelAndView createMetadata(@ModelAttribute("metadata") MetadataForm metadata, BindingResult bindingResult) throws MetadataProviderException, MarshallingException, KeyStoreException { 130 | 131 | new MetadataValidator(metadataManager).validate(metadata, bindingResult); 132 | 133 | if (bindingResult.hasErrors()) { 134 | ModelAndView modelAndView = new ModelAndView(new InternalResourceView("/WEB-INF/security/metadataGenerator.jsp", true)); 135 | modelAndView.addObject("availableKeys", getAvailablePrivateKeys()); 136 | return modelAndView; 137 | } 138 | 139 | ExtendedMetadata extendedMetadata = new ExtendedMetadata(); 140 | MetadataGenerator generator = new MetadataGenerator(); 141 | generator.setKeyManager(keyManager); 142 | generator.setExtendedMetadata(extendedMetadata); 143 | 144 | // Basic metadata properties 145 | generator.setEntityId(metadata.getEntityId()); 146 | generator.setEntityBaseURL(metadata.getBaseURL()); 147 | generator.setRequestSigned(metadata.isRequestSigned()); 148 | generator.setWantAssertionSigned(metadata.isWantAssertionSigned()); 149 | 150 | Collection bindingsSSO = new LinkedList(); 151 | Collection bindingsHoKSSO = new LinkedList(); 152 | String defaultBinding = metadata.getSsoDefaultBinding(); 153 | int assertionConsumerIndex = 0; 154 | 155 | // Set default and included bindings 156 | for (String binding : metadata.getSsoBindings()) { 157 | if (binding.equalsIgnoreCase(defaultBinding)) { 158 | assertionConsumerIndex = bindingsSSO.size() + bindingsHoKSSO.size(); 159 | } 160 | if (AllowedSSOBindings.SSO_POST.toString().equalsIgnoreCase(binding)) { 161 | bindingsSSO.add(SAMLConstants.SAML2_POST_BINDING_URI); 162 | } else if (AllowedSSOBindings.SSO_ARTIFACT.toString().equalsIgnoreCase(binding)) { 163 | bindingsSSO.add(SAMLConstants.SAML2_ARTIFACT_BINDING_URI); 164 | } else if (AllowedSSOBindings.SSO_PAOS.toString().equalsIgnoreCase(binding)) { 165 | bindingsSSO.add(SAMLConstants.SAML2_PAOS_BINDING_URI); 166 | } else if (AllowedSSOBindings.HOKSSO_POST.toString().equalsIgnoreCase(binding)) { 167 | bindingsHoKSSO.add(SAMLConstants.SAML2_POST_BINDING_URI); 168 | } else if (AllowedSSOBindings.HOKSSO_ARTIFACT.toString().equalsIgnoreCase(binding)) { 169 | bindingsHoKSSO.add(SAMLConstants.SAML2_ARTIFACT_BINDING_URI); 170 | } 171 | } 172 | 173 | // Set bindings 174 | generator.setBindingsSSO(bindingsSSO); 175 | generator.setBindingsHoKSSO(bindingsHoKSSO); 176 | generator.setAssertionConsumerIndex(assertionConsumerIndex); 177 | 178 | // Name IDs 179 | generator.setNameID(Arrays.asList(metadata.getNameID())); 180 | 181 | // Keys 182 | extendedMetadata.setSigningKey(metadata.getSigningKey()); 183 | extendedMetadata.setEncryptionKey(metadata.getEncryptionKey()); 184 | if (hasLength(metadata.getTlsKey())) { 185 | extendedMetadata.setTlsKey(metadata.getTlsKey()); 186 | } 187 | 188 | // Discovery 189 | if (metadata.isIncludeDiscovery()) { 190 | extendedMetadata.setIdpDiscoveryEnabled(true); 191 | generator.setIncludeDiscoveryExtension(metadata.isIncludeDiscoveryExtension()); 192 | if (metadata.getCustomDiscoveryURL() != null && metadata.getCustomDiscoveryURL().length() > 0) { 193 | extendedMetadata.setIdpDiscoveryURL(metadata.getCustomDiscoveryURL()); 194 | } 195 | if (metadata.getCustomDiscoveryResponseURL() != null && metadata.getCustomDiscoveryResponseURL().length() > 0) { 196 | extendedMetadata.setIdpDiscoveryResponseURL(metadata.getCustomDiscoveryResponseURL()); 197 | } 198 | } else { 199 | extendedMetadata.setIdpDiscoveryEnabled(false); 200 | generator.setIncludeDiscoveryExtension(false); 201 | } 202 | 203 | // Alias 204 | if (hasLength(metadata.getAlias())) { 205 | extendedMetadata.setAlias(metadata.getAlias()); 206 | } 207 | 208 | // Security settings 209 | extendedMetadata.setSecurityProfile(metadata.getSecurityProfile()); 210 | extendedMetadata.setSslSecurityProfile(metadata.getSslSecurityProfile()); 211 | extendedMetadata.setRequireLogoutRequestSigned(metadata.isRequireLogoutRequestSigned()); 212 | extendedMetadata.setRequireLogoutResponseSigned(metadata.isRequireLogoutResponseSigned()); 213 | extendedMetadata.setRequireArtifactResolveSigned(metadata.isRequireArtifactResolveSigned()); 214 | extendedMetadata.setSslHostnameVerification(metadata.getSslHostnameVerification()); 215 | 216 | // Metadata signing 217 | extendedMetadata.setSignMetadata(metadata.isSignMetadata()); 218 | if (hasLength(metadata.getSigningAlgorithm())) { 219 | extendedMetadata.setSigningAlgorithm(metadata.getSigningAlgorithm()); 220 | } 221 | 222 | // Generate values 223 | EntityDescriptor generatedDescriptor = generator.generateMetadata(); 224 | ExtendedMetadata generatedExtendedMetadata = generator.generateExtendedMetadata(); 225 | 226 | if (metadata.isStore()) { 227 | 228 | MetadataMemoryProvider memoryProvider = new MetadataMemoryProvider(generatedDescriptor); 229 | memoryProvider.initialize(); 230 | MetadataProvider metadataProvider = new ExtendedMetadataDelegate(memoryProvider, generatedExtendedMetadata); 231 | metadataManager.addMetadataProvider(metadataProvider); 232 | metadataManager.setHostedSPName(generatedDescriptor.getEntityID()); 233 | metadataManager.setRefreshRequired(true); 234 | metadataManager.refreshMetadata(); 235 | 236 | } 237 | 238 | return displayMetadata(generatedDescriptor, generatedExtendedMetadata); 239 | 240 | } 241 | 242 | /** 243 | * Displays stored metadata. 244 | * 245 | * @param entityId entity ID of metadata to display 246 | * @return model and view 247 | * @throws MetadataProviderException in case metadata can't be located 248 | * @throws MarshallingException in case de-serialization into string fails 249 | */ 250 | @RequestMapping(value = "/display") 251 | public ModelAndView displayMetadata(@RequestParam("entityId") String entityId) throws MetadataProviderException, MarshallingException { 252 | 253 | EntityDescriptor entityDescriptor = metadataManager.getEntityDescriptor(entityId); 254 | ExtendedMetadata extendedMetadata = metadataManager.getExtendedMetadata(entityId); 255 | 256 | if (entityDescriptor == null) { 257 | throw new MetadataProviderException("Metadata with ID " + entityId + " not found"); 258 | } 259 | 260 | return displayMetadata(entityDescriptor, extendedMetadata); 261 | 262 | } 263 | 264 | protected ModelAndView displayMetadata(EntityDescriptor entityDescriptor, ExtendedMetadata extendedMetadata) throws MarshallingException { 265 | 266 | MetadataForm metadata = new MetadataForm(); 267 | String fileName = getFileName(entityDescriptor); 268 | 269 | metadata.setLocal(extendedMetadata.isLocal()); 270 | metadata.setSecurityProfile(extendedMetadata.getSecurityProfile()); 271 | metadata.setSslSecurityProfile(extendedMetadata.getSslSecurityProfile()); 272 | metadata.setSerializedMetadata(getMetadataAsString(entityDescriptor, extendedMetadata)); 273 | metadata.setConfiguration(getConfiguration(fileName, extendedMetadata)); 274 | metadata.setEntityId(entityDescriptor.getEntityID()); 275 | metadata.setAlias(extendedMetadata.getAlias()); 276 | metadata.setRequireArtifactResolveSigned(extendedMetadata.isRequireArtifactResolveSigned()); 277 | metadata.setRequireLogoutRequestSigned(extendedMetadata.isRequireLogoutRequestSigned()); 278 | metadata.setRequireLogoutResponseSigned(extendedMetadata.isRequireLogoutResponseSigned()); 279 | metadata.setEncryptionKey(extendedMetadata.getEncryptionKey()); 280 | metadata.setSigningKey(extendedMetadata.getSigningKey()); 281 | metadata.setTlsKey(extendedMetadata.getTlsKey()); 282 | metadata.setSslHostnameVerification(extendedMetadata.getSslHostnameVerification()); 283 | 284 | metadata.setSignMetadata(extendedMetadata.isSignMetadata()); 285 | metadata.setSigningAlgorithm(extendedMetadata.getSigningAlgorithm()); 286 | 287 | metadata.setIncludeDiscovery(extendedMetadata.isIdpDiscoveryEnabled()); 288 | metadata.setCustomDiscoveryURL(extendedMetadata.getIdpDiscoveryResponseURL()); 289 | metadata.setCustomDiscoveryResponseURL(extendedMetadata.getIdpDiscoveryURL()); 290 | 291 | // TODO other fields nameIDs 292 | 293 | ModelAndView model = new ModelAndView(new InternalResourceView("/WEB-INF/security/metadataView.jsp", true)); 294 | model.addObject("metadata", metadata); 295 | model.addObject("storagePath", fileName); 296 | 297 | return model; 298 | 299 | } 300 | 301 | protected String getMetadataAsString(EntityDescriptor descriptor, ExtendedMetadata extendedMetadata) throws MarshallingException { 302 | return SAMLUtil.getMetadataAsString(metadataManager, keyManager, descriptor, extendedMetadata); 303 | } 304 | 305 | protected String getBaseURL(HttpServletRequest request) { 306 | 307 | String baseURL = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath(); 308 | log.debug("Base URL {}", baseURL); 309 | return baseURL; 310 | 311 | } 312 | 313 | protected String getEntityId(HttpServletRequest request) { 314 | log.debug("Server name used as entity id {}", request.getServerName()); 315 | return request.getServerName(); 316 | } 317 | 318 | protected Map getAvailablePrivateKeys() throws KeyStoreException { 319 | Map availableKeys = new HashMap(); 320 | Set aliases = keyManager.getAvailableCredentials(); 321 | for (String key : aliases) { 322 | try { 323 | log.debug("Found key {}", key); 324 | Credential credential = keyManager.getCredential(key); 325 | if (credential.getPrivateKey() != null) { 326 | log.debug("Adding private key with alias {} and entityID {}", key, credential.getEntityId()); 327 | availableKeys.put(key, key + " (" + credential.getEntityId() + ")"); 328 | } 329 | } catch (Exception e) { 330 | log.debug("Error loading key", e); 331 | } 332 | } 333 | return availableKeys; 334 | } 335 | 336 | protected String getFileName(EntityDescriptor entityDescriptor) { 337 | StringBuilder fileName = new StringBuilder(); 338 | for (Character c : entityDescriptor.getEntityID().toCharArray()) { 339 | if (Character.isJavaIdentifierPart(c)) { 340 | fileName.append(c); 341 | } 342 | } 343 | if (fileName.length() > 0) { 344 | fileName.append("_sp.xml"); 345 | return fileName.toString(); 346 | } else { 347 | return "default_sp.xml"; 348 | } 349 | } 350 | 351 | protected String getConfiguration(String fileName, ExtendedMetadata metadata) { 352 | StringBuilder sb = new StringBuilder(); 353 | sb.append("\n" + 354 | " \n" + 355 | " \n" + 356 | " \n" + 357 | " \n" + 358 | " \n" + 359 | " \n" + 360 | " \n" + 361 | " \n" + 362 | " \n" + 363 | " \n" + 364 | " \n" + 365 | " \n" + 366 | " \n" + 367 | " \n" + 368 | " \n" + 369 | " \n"); 370 | if (metadata.getAlias() != null) { 371 | sb.append(" \n"); 372 | } 373 | sb.append(" \n" + 374 | " \n" + 375 | " \n" + 376 | " \n" + 377 | " \n" + 378 | " \n"); 379 | if (metadata.getTlsKey() != null) { 380 | sb.append(" \n"); 381 | } 382 | if (metadata.getSigningAlgorithm() != null) { 383 | sb.append(" \n"); 384 | } 385 | sb.append(" \n" + 386 | " \n" + 387 | " \n"); 388 | sb.append(" \n"); 389 | if (metadata.isIdpDiscoveryEnabled()) { 390 | sb.append(" \n" + 391 | " \n"); 392 | } 393 | sb.append(" \n" + 394 | " \n" + 395 | ""); 396 | return sb.toString(); 397 | } 398 | 399 | @ModelAttribute(value = "tab") 400 | public String getTabName() { 401 | return "metadata"; 402 | } 403 | 404 | } -------------------------------------------------------------------------------- /src/main/java/org/springframework/security/saml/web/MetadataForm.java: -------------------------------------------------------------------------------- 1 | /* Copyright 2011 Vladimir Schafer 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | package org.springframework.security.saml.web; 16 | 17 | /** 18 | * Form able to store UI data related to metadata. 19 | */ 20 | public class MetadataForm { 21 | 22 | private boolean store; 23 | private String entityId; 24 | private String securityProfile; 25 | private String sslSecurityProfile; 26 | private String sslHostnameVerification; 27 | private String baseURL; 28 | private String alias; 29 | private boolean signMetadata; 30 | private String serializedMetadata; 31 | private String configuration; 32 | private String signingAlgorithm; 33 | private String[] nameID; 34 | 35 | private String[] ssoBindings = new String[] { 36 | MetadataController.AllowedSSOBindings.SSO_POST.toString(), 37 | MetadataController.AllowedSSOBindings.SSO_ARTIFACT.toString() 38 | }; 39 | private String ssoDefaultBinding = MetadataController.AllowedSSOBindings.SSO_POST.toString(); 40 | 41 | private String signingKey; 42 | private String encryptionKey; 43 | private String tlsKey; 44 | 45 | private boolean local; 46 | 47 | private boolean includeDiscovery = true; 48 | private boolean includeDiscoveryExtension = false; 49 | 50 | private String customDiscoveryURL; 51 | private String customDiscoveryResponseURL; 52 | 53 | private boolean requestSigned = true; 54 | private boolean wantAssertionSigned; 55 | private boolean requireLogoutRequestSigned; 56 | private boolean requireLogoutResponseSigned; 57 | private boolean requireArtifactResolveSigned; 58 | 59 | public MetadataForm() { 60 | } 61 | 62 | public String getEntityId() { 63 | return entityId; 64 | } 65 | 66 | public void setEntityId(String entityId) { 67 | this.entityId = entityId; 68 | } 69 | 70 | public String getAlias() { 71 | return alias; 72 | } 73 | 74 | public void setAlias(String alias) { 75 | this.alias = alias; 76 | } 77 | 78 | public boolean isSignMetadata() { 79 | return signMetadata; 80 | } 81 | 82 | public void setSignMetadata(boolean signMetadata) { 83 | this.signMetadata = signMetadata; 84 | } 85 | 86 | public boolean isRequestSigned() { 87 | return requestSigned; 88 | } 89 | 90 | public void setRequestSigned(boolean requestSigned) { 91 | this.requestSigned = requestSigned; 92 | } 93 | 94 | public boolean isWantAssertionSigned() { 95 | return wantAssertionSigned; 96 | } 97 | 98 | public void setWantAssertionSigned(boolean wantAssertionSigned) { 99 | this.wantAssertionSigned = wantAssertionSigned; 100 | } 101 | 102 | public boolean isRequireLogoutRequestSigned() { 103 | return requireLogoutRequestSigned; 104 | } 105 | 106 | public void setRequireLogoutRequestSigned(boolean requireLogoutRequestSigned) { 107 | this.requireLogoutRequestSigned = requireLogoutRequestSigned; 108 | } 109 | 110 | public boolean isRequireLogoutResponseSigned() { 111 | return requireLogoutResponseSigned; 112 | } 113 | 114 | public void setRequireLogoutResponseSigned(boolean requireLogoutResponseSigned) { 115 | this.requireLogoutResponseSigned = requireLogoutResponseSigned; 116 | } 117 | 118 | public boolean isRequireArtifactResolveSigned() { 119 | return requireArtifactResolveSigned; 120 | } 121 | 122 | public void setRequireArtifactResolveSigned(boolean requireArtifactResolveSigned) { 123 | this.requireArtifactResolveSigned = requireArtifactResolveSigned; 124 | } 125 | 126 | public boolean isStore() { 127 | return store; 128 | } 129 | 130 | public void setStore(boolean store) { 131 | this.store = store; 132 | } 133 | 134 | public String getSerializedMetadata() { 135 | return serializedMetadata; 136 | } 137 | 138 | public void setSerializedMetadata(String serializedMetadata) { 139 | this.serializedMetadata = serializedMetadata; 140 | } 141 | 142 | public String getSigningKey() { 143 | return signingKey; 144 | } 145 | 146 | public void setSigningKey(String signingKey) { 147 | this.signingKey = signingKey; 148 | } 149 | 150 | public String getEncryptionKey() { 151 | return encryptionKey; 152 | } 153 | 154 | public void setEncryptionKey(String encryptionKey) { 155 | this.encryptionKey = encryptionKey; 156 | } 157 | 158 | public String getBaseURL() { 159 | return baseURL; 160 | } 161 | 162 | public void setBaseURL(String baseURL) { 163 | this.baseURL = baseURL; 164 | } 165 | 166 | public String getConfiguration() { 167 | return configuration; 168 | } 169 | 170 | public void setConfiguration(String configuration) { 171 | this.configuration = configuration; 172 | } 173 | 174 | public boolean isLocal() { 175 | return local; 176 | } 177 | 178 | public void setLocal(boolean local) { 179 | this.local = local; 180 | } 181 | 182 | public String getSecurityProfile() { 183 | return securityProfile; 184 | } 185 | 186 | public void setSecurityProfile(String securityProfile) { 187 | this.securityProfile = securityProfile; 188 | } 189 | 190 | public String getSslSecurityProfile() { 191 | return sslSecurityProfile; 192 | } 193 | 194 | public void setSslSecurityProfile(String sslSecurityProfile) { 195 | this.sslSecurityProfile = sslSecurityProfile; 196 | } 197 | 198 | public String getTlsKey() { 199 | return tlsKey; 200 | } 201 | 202 | public void setTlsKey(String tlsKey) { 203 | this.tlsKey = tlsKey; 204 | } 205 | 206 | public boolean isIncludeDiscovery() { 207 | return includeDiscovery; 208 | } 209 | 210 | public void setIncludeDiscovery(boolean includeDiscovery) { 211 | this.includeDiscovery = includeDiscovery; 212 | } 213 | 214 | public boolean isIncludeDiscoveryExtension() { 215 | return includeDiscoveryExtension; 216 | } 217 | 218 | public void setIncludeDiscoveryExtension(boolean includeDiscoveryExtension) { 219 | this.includeDiscoveryExtension = includeDiscoveryExtension; 220 | } 221 | 222 | public String[] getNameID() { 223 | return nameID; 224 | } 225 | 226 | public void setNameID(String[] nameID) { 227 | this.nameID = nameID; 228 | } 229 | 230 | public String getCustomDiscoveryURL() { 231 | return customDiscoveryURL; 232 | } 233 | 234 | public void setCustomDiscoveryURL(String customDiscoveryURL) { 235 | this.customDiscoveryURL = customDiscoveryURL; 236 | } 237 | 238 | public String getCustomDiscoveryResponseURL() { 239 | return customDiscoveryResponseURL; 240 | } 241 | 242 | public void setCustomDiscoveryResponseURL(String customDiscoveryResponseURL) { 243 | this.customDiscoveryResponseURL = customDiscoveryResponseURL; 244 | } 245 | 246 | public String[] getSsoBindings() { 247 | return ssoBindings; 248 | } 249 | 250 | public void setSsoBindings(String[] ssoBindings) { 251 | this.ssoBindings = ssoBindings; 252 | } 253 | 254 | public String getSsoDefaultBinding() { 255 | return ssoDefaultBinding; 256 | } 257 | 258 | public void setSsoDefaultBinding(String ssoDefaultBinding) { 259 | this.ssoDefaultBinding = ssoDefaultBinding; 260 | } 261 | 262 | public String getSslHostnameVerification() { 263 | return sslHostnameVerification; 264 | } 265 | 266 | public void setSslHostnameVerification(String sslHostnameVerification) { 267 | this.sslHostnameVerification = sslHostnameVerification; 268 | } 269 | 270 | public String getSigningAlgorithm() { 271 | return signingAlgorithm; 272 | } 273 | 274 | public void setSigningAlgorithm(String signingAlgorithm) { 275 | this.signingAlgorithm = signingAlgorithm; 276 | } 277 | 278 | } 279 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/security/saml/web/MetadataValidator.java: -------------------------------------------------------------------------------- 1 | /* Copyright 2011 Vladimir Schafer 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | package org.springframework.security.saml.web; 16 | 17 | import org.opensaml.saml2.metadata.EntityDescriptor; 18 | import org.opensaml.saml2.metadata.provider.MetadataProviderException; 19 | import org.springframework.security.saml.metadata.MetadataManager; 20 | import org.springframework.validation.Errors; 21 | import org.springframework.validation.ValidationUtils; 22 | import org.springframework.validation.Validator; 23 | 24 | import java.net.MalformedURLException; 25 | import java.net.URL; 26 | 27 | /** 28 | * Validator for metadata from. 29 | */ 30 | public class MetadataValidator implements Validator { 31 | 32 | MetadataManager manager; 33 | 34 | public MetadataValidator(MetadataManager manager) { 35 | this.manager = manager; 36 | } 37 | 38 | public boolean supports(Class clazz) { 39 | return clazz.equals(MetadataForm.class); 40 | } 41 | 42 | public void validate(Object target, Errors errors) { 43 | 44 | MetadataForm metadata = (MetadataForm) target; 45 | 46 | ValidationUtils.rejectIfEmptyOrWhitespace(errors, "entityId", "required", "Entity id must be set."); 47 | ValidationUtils.rejectIfEmptyOrWhitespace(errors, "baseURL", "required", "Base URL is required."); 48 | 49 | if (metadata.getSecurityProfile() == null) { 50 | errors.rejectValue("securityProfile", null, "Security profile must be specified."); 51 | } else if (!"pkix".equalsIgnoreCase(metadata.getSecurityProfile()) && !"metaiop".equals(metadata.getSecurityProfile())) { 52 | errors.rejectValue("securityProfile", null, "Selected value is not supported."); 53 | } 54 | 55 | if (metadata.getSslSecurityProfile() == null) { 56 | errors.rejectValue("sslSecurityProfile", null, "SSL/TLS Security profile must be specified."); 57 | } else if (!"pkix".equalsIgnoreCase(metadata.getSslSecurityProfile()) && !"metaiop".equals(metadata.getSslSecurityProfile())) { 58 | errors.rejectValue("sslSecurityProfile", null, "Selected value is not supported."); 59 | } 60 | 61 | if (metadata.isIncludeDiscovery() && metadata.getCustomDiscoveryURL() != null && metadata.getCustomDiscoveryURL().length() > 0) { 62 | try { 63 | new URL(metadata.getCustomDiscoveryURL()); 64 | } catch (MalformedURLException e) { 65 | errors.rejectValue("customDiscoveryURL", null, "Value is not a valid URL."); 66 | } 67 | } 68 | 69 | if (metadata.isIncludeDiscovery() && metadata.getCustomDiscoveryResponseURL() != null && metadata.getCustomDiscoveryResponseURL().length() > 0) { 70 | try { 71 | new URL(metadata.getCustomDiscoveryResponseURL()); 72 | } catch (MalformedURLException e) { 73 | errors.rejectValue("customDiscoveryResponseURL", null, "Value is not a valid URL."); 74 | } 75 | } 76 | 77 | // Bindings 78 | if (metadata.getSsoBindings() == null || metadata.getSsoBindings().length == 0) { 79 | errors.rejectValue("ssoBindings", null, "At least one binding must be specified."); 80 | } 81 | 82 | // Default binding 83 | if (metadata.getSsoDefaultBinding() != null && metadata.getSsoBindings() != null) { 84 | boolean found = false; 85 | for (String binding : metadata.getSsoBindings()) { 86 | if (binding.equals(metadata.getSsoDefaultBinding())) { 87 | found = true; 88 | break; 89 | } 90 | } 91 | if (!found) { 92 | errors.rejectValue("ssoDefaultBinding", null, "Default binding must be selected as included."); 93 | } 94 | } 95 | 96 | if (metadata.getNameID() == null || metadata.getNameID().length == 0) { 97 | errors.rejectValue("nameID", null, "At least one NameID must be selected."); 98 | } 99 | 100 | try { 101 | if (!errors.hasErrors() && metadata.isStore()) { 102 | EntityDescriptor entityDescriptor = manager.getEntityDescriptor(metadata.getEntityId()); 103 | if (entityDescriptor != null) { 104 | errors.rejectValue("entityId", null, "Selected entity ID is already used."); 105 | } 106 | String idForAlias = manager.getEntityIdForAlias(metadata.getAlias()); 107 | if (idForAlias != null) { 108 | errors.rejectValue("alias", null, "Selected alias is already used."); 109 | } 110 | } 111 | } catch (MetadataProviderException e) { 112 | throw new RuntimeException("Error loading alias data", e); 113 | } 114 | 115 | } 116 | 117 | } -------------------------------------------------------------------------------- /src/main/java/org/springframework/security/saml/web/StatusServlet.java: -------------------------------------------------------------------------------- 1 | package org.springframework.security.saml.web; 2 | 3 | import org.springframework.http.HttpStatus; 4 | 5 | import javax.servlet.GenericServlet; 6 | import javax.servlet.ServletRequest; 7 | import javax.servlet.ServletResponse; 8 | import javax.servlet.http.HttpServletResponse; 9 | 10 | /** 11 | * This is {@link StatusServlet}. 12 | * 13 | * @author Misagh Moayyed 14 | * @since 6.4.0 15 | */ 16 | public class StatusServlet extends GenericServlet { 17 | @Override 18 | public void service(final ServletRequest req, final ServletResponse res) { 19 | try { 20 | HttpServletResponse response = HttpServletResponse.class.cast(res); 21 | response.setStatus(HttpStatus.OK.value()); 22 | response.getWriter().println("OK"); 23 | } catch (final Exception e) { 24 | e.printStackTrace(); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootCategory=INFO, CONSOLE 2 | 3 | # CONSOLE is set to be a ConsoleAppender using a PatternLayout. 4 | log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender 5 | log4j.appender.CONSOLE.Threshold=INFO 6 | log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout 7 | log4j.appender.CONSOLE.layout.ConversionPattern=- %m%n 8 | 9 | # Logging of Spring Security extension 10 | log4j.logger.org.springframework.security.saml=DEBUG 11 | log4j.logger.org.springframework.security=INFO 12 | log4j.logger.org.springframework.web=INFO 13 | 14 | # Logging of SAML messages, set to FINEST to enable 15 | log4j.logger.PROTOCOL_MESSAGE=FINEST 16 | 17 | # Logging of OpenSAML library 18 | log4j.logger.org.opensaml=FINEST 19 | 20 | # Logging for ClassLoader Leak Prevention 21 | log4j.logger.se.jiderhamn.classloader.leak.prevention=ERROR 22 | -------------------------------------------------------------------------------- /src/main/resources/metadata/sp-metadata.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 8 | 9 | MIIDUjCCAjqgAwIBAgIEUOLIQTANBgkqhkiG9w0BAQUFADBrMQswCQYDVQQGEwJGSTEQMA4GA1UE CBMHVXVzaW1hYTERMA8GA1UEBxMISGVsc2lua2kxGDAWBgNVBAoTD1JNNSBTb2Z0d2FyZSBPeTEM MAoGA1UECwwDUiZEMQ8wDQYDVQQDEwZhcG9sbG8wHhcNMTMwMTAxMTEyODAxWhcNMjIxMjMwMTEy ODAxWjBrMQswCQYDVQQGEwJGSTEQMA4GA1UECBMHVXVzaW1hYTERMA8GA1UEBxMISGVsc2lua2kx GDAWBgNVBAoTD1JNNSBTb2Z0d2FyZSBPeTEMMAoGA1UECwwDUiZEMQ8wDQYDVQQDEwZhcG9sbG8w ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCXqP0wqL2Ai1haeTj0alwsLafhrDtUt00E 5xc7kdD7PISRA270ZmpYMB4W24Uk2QkuwaBp6dI/yRdUvPfOT45YZrqIxMe2451PAQWtEKWF5Z13 F0J4/lB71TtrzyH94RnqSHXFfvRN8EY/rzuEzrpZrHdtNs9LRyLqcRTXMMO4z7QghBuxh3K5gu7K qxpHx6No83WNZj4B3gvWLRWv05nbXh/F9YMeQClTX1iBNAhLQxWhwXMKB4u1iPQ/KSaal3R26pON UUmu1qVtU1quQozSTPD8HvsDqGG19v2+/N3uf5dRYtvEPfwXN3wIY+/R93vBA6lnl5nTctZIRsyg 0Gv5AgMBAAEwDQYJKoZIhvcNAQEFBQADggEBAFQwAAYUjso1VwjDc2kypK/RRcB8bMAUUIG0hLGL 82IvnKouGixGqAcULwQKIvTs6uGmlgbSG6Gn5ROb2mlBztXqQ49zRvi5qWNRttir6eyqwRFGOM6A 8rxj3Jhxi2Vb/MJn7XzeVHHLzA1sV5hwl/2PLnaL2h9WyG9QwBbwtmkMEqUt/dgixKb1Rvby/tBu RogWgPONNSACiW+Z5o8UdAOqNMZQozD/i1gOjBXoF0F5OksjQN7xoQZLj9xXefxCFQ69FPcFDeEW bHwSoBy5hLPNALaEUoa5zPDwlixwRjFQTc5XXaRpgIjy/2gsL8+Y5QRhyXnLqgO67BlLYW/GuHE= 10 | 11 | 12 | 13 | 14 | 16 | 17 | MIIDUjCCAjqgAwIBAgIEUOLIQTANBgkqhkiG9w0BAQUFADBrMQswCQYDVQQGEwJGSTEQMA4GA1UE CBMHVXVzaW1hYTERMA8GA1UEBxMISGVsc2lua2kxGDAWBgNVBAoTD1JNNSBTb2Z0d2FyZSBPeTEM MAoGA1UECwwDUiZEMQ8wDQYDVQQDEwZhcG9sbG8wHhcNMTMwMTAxMTEyODAxWhcNMjIxMjMwMTEy ODAxWjBrMQswCQYDVQQGEwJGSTEQMA4GA1UECBMHVXVzaW1hYTERMA8GA1UEBxMISGVsc2lua2kx GDAWBgNVBAoTD1JNNSBTb2Z0d2FyZSBPeTEMMAoGA1UECwwDUiZEMQ8wDQYDVQQDEwZhcG9sbG8w ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCXqP0wqL2Ai1haeTj0alwsLafhrDtUt00E 5xc7kdD7PISRA270ZmpYMB4W24Uk2QkuwaBp6dI/yRdUvPfOT45YZrqIxMe2451PAQWtEKWF5Z13 F0J4/lB71TtrzyH94RnqSHXFfvRN8EY/rzuEzrpZrHdtNs9LRyLqcRTXMMO4z7QghBuxh3K5gu7K qxpHx6No83WNZj4B3gvWLRWv05nbXh/F9YMeQClTX1iBNAhLQxWhwXMKB4u1iPQ/KSaal3R26pON UUmu1qVtU1quQozSTPD8HvsDqGG19v2+/N3uf5dRYtvEPfwXN3wIY+/R93vBA6lnl5nTctZIRsyg 0Gv5AgMBAAEwDQYJKoZIhvcNAQEFBQADggEBAFQwAAYUjso1VwjDc2kypK/RRcB8bMAUUIG0hLGL 82IvnKouGixGqAcULwQKIvTs6uGmlgbSG6Gn5ROb2mlBztXqQ49zRvi5qWNRttir6eyqwRFGOM6A 8rxj3Jhxi2Vb/MJn7XzeVHHLzA1sV5hwl/2PLnaL2h9WyG9QwBbwtmkMEqUt/dgixKb1Rvby/tBu RogWgPONNSACiW+Z5o8UdAOqNMZQozD/i1gOjBXoF0F5OksjQN7xoQZLj9xXefxCFQ69FPcFDeEW bHwSoBy5hLPNALaEUoa5zPDwlixwRjFQTc5XXaRpgIjy/2gsL8+Y5QRhyXnLqgO67BlLYW/GuHE= 18 | 19 | 20 | 21 | 22 | 23 | 24 | 27 | 28 | 29 | urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress 30 | urn:oasis:names:tc:SAML:2.0:nameid-format:transient 31 | urn:oasis:names:tc:SAML:2.0:nameid-format:persistent 32 | urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified 33 | urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName 34 | 35 | 37 | 38 | 39 | 44 | 45 | 47 | 48 | 51 | Misagh 52 | Moayyed 53 | 54 | 58 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /src/main/resources/security/samlKeystore.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apereo/saml2-sample-java-webapp/75a1bc380c5a8f89ea0b369888b39b5a09cec2f9/src/main/resources/security/samlKeystore.jks -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/saml-servlet.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/security/adminLogin.jsp: -------------------------------------------------------------------------------- 1 | <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 2 | 3 | 4 | 5 | 6 |
7 | 8 |
9 |
10 |
11 |
12 |
13 |

Login to administration

14 |

Please login to the metadata administration using a local account (by default admin/admin).

15 |
16 | <% String errorString = (String) request.getAttribute("error"); %> 17 | <% if (errorString != null && errorString.trim().equals("true")) { %> 18 |
Incorrect login name or password. Please retry using correct login name and password.
19 | <% } %> 20 |

21 |

22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |
36 |
37 |
38 |
39 |
 
40 |
41 |
42 |
 
43 |
44 | 45 |
46 | 47 |
48 | 49 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/security/idpSelection.jsp: -------------------------------------------------------------------------------- 1 | <%@ page import="org.springframework.security.saml.metadata.MetadataManager" %> 2 | <%@ page import="org.springframework.web.context.WebApplicationContext" %> 3 | <%@ page import="org.springframework.web.context.support.WebApplicationContextUtils" %> 4 | <%@ page import="java.util.Set" %> 5 | <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 |
14 |
15 |
16 |
17 |

IDP Selection

18 |

Please select Identity Provider to authenticate with.

19 |
20 | <% 21 | WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(getServletConfig().getServletContext()); 22 | MetadataManager mm = context.getBean("metadata", MetadataManager.class); 23 | Set idps = mm.getIDPEntityNames(); 24 | pageContext.setAttribute("idp", idps); 25 | %> 26 |

27 |

" method="GET"> 28 | 29 | " value="" 31 | <% if (idps.size() == 1) { %> 32 | checked 33 | <% } else { %> 34 | <% } %> 35 | /> 36 | 37 |
38 |
39 |
40 | 41 |
42 |
43 |
44 |
 
45 |
46 |
47 |
 
48 |
49 | 50 |
51 | 52 |
53 | 54 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/security/metadataGenerator.jsp: -------------------------------------------------------------------------------- 1 | <%@ page import="org.springframework.security.saml.web.MetadataController" %> 2 | <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 3 | <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> 4 | 5 | 6 | 7 | 8 |
9 | 10 |
11 |
12 |
13 |
14 |
15 |

Metadata generation

16 |

Generates new metadata for service provider. Output can be used to configure your 17 | securityContext.xml descriptor.

18 |
19 |

"><< Back

20 | 21 | 22 | 23 | 24 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 47 | 48 | 49 | 50 | 55 | 56 | 57 | 58 | 62 | 63 | 64 | 65 | 70 | 71 | 72 | 73 | 77 | 78 | 79 | 80 | 85 | 86 | 87 | 88 | 92 | 93 | 94 | 95 | 98 | 99 | 100 | 101 | 105 | 106 | 107 | 108 | 111 | 112 | 113 | 114 | 121 | 122 | 123 | 124 | 145 | 146 | 147 | 148 | 155 | 156 | 157 | 158 | 178 | 179 | 180 | 181 | 191 | 192 | 193 | 194 | 198 | 199 | 200 | 201 | 208 | 209 | 210 | 211 | 214 | 215 | 216 | 217 | 224 | 225 | 226 | 227 | 231 | 232 | 233 | 234 | 238 | 239 | 240 | 241 | 247 | 248 | 249 | 250 | 257 | 258 | 259 | 260 | 267 | 268 | 269 | 270 | 277 | 278 | 279 | 280 | 287 | 288 | 289 | 290 | 297 | 298 | 299 | 300 | 347 | 348 | 349 | 350 | 380 | 381 | 382 | 383 | 390 | 391 | 392 | 393 | 401 | 402 | 403 | 404 | 408 | 409 | 410 | 411 | 414 | 415 | 416 | 417 | 424 | 425 | 426 | 430 | 431 |
25 | 26 | 27 | Yes 28 | No 29 | 30 |
35 | When set to true the generated metadata will be stored in the local metadata manager. The value 36 | will be available 37 | only until restart of the application server. 38 | 39 |
44 | 45 | 46 |
51 | Entity ID is a unique identifier for an identity or service provider. Value is included in the 52 | generated metadata. 53 | 54 |
59 | 60 | 61 |
66 | Base to generate URLs for this server. For example: https://myServer:443/saml-app. The public 67 | address your server will be accessed from should be used here. 68 | 69 |
74 | 75 | 76 |
81 | Alias is an internal mechanism allowing collocating multiple service providers on one server. 82 | When set, alias must be unique. 83 | 84 |
89 | 90 | 91 |
96 | Key used for digital signatures of SAML messages. Public key will be included in the metadata. 97 |
102 | 103 | 104 |
109 | Key used for digital encryption of SAML messages. Public key will be included in the metadata. 110 |
115 | 116 | 117 | MetaIOP 118 | PKIX 119 | 120 |
125 | 126 | Security profile determines how is trust of digital signatures handled: 127 |
    128 |
  • 129 | In MetaIOP mode certificate is 130 | deemed 131 | valid when it's declared in the metadata or extended metadata of the peer entity. No validation of 132 | the certificate is 133 | performed (e.g. revocation) and no certificate chains are evaluated. The value is recommended as a 134 | default. 135 |
  • 136 |
  • 137 | PKIX profile verifies credentials against a set of trust anchors. Certificates present in the 138 | metadata or extended metadata of the peer entity are treated as trust anchors, together with all 139 | keys in 140 | the keystore. Certificate chains are verified in this mode. 141 |
  • 142 |
143 |
144 |
149 | 150 | 151 | PKIX 152 | MetaIOP 153 | 154 |
159 | 160 | SSL/TLS Security profile determines how is trust of peer's SSL/TLS certificate (e.g. during Artifact 161 | resolution) handled: 162 |
    163 |
  • 164 | PKIX profile verifies peer's certificate against a set of trust anchors. All certificates defined in 165 | metadata, 166 | extended metadata or present in the keystore are considered as trusted anchors (certification 167 | authorities) 168 | for PKIX validation. 169 |
  • 170 |
  • 171 | In MetaIOP mode server's SSL/TLS certificate is trusted when it's explicitly declared in metadata or 172 | extended metadata of 173 | the peer. 174 |
  • 175 |
176 |
177 |
182 | 183 | 184 | Standard hostname verifier 185 | Standard hostname verifier (skips verification for localhost) 187 | Strict hostname verifier 188 | Disable hostname verification (allow all) 189 | 190 |
195 | Algorithm for verification of match between hostname in URL and hostname in the presented certificate. 196 | 197 |
202 | 203 | 204 | None 205 | 206 | 207 |
212 | Key used to authenticate this instance for SSL/TLS connections. 213 |
218 | 219 | 220 | Yes 221 | No 222 | 223 |
228 | If true the generated metadata will be digitally signed using the specified signature key. 229 | 230 |
235 | 236 | 237 |
242 | Algorithm used for creation of digital signature on metadata. Typical values are 243 | "http://www.w3.org/2000/09/xmldsig#rsa-sha1", 244 | "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" and "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512" 245 | 246 |
251 | 252 | 253 | Yes 254 | No 255 | 256 |
261 | 262 | 263 | Yes 264 | No 265 | 266 |
271 | 272 | 273 | Yes 274 | No 275 | 276 |
281 | 282 | 283 | Yes 284 | No 285 | 286 |
291 | 292 | 293 | Yes 294 | No 295 | 296 |
301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 312 | 314 | 315 | 316 | 317 | 319 | 321 | 322 | 323 | 324 | 326 | 328 | 329 | 330 | 331 | 333 | 336 | 337 | 338 | 339 | 341 | 343 | 344 | 345 |
DefaultIncludedName
346 |
351 | 352 | 353 | 354 | 356 | 357 | 358 | 359 | 361 | 362 | 363 | 364 | 366 | 367 | 368 | 369 | 371 | 372 | 373 | 374 | 376 | 377 | 378 |
379 |
384 | 385 | 386 | Yes 387 | No 388 | 389 |
394 | 395 | Discovery 396 | profile enables service provider to determine which identity provider should be used 397 | for a particular user. Spring Security SAML contains it's own discovery service which presents 398 | user with an IDP list to select from. 399 | 400 |
405 | 406 | 407 |
412 | When not set local IDP discovery URL is automatically generated when IDP discovery is enabled. 413 |
418 | 419 | 420 | Yes 421 | No 422 | 423 |
427 |
428 | 429 |
432 |
433 |

"><< Back

434 |
435 |
436 |
 
437 |
438 |
439 |
 
440 |
441 | 442 |
443 | 444 |
445 | 446 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/security/metadataList.jsp: -------------------------------------------------------------------------------- 1 | <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 2 | 3 | 4 | 5 | 6 |
7 | 8 |
9 |
10 |
11 |
12 |
13 |

Metadata administration

14 |

Overview of all configured metadata for local service providers and remote identity providers.

15 |
16 |

17 | Default local service provider:
18 | 19 | "> 20 | 21 | 22 | - 23 |
24 | Default service provider is available without selection of alias. 25 |

26 |

27 | Service providers:
28 | 29 | "> 30 |
31 |
32 | - 33 |

34 |

35 | Identity providers:
36 | 37 | "> 38 |
39 |
40 | - 41 |

42 |

43 | Metadata providers:
44 | 45 | "> 46 |
47 |
48 | - 49 |

50 |
51 |
" method="get"> 52 | 53 |
54 |
"> 55 | 56 |
57 |
58 |
59 |
60 |
 
61 |
62 |
63 |
 
64 |
65 | 66 |
67 | 68 |
69 | 70 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/security/metadataView.jsp: -------------------------------------------------------------------------------- 1 | <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 2 | <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> 3 | 4 | 5 | 6 | 7 |
8 | 9 |
10 |
11 |
12 |
13 |
14 |

Metadata detail

15 |

Detail of a single entity imported to Spring SAML's MetadataManager.

16 |
17 |

"><< Back

18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 47 | 48 | 49 | 50 | 55 | 56 | 57 | 65 | 66 | 67 |
Local entity:
Entity ID:
Entity alias:
Signing key:
Encryption key:
44 |
45 | 46 |
51 |
52 | 54 |
58 | In order to permanently store the metadata follow these instructions: 59 |
    60 |
  • Store metadata content inside your achive at /WEB-INF/classes/metadata/${storagePath}
  • 61 |
  • Make sure to update your identity provider(s) with the generated metadata
  • 62 |
  • Modify bean "metadata" in your securityContext.xml and include content from the configuration above
  • 63 |
64 |
68 |
69 | 70 | 71 |
" method="get"> 72 | 73 |
74 |
75 | 76 |
" method="get"> 77 | 78 |
79 |
80 |
81 |
82 |

"><< Back

83 |
84 |
85 |
 
86 |
87 |
88 |
 
89 |
90 | 91 |
92 | 93 |
94 | 95 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/security/providerView.jsp: -------------------------------------------------------------------------------- 1 | <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 2 | <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> 3 | 4 | 5 | 6 | 7 |
8 | 9 |
10 |
11 |
12 |
13 |
14 |

Metadata provider detail

15 |

Overview of a metadata provider which can include multiple SAML entities.

16 |
17 |

"><< Back

18 | Provider: 19 |

20 | 21 | "/> 22 | 23 | 24 |
25 |

"><< Back

26 |
27 |
28 |
 
29 |
30 |
31 |
 
32 |
33 | 34 |
35 | 36 |
37 | 38 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/securityContext.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 68 | 69 | 70 | 76 | 77 | 78 | 80 | 81 | 82 | 83 | 84 | 85 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | ${allowedIdP1:} 132 | ${allowedIdP2:} 133 | ${allowedIdP3:} 134 | 135 | 136 | 137 | 138 | ${authnContext:} 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/sp.properties: -------------------------------------------------------------------------------- 1 | host=https://localhost:9876 2 | 3 | idpMetadataType=idpMetadataFile 4 | idpMetadata=/etc/cas/config/saml/idp-metadata.xml 5 | 6 | metadataType=metadataClasspath 7 | metadataLocation=/metadata/sp-metadata.xml 8 | 9 | # includeScoping=false 10 | relayState=saml-sp-relay-state 11 | # nameIdFormat= 12 | # allowedIdP1=http://www.okta.com/exk9xexqanqWBEjsg0h7 13 | # acsUrl=https://httpbin.org/post 14 | # signAuthnRequests=false 15 | # forcedAuthn=true 16 | # authnRequestBindingOptional=true 17 | # authnContext=https://refeds.org/profile/mfa 18 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/templates/footer.jsp: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/templates/head.jsp: -------------------------------------------------------------------------------- 1 | <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 2 | 3 | 4 | 5 | 6 | 7 | "/> 8 | " media="screen" /> 9 | Spring SAML Sample application 10 | 11 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/templates/navigation.jsp: -------------------------------------------------------------------------------- 1 | <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 2 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/templates/sidebar.jsp: -------------------------------------------------------------------------------- 1 | 32 |
 
-------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | Spring Security SAML 8 | Sample application demonstrating Spring security SAML integration. 9 | 10 | 11 | contextConfigLocation 12 | 13 | /WEB-INF/securityContext.xml 14 | 15 | 16 | 17 | 18 | ClassLoaderLeakPreventor.threadWaitMs 19 | 100 20 | 21 | 22 | 23 | ClassLoaderLeakPreventor.shutdownHookWaitMs 24 | 200 25 | 26 | 27 | 28 | status 29 | org.springframework.security.saml.web.StatusServlet 30 | 1 31 | 32 | 33 | 34 | saml 35 | org.springframework.web.servlet.DispatcherServlet 36 | 1 37 | 38 | 39 | 40 | saml 41 | /saml/web/* 42 | 43 | 44 | 45 | status 46 | /saml/status/* 47 | 48 | 49 | 50 | springSecurityFilterChain 51 | org.springframework.web.filter.DelegatingFilterProxy 52 | 53 | 54 | springSecurityFilterChain 55 | /* 56 | 57 | 58 | 59 | 60 | org.springframework.security.saml.web.ClassLoaderLeakPreventorListener 61 | 62 | 63 | 64 | org.springframework.web.context.ContextLoaderListener 65 | 66 | 67 | 68 | index.jsp 69 | 70 | 71 | 72 | java.lang.Exception 73 | /error.jsp 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /src/main/webapp/css/style.css: -------------------------------------------------------------------------------- 1 | /* 2 | Template URI: http://templates.arcsin.se/simple-organization-website-template/ 3 | Author: Viktor Persson 4 | Author URI: http://arcsin.se/ 5 | This template is licensed under a Creative Commons Attribution 2.5 License: http://templates.arcsin.se/license/ 6 | Modifications: file contains modifications from the original version 7 | */ 8 | 9 | /* 10 | Reset 11 | ------------------------------------------------------------------- */ 12 | 13 | html, body, div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, code, del, dfn, em, img, q, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, textarea, input, select {margin: 0; padding: 0; border: 0; font-weight: inherit; font-style: inherit; font-size: 100%; font-family: inherit; vertical-align: baseline;} 14 | table {border-collapse: collapse; border-spacing: 0;} 15 | caption, th, td {text-align: left; font-weight: normal;} 16 | table, td, th {vertical-align: middle;} 17 | blockquote:before, blockquote:after, q:before, q:after {content: "";} 18 | blockquote, q {quotes: "" "";} 19 | a img {border: none;} 20 | :focus {outline: 0;} 21 | 22 | 23 | /* 24 | General 25 | ------------------------------------------------------------------- */ 26 | 27 | html { 28 | height: 100%; 29 | padding-bottom: 1px; /* force scrollbars */ 30 | } 31 | 32 | body { 33 | background: #FFF; 34 | color: #444; 35 | font: normal 75% sans-serif; 36 | line-height: 1.5; 37 | } 38 | 39 | 40 | /* 41 | Typography 42 | ------------------------------------------------------------------- */ 43 | 44 | /* Headings */ 45 | 46 | h1,h2,h3,h4,h5,h6 { 47 | color: #444; 48 | font-weight: normal; 49 | line-height: 1; 50 | margin-bottom: 0.3em; 51 | } 52 | h4,h5,h6 {font-weight: bold;} 53 | 54 | h1 {font-size: 2.6em;} 55 | h2 {font-size: 2em;} 56 | h3 {font-size: 1.5em;} 57 | h4 {font-size: 1.25em;} 58 | h5 {font-size: 1.1em;} 59 | h6 {font-size: 1em;} 60 | 61 | h1 img, h2 img, h3 img, h4 img, h5 img, h6 img {margin: 0;} 62 | 63 | 64 | /* Links */ 65 | 66 | a:focus,a:hover {color: #039;} 67 | a { 68 | color: #456; 69 | text-decoration: none; 70 | } 71 | a:hover {text-decoration: underline;} 72 | 73 | a.more { 74 | color: #579; 75 | font-weight: bold; 76 | } 77 | a.more:hover {color: #234;} 78 | 79 | 80 | /* Text elements */ 81 | 82 | p {margin-bottom: 1em;} 83 | 84 | abbr, acronym {border-bottom: 1px dotted #666;} 85 | address {margin-bottom: 1.5em;} 86 | blockquote {margin: 1.5em;} 87 | del, blockquote { color:#666; } 88 | em, dfn, blockquote, address {font-style: italic;} 89 | strong, dfn {font-weight: bold;} 90 | sup, sub {line-height: 0;} 91 | 92 | pre { 93 | margin: 1.5em 0; 94 | white-space: pre; 95 | } 96 | pre,code,tt { 97 | font: 1em monospace; 98 | line-height: 1.5; 99 | } 100 | 101 | code { 102 | color:rgb(0, 102, 153); 103 | font-size: 11pt; 104 | font-weight: bold; 105 | } 106 | 107 | /* Lists */ 108 | 109 | li ul, li ol {margin-left: 1.5em;} 110 | ul, ol {margin: 0.5em 0 0.5em 1.5em;} 111 | 112 | ul {list-style-type: disc;} 113 | ol { 114 | list-style-type: decimal; 115 | margin-left: 1.9em; 116 | } 117 | 118 | dl {margin: 0 0 1.5em 0;} 119 | dl dt {font-weight: bold;} 120 | dd {margin-left: 1.5em;} 121 | 122 | 123 | /* Special lists */ 124 | 125 | ul.plain-list li, ul.nice-list li, ul.tabbed li { 126 | list-style: none; 127 | margin-top: 0; 128 | } 129 | 130 | ul.tabbed { 131 | display: inline; 132 | margin: 0; 133 | } 134 | ul.tabbed li {float: left;} 135 | 136 | ul.plain-list {margin: 0;} 137 | 138 | ul.nice-list {margin-left: 0;} 139 | ul.nice-list li { 140 | border-top: 1px solid #EEE; 141 | list-style: none; 142 | padding: 4px 0; 143 | } 144 | ul.nice-list li:first-child {border-top: none;} 145 | ul.nice-list li .right {color: #999;} 146 | 147 | 148 | /* Tables */ 149 | 150 | table {margin-bottom: 1.4em;} 151 | th {font-weight: bold;} 152 | thead th {background: #C3D9FF;} 153 | th,td,caption {padding: 4px 10px 4px 5px;} 154 | tr.even td {background: #F2F6FA;} 155 | tfoot {font-style: italic;} 156 | caption {background: #EEE;} 157 | 158 | table.data-table { 159 | border: 1px solid #CCB; 160 | margin-bottom: 2em; 161 | width: 100%; 162 | } 163 | table.data-table th { 164 | background: #F0F0F0; 165 | border: 1px solid #DDD; 166 | color: #555; 167 | text-align: left; 168 | } 169 | table.data-table tr {border-bottom: 1px solid #DDD;} 170 | table.data-table td, table th {padding: 10px;} 171 | table.data-table td { 172 | background: #F6F6F6; 173 | border: 1px solid #DDD; 174 | } 175 | table.data-table tr.even td {background: #FCFCFC;} 176 | 177 | 178 | /* Misc classes */ 179 | 180 | .small {font-size: 0.9em;} 181 | .smaller {font-size: 0.8em;} 182 | .smallest {font-size: 0.7em;} 183 | 184 | .large {font-size: 1.15em;} 185 | .larger {font-size: 1.25em;} 186 | .largest {font-size: 1.35em;} 187 | 188 | .hidden {display: none;} 189 | 190 | .quiet, .quiet a {color: #999;} 191 | .loud, .loud a {color: #000;} 192 | .highlight, .highlight a {background:#ff0;} 193 | 194 | .text-left {text-align: left;} 195 | .text-right {text-align: right;} 196 | .text-center {text-align: center;} 197 | .text-separator {padding: 0 5px;} 198 | 199 | .error, .notice, .success { 200 | border: 1px solid #DDD; 201 | margin-bottom: 1em; 202 | padding: 0.6em 0.8em; 203 | } 204 | 205 | .error {background: #FBE3E4; color: #8A1F11; border-color: #FBC2C4;} 206 | .error a {color: #8A1F11;} 207 | 208 | .notice {background: #FFF6BF; color: #514721; border-color: #FFD324;} 209 | .notice a {color: #514721;} 210 | 211 | .success {background: #E6EFC2; color: #264409; border-color: #C6D880;} 212 | .success a {color: #264409;} 213 | 214 | 215 | /* Labels */ 216 | .label { 217 | border-left-style: solid; 218 | border-left-width: 4px; 219 | margin-bottom: 0.2em; 220 | padding-left: 10px; 221 | } 222 | 223 | .label-blue {border-left-color: #55AADA;} 224 | .label-green {border-left-color: #B7D897;} 225 | .label-orange {border-left-color: #FA8F6F;} 226 | 227 | 228 | /* 229 | Forms 230 | ------------------------------------------------------------------- */ 231 | 232 | label { 233 | cursor: pointer; 234 | font-weight: bold; 235 | } 236 | label.checkbox, label.radio {font-weight: normal;} 237 | legend { 238 | font-weight: bold; 239 | font-size: 1.2em; 240 | } 241 | textarea {overflow: auto;} 242 | input.text, textarea, select { 243 | background: #FCFCFC; 244 | border: 1px inset #AAA; 245 | margin: 0.5em 0; 246 | padding: 4px 5px; 247 | } 248 | input.text:focus, textarea:focus, select:focus {background: #FFFFF5;} 249 | 250 | input.button { 251 | background: #DDD; 252 | border: 1px outset #AAA; 253 | padding: 4px 5px; 254 | } 255 | input.button:active {border-style: inset;} 256 | 257 | 258 | /* Specific */ 259 | 260 | form .required {font-weight: bold;} 261 | 262 | .form-error {border-color: #F00;} 263 | .form-row {padding: 5px 0;} 264 | .form-row-submit { 265 | border-top: 1px solid #DDD; 266 | padding: 8px 0 10px 76px; 267 | margin-top: 10px; 268 | } 269 | .legend { 270 | background: #F0FAF0; 271 | border: 1px solid #D6DFD6; 272 | font-size: 1.5em; 273 | margin: 0; 274 | padding: 8px 14px; 275 | } 276 | .form-property, .form-value {float: left;} 277 | .form-property { 278 | padding-top: 8px; 279 | text-align: right; 280 | width: 60px; 281 | } 282 | .form-value {padding-left: 16px;} 283 | .form-error {border-color: #F00;} 284 | 285 | 286 | 287 | /* 288 | Alignment 289 | ------------------------------------------------------------------- */ 290 | 291 | /* General */ 292 | 293 | .center,.aligncenter { 294 | display: block; 295 | margin-left: auto; 296 | margin-right: auto; 297 | } 298 | 299 | 300 | /* Images */ 301 | 302 | img.bordered,img.alignleft,img.alignright,img.aligncenter { 303 | background-color: #FFF; 304 | border: 1px solid #DDD; 305 | padding: 3px; 306 | } 307 | img.alignleft, img.left {margin: 0 1.5em 1em 0;} 308 | img.alignright, img.right {margin: 0 0 1em 1.5em;} 309 | 310 | 311 | /* Floats */ 312 | 313 | .left,.alignleft {float: left;} 314 | .right,.alignright {float: right;} 315 | 316 | .clear,.clearer {clear: both;} 317 | .clearer { 318 | display: block; 319 | font-size: 0; 320 | line-height: 0; 321 | height: 0; 322 | } 323 | 324 | 325 | /* 326 | Layout 327 | ------------------------------------------------------------------- */ 328 | 329 | /* Common */ 330 | #top, #sub-nav {border-bottom: 1px solid #DDD;} 331 | 332 | 333 | /* Wrapper */ 334 | #site-wrapper { 335 | margin: 0 auto; 336 | width: 1100px; 337 | } 338 | 339 | 340 | /* Header */ 341 | #header {padding-top: 24px;} 342 | 343 | /* Top */ 344 | #top {padding-bottom: 20px;} 345 | 346 | 347 | /* Logo */ 348 | #logo { border-right: 1px solid #DDD; 349 | padding: 10px 40px 10px 0; 350 | margin-right: 40px; 351 | } 352 | #logo img {} 353 | 354 | /* Splash */ 355 | #splash {padding-top: 32px;} 356 | 357 | 358 | /* Navigation */ 359 | .navigation a { 360 | color: #888; 361 | text-decoration: none; 362 | } 363 | .navigation a:hover {color: #002;} 364 | .navigation li.current-tab a {color: #222;} 365 | 366 | #main-nav li:first-child, #sub-nav li:first-child {margin-left: 0;} 367 | 368 | /* Main navigation */ 369 | #main-nav {padding-top: 32px;} 370 | #main-nav li {margin: 0 1.5em;} 371 | #main-nav a { 372 | font-size: 1.8em; 373 | line-height: 2em; 374 | padding-bottom: 2px; 375 | } 376 | #main-nav li.current-tab a {color: #333;} 377 | #main-nav a:hover {color: #002;} 378 | #main-nav li.current-tab a {border-bottom: 2px solid #94CC5F;} 379 | 380 | 381 | /* Subnav */ 382 | #sub-nav { 383 | border-bottom: 1px solid #DDD; 384 | padding: 12px 0; 385 | } 386 | #sub-nav a { 387 | font-size: 1.2em; 388 | text-decoration: none; 389 | } 390 | #sub-nav li {margin: 0 1em;} 391 | #sub-nav li.current-tab a {font-weight: bold;} 392 | 393 | 394 | /* Main */ 395 | .main {margin: 24px 0;} 396 | 397 | .main#main-two-columns {background: url('../images/main-two-columns.gif') repeat-y right top;} 398 | .main#main-two-columns-left {background: url('../images/main-two-columns-left.gif') repeat-y left top;} 399 | .main#main-two-columns #main-content, .main#main-two-columns-left #main-content {width: 620px;} 400 | 401 | /* Sidebar */ 402 | #sidebar {width: 255px;} 403 | 404 | 405 | /* Columns */ 406 | .col3, .col3-mid {width: 31%;} 407 | .col3-mid {margin-left: 3%;} 408 | 409 | /* Sections */ 410 | .section {margin-bottom: 24px;} 411 | .section-title { 412 | margin-bottom: 16px; 413 | padding: 7px 10px 6px; 414 | } 415 | #sidebar .section-title {margin-bottom: 8px;} 416 | 417 | 418 | /* Footer */ 419 | 420 | #footer { 421 | border-top: 1px solid #DDD; 422 | color: #777; 423 | padding: 16px 0 4px; 424 | } 425 | #footer-left {width: 459px;} 426 | #footer-right { 427 | width: 459px; 428 | text-align: right; 429 | } 430 | #footer p {margin-bottom: 0.4em;} 431 | #footer .text-separator { 432 | padding: 0 3px; 433 | color: #BBB; 434 | } 435 | #footer a:hover {color: #000;} 436 | 437 | 438 | 439 | /* 440 | Misc overriding classes 441 | ------------------------------------------------------------------- */ 442 | 443 | /* Border */ 444 | 445 | .noborder {border: 0;} 446 | .notborder {border-top: 0;} 447 | .norborder {border-right: 0;} 448 | .nobborder {border-bottom: 0;} 449 | .nolborder {border-left: 0;} 450 | 451 | /* Margin */ 452 | 453 | .nomargin {margin: 0;} 454 | .notmargin {margin-top: 0;} 455 | .normargin {margin-right: 0;} 456 | .nobmargin {margin-bottom: 0;} 457 | .nolmargin {margin-left: 0;} 458 | 459 | /* Padding */ 460 | 461 | .nopadding {padding: 0;} 462 | .notpadding {padding-top: 0;} 463 | .norpadding {padding-right: 0;} 464 | .nobpadding {padding-bottom: 0;} 465 | .nolpadding {padding-left: 0;} 466 | 467 | 468 | /* 469 | IE Fixes (zzz) 470 | ------------------------------------------------------------------- */ 471 | 472 | * html .navigation, * html #footer {height: 0.01%;} 473 | * html #footer-left {width: 500px;} 474 | .navigation {min-height: 0.01%;} -------------------------------------------------------------------------------- /src/main/webapp/error.jsp: -------------------------------------------------------------------------------- 1 | <%@ page import="java.io.StringWriter" %> 2 | <%@ page import="java.io.PrintWriter" %> 3 | <%@ page import="org.springframework.security.web.WebAttributes" %> 4 | <%@ page isErrorPage="true" %> 5 | <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 |
14 |
15 |
16 |
17 |

Error

18 |

An error occurred.

19 |
20 | <% 21 | // Load exception set from Spring Security unless set from web.xml error handler 22 | if (exception == null) { 23 | exception = (Throwable) request.getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION); 24 | } 25 | %> 26 | <% if (exception != null) { %> 27 | Message:
28 | <%= exception.getMessage() %> 29 |

30 | StackTrace:
31 |
<%
32 |                                     StringWriter stringWriter = new StringWriter();
33 |                                     PrintWriter printWriter = new PrintWriter(stringWriter);
34 |                                     exception.printStackTrace(printWriter);
35 |                                     out.println(stringWriter);
36 |                                     printWriter.close();
37 |                                     stringWriter.close();
38 |                                 %>
39 |
40 | Make sure to hide error content from your production environments to minimize leakage of useful data to potential 41 | attackers. 42 | <% } %> 43 |
44 |
45 |
 
46 |
47 |
48 |
 
49 |
50 | 51 |
52 | 53 |
54 | 55 | -------------------------------------------------------------------------------- /src/main/webapp/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apereo/saml2-sample-java-webapp/75a1bc380c5a8f89ea0b369888b39b5a09cec2f9/src/main/webapp/images/favicon.png -------------------------------------------------------------------------------- /src/main/webapp/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apereo/saml2-sample-java-webapp/75a1bc380c5a8f89ea0b369888b39b5a09cec2f9/src/main/webapp/images/logo.png -------------------------------------------------------------------------------- /src/main/webapp/images/main-two-columns-left.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apereo/saml2-sample-java-webapp/75a1bc380c5a8f89ea0b369888b39b5a09cec2f9/src/main/webapp/images/main-two-columns-left.gif -------------------------------------------------------------------------------- /src/main/webapp/images/main-two-columns.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apereo/saml2-sample-java-webapp/75a1bc380c5a8f89ea0b369888b39b5a09cec2f9/src/main/webapp/images/main-two-columns.gif -------------------------------------------------------------------------------- /src/main/webapp/index.jsp: -------------------------------------------------------------------------------- 1 | <%@ page import="org.springframework.security.saml.SAMLCredential" %> 2 | <%@ page import="org.springframework.security.core.context.SecurityContextHolder" %> 3 | <%@ page import="org.springframework.security.core.Authentication" %> 4 | <%@ page import="org.opensaml.saml2.core.Attribute" %> 5 | <%@ page import="org.springframework.security.saml.util.SAMLUtil" %> 6 | <%@ page import="org.opensaml.xml.util.XMLHelper" %> 7 | <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 8 | 9 | 10 | 11 | 12 |
13 | 14 |
15 |
16 |
17 |
18 |
19 |

Authenticated user

20 |

Overview of the authenticated user's data.

21 |
22 | <% 23 | Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); 24 | SAMLCredential credential = (SAMLCredential) authentication.getCredentials(); 25 | pageContext.setAttribute("authentication", authentication); 26 | pageContext.setAttribute("credential", credential); 27 | pageContext.setAttribute("assertion", XMLHelper.nodeToString(SAMLUtil.marshallMessage(credential.getAuthenticationAssertion()))); 28 | %> 29 |

30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 |
General information
Name:
Principal:
Name ID:
Name ID format:
IDP:
Assertion issue time:
59 |

60 | 61 | 62 | 63 | 64 | 65 | 66 | 69 | 74 | 86 | 87 | 88 |
Principal's SAML attributes
> 67 | 68 | 70 | 71 | () 72 | 73 | > 75 | <% 76 | Attribute a = (Attribute) pageContext.getAttribute("attribute"); 77 | String[] attributeValues = credential.getAttributeAsStringArray(a.getName()); 78 | pageContext.setAttribute("attributeValues", attributeValues); 79 | %> 80 | 81 | 82 |   83 | 84 | 85 |
89 |

90 | 91 | 92 | 93 | 94 | 95 | 96 | 98 | 99 | 100 | 101 | 103 | 104 | 105 | 106 | 108 | 109 | 110 | 111 | 115 | 116 |
Subject confirmation
Method:
In response to:
Not on or after:
Recipient: 112 | 113 |
117 |

118 | 119 | 120 | 121 | 122 | 123 | 124 | 126 | 127 | 128 | 129 | 131 | 132 | 133 | 134 | 136 | 137 | 138 | 139 | 141 | 142 | 143 | 144 | 146 | 147 |
Authentication statement
Authentication instance:
Session validity:
Authentication context class:
Session index:
Subject locality:
148 |

149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 161 | 162 | 163 | 164 | 172 | 173 |
Conditions
Not before:
Not on or after:
Audience restriction: 165 | 166 | 168 |
169 |
170 |
171 |
174 |

175 | 176 | 177 | 178 | 179 | 180 | 183 | 184 |
Assertion XML
181 |

182 |
185 |

186 |
" method="get"> 187 | 188 |
189 |
" method="get"> 190 | 191 | 192 |
193 |
194 |
195 |
196 |
 
197 |
198 |
199 |
 
200 |
201 | 202 |
203 | 204 |
205 | 206 | -------------------------------------------------------------------------------- /src/main/webapp/logout.jsp: -------------------------------------------------------------------------------- 1 | <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 2 | 3 | 4 | 5 | 6 |
7 | 8 |
9 |
10 |
11 |
12 |
13 |

Logout

14 |

You have been logged out.

15 |
16 |

17 | ">Back to index 18 |

19 |
20 |
21 |
 
22 |
23 |
24 |
 
25 |
26 | 27 |
28 | 29 |
30 | 31 | --------------------------------------------------------------------------------