diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index 91f31875fdd..58358c6e594 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -33,15 +33,15 @@ jobs: with: name: errors path: job-initiate-error-tracking.txt - build_jdk_8: - name: Build JDK 8 + build_jdk_11: + name: Build JDK 11 runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - name: Set up JDK 8 + - name: Set up JDK 11 uses: actions/setup-java@v1 with: - java-version: '8' + java-version: '11' - name: Cache Gradle packages uses: actions/cache@v2 with: @@ -64,41 +64,6 @@ jobs: with: name: errors path: job-${{ github.job }}.txt - test_alternate_jdks: - name: Test JDK 11 and 12 - runs-on: ubuntu-latest - strategy: - matrix: - jdk: [11, 12] - fail-fast: false - steps: - - uses: actions/checkout@v2 - - name: Set up JDK ${{ matrix.jdk }} - uses: actions/setup-java@v1 - with: - java-version: ${{ matrix.jdk }} - - name: Cache Gradle packages - uses: actions/cache@v2 - with: - path: ~/.gradle/caches - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} - - name: Test with Gradle - run: | - export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER" - export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD" - export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY" - ./gradlew test -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" --stacktrace - - name: Track error step - uses: spring-projects/track-build-errors-action@v1 - if: ${{ failure() }} - with: - job-name: ${{ github.job }}-${{ matrix.jdk }} - - name: Export errors file - uses: actions/upload-artifact@v2 - if: ${{ failure() }} - with: - name: errors - path: job-${{ github.job }}-${{ matrix.jdk }}.txt snapshot_tests: name: Test against snapshots runs-on: ubuntu-latest @@ -107,7 +72,7 @@ jobs: - name: Set up JDK uses: actions/setup-java@v1 with: - java-version: '8' + java-version: '11' - name: Snapshot Tests run: | export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER" @@ -136,7 +101,7 @@ jobs: - name: Set up JDK uses: actions/setup-java@v1 with: - java-version: '8' + java-version: '11' - name: Run Sonar on given (non-master) branch if: ${{ github.ref != 'refs/heads/master' }} run: | @@ -165,21 +130,21 @@ jobs: path: job-${{ github.job }}.txt deploy_artifacts: name: Deploy Artifacts - needs: [build_jdk_8, test_alternate_jdks, snapshot_tests, sonar_analysis] + needs: [build_jdk_11, snapshot_tests, sonar_analysis] runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up JDK uses: actions/setup-java@v1 with: - java-version: '8' + java-version: '11' - name: Deploy artifacts run: | export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER" export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD" export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY" export VERSION_HEADER=$'Version: GnuPG v2\n\n' - export ORG_GRADLE_PROJECT_signingKey=${GPG_PRIVATE_KEY#"$VERSION_HEADER"} + export ORG_GRADLE_PROJECT_signingKey=${GPG_PRIVATE_KEY_NO_HEADER#"$VERSION_HEADER"} export ORG_GRADLE_PROJECT_signingPassword="$GPG_PASSPHRASE" ./gradlew deployArtifacts -PossrhUsername="$OSSRH_TOKEN_USERNAME" -PossrhPassword="$OSSRH_TOKEN_PASSWORD" -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" --stacktrace --no-parallel ./gradlew finalizeDeployArtifacts -PossrhUsername="$OSSRH_TOKEN_USERNAME" -PossrhPassword="$OSSRH_TOKEN_PASSWORD" -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" --stacktrace --no-parallel @@ -203,14 +168,14 @@ jobs: path: job-${{ github.job }}.txt deploy_docs: name: Deploy Docs - needs: [build_jdk_8, test_alternate_jdks, snapshot_tests, sonar_analysis] + needs: [build_jdk_11, snapshot_tests, sonar_analysis] runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up JDK uses: actions/setup-java@v1 with: - java-version: '8' + java-version: '11' - name: Deploy Docs run: | export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER" @@ -234,14 +199,14 @@ jobs: path: job-${{ github.job }}.txt deploy_schema: name: Deploy Schema - needs: [build_jdk_8, test_alternate_jdks, snapshot_tests, sonar_analysis] + needs: [build_jdk_11, snapshot_tests, sonar_analysis] runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up JDK uses: actions/setup-java@v1 with: - java-version: '8' + java-version: '11' - name: Deploy Schema run: | export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER" @@ -265,7 +230,7 @@ jobs: path: job-${{ github.job }}.txt notify_result: name: Check for failures - needs: [build_jdk_8, test_alternate_jdks, snapshot_tests, sonar_analysis, deploy_artifacts, deploy_docs, deploy_schema] + needs: [build_jdk_11, snapshot_tests, sonar_analysis, deploy_artifacts, deploy_docs, deploy_schema] if: always() runs-on: ubuntu-latest steps: diff --git a/.github/workflows/pr-build-workflow.yml b/.github/workflows/pr-build-workflow.yml index 4c2fc44d1a9..7d98efeaa08 100644 --- a/.github/workflows/pr-build-workflow.yml +++ b/.github/workflows/pr-build-workflow.yml @@ -12,7 +12,7 @@ jobs: - name: Set up JDK uses: actions/setup-java@v1 with: - java-version: '8' + java-version: '11' - name: Cache Gradle packages uses: actions/cache@v2 with: diff --git a/README.adoc b/README.adoc index 7551064039a..4fda007d1a0 100644 --- a/README.adoc +++ b/README.adoc @@ -30,9 +30,9 @@ In the instructions below, https://vimeo.com/34436402[`./gradlew`] is invoked fr a cross-platform, self-contained bootstrap mechanism for the build. === Prerequisites -https://help.github.com/set-up-git-redirect[Git] and the https://www.oracle.com/technetwork/java/javase/downloads[JDK8 build]. +https://help.github.com/set-up-git-redirect[Git] and the https://www.oracle.com/technetwork/java/javase/downloads[JDK11 build]. -Be sure that your `JAVA_HOME` environment variable points to the `jdk1.8.0` folder extracted from the JDK download. +Be sure that your `JAVA_HOME` environment variable points to the `jdk-11` folder extracted from the JDK download. === Check out sources [indent=0] diff --git a/acl/spring-security-acl.gradle b/acl/spring-security-acl.gradle index 53b4864c38e..27dc016aaf5 100644 --- a/acl/spring-security-acl.gradle +++ b/acl/spring-security-acl.gradle @@ -1,18 +1,19 @@ apply plugin: 'io.spring.convention.spring-module' dependencies { - compile project(':spring-security-core') - compile 'org.springframework:spring-aop' - compile 'org.springframework:spring-context' - compile 'org.springframework:spring-core' - compile 'org.springframework:spring-jdbc' - compile 'org.springframework:spring-tx' + management platform(project(":spring-security-dependencies")) + api project(':spring-security-core') + api 'org.springframework:spring-aop' + api 'org.springframework:spring-context' + api 'org.springframework:spring-core' + api 'org.springframework:spring-jdbc' + api 'org.springframework:spring-tx' optional 'net.sf.ehcache:ehcache' - testCompile 'org.springframework:spring-beans' - testCompile 'org.springframework:spring-context-support' - testCompile 'org.springframework:spring-test' + testImplementation 'org.springframework:spring-beans' + testImplementation 'org.springframework:spring-context-support' + testImplementation 'org.springframework:spring-test' - testRuntime 'org.hsqldb:hsqldb' + testRuntimeOnly 'org.hsqldb:hsqldb' } diff --git a/acl/src/main/java/org/springframework/security/acls/domain/AclAuthorizationStrategyImpl.java b/acl/src/main/java/org/springframework/security/acls/domain/AclAuthorizationStrategyImpl.java index 34e62babb5a..4753f973f41 100644 --- a/acl/src/main/java/org/springframework/security/acls/domain/AclAuthorizationStrategyImpl.java +++ b/acl/src/main/java/org/springframework/security/acls/domain/AclAuthorizationStrategyImpl.java @@ -93,11 +93,17 @@ public void securityCheck(Acl acl, int changeType) { && ((changeType == CHANGE_GENERAL) || (changeType == CHANGE_OWNERSHIP))) { return; } - // Not authorized by ACL ownership; try via adminstrative permissions - GrantedAuthority requiredAuthority = getRequiredAuthority(changeType); // Iterate this principal's authorities to determine right Set authorities = AuthorityUtils.authorityListToSet(authentication.getAuthorities()); + if (acl.getOwner() instanceof GrantedAuthoritySid + && authorities.contains(((GrantedAuthoritySid) acl.getOwner()).getGrantedAuthority())) { + return; + } + + // Not authorized by ACL ownership; try via adminstrative permissions + GrantedAuthority requiredAuthority = getRequiredAuthority(changeType); + if (authorities.contains(requiredAuthority.getAuthority())) { return; } diff --git a/acl/src/test/java/org/springframework/security/acls/domain/AclAuthorizationStrategyImplTests.java b/acl/src/test/java/org/springframework/security/acls/domain/AclAuthorizationStrategyImplTests.java index e1b06b74180..992a036569e 100644 --- a/acl/src/test/java/org/springframework/security/acls/domain/AclAuthorizationStrategyImplTests.java +++ b/acl/src/test/java/org/springframework/security/acls/domain/AclAuthorizationStrategyImplTests.java @@ -31,6 +31,8 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; +import static org.mockito.BDDMockito.given; + /** * @author Rob Winch * @@ -66,6 +68,14 @@ public void securityCheckWhenCustomAuthorityThenNameIsUsed() { this.strategy.securityCheck(this.acl, AclAuthorizationStrategy.CHANGE_GENERAL); } + // gh-9425 + @Test + public void securityCheckWhenAclOwnedByGrantedAuthority() { + given(this.acl.getOwner()).willReturn(new GrantedAuthoritySid("ROLE_AUTH")); + this.strategy = new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_SYSTEM_ADMIN")); + this.strategy.securityCheck(this.acl, AclAuthorizationStrategy.CHANGE_GENERAL); + } + @SuppressWarnings("serial") class CustomAuthority implements GrantedAuthority { diff --git a/aspects/spring-security-aspects.gradle b/aspects/spring-security-aspects.gradle index 6b50ef72350..d66ebe447c1 100644 --- a/aspects/spring-security-aspects.gradle +++ b/aspects/spring-security-aspects.gradle @@ -2,13 +2,14 @@ apply plugin: 'io.spring.convention.spring-module' apply plugin: 'io.freefair.aspectj' dependencies { - compile "org.aspectj:aspectjrt" - compile project(':spring-security-core') - compile 'org.springframework:spring-beans' - compile 'org.springframework:spring-context' - compile 'org.springframework:spring-core' + management platform(project(":spring-security-dependencies")) + api "org.aspectj:aspectjrt" + api project(':spring-security-core') + api 'org.springframework:spring-beans' + api 'org.springframework:spring-context' + api 'org.springframework:spring-core' - testCompile 'org.springframework:spring-aop' + testImplementation 'org.springframework:spring-aop' testAspect sourceSets.main.output } diff --git a/build.gradle b/build.gradle index ea5a5b6c893..d7287fe39fd 100644 --- a/build.gradle +++ b/build.gradle @@ -1,10 +1,8 @@ buildscript { dependencies { - classpath 'io.spring.gradle:spring-build-conventions:0.0.36' classpath "io.spring.javaformat:spring-javaformat-gradle-plugin:$springJavaformatVersion" - classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion" classpath 'io.spring.nohttp:nohttp-gradle:0.0.5.RELEASE' - classpath "io.freefair.gradle:aspectj-plugin:5.0.1" + classpath "io.freefair.gradle:aspectj-plugin:5.3.3.3" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" } repositories { @@ -17,6 +15,7 @@ apply plugin: 'io.spring.nohttp' apply plugin: 'locks' apply plugin: 'io.spring.convention.root' apply plugin: 'org.jetbrains.kotlin.jvm' +apply plugin: 'org.springframework.security.update-dependencies' group = 'org.springframework.security' description = 'Spring Security' @@ -25,12 +24,58 @@ ext.snapshotBuild = version.contains("SNAPSHOT") ext.releaseBuild = version.contains("SNAPSHOT") ext.milestoneBuild = !(snapshotBuild || releaseBuild) -dependencyManagementExport.projects = subprojects.findAll { !it.name.contains('-boot') } - repositories { mavenCentral() } +updateDependenciesSettings { + gitHub { + organization = "spring-projects" + repository = "spring-security" + } + addFiles({ + return [ + project.file("buildSrc/src/main/java/io/spring/gradle/convention/AsciidoctorConventionPlugin.java"), + project.file("buildSrc/src/main/groovy/io/spring/gradle/convention/CheckstylePlugin.groovy") + ] + }) + dependencyExcludes { + majorVersionBump() + alphaBetaVersions() + releaseCandidatesVersions() + milestoneVersions() + snapshotVersions() + addRule { components -> + components.withModule("commons-codec:commons-codec") { selection -> + ModuleComponentIdentifier candidate = selection.getCandidate(); + if (!candidate.getVersion().equals(selection.getCurrentVersion())) { + selection.reject("commons-codec updates break saml tests"); + } + } + components.withModule("org.python:jython") { selection -> + ModuleComponentIdentifier candidate = selection.getCandidate(); + if (!candidate.getVersion().equals(selection.getCurrentVersion())) { + selection.reject("jython updates break integration tests"); + } + } + components.withModule("com.nimbusds:nimbus-jose-jwt") { selection -> + ModuleComponentIdentifier candidate = selection.getCandidate(); + if (!candidate.getVersion().equals(selection.getCurrentVersion())) { + selection.reject("nimbus-jose-jwt gets updated when oauth2-oidc-sdk is updated to ensure consistency"); + } + } + components.all { selection -> + ModuleComponentIdentifier candidate = selection.getCandidate(); + // Do not compare version due to multiple versions existing + // will cause opensaml 3.x to be updated to 4.x + if (candidate.getGroup().equals("org.opensaml")) { + selection.reject("org.opensaml maintains two different versions, so it must be updated manually"); + } + } + } + } +} + subprojects { plugins.withType(JavaPlugin) { project.sourceCompatibility='1.8' @@ -40,6 +85,7 @@ subprojects { } } + allprojects { if (!['spring-security-bom', 'spring-security-docs'].contains(project.name)) { apply plugin: 'io.spring.javaformat' @@ -75,4 +121,6 @@ if (hasProperty('buildScan')) { nohttp { allowlistFile = project.file("etc/nohttp/allowlist.lines") + source.exclude "buildSrc/build/**" + } diff --git a/buildSrc/.idea/compiler.xml b/buildSrc/.idea/compiler.xml new file mode 100644 index 00000000000..61a9130cd96 --- /dev/null +++ b/buildSrc/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/buildSrc/.idea/gradle.xml b/buildSrc/.idea/gradle.xml new file mode 100644 index 00000000000..5c59556b93d --- /dev/null +++ b/buildSrc/.idea/gradle.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/buildSrc/.idea/jarRepositories.xml b/buildSrc/.idea/jarRepositories.xml new file mode 100644 index 00000000000..36cc8fcb9de --- /dev/null +++ b/buildSrc/.idea/jarRepositories.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/buildSrc/.idea/misc.xml b/buildSrc/.idea/misc.xml new file mode 100644 index 00000000000..3a9d81e2a8f --- /dev/null +++ b/buildSrc/.idea/misc.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/buildSrc/.idea/uiDesigner.xml b/buildSrc/.idea/uiDesigner.xml new file mode 100644 index 00000000000..e96534fb27b --- /dev/null +++ b/buildSrc/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/buildSrc/.idea/workspace.xml b/buildSrc/.idea/workspace.xml new file mode 100644 index 00000000000..2ca122bc89f --- /dev/null +++ b/buildSrc/.idea/workspace.xml @@ -0,0 +1,206 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + c.binding(Saml2MessageBinding.POST)).build(); - Saml2PostAuthenticationRequest authenticationRequest = mock(Saml2PostAuthenticationRequest.class); - given(authenticationRequest.getAuthenticationRequestUri()).willReturn("uri"); - given(authenticationRequest.getRelayState()).willReturn("relay"); - given(authenticationRequest.getSamlRequest()).willReturn("saml"); - given(this.resolver.resolve(this.request)).willReturn(TestSaml2AuthenticationRequestContexts - .authenticationRequestContext().relyingPartyRegistration(relyingParty).build()); - given(this.factory.createPostAuthenticationRequest(any())).willReturn(authenticationRequest); + public void setRequestMatcherWhenNullThenException() { Saml2WebSsoAuthenticationRequestFilter filter = new Saml2WebSsoAuthenticationRequestFilter(this.resolver, this.factory); - filter.doFilterInternal(this.request, this.response, this.filterChain); - assertThat(this.response.getContentAsString()).contains("
") - .contains(" filter.setRedirectMatcher(null)); } @Test public void setAuthenticationRequestFactoryWhenNullThenException() { - Saml2WebSsoAuthenticationRequestFilter filter = new Saml2WebSsoAuthenticationRequestFilter(this.repository); + Saml2WebSsoAuthenticationRequestFilter filter = new Saml2WebSsoAuthenticationRequestFilter(this.resolver, + this.factory); assertThatIllegalArgumentException().isThrownBy(() -> filter.setAuthenticationRequestFactory(null)); } @Test public void doFilterWhenRequestMatcherFailsThenSkipsFilter() throws Exception { - Saml2WebSsoAuthenticationRequestFilter filter = new Saml2WebSsoAuthenticationRequestFilter(this.repository); + Saml2WebSsoAuthenticationRequestFilter filter = new Saml2WebSsoAuthenticationRequestFilter(this.resolver, + this.factory); filter.setRedirectMatcher((request) -> false); filter.doFilter(this.request, this.response, this.filterChain); - verifyNoInteractions(this.repository); + verifyNoInteractions(this.resolver, this.factory); } @Test public void doFilterWhenRelyingPartyRegistrationNotFoundThenUnauthorized() throws Exception { - Saml2WebSsoAuthenticationRequestFilter filter = new Saml2WebSsoAuthenticationRequestFilter(this.repository); + Saml2WebSsoAuthenticationRequestFilter filter = new Saml2WebSsoAuthenticationRequestFilter(this.resolver, + this.factory); filter.doFilter(this.request, this.response, this.filterChain); assertThat(this.response.getStatus()).isEqualTo(401); } diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/web/DefaultRelyingPartyRegistrationResolverTests.java b/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/DefaultRelyingPartyRegistrationResolverTests.java similarity index 100% rename from saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/web/DefaultRelyingPartyRegistrationResolverTests.java rename to saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/DefaultRelyingPartyRegistrationResolverTests.java diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/web/DefaultSaml2AuthenticationRequestContextResolverTests.java b/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/DefaultSaml2AuthenticationRequestContextResolverTests.java similarity index 100% rename from saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/web/DefaultSaml2AuthenticationRequestContextResolverTests.java rename to saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/DefaultSaml2AuthenticationRequestContextResolverTests.java diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/web/Saml2AuthenticationTokenConverterTests.java b/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/Saml2AuthenticationTokenConverterTests.java similarity index 72% rename from saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/web/Saml2AuthenticationTokenConverterTests.java rename to saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/Saml2AuthenticationTokenConverterTests.java index 91038de6ae8..4a5e9e2feaf 100644 --- a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/web/Saml2AuthenticationTokenConverterTests.java +++ b/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/Saml2AuthenticationTokenConverterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,7 +29,9 @@ import org.springframework.core.convert.converter.Converter; import org.springframework.core.io.ClassPathResource; import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.security.saml2.core.Saml2ErrorCodes; import org.springframework.security.saml2.core.Saml2Utils; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationToken; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations; @@ -37,6 +39,7 @@ import org.springframework.web.util.UriUtils; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; @@ -64,6 +67,22 @@ public void convertWhenSamlResponseThenToken() { .isEqualTo(this.relyingPartyRegistration.getRegistrationId()); } + @Test + public void convertWhenSamlResponseInvalidBase64ThenSaml2AuthenticationException() { + Saml2AuthenticationTokenConverter converter = new Saml2AuthenticationTokenConverter( + this.relyingPartyRegistrationResolver); + given(this.relyingPartyRegistrationResolver.convert(any(HttpServletRequest.class))) + .willReturn(this.relyingPartyRegistration); + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setParameter("SAMLResponse", "invalid"); + assertThatExceptionOfType(Saml2AuthenticationException.class).isThrownBy(() -> converter.convert(request)) + .withCauseInstanceOf(IllegalArgumentException.class) + .satisfies((ex) -> assertThat(ex.getSaml2Error().getErrorCode()) + .isEqualTo(Saml2ErrorCodes.INVALID_RESPONSE)) + .satisfies((ex) -> assertThat(ex.getSaml2Error().getDescription()) + .isEqualTo("Failed to decode SAMLResponse")); + } + @Test public void convertWhenNoSamlResponseThenNull() { Saml2AuthenticationTokenConverter converter = new Saml2AuthenticationTokenConverter( @@ -100,6 +119,25 @@ public void convertWhenGetRequestThenInflates() { .isEqualTo(this.relyingPartyRegistration.getRegistrationId()); } + @Test + public void convertWhenGetRequestInvalidDeflatedThenSaml2AuthenticationException() { + Saml2AuthenticationTokenConverter converter = new Saml2AuthenticationTokenConverter( + this.relyingPartyRegistrationResolver); + given(this.relyingPartyRegistrationResolver.convert(any(HttpServletRequest.class))) + .willReturn(this.relyingPartyRegistration); + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setMethod("GET"); + byte[] invalidDeflated = "invalid".getBytes(); + String encoded = Saml2Utils.samlEncode(invalidDeflated); + request.setParameter("SAMLResponse", encoded); + assertThatExceptionOfType(Saml2AuthenticationException.class).isThrownBy(() -> converter.convert(request)) + .withCauseInstanceOf(IOException.class) + .satisfies((ex) -> assertThat(ex.getSaml2Error().getErrorCode()) + .isEqualTo(Saml2ErrorCodes.INVALID_RESPONSE)) + .satisfies( + (ex) -> assertThat(ex.getSaml2Error().getDescription()).isEqualTo("Unable to inflate string")); + } + @Test public void constructorWhenResolverIsNullThenIllegalArgument() { assertThatIllegalArgumentException().isThrownBy(() -> new Saml2AuthenticationTokenConverter(null)); diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/web/Saml2MetadataFilterTests.java b/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/Saml2MetadataFilterTests.java similarity index 72% rename from saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/web/Saml2MetadataFilterTests.java rename to saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/Saml2MetadataFilterTests.java index 12e024a3a14..f71bfcea89b 100644 --- a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/web/Saml2MetadataFilterTests.java +++ b/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/Saml2MetadataFilterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,11 +16,15 @@ package org.springframework.security.saml2.provider.service.web; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; + import javax.servlet.FilterChain; import org.junit.Before; import org.junit.Test; +import org.springframework.http.HttpHeaders; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.saml2.core.TestSaml2X509Credentials; @@ -31,6 +35,7 @@ import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; @@ -115,9 +120,38 @@ public void doFilterWhenCustomRequestMatcherThenUses() throws Exception { verify(this.repository).findByRegistrationId("path"); } + @Test + public void doFilterWhenSetMetadataFilenameThenUses() throws Exception { + RelyingPartyRegistration validRegistration = TestRelyingPartyRegistrations.full().build(); + String testMetadataFilename = "test-{registrationId}-metadata.xml"; + String fileName = testMetadataFilename.replace("{registrationId}", validRegistration.getRegistrationId()); + String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()); + String generatedMetadata = "test"; + this.request.setPathInfo("/saml2/service-provider-metadata/registration-id"); + given(this.resolver.resolve(validRegistration)).willReturn(generatedMetadata); + this.filter = new Saml2MetadataFilter((request) -> validRegistration, this.resolver); + this.filter.setMetadataFilename(testMetadataFilename); + this.filter.doFilter(this.request, this.response, this.chain); + assertThat(this.response.getHeaderValue(HttpHeaders.CONTENT_DISPOSITION)).asString() + .isEqualTo("attachment; filename=\"%s\"; filename*=UTF-8''%s", fileName, encodedFileName); + } + @Test public void setRequestMatcherWhenNullThenIllegalArgument() { assertThatIllegalArgumentException().isThrownBy(() -> this.filter.setRequestMatcher(null)); } + @Test + public void setMetadataFilenameWhenEmptyThenThrowsException() { + assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> this.filter.setMetadataFilename(" ")) + .withMessage("metadataFilename cannot be empty"); + } + + @Test + public void setMetadataFilenameWhenMissingRegistrationIdVariableThenThrowsException() { + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> this.filter.setMetadataFilename("metadata-filename.xml")) + .withMessage("metadataFilename must contain a {registrationId} match variable"); + } + } diff --git a/saml2/saml2-service-provider/src/test/resources/logback-test.xml b/saml2/saml2-service-provider/core/src/test/resources/logback-test.xml similarity index 100% rename from saml2/saml2-service-provider/src/test/resources/logback-test.xml rename to saml2/saml2-service-provider/core/src/test/resources/logback-test.xml diff --git a/saml2/saml2-service-provider/src/test/resources/saml2-response-sso-circle.encoded b/saml2/saml2-service-provider/core/src/test/resources/saml2-response-sso-circle.encoded similarity index 100% rename from saml2/saml2-service-provider/src/test/resources/saml2-response-sso-circle.encoded rename to saml2/saml2-service-provider/core/src/test/resources/saml2-response-sso-circle.encoded diff --git a/saml2/saml2-service-provider/src/test/resources/test-metadata.xml b/saml2/saml2-service-provider/core/src/test/resources/test-metadata.xml similarity index 100% rename from saml2/saml2-service-provider/src/test/resources/test-metadata.xml rename to saml2/saml2-service-provider/core/src/test/resources/test-metadata.xml diff --git a/saml2/saml2-service-provider/opensaml3/saml2-service-provider-opensaml3.gradle b/saml2/saml2-service-provider/opensaml3/saml2-service-provider-opensaml3.gradle new file mode 100644 index 00000000000..4771290e3f0 --- /dev/null +++ b/saml2/saml2-service-provider/opensaml3/saml2-service-provider-opensaml3.gradle @@ -0,0 +1,61 @@ +buildscript { + repositories { + maven { url 'https://repo.spring.io/plugins-release' } + } + dependencies { + classpath 'io.spring.gradle:propdeps-plugin:0.0.10.RELEASE' + } +} + +plugins { + id 'java' + id 'java-library' + id 'io.spring.convention.repository' + id 'io.spring.convention.management-configuration' + id 'io.spring.convention.dependency-set' + id 'io.spring.convention.checkstyle' + id 'io.spring.convention.tests-configuration' + id 'io.spring.convention.integration-test' + id 'propdeps' +} + +configurations { + classesOnlyElements { + canBeConsumed = true + canBeResolved = false + } + sourceElements { + canBeConsumed = true + canBeResolved = false + } + javadocElements { + canBeConsumed = true + canBeResolved = false + } +} + +artifacts { + classesOnlyElements(compileJava.destinationDir) + sourceSets.main.allSource.srcDirs.forEach({ dir -> + sourceElements(dir) + }) + javadocElements(javadoc.destinationDir) +} + +repositories { + maven { url "https://build.shibboleth.net/nexus/content/repositories/releases/" } +} + +dependencies { + management platform(project(":spring-security-dependencies")) + api project(':saml2-service-provider-core') + + api("org.opensaml:opensaml-core") + api("org.opensaml:opensaml-saml-api") + api("org.opensaml:opensaml-saml-impl") + + provided 'javax.servlet:javax.servlet-api' + + testImplementation 'com.squareup.okhttp3:mockwebserver' + testImplementation project(path : ':saml2-service-provider-core', configuration : 'tests') +} diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProvider.java b/saml2/saml2-service-provider/opensaml3/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProvider.java similarity index 81% rename from saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProvider.java rename to saml2/saml2-service-provider/opensaml3/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProvider.java index 46bc579bbf2..e8531b1d397 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProvider.java +++ b/saml2/saml2-service-provider/opensaml3/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProvider.java @@ -21,27 +21,21 @@ import java.time.Duration; import java.time.Instant; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.function.Consumer; import javax.annotation.Nonnull; import javax.xml.namespace.QName; -import net.shibboleth.utilities.java.support.resolver.CriteriaSet; import net.shibboleth.utilities.java.support.xml.ParserPool; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.joda.time.DateTime; import org.opensaml.core.config.ConfigurationService; -import org.opensaml.core.criterion.EntityIdCriterion; import org.opensaml.core.xml.XMLObject; import org.opensaml.core.xml.config.XMLObjectProviderRegistry; import org.opensaml.core.xml.schema.XSAny; @@ -51,11 +45,9 @@ import org.opensaml.core.xml.schema.XSInteger; import org.opensaml.core.xml.schema.XSString; import org.opensaml.core.xml.schema.XSURI; +import org.opensaml.saml.common.assertion.AssertionValidationException; import org.opensaml.saml.common.assertion.ValidationContext; import org.opensaml.saml.common.assertion.ValidationResult; -import org.opensaml.saml.common.xml.SAMLConstants; -import org.opensaml.saml.criterion.ProtocolCriterion; -import org.opensaml.saml.metadata.criteria.role.impl.EvaluableProtocolRoleDescriptorCriterion; import org.opensaml.saml.saml2.assertion.ConditionValidator; import org.opensaml.saml.saml2.assertion.SAML20AssertionValidator; import org.opensaml.saml.saml2.assertion.SAML2AssertionValidationParameters; @@ -69,35 +61,15 @@ import org.opensaml.saml.saml2.core.AttributeStatement; import org.opensaml.saml.saml2.core.Condition; import org.opensaml.saml.saml2.core.EncryptedAssertion; -import org.opensaml.saml.saml2.core.EncryptedAttribute; -import org.opensaml.saml.saml2.core.NameID; import org.opensaml.saml.saml2.core.OneTimeUse; import org.opensaml.saml.saml2.core.Response; import org.opensaml.saml.saml2.core.StatusCode; import org.opensaml.saml.saml2.core.SubjectConfirmation; import org.opensaml.saml.saml2.core.impl.ResponseUnmarshaller; import org.opensaml.saml.saml2.encryption.Decrypter; -import org.opensaml.saml.saml2.encryption.EncryptedElementTypeEncryptedKeyResolver; import org.opensaml.saml.security.impl.SAMLSignatureProfileValidator; -import org.opensaml.security.credential.Credential; -import org.opensaml.security.credential.CredentialResolver; -import org.opensaml.security.credential.CredentialSupport; -import org.opensaml.security.credential.UsageType; -import org.opensaml.security.credential.criteria.impl.EvaluableEntityIDCredentialCriterion; -import org.opensaml.security.credential.criteria.impl.EvaluableUsageCredentialCriterion; -import org.opensaml.security.credential.impl.CollectionCredentialResolver; -import org.opensaml.security.criteria.UsageCriterion; -import org.opensaml.security.x509.BasicX509Credential; -import org.opensaml.xmlsec.config.impl.DefaultSecurityConfigurationBootstrap; -import org.opensaml.xmlsec.encryption.support.ChainingEncryptedKeyResolver; -import org.opensaml.xmlsec.encryption.support.EncryptedKeyResolver; -import org.opensaml.xmlsec.encryption.support.InlineEncryptedKeyResolver; -import org.opensaml.xmlsec.encryption.support.SimpleRetrievalMethodEncryptedKeyResolver; -import org.opensaml.xmlsec.keyinfo.KeyInfoCredentialResolver; -import org.opensaml.xmlsec.keyinfo.impl.CollectionKeyInfoCredentialResolver; import org.opensaml.xmlsec.signature.support.SignaturePrevalidator; import org.opensaml.xmlsec.signature.support.SignatureTrustEngine; -import org.opensaml.xmlsec.signature.support.impl.ExplicitKeySignatureTrustEngine; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -115,7 +87,7 @@ import org.springframework.security.saml2.core.Saml2Error; import org.springframework.security.saml2.core.Saml2ErrorCodes; import org.springframework.security.saml2.core.Saml2ResponseValidatorResult; -import org.springframework.security.saml2.core.Saml2X509Credential; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; @@ -131,18 +103,15 @@ * {@link Saml2AuthenticationToken#getSaml2Response()} along with the information about * the asserting party, the identity provider (IDP), as well as the relying party, the * service provider (SP, this application). - *

*

* The {@link Saml2AuthenticationToken} will be processed into a SAML Response object. The * SAML response object can be signed. If the Response is signed, a signature will not be * required on the assertion. - *

*

* While a response object can contain a list of assertion, this provider will only * leverage the first valid assertion for the purpose of authentication. Assertions that * do not pass validation will be ignored. If no valid assertions are found a * {@link Saml2AuthenticationException} is thrown. - *

*

* This provider supports two types of encrypted SAML elements *

    @@ -153,11 +122,9 @@ *
* If the assertion is encrypted, then signature validation on the assertion is no longer * required. - *

*

* This provider does not perform an X509 certificate validation on the configured * asserting party, IDP, verification certificates. - *

* * @author Ryan Cassar * @since 5.2 @@ -165,6 +132,8 @@ * "https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf#page=38">SAML 2 * StatusResponse * @see OpenSAML 3 + * @deprecated Because OpenSAML 3 has reached End-of-Life, please update to + * {@code OpenSaml4AuthenticationProvider} */ public final class OpenSamlAuthenticationProvider implements AuthenticationProvider { @@ -201,10 +170,6 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi private Converter responseAuthenticationConverter = createCompatibleResponseAuthenticationConverter(); - private Converter signatureTrustEngineConverter = new SignatureTrustEngineConverter(); - - private Converter decrypterConverter = new DecrypterConverter(); - /** * Creates an {@link OpenSamlAuthenticationProvider} */ @@ -225,7 +190,7 @@ public OpenSamlAuthenticationProvider() { * *
 	 *	OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
-	 *	provider.setResponseElementsDecrypter((responseToken) -> {
+	 *	provider.setResponseElementsDecrypter((responseToken) -> {
 	 *	    DecrypterParameters parameters = new DecrypterParameters();
 	 *	    // ... set parameters as needed
 	 *	    Decrypter decrypter = new Decrypter(parameters);
@@ -246,7 +211,7 @@ public OpenSamlAuthenticationProvider() {
 	 * 
 	 *	OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
 	 *	Converter<EncryptedAssertion, Assertion> myService = ...
-	 *	provider.setResponseDecrypter((responseToken) -> {
+	 *	provider.setResponseDecrypter((responseToken) -> {
 	 *	   Response response = responseToken.getResponse();
 	 *	   response.getEncryptedAssertions().stream()
 	 *	   		.map(service::decrypt).forEach(response.getAssertions()::add);
@@ -272,7 +237,7 @@ public void setResponseElementsDecrypter(Consumer responseElement
 	 *
 	 * 
 	 *	OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
-	 *  provider.setAssertionValidator(assertionToken -> {
+	 *  provider.setAssertionValidator(assertionToken -> {
 	 *		Saml2ResponseValidatorResult result = createDefaultAssertionValidator()
 	 *			.convert(assertionToken)
 	 *		return result.concat(myCustomValidator.convert(assertionToken));
@@ -285,7 +250,7 @@ public void setResponseElementsDecrypter(Consumer responseElement
 	 * 
 	 *	OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
 	 *	provider.setAssertionValidator(
-	 *		createDefaultAssertionValidator(assertionToken -> {
+	 *		createDefaultAssertionValidator(assertionToken -> {
 	 *			Map<String, Object> params = new HashMap<>();
 	 *			params.put(CLOCK_SKEW, 2 * 60 * 1000);
 	 *			// other parameters
@@ -301,7 +266,7 @@ public void setResponseElementsDecrypter(Consumer responseElement
 	 * step from this validator.
 	 *
 	 * This method takes precedence over {@link #setResponseTimeValidationSkew}.
-	 * @param assertionValidator
+	 * @param assertionValidator the strategy for validating a given assertion
 	 * @since 5.4
 	 */
 	public void setAssertionValidator(Converter assertionValidator) {
@@ -317,7 +282,7 @@ public void setAssertionValidator(Converter
 	 *	OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
-	 *	provider.setResponseDecrypter((assertionToken) -> {
+	 *	provider.setResponseDecrypter((assertionToken) -> {
 	 *	    DecrypterParameters parameters = new DecrypterParameters();
 	 *	    // ... set parameters as needed
 	 *	    Decrypter decrypter = new Decrypter(parameters);
@@ -337,7 +302,7 @@ public void setAssertionValidator(Converter
 	 *	OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
 	 *	MyDecryptionService myService = ...
-	 *	provider.setResponseDecrypter((responseToken) -> {
+	 *	provider.setResponseDecrypter((responseToken) -> {
 	 *	   	Assertion assertion = assertionToken.getAssertion();
 	 *	   	EncryptedID encrypted = assertion.getSubject().getEncryptedID();
 	 *		NameID name = myService.decrypt(encrypted);
@@ -363,7 +328,7 @@ public void setAssertionElementsDecrypter(Consumer assertionDecr
 	 *	OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
 	 * 	Converter<ResponseToken, Saml2Authentication> authenticationConverter =
 	 * 			createDefaultResponseAuthenticationConverter();
-	 *	provider.setResponseAuthenticationConverter(responseToken -> {
+	 *	provider.setResponseAuthenticationConverter(responseToken -> {
 	 *		Saml2Authentication authentication = authenticationConverter.convert(responseToken);
 	 *		User user = myUserRepository.findByUsername(authentication.getName());
 	 *		return new MyAuthentication(authentication, user);
@@ -560,53 +525,23 @@ else if (logger.isDebugEnabled()) {
 	private Converter createDefaultResponseSignatureValidator() {
 		return (responseToken) -> {
 			Response response = responseToken.getResponse();
-			Saml2AuthenticationToken token = responseToken.getToken();
-			Collection errors = new ArrayList<>();
-			String issuer = response.getIssuer().getValue();
+			RelyingPartyRegistration registration = responseToken.getToken().getRelyingPartyRegistration();
 			if (response.isSigned()) {
-				SAMLSignatureProfileValidator profileValidator = new SAMLSignatureProfileValidator();
-				try {
-					profileValidator.validate(response.getSignature());
-				}
-				catch (Exception ex) {
-					errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE,
-							"Invalid signature for SAML Response [" + response.getID() + "]: "));
-				}
-
-				try {
-					CriteriaSet criteriaSet = new CriteriaSet();
-					criteriaSet.add(new EvaluableEntityIDCredentialCriterion(new EntityIdCriterion(issuer)));
-					criteriaSet.add(new EvaluableProtocolRoleDescriptorCriterion(
-							new ProtocolCriterion(SAMLConstants.SAML20P_NS)));
-					criteriaSet.add(new EvaluableUsageCredentialCriterion(new UsageCriterion(UsageType.SIGNING)));
-					if (!this.signatureTrustEngineConverter.convert(token).validate(response.getSignature(),
-							criteriaSet)) {
-						errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE,
-								"Invalid signature for SAML Response [" + response.getID() + "]"));
-					}
-				}
-				catch (Exception ex) {
-					errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE,
-							"Invalid signature for SAML Response [" + response.getID() + "]: "));
-				}
+				return OpenSamlVerificationUtils.verifySignature(response, registration).post(response.getSignature());
 			}
-
-			return Saml2ResponseValidatorResult.failure(errors);
+			return Saml2ResponseValidatorResult.success();
 		};
 	}
 
 	private Consumer createDefaultResponseElementsDecrypter() {
 		return (responseToken) -> {
-			Decrypter decrypter = this.decrypterConverter.convert(responseToken.getToken());
 			Response response = responseToken.getResponse();
-			for (EncryptedAssertion encryptedAssertion : responseToken.getResponse().getEncryptedAssertions()) {
-				try {
-					Assertion assertion = decrypter.decrypt(encryptedAssertion);
-					response.getAssertions().add(assertion);
-				}
-				catch (Exception ex) {
-					throw createAuthenticationException(Saml2ErrorCodes.DECRYPTION_ERROR, ex.getMessage(), ex);
-				}
+			RelyingPartyRegistration registration = responseToken.getToken().getRelyingPartyRegistration();
+			try {
+				OpenSamlDecryptionUtils.decryptResponseElements(response, registration);
+			}
+			catch (Saml2Exception ex) {
+				throw createAuthenticationException(Saml2ErrorCodes.DECRYPTION_ERROR, ex.getMessage(), ex);
 			}
 		};
 	}
@@ -656,7 +591,8 @@ private String getStatusCode(Response response) {
 
 	private Converter createDefaultAssertionSignatureValidator() {
 		return createAssertionValidator(Saml2ErrorCodes.INVALID_SIGNATURE, (assertionToken) -> {
-			SignatureTrustEngine engine = this.signatureTrustEngineConverter.convert(assertionToken.token);
+			RelyingPartyRegistration registration = assertionToken.getToken().getRelyingPartyRegistration();
+			SignatureTrustEngine engine = OpenSamlVerificationUtils.trustEngine(registration);
 			return SAML20AssertionValidators.createSignatureValidator(engine);
 		}, (assertionToken) -> new ValidationContext(
 				Collections.singletonMap(SAML2AssertionValidationParameters.SIGNATURE_REQUIRED, false)));
@@ -664,29 +600,12 @@ private Converter createDefaultAss
 
 	private Consumer createDefaultAssertionElementsDecrypter() {
 		return (assertionToken) -> {
-			Decrypter decrypter = this.decrypterConverter.convert(assertionToken.getToken());
 			Assertion assertion = assertionToken.getAssertion();
-			for (AttributeStatement statement : assertion.getAttributeStatements()) {
-				for (EncryptedAttribute encryptedAttribute : statement.getEncryptedAttributes()) {
-					try {
-						Attribute attribute = decrypter.decrypt(encryptedAttribute);
-						statement.getAttributes().add(attribute);
-					}
-					catch (Exception ex) {
-						throw createAuthenticationException(Saml2ErrorCodes.DECRYPTION_ERROR, ex.getMessage(), ex);
-					}
-				}
-			}
-			if (assertion.getSubject() == null) {
-				return;
-			}
-			if (assertion.getSubject().getEncryptedID() == null) {
-				return;
-			}
+			RelyingPartyRegistration registration = assertionToken.getToken().getRelyingPartyRegistration();
 			try {
-				assertion.getSubject().setNameID((NameID) decrypter.decrypt(assertion.getSubject().getEncryptedID()));
+				OpenSamlDecryptionUtils.decryptAssertionElements(assertion, registration);
 			}
-			catch (Exception ex) {
+			catch (Saml2Exception ex) {
 				throw createAuthenticationException(Saml2ErrorCodes.DECRYPTION_ERROR, ex.getMessage(), ex);
 			}
 		};
@@ -765,8 +684,7 @@ private static Object getXmlObjectValue(XMLObject xmlObject) {
 			return (xsBooleanValue != null) ? xsBooleanValue.getValue() : null;
 		}
 		if (xmlObject instanceof XSDateTime) {
-			DateTime dateTime = ((XSDateTime) xmlObject).getValue();
-			return (dateTime != null) ? Instant.ofEpochMilli(dateTime.getMillis()) : null;
+			return Instant.ofEpochMilli(((XSDateTime) xmlObject).getValue().getMillis());
 		}
 		return null;
 	}
@@ -812,27 +730,6 @@ private static ValidationContext createValidationContext(AssertionToken assertio
 		return new ValidationContext(params);
 	}
 
-	private static class SignatureTrustEngineConverter
-			implements Converter {
-
-		@Override
-		public SignatureTrustEngine convert(Saml2AuthenticationToken token) {
-			Set credentials = new HashSet<>();
-			Collection keys = token.getRelyingPartyRegistration().getAssertingPartyDetails()
-					.getVerificationX509Credentials();
-			for (Saml2X509Credential key : keys) {
-				BasicX509Credential cred = new BasicX509Credential(key.getCertificate());
-				cred.setUsageType(UsageType.SIGNING);
-				cred.setEntityId(token.getRelyingPartyRegistration().getAssertingPartyDetails().getEntityId());
-				credentials.add(cred);
-			}
-			CredentialResolver credentialsResolver = new CollectionCredentialResolver(credentials);
-			return new ExplicitKeySignatureTrustEngine(credentialsResolver,
-					DefaultSecurityConfigurationBootstrap.buildBasicInlineKeyInfoCredentialResolver());
-		}
-
-	}
-
 	private static class SAML20AssertionValidators {
 
 		private static final Collection conditions = new ArrayList<>();
@@ -861,10 +758,9 @@ public ValidationResult validate(Condition condition, Assertion assertion, Valid
 				}
 			});
 			subjects.add(new BearerSubjectConfirmationValidator() {
-				@Nonnull
 				@Override
-				protected ValidationResult validateAddress(@Nonnull SubjectConfirmation confirmation,
-						@Nonnull Assertion assertion, @Nonnull ValidationContext context) {
+				protected ValidationResult validateAddress(SubjectConfirmation confirmation, Assertion assertion,
+						ValidationContext context) throws AssertionValidationException {
 					// applications should validate their own addresses - gh-7514
 					return ValidationResult.VALID;
 				}
@@ -906,27 +802,6 @@ protected ValidationResult validateStatements(Assertion assertion, ValidationCon
 
 	}
 
-	private static class DecrypterConverter implements Converter {
-
-		private final EncryptedKeyResolver encryptedKeyResolver = new ChainingEncryptedKeyResolver(
-				Arrays.asList(new InlineEncryptedKeyResolver(), new EncryptedElementTypeEncryptedKeyResolver(),
-						new SimpleRetrievalMethodEncryptedKeyResolver()));
-
-		@Override
-		public Decrypter convert(Saml2AuthenticationToken token) {
-			Collection credentials = new ArrayList<>();
-			for (Saml2X509Credential key : token.getRelyingPartyRegistration().getDecryptionX509Credentials()) {
-				Credential cred = CredentialSupport.getSimpleCredential(key.getCertificate(), key.getPrivateKey());
-				credentials.add(cred);
-			}
-			KeyInfoCredentialResolver resolver = new CollectionKeyInfoCredentialResolver(credentials);
-			Decrypter decrypter = new Decrypter(null, resolver, this.encryptedKeyResolver);
-			decrypter.setRootInNewDocument(true);
-			return decrypter;
-		}
-
-	}
-
 	/**
 	 * A tuple containing an OpenSAML {@link Response} and its associated authentication
 	 * token.
diff --git a/saml2/saml2-service-provider/opensaml3/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactory.java b/saml2/saml2-service-provider/opensaml3/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactory.java
new file mode 100644
index 00000000000..849a95a4c9d
--- /dev/null
+++ b/saml2/saml2-service-provider/opensaml3/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactory.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2002-2021 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.security.saml2.provider.service.authentication;
+
+import java.nio.charset.StandardCharsets;
+import java.time.Clock;
+import java.time.Instant;
+import java.util.Map;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.opensaml.core.config.ConfigurationService;
+import org.opensaml.core.xml.config.XMLObjectProviderRegistry;
+import org.opensaml.saml.common.xml.SAMLConstants;
+import org.opensaml.saml.saml2.core.AuthnRequest;
+import org.opensaml.saml.saml2.core.Issuer;
+import org.opensaml.saml.saml2.core.impl.AuthnRequestBuilder;
+import org.opensaml.saml.saml2.core.impl.IssuerBuilder;
+
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.security.saml2.core.OpenSamlInitializationService;
+import org.springframework.security.saml2.provider.service.authentication.OpenSamlSigningUtils.QueryParametersPartial;
+import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
+import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * A {@link Saml2AuthenticationRequestFactory} that generates, signs, and serializes a
+ * SAML 2.0 AuthnRequest using OpenSAML 3
+ *
+ * @author Filip Hanik
+ * @author Josh Cummings
+ * @since 5.2
+ * @deprecated Because OpenSAML 3 has reached End-of-Life, please update to
+ * {@code OpenSaml4AuthenticationRequestFactory}
+ */
+public class OpenSamlAuthenticationRequestFactory implements Saml2AuthenticationRequestFactory {
+
+	static {
+		OpenSamlInitializationService.initialize();
+	}
+
+	private AuthnRequestBuilder authnRequestBuilder;
+
+	private IssuerBuilder issuerBuilder;
+
+	private Clock clock = Clock.systemUTC();
+
+	private Converter protocolBindingResolver = (context) -> {
+		if (context == null) {
+			return Saml2MessageBinding.POST;
+		}
+		return context.getRelyingPartyRegistration().getAssertionConsumerServiceBinding();
+	};
+
+	private Converter authenticationRequestContextConverter;
+
+	/**
+	 * Creates an {@link OpenSamlAuthenticationRequestFactory}
+	 */
+	public OpenSamlAuthenticationRequestFactory() {
+		this.authenticationRequestContextConverter = this::createAuthnRequest;
+		XMLObjectProviderRegistry registry = ConfigurationService.get(XMLObjectProviderRegistry.class);
+		this.authnRequestBuilder = (AuthnRequestBuilder) registry.getBuilderFactory()
+				.getBuilder(AuthnRequest.DEFAULT_ELEMENT_NAME);
+		this.issuerBuilder = (IssuerBuilder) registry.getBuilderFactory().getBuilder(Issuer.DEFAULT_ELEMENT_NAME);
+	}
+
+	@Override
+	@Deprecated
+	public String createAuthenticationRequest(Saml2AuthenticationRequest request) {
+		Saml2MessageBinding binding = this.protocolBindingResolver.convert(null);
+		RelyingPartyRegistration registration = RelyingPartyRegistration.withRegistrationId("noId")
+				.assertionConsumerServiceBinding(binding)
+				.assertionConsumerServiceLocation(request.getAssertionConsumerServiceUrl())
+				.entityId(request.getIssuer()).remoteIdpEntityId("noIssuer").idpWebSsoUrl("noUrl")
+				.credentials((credentials) -> credentials.addAll(request.getCredentials())).build();
+		Saml2AuthenticationRequestContext context = Saml2AuthenticationRequestContext.builder()
+				.relyingPartyRegistration(registration).issuer(request.getIssuer())
+				.assertionConsumerServiceUrl(request.getAssertionConsumerServiceUrl()).build();
+		AuthnRequest authnRequest = this.authenticationRequestContextConverter.convert(context);
+		return OpenSamlSigningUtils.serialize(OpenSamlSigningUtils.sign(authnRequest, registration));
+	}
+
+	@Override
+	public Saml2PostAuthenticationRequest createPostAuthenticationRequest(Saml2AuthenticationRequestContext context) {
+		AuthnRequest authnRequest = this.authenticationRequestContextConverter.convert(context);
+		RelyingPartyRegistration registration = context.getRelyingPartyRegistration();
+		if (registration.getAssertingPartyDetails().getWantAuthnRequestsSigned()) {
+			OpenSamlSigningUtils.sign(authnRequest, registration);
+		}
+		String xml = OpenSamlSigningUtils.serialize(authnRequest);
+		return Saml2PostAuthenticationRequest.withAuthenticationRequestContext(context)
+				.samlRequest(Saml2Utils.samlEncode(xml.getBytes(StandardCharsets.UTF_8))).build();
+	}
+
+	@Override
+	public Saml2RedirectAuthenticationRequest createRedirectAuthenticationRequest(
+			Saml2AuthenticationRequestContext context) {
+		AuthnRequest authnRequest = this.authenticationRequestContextConverter.convert(context);
+		RelyingPartyRegistration registration = context.getRelyingPartyRegistration();
+		String xml = OpenSamlSigningUtils.serialize(authnRequest);
+		Saml2RedirectAuthenticationRequest.Builder result = Saml2RedirectAuthenticationRequest
+				.withAuthenticationRequestContext(context);
+		String deflatedAndEncoded = Saml2Utils.samlEncode(Saml2Utils.samlDeflate(xml));
+		result.samlRequest(deflatedAndEncoded).relayState(context.getRelayState());
+		if (registration.getAssertingPartyDetails().getWantAuthnRequestsSigned()) {
+			QueryParametersPartial partial = OpenSamlSigningUtils.sign(registration).param("SAMLRequest",
+					deflatedAndEncoded);
+			if (StringUtils.hasText(context.getRelayState())) {
+				partial.param("RelayState", context.getRelayState());
+			}
+			Map parameters = partial.parameters();
+			return result.sigAlg(parameters.get("SigAlg")).signature(parameters.get("Signature")).build();
+		}
+		return result.build();
+	}
+
+	private AuthnRequest createAuthnRequest(Saml2AuthenticationRequestContext context) {
+		String issuer = context.getIssuer();
+		String destination = context.getDestination();
+		String assertionConsumerServiceUrl = context.getAssertionConsumerServiceUrl();
+		Saml2MessageBinding protocolBinding = this.protocolBindingResolver.convert(context);
+		AuthnRequest auth = this.authnRequestBuilder.buildObject();
+		if (auth.getID() == null) {
+			auth.setID("ARQ" + UUID.randomUUID().toString().substring(1));
+		}
+		if (auth.getIssueInstant() == null) {
+			auth.setIssueInstant(new DateTime(this.clock.millis()));
+		}
+		if (auth.isForceAuthn() == null) {
+			auth.setForceAuthn(Boolean.FALSE);
+		}
+		if (auth.isPassive() == null) {
+			auth.setIsPassive(Boolean.FALSE);
+		}
+		if (auth.getProtocolBinding() == null) {
+			auth.setProtocolBinding(protocolBinding.getUrn());
+		}
+		Issuer iss = this.issuerBuilder.buildObject();
+		iss.setValue(issuer);
+		auth.setIssuer(iss);
+		auth.setDestination(destination);
+		auth.setAssertionConsumerServiceURL(assertionConsumerServiceUrl);
+		return auth;
+	}
+
+	/**
+	 * Set the {@link AuthnRequest} post-processor resolver
+	 * @param authenticationRequestContextConverter a strategy for creating an
+	 * {@link AuthnRequest}
+	 * @since 5.4
+	 */
+	public void setAuthenticationRequestContextConverter(
+			Converter authenticationRequestContextConverter) {
+		Assert.notNull(authenticationRequestContextConverter, "authenticationRequestContextConverter cannot be null");
+		this.authenticationRequestContextConverter = authenticationRequestContextConverter;
+	}
+
+	/**
+	 * ' Use this {@link Clock} with {@link Instant#now()} for generating timestamps
+	 * @param clock the {@link Clock} to use
+	 */
+	public void setClock(Clock clock) {
+		Assert.notNull(clock, "clock cannot be null");
+		this.clock = clock;
+	}
+
+	/**
+	 * Sets the {@code protocolBinding} to use when generating authentication requests.
+	 * Acceptable values are {@link SAMLConstants#SAML2_POST_BINDING_URI} and
+	 * {@link SAMLConstants#SAML2_REDIRECT_BINDING_URI} The IDP will be reading this value
+	 * in the {@code AuthNRequest} to determine how to send the Response/Assertion to the
+	 * ACS URL, assertion consumer service URL.
+	 * @param protocolBinding either {@link SAMLConstants#SAML2_POST_BINDING_URI} or
+	 * {@link SAMLConstants#SAML2_REDIRECT_BINDING_URI}
+	 * @throws IllegalArgumentException if the protocolBinding is not valid
+	 * @deprecated Use
+	 * {@link org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration.Builder#assertionConsumerServiceBinding(Saml2MessageBinding)}
+	 * instead
+	 */
+	@Deprecated
+	public void setProtocolBinding(String protocolBinding) {
+		Saml2MessageBinding binding = Saml2MessageBinding.from(protocolBinding);
+		Assert.notNull(binding, "Invalid protocol binding: " + protocolBinding);
+		this.protocolBindingResolver = (context) -> binding;
+	}
+
+}
diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProviderTests.java b/saml2/saml2-service-provider/opensaml3/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProviderTests.java
similarity index 88%
rename from saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProviderTests.java
rename to saml2/saml2-service-provider/opensaml3/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProviderTests.java
index 3e9f1995ba2..93e263a2e31 100644
--- a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProviderTests.java
+++ b/saml2/saml2-service-provider/opensaml3/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProviderTests.java
@@ -38,10 +38,15 @@
 import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
 import org.opensaml.core.xml.io.Marshaller;
 import org.opensaml.core.xml.io.MarshallingException;
+import org.opensaml.core.xml.schema.XSDateTime;
+import org.opensaml.core.xml.schema.impl.XSDateTimeBuilder;
 import org.opensaml.saml.common.assertion.ValidationContext;
 import org.opensaml.saml.saml2.assertion.SAML2AssertionValidationParameters;
 import org.opensaml.saml.saml2.core.Assertion;
+import org.opensaml.saml.saml2.core.Attribute;
 import org.opensaml.saml.saml2.core.AttributeStatement;
+import org.opensaml.saml.saml2.core.AttributeValue;
+import org.opensaml.saml.saml2.core.Conditions;
 import org.opensaml.saml.saml2.core.EncryptedAssertion;
 import org.opensaml.saml.saml2.core.EncryptedAttribute;
 import org.opensaml.saml.saml2.core.EncryptedID;
@@ -49,6 +54,9 @@
 import org.opensaml.saml.saml2.core.OneTimeUse;
 import org.opensaml.saml.saml2.core.Response;
 import org.opensaml.saml.saml2.core.StatusCode;
+import org.opensaml.saml.saml2.core.SubjectConfirmation;
+import org.opensaml.saml.saml2.core.SubjectConfirmationData;
+import org.opensaml.saml.saml2.core.impl.AttributeBuilder;
 import org.opensaml.saml.saml2.core.impl.EncryptedAssertionBuilder;
 import org.opensaml.saml.saml2.core.impl.EncryptedIDBuilder;
 import org.opensaml.saml.saml2.core.impl.NameIDBuilder;
@@ -134,8 +142,8 @@ public void authenticateWhenXmlErrorThenThrowAuthenticationException() {
 
 	@Test
 	public void authenticateWhenInvalidDestinationThenThrowAuthenticationException() {
-		Response response = TestOpenSamlObjects.response(DESTINATION + "invalid", ASSERTING_PARTY_ENTITY_ID);
-		response.getAssertions().add(TestOpenSamlObjects.assertion());
+		Response response = response(DESTINATION + "invalid", ASSERTING_PARTY_ENTITY_ID);
+		response.getAssertions().add(assertion());
 		TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(),
 				RELYING_PARTY_ENTITY_ID);
 		Saml2AuthenticationToken token = token(response, verifying(registration()));
@@ -154,8 +162,8 @@ public void authenticateWhenNoAssertionsPresentThenThrowAuthenticationException(
 
 	@Test
 	public void authenticateWhenInvalidSignatureOnAssertionThenThrowAuthenticationException() {
-		Response response = TestOpenSamlObjects.response();
-		response.getAssertions().add(TestOpenSamlObjects.assertion());
+		Response response = response();
+		response.getAssertions().add(assertion());
 		Saml2AuthenticationToken token = token(response, verifying(registration()));
 		assertThatExceptionOfType(Saml2AuthenticationException.class)
 				.isThrownBy(() -> this.provider.authenticate(token))
@@ -164,8 +172,8 @@ public void authenticateWhenInvalidSignatureOnAssertionThenThrowAuthenticationEx
 
 	@Test
 	public void authenticateWhenOpenSAMLValidationErrorThenThrowAuthenticationException() {
-		Response response = TestOpenSamlObjects.response();
-		Assertion assertion = TestOpenSamlObjects.assertion();
+		Response response = response();
+		Assertion assertion = assertion();
 		assertion.getSubject().getSubjectConfirmations().get(0).getSubjectConfirmationData()
 				.setNotOnOrAfter(DateTime.now().minus(Duration.standardDays(3)));
 		TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.assertingPartySigningCredential(),
@@ -179,8 +187,8 @@ public void authenticateWhenOpenSAMLValidationErrorThenThrowAuthenticationExcept
 
 	@Test
 	public void authenticateWhenMissingSubjectThenThrowAuthenticationException() {
-		Response response = TestOpenSamlObjects.response();
-		Assertion assertion = TestOpenSamlObjects.assertion();
+		Response response = response();
+		Assertion assertion = assertion();
 		assertion.setSubject(null);
 		TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.assertingPartySigningCredential(),
 				RELYING_PARTY_ENTITY_ID);
@@ -193,8 +201,8 @@ public void authenticateWhenMissingSubjectThenThrowAuthenticationException() {
 
 	@Test
 	public void authenticateWhenUsernameMissingThenThrowAuthenticationException() {
-		Response response = TestOpenSamlObjects.response();
-		Assertion assertion = TestOpenSamlObjects.assertion();
+		Response response = response();
+		Assertion assertion = assertion();
 		assertion.getSubject().getNameID().setValue(null);
 		TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.assertingPartySigningCredential(),
 				RELYING_PARTY_ENTITY_ID);
@@ -207,8 +215,8 @@ public void authenticateWhenUsernameMissingThenThrowAuthenticationException() {
 
 	@Test
 	public void authenticateWhenAssertionContainsValidationAddressThenItSucceeds() {
-		Response response = TestOpenSamlObjects.response();
-		Assertion assertion = TestOpenSamlObjects.assertion();
+		Response response = response();
+		Assertion assertion = assertion();
 		assertion.getSubject().getSubjectConfirmations()
 				.forEach((sc) -> sc.getSubjectConfirmationData().setAddress("10.10.10.10"));
 		TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.assertingPartySigningCredential(),
@@ -220,9 +228,9 @@ public void authenticateWhenAssertionContainsValidationAddressThenItSucceeds() {
 
 	@Test
 	public void authenticateWhenAssertionContainsAttributesThenItSucceeds() {
-		Response response = TestOpenSamlObjects.response();
-		Assertion assertion = TestOpenSamlObjects.assertion();
-		List attributes = TestOpenSamlObjects.attributeStatements();
+		Response response = response();
+		Assertion assertion = assertion();
+		List attributes = attributeStatements();
 		assertion.getAttributeStatements().addAll(attributes);
 		TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.assertingPartySigningCredential(),
 				RELYING_PARTY_ENTITY_ID);
@@ -244,8 +252,8 @@ public void authenticateWhenAssertionContainsAttributesThenItSucceeds() {
 
 	@Test
 	public void authenticateWhenEncryptedAssertionWithoutSignatureThenItFails() {
-		Response response = TestOpenSamlObjects.response();
-		EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(TestOpenSamlObjects.assertion(),
+		Response response = response();
+		EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion(),
 				TestSaml2X509Credentials.assertingPartyEncryptingCredential());
 		response.getEncryptedAssertions().add(encryptedAssertion);
 		TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(),
@@ -258,8 +266,8 @@ public void authenticateWhenEncryptedAssertionWithoutSignatureThenItFails() {
 
 	@Test
 	public void authenticateWhenEncryptedAssertionWithSignatureThenItSucceeds() {
-		Response response = TestOpenSamlObjects.response();
-		Assertion assertion = TestOpenSamlObjects.signed(TestOpenSamlObjects.assertion(),
+		Response response = response();
+		Assertion assertion = TestOpenSamlObjects.signed(assertion(),
 				TestSaml2X509Credentials.assertingPartySigningCredential(), RELYING_PARTY_ENTITY_ID);
 		EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion,
 				TestSaml2X509Credentials.assertingPartyEncryptingCredential());
@@ -272,8 +280,8 @@ public void authenticateWhenEncryptedAssertionWithSignatureThenItSucceeds() {
 
 	@Test
 	public void authenticateWhenEncryptedAssertionWithResponseSignatureThenItSucceeds() {
-		Response response = TestOpenSamlObjects.response();
-		EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(TestOpenSamlObjects.assertion(),
+		Response response = response();
+		EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion(),
 				TestSaml2X509Credentials.assertingPartyEncryptingCredential());
 		response.getEncryptedAssertions().add(encryptedAssertion);
 		TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(),
@@ -284,8 +292,8 @@ public void authenticateWhenEncryptedAssertionWithResponseSignatureThenItSucceed
 
 	@Test
 	public void authenticateWhenEncryptedNameIdWithSignatureThenItSucceeds() {
-		Response response = TestOpenSamlObjects.response();
-		Assertion assertion = TestOpenSamlObjects.assertion();
+		Response response = response();
+		Assertion assertion = assertion();
 		NameID nameId = assertion.getSubject().getNameID();
 		EncryptedID encryptedID = TestOpenSamlObjects.encrypted(nameId,
 				TestSaml2X509Credentials.assertingPartyEncryptingCredential());
@@ -300,8 +308,8 @@ public void authenticateWhenEncryptedNameIdWithSignatureThenItSucceeds() {
 
 	@Test
 	public void authenticateWhenEncryptedAttributeThenDecrypts() {
-		Response response = TestOpenSamlObjects.response();
-		Assertion assertion = TestOpenSamlObjects.assertion();
+		Response response = response();
+		Assertion assertion = assertion();
 		EncryptedAttribute attribute = TestOpenSamlObjects.encrypted("name", "value",
 				TestSaml2X509Credentials.assertingPartyEncryptingCredential());
 		AttributeStatement statement = build(AttributeStatement.DEFAULT_ELEMENT_NAME);
@@ -318,8 +326,8 @@ public void authenticateWhenEncryptedAttributeThenDecrypts() {
 
 	@Test
 	public void authenticateWhenDecryptionKeysAreMissingThenThrowAuthenticationException() {
-		Response response = TestOpenSamlObjects.response();
-		EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(TestOpenSamlObjects.assertion(),
+		Response response = response();
+		EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion(),
 				TestSaml2X509Credentials.assertingPartyEncryptingCredential());
 		response.getEncryptedAssertions().add(encryptedAssertion);
 		TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(),
@@ -332,8 +340,8 @@ public void authenticateWhenDecryptionKeysAreMissingThenThrowAuthenticationExcep
 
 	@Test
 	public void authenticateWhenDecryptionKeysAreWrongThenThrowAuthenticationException() {
-		Response response = TestOpenSamlObjects.response();
-		EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(TestOpenSamlObjects.assertion(),
+		Response response = response();
+		EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion(),
 				TestSaml2X509Credentials.assertingPartyEncryptingCredential());
 		response.getEncryptedAssertions().add(encryptedAssertion);
 		TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(),
@@ -347,8 +355,8 @@ public void authenticateWhenDecryptionKeysAreWrongThenThrowAuthenticationExcepti
 
 	@Test
 	public void writeObjectWhenTypeIsSaml2AuthenticationThenNoException() throws IOException {
-		Response response = TestOpenSamlObjects.response();
-		Assertion assertion = TestOpenSamlObjects.signed(TestOpenSamlObjects.assertion(),
+		Response response = response();
+		Assertion assertion = TestOpenSamlObjects.signed(assertion(),
 				TestSaml2X509Credentials.assertingPartySigningCredential(), RELYING_PARTY_ENTITY_ID);
 		EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion,
 				TestSaml2X509Credentials.assertingPartyEncryptingCredential());
@@ -384,8 +392,8 @@ public void authenticateWhenDelegatingToDefaultAssertionValidatorThenUses() {
 				.concat(new Saml2Error("wrong error", "wrong error"))
 		);
 		// @formatter:on
-		Response response = TestOpenSamlObjects.response();
-		Assertion assertion = TestOpenSamlObjects.assertion();
+		Response response = response();
+		Assertion assertion = assertion();
 		OneTimeUse oneTimeUse = build(OneTimeUse.DEFAULT_ELEMENT_NAME);
 		assertion.getConditions().getConditions().add(oneTimeUse);
 		response.getAssertions().add(assertion);
@@ -410,8 +418,8 @@ public void authenticateWhenCustomAssertionValidatorThenUses() {
 				.concat(validator.convert(assertionToken))
 		);
 		// @formatter:on
-		Response response = TestOpenSamlObjects.response();
-		Assertion assertion = TestOpenSamlObjects.assertion();
+		Response response = response();
+		Assertion assertion = assertion();
 		response.getAssertions().add(assertion);
 		TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(),
 				ASSERTING_PARTY_ENTITY_ID);
@@ -426,8 +434,8 @@ public void authenticateWhenCustomAssertionValidatorThenUses() {
 	public void authenticateWhenDefaultConditionValidatorNotUsedThenSignatureStillChecked() {
 		OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
 		provider.setAssertionValidator((assertionToken) -> Saml2ResponseValidatorResult.success());
-		Response response = TestOpenSamlObjects.response();
-		Assertion assertion = TestOpenSamlObjects.assertion();
+		Response response = response();
+		Assertion assertion = assertion();
 		TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.relyingPartyDecryptingCredential(),
 				RELYING_PARTY_ENTITY_ID); // broken
 		// signature
@@ -451,8 +459,8 @@ public void authenticateWhenValidationContextCustomizedThenUsers() {
 		OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
 		provider.setAssertionValidator(
 				OpenSamlAuthenticationProvider.createDefaultAssertionValidator((assertionToken) -> context));
-		Response response = TestOpenSamlObjects.response();
-		Assertion assertion = TestOpenSamlObjects.assertion();
+		Response response = response();
+		Assertion assertion = assertion();
 		response.getAssertions().add(assertion);
 		TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(),
 				ASSERTING_PARTY_ENTITY_ID);
@@ -467,8 +475,8 @@ public void authenticateWhenValidationContextCustomizedThenUsers() {
 
 	@Test
 	public void authenticateWithSHA1SignatureThenItSucceeds() throws Exception {
-		Response response = TestOpenSamlObjects.response();
-		Assertion assertion = TestOpenSamlObjects.signed(TestOpenSamlObjects.assertion(),
+		Response response = response();
+		Assertion assertion = TestOpenSamlObjects.signed(assertion(),
 				TestSaml2X509Credentials.assertingPartySigningCredential(), RELYING_PARTY_ENTITY_ID,
 				SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1);
 		response.getAssertions().add(assertion);
@@ -525,8 +533,8 @@ public void setAssertionElementsDecrypterWhenNullThenIllegalArgument() {
 
 	@Test
 	public void authenticateWhenCustomResponseElementsDecrypterThenDecryptsResponse() {
-		Response response = TestOpenSamlObjects.response();
-		Assertion assertion = TestOpenSamlObjects.assertion();
+		Response response = response();
+		Assertion assertion = assertion();
 		TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.assertingPartySigningCredential(),
 				RELYING_PARTY_ENTITY_ID);
 		response.getEncryptedAssertions().add(new EncryptedAssertionBuilder().buildObject());
@@ -540,8 +548,8 @@ public void authenticateWhenCustomResponseElementsDecrypterThenDecryptsResponse(
 
 	@Test
 	public void authenticateWhenCustomAssertionElementsDecrypterThenDecryptsAssertion() {
-		Response response = TestOpenSamlObjects.response();
-		Assertion assertion = TestOpenSamlObjects.assertion();
+		Response response = response();
+		Assertion assertion = assertion();
 		EncryptedID id = new EncryptedIDBuilder().buildObject();
 		id.setEncryptedData(new EncryptedDataBuilder().buildObject());
 		assertion.getSubject().setEncryptedID(id);
@@ -600,13 +608,52 @@ private Consumer errorOf(String errorCode, String
 		return (ex) -> {
 			assertThat(ex.getError().getErrorCode()).isEqualTo(errorCode);
 			if (StringUtils.hasText(description)) {
-				assertThat(ex.getError().getDescription()).isEqualTo(description);
+				assertThat(ex.getError().getDescription()).contains(description);
 			}
 		};
 	}
 
-	private Saml2AuthenticationToken token() {
+	private Response response() {
 		Response response = TestOpenSamlObjects.response();
+		response.setIssueInstant(DateTime.now());
+		return response;
+	}
+
+	private Response response(String destination, String issuerEntityId) {
+		Response response = TestOpenSamlObjects.response(destination, issuerEntityId);
+		response.setIssueInstant(DateTime.now());
+		return response;
+	}
+
+	private Assertion assertion() {
+		Assertion assertion = TestOpenSamlObjects.assertion();
+		assertion.setIssueInstant(DateTime.now());
+		for (SubjectConfirmation confirmation : assertion.getSubject().getSubjectConfirmations()) {
+			SubjectConfirmationData data = confirmation.getSubjectConfirmationData();
+			data.setNotBefore(DateTime.now().minus(Duration.millis(5 * 60 * 1000)));
+			data.setNotOnOrAfter(DateTime.now().plus(Duration.millis(5 * 60 * 1000)));
+		}
+		Conditions conditions = assertion.getConditions();
+		conditions.setNotBefore(DateTime.now().minus(Duration.millis(5 * 60 * 1000)));
+		conditions.setNotOnOrAfter(DateTime.now().plus(Duration.millis(5 * 60 * 1000)));
+		return assertion;
+	}
+
+	private List attributeStatements() {
+		List attributeStatements = TestOpenSamlObjects.attributeStatements();
+		AttributeBuilder attributeBuilder = new AttributeBuilder();
+		Attribute registeredDateAttr = attributeBuilder.buildObject();
+		registeredDateAttr.setName("registeredDate");
+		XSDateTime registeredDate = new XSDateTimeBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME,
+				XSDateTime.TYPE_NAME);
+		registeredDate.setValue(DateTime.parse("1970-01-01T00:00:00Z"));
+		registeredDateAttr.getAttributeValues().add(registeredDate);
+		attributeStatements.get(0).getAttributes().add(registeredDateAttr);
+		return attributeStatements;
+	}
+
+	private Saml2AuthenticationToken token() {
+		Response response = response();
 		RelyingPartyRegistration registration = verifying(registration()).build();
 		return new Saml2AuthenticationToken(registration, serialize(response));
 	}
diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactoryTests.java b/saml2/saml2-service-provider/opensaml3/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactoryTests.java
similarity index 98%
rename from saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactoryTests.java
rename to saml2/saml2-service-provider/opensaml3/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactoryTests.java
index f0f03819fec..4b222b01d1e 100644
--- a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactoryTests.java
+++ b/saml2/saml2-service-provider/opensaml3/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactoryTests.java
@@ -19,6 +19,7 @@
 import java.io.ByteArrayInputStream;
 import java.nio.charset.StandardCharsets;
 
+import org.joda.time.DateTime;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -200,8 +201,7 @@ public void createAuthenticationRequestWhenSetUnsupportredUriThenThrowsIllegalAr
 	public void createPostAuthenticationRequestWhenAuthnRequestConsumerThenUses() {
 		Converter authenticationRequestContextConverter = mock(
 				Converter.class);
-		given(authenticationRequestContextConverter.convert(this.context))
-				.willReturn(TestOpenSamlObjects.authnRequest());
+		given(authenticationRequestContextConverter.convert(this.context)).willReturn(authnRequest());
 		this.factory.setAuthenticationRequestContextConverter(authenticationRequestContextConverter);
 
 		this.factory.createPostAuthenticationRequest(this.context);
@@ -212,8 +212,7 @@ public void createPostAuthenticationRequestWhenAuthnRequestConsumerThenUses() {
 	public void createRedirectAuthenticationRequestWhenAuthnRequestConsumerThenUses() {
 		Converter authenticationRequestContextConverter = mock(
 				Converter.class);
-		given(authenticationRequestContextConverter.convert(this.context))
-				.willReturn(TestOpenSamlObjects.authnRequest());
+		given(authenticationRequestContextConverter.convert(this.context)).willReturn(authnRequest());
 		this.factory.setAuthenticationRequestContextConverter(authenticationRequestContextConverter);
 
 		this.factory.createRedirectAuthenticationRequest(this.context);
@@ -256,6 +255,12 @@ public void createRedirectAuthenticationRequestWhenSHA1SignRequestThenSignatureI
 		assertThat(result.getBinding()).isEqualTo(Saml2MessageBinding.REDIRECT);
 	}
 
+	private AuthnRequest authnRequest() {
+		AuthnRequest authnRequest = TestOpenSamlObjects.authnRequest();
+		authnRequest.setIssueInstant(DateTime.now());
+		return authnRequest;
+	}
+
 	private AuthnRequest getAuthNRequest(Saml2MessageBinding binding) {
 		AbstractSaml2AuthenticationRequest result = (binding == Saml2MessageBinding.REDIRECT)
 				? this.factory.createRedirectAuthenticationRequest(this.context)
diff --git a/saml2/saml2-service-provider/opensaml4/saml2-service-provider-opensaml4.gradle b/saml2/saml2-service-provider/opensaml4/saml2-service-provider-opensaml4.gradle
new file mode 100644
index 00000000000..ca313a9e4e4
--- /dev/null
+++ b/saml2/saml2-service-provider/opensaml4/saml2-service-provider-opensaml4.gradle
@@ -0,0 +1,72 @@
+buildscript {
+	repositories {
+		maven { url 'https://repo.spring.io/plugins-release' }
+	}
+	dependencies {
+		classpath 'io.spring.gradle:propdeps-plugin:0.0.10.RELEASE'
+	}
+}
+
+plugins {
+	id 'java'
+	id 'java-library'
+	id 'io.spring.convention.repository'
+	id 'io.spring.convention.management-configuration'
+	id 'io.spring.convention.dependency-set'
+	id 'io.spring.convention.checkstyle'
+	id 'io.spring.convention.tests-configuration'
+	id 'io.spring.convention.integration-test'
+	id 'propdeps'
+}
+
+configurations {
+	classesOnlyElements {
+		canBeConsumed = true
+		canBeResolved = false
+		attributes {
+			attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 11)
+		}
+	}
+	sourceElements {
+		canBeConsumed = true
+		canBeResolved = false
+	}
+	javadocElements {
+		canBeConsumed = true
+		canBeResolved = false
+	}
+}
+
+artifacts {
+	classesOnlyElements(compileJava.destinationDir)
+	sourceSets.main.allSource.srcDirs.forEach({ dir ->
+		sourceElements(dir)
+	})
+	javadocElements(javadoc.destinationDir)
+}
+
+sourceCompatibility = '11'
+
+repositories {
+	maven { url "https://build.shibboleth.net/nexus/content/repositories/releases/" }
+}
+
+dependencies {
+	management platform(project(":spring-security-dependencies"))
+	constraints {
+		management("org.opensaml:opensaml-core:4.1.0")
+		management("org.opensaml:opensaml-saml-api:4.1.0")
+		management("org.opensaml:opensaml-saml-impl:4.1.0")
+	}
+
+	api project(':saml2-service-provider-core')
+
+	api("org.opensaml:opensaml-core")
+	api("org.opensaml:opensaml-saml-api")
+	api("org.opensaml:opensaml-saml-impl")
+
+	provided 'javax.servlet:javax.servlet-api'
+
+	testImplementation 'com.squareup.okhttp3:mockwebserver'
+	testImplementation project(path : ':saml2-service-provider-core', configuration : 'tests')
+}
diff --git a/saml2/saml2-service-provider/opensaml4/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationProvider.java b/saml2/saml2-service-provider/opensaml4/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationProvider.java
new file mode 100644
index 00000000000..f66dbe8dc01
--- /dev/null
+++ b/saml2/saml2-service-provider/opensaml4/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationProvider.java
@@ -0,0 +1,765 @@
+/*
+ * Copyright 2002-2021 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.security.saml2.provider.service.authentication;
+
+import java.io.ByteArrayInputStream;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+
+import javax.annotation.Nonnull;
+import javax.xml.namespace.QName;
+
+import net.shibboleth.utilities.java.support.xml.ParserPool;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.opensaml.core.config.ConfigurationService;
+import org.opensaml.core.xml.XMLObject;
+import org.opensaml.core.xml.config.XMLObjectProviderRegistry;
+import org.opensaml.core.xml.schema.XSAny;
+import org.opensaml.core.xml.schema.XSBoolean;
+import org.opensaml.core.xml.schema.XSBooleanValue;
+import org.opensaml.core.xml.schema.XSDateTime;
+import org.opensaml.core.xml.schema.XSInteger;
+import org.opensaml.core.xml.schema.XSString;
+import org.opensaml.core.xml.schema.XSURI;
+import org.opensaml.saml.common.assertion.ValidationContext;
+import org.opensaml.saml.common.assertion.ValidationResult;
+import org.opensaml.saml.saml2.assertion.ConditionValidator;
+import org.opensaml.saml.saml2.assertion.SAML20AssertionValidator;
+import org.opensaml.saml.saml2.assertion.SAML2AssertionValidationParameters;
+import org.opensaml.saml.saml2.assertion.StatementValidator;
+import org.opensaml.saml.saml2.assertion.SubjectConfirmationValidator;
+import org.opensaml.saml.saml2.assertion.impl.AudienceRestrictionConditionValidator;
+import org.opensaml.saml.saml2.assertion.impl.BearerSubjectConfirmationValidator;
+import org.opensaml.saml.saml2.assertion.impl.DelegationRestrictionConditionValidator;
+import org.opensaml.saml.saml2.core.Assertion;
+import org.opensaml.saml.saml2.core.Attribute;
+import org.opensaml.saml.saml2.core.AttributeStatement;
+import org.opensaml.saml.saml2.core.Condition;
+import org.opensaml.saml.saml2.core.EncryptedAssertion;
+import org.opensaml.saml.saml2.core.OneTimeUse;
+import org.opensaml.saml.saml2.core.Response;
+import org.opensaml.saml.saml2.core.SubjectConfirmation;
+import org.opensaml.saml.saml2.core.impl.ResponseUnmarshaller;
+import org.opensaml.saml.saml2.encryption.Decrypter;
+import org.opensaml.saml.security.impl.SAMLSignatureProfileValidator;
+import org.opensaml.xmlsec.signature.support.SignaturePrevalidator;
+import org.opensaml.xmlsec.signature.support.SignatureTrustEngine;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.core.log.LogMessage;
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.authority.AuthorityUtils;
+import org.springframework.security.saml2.Saml2Exception;
+import org.springframework.security.saml2.core.OpenSamlInitializationService;
+import org.springframework.security.saml2.core.Saml2Error;
+import org.springframework.security.saml2.core.Saml2ErrorCodes;
+import org.springframework.security.saml2.core.Saml2ResponseValidatorResult;
+import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
+import org.springframework.util.Assert;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * Implementation of {@link AuthenticationProvider} for SAML authentications when
+ * receiving a {@code Response} object containing an {@code Assertion}. This
+ * implementation uses the {@code OpenSAML 4} library.
+ *
+ * 

+ * The {@link OpenSaml4AuthenticationProvider} supports {@link Saml2AuthenticationToken} + * objects that contain a SAML response in its decoded XML format + * {@link Saml2AuthenticationToken#getSaml2Response()} along with the information about + * the asserting party, the identity provider (IDP), as well as the relying party, the + * service provider (SP, this application). + *

+ * The {@link Saml2AuthenticationToken} will be processed into a SAML Response object. The + * SAML response object can be signed. If the Response is signed, a signature will not be + * required on the assertion. + *

+ * While a response object can contain a list of assertion, this provider will only + * leverage the first valid assertion for the purpose of authentication. Assertions that + * do not pass validation will be ignored. If no valid assertions are found a + * {@link Saml2AuthenticationException} is thrown. + *

+ * This provider supports two types of encrypted SAML elements + *

+ * If the assertion is encrypted, then signature validation on the assertion is no longer + * required. + *

+ * This provider does not perform an X509 certificate validation on the configured + * asserting party, IDP, verification certificates. + * + * @author Josh Cummings + * @since 5.5 + * @see SAML 2 + * StatusResponse + * @see OpenSAML 3 + */ +public final class OpenSaml4AuthenticationProvider implements AuthenticationProvider { + + static { + OpenSamlInitializationService.initialize(); + } + + private final Log logger = LogFactory.getLog(this.getClass()); + + private final ResponseUnmarshaller responseUnmarshaller; + + private final ParserPool parserPool; + + private final Converter responseSignatureValidator = createDefaultResponseSignatureValidator(); + + private Consumer responseElementsDecrypter = createDefaultResponseElementsDecrypter(); + + private final Converter responseValidator = createDefaultResponseValidator(); + + private final Converter assertionSignatureValidator = createDefaultAssertionSignatureValidator(); + + private Consumer assertionElementsDecrypter = createDefaultAssertionElementsDecrypter(); + + private Converter assertionValidator = createDefaultAssertionValidator(); + + private Converter responseAuthenticationConverter = createDefaultResponseAuthenticationConverter(); + + /** + * Creates an {@link OpenSaml4AuthenticationProvider} + */ + public OpenSaml4AuthenticationProvider() { + XMLObjectProviderRegistry registry = ConfigurationService.get(XMLObjectProviderRegistry.class); + this.responseUnmarshaller = (ResponseUnmarshaller) registry.getUnmarshallerFactory() + .getUnmarshaller(Response.DEFAULT_ELEMENT_NAME); + this.parserPool = registry.getParserPool(); + } + + /** + * Set the {@link Consumer} strategy to use for decrypting elements of a validated + * {@link Response}. The default strategy decrypts all {@link EncryptedAssertion}s + * using OpenSAML's {@link Decrypter}, adding the results to + * {@link Response#getAssertions()}. + * + * You can use this method to configure the {@link Decrypter} instance like so: + * + *

+	 *	OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
+	 *	provider.setResponseElementsDecrypter((responseToken) -> {
+	 *	    DecrypterParameters parameters = new DecrypterParameters();
+	 *	    // ... set parameters as needed
+	 *	    Decrypter decrypter = new Decrypter(parameters);
+	 *		Response response = responseToken.getResponse();
+	 *  	EncryptedAssertion encrypted = response.getEncryptedAssertions().get(0);
+	 *  	try {
+	 *  		Assertion assertion = decrypter.decrypt(encrypted);
+	 *  		response.getAssertions().add(assertion);
+	 *  	} catch (Exception e) {
+	 *  	 	throw new Saml2AuthenticationException(...);
+	 *  	}
+	 *	});
+	 * 
+ * + * Or, in the event that you have your own custom decryption interface, the same + * pattern applies: + * + *
+	 *	OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
+	 *	Converter<EncryptedAssertion, Assertion> myService = ...
+	 *	provider.setResponseDecrypter((responseToken) -> {
+	 *	   Response response = responseToken.getResponse();
+	 *	   response.getEncryptedAssertions().stream()
+	 *	   		.map(service::decrypt).forEach(response.getAssertions()::add);
+	 *	});
+	 * 
+ * + * This is valuable when using an external service to perform the decryption. + * @param responseElementsDecrypter the {@link Consumer} for decrypting response + * elements + * @since 5.5 + */ + public void setResponseElementsDecrypter(Consumer responseElementsDecrypter) { + Assert.notNull(responseElementsDecrypter, "responseElementsDecrypter cannot be null"); + this.responseElementsDecrypter = responseElementsDecrypter; + } + + /** + * Set the {@link Converter} to use for validating each {@link Assertion} in the SAML + * 2.0 Response. + * + * You can still invoke the default validator by delgating to + * {@link #createAssertionValidator}, like so: + * + *
+	 *	OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
+	 *  provider.setAssertionValidator(assertionToken -> {
+	 *		Saml2ResponseValidatorResult result = createDefaultAssertionValidator()
+	 *			.convert(assertionToken)
+	 *		return result.concat(myCustomValidator.convert(assertionToken));
+	 *  });
+	 * 
+ * + * You can also use this method to configure the provider to use a different + * {@link ValidationContext} from the default, like so: + * + *
+	 *	OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
+	 *	provider.setAssertionValidator(
+	 *		createDefaultAssertionValidator(assertionToken -> {
+	 *			Map<String, Object> params = new HashMap<>();
+	 *			params.put(CLOCK_SKEW, 2 * 60 * 1000);
+	 *			// other parameters
+	 *			return new ValidationContext(params);
+	 *		}));
+	 * 
+ * + * Consider taking a look at {@link #createValidationContext} to see how it constructs + * a {@link ValidationContext}. + * + * It is not necessary to delegate to the default validator. You can safely replace it + * entirely with your own. Note that signature verification is performed as a separate + * step from this validator. + * @param assertionValidator the validator to use + * @since 5.4 + */ + public void setAssertionValidator(Converter assertionValidator) { + Assert.notNull(assertionValidator, "assertionValidator cannot be null"); + this.assertionValidator = assertionValidator; + } + + /** + * Set the {@link Consumer} strategy to use for decrypting elements of a validated + * {@link Assertion}. + * + * You can use this method to configure the {@link Decrypter} used like so: + * + *
+	 *	OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
+	 *	provider.setResponseDecrypter((assertionToken) -> {
+	 *	    DecrypterParameters parameters = new DecrypterParameters();
+	 *	    // ... set parameters as needed
+	 *	    Decrypter decrypter = new Decrypter(parameters);
+	 *		Assertion assertion = assertionToken.getAssertion();
+	 *  	EncryptedID encrypted = assertion.getSubject().getEncryptedID();
+	 *  	try {
+	 *  		NameID name = decrypter.decrypt(encrypted);
+	 *  		assertion.getSubject().setNameID(name);
+	 *  	} catch (Exception e) {
+	 *  	 	throw new Saml2AuthenticationException(...);
+	 *  	}
+	 *	});
+	 * 
+ * + * Or, in the event that you have your own custom interface, the same pattern applies: + * + *
+	 *	OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
+	 *	MyDecryptionService myService = ...
+	 *	provider.setResponseDecrypter((responseToken) -> {
+	 *	   	Assertion assertion = assertionToken.getAssertion();
+	 *	   	EncryptedID encrypted = assertion.getSubject().getEncryptedID();
+	 *		NameID name = myService.decrypt(encrypted);
+	 *		assertion.getSubject().setNameID(name);
+	 *	});
+	 * 
+ * @param assertionDecrypter the {@link Consumer} for decrypting assertion elements + * @since 5.5 + */ + public void setAssertionElementsDecrypter(Consumer assertionDecrypter) { + Assert.notNull(assertionDecrypter, "assertionDecrypter cannot be null"); + this.assertionElementsDecrypter = assertionDecrypter; + } + + /** + * Set the {@link Converter} to use for converting a validated {@link Response} into + * an {@link AbstractAuthenticationToken}. + * + * You can delegate to the default behavior by calling + * {@link #createDefaultResponseAuthenticationConverter()} like so: + * + *
+	 *	OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
+	 * 	Converter<ResponseToken, Saml2Authentication> authenticationConverter =
+	 * 			createDefaultResponseAuthenticationConverter();
+	 *	provider.setResponseAuthenticationConverter(responseToken -> {
+	 *		Saml2Authentication authentication = authenticationConverter.convert(responseToken);
+	 *		User user = myUserRepository.findByUsername(authentication.getName());
+	 *		return new MyAuthentication(authentication, user);
+	 *	});
+	 * 
+ * @param responseAuthenticationConverter the {@link Converter} to use + * @since 5.4 + */ + public void setResponseAuthenticationConverter( + Converter responseAuthenticationConverter) { + Assert.notNull(responseAuthenticationConverter, "responseAuthenticationConverter cannot be null"); + this.responseAuthenticationConverter = responseAuthenticationConverter; + } + + /** + * Construct a default strategy for validating each SAML 2.0 Assertion and associated + * {@link Authentication} token + * @return the default assertion validator strategy + */ + public static Converter createDefaultAssertionValidator() { + + return createAssertionValidator(Saml2ErrorCodes.INVALID_ASSERTION, + (assertionToken) -> SAML20AssertionValidators.attributeValidator, + (assertionToken) -> createValidationContext(assertionToken, (params) -> params + .put(SAML2AssertionValidationParameters.CLOCK_SKEW, Duration.ofMinutes(5).toMillis()))); + } + + /** + * Construct a default strategy for validating each SAML 2.0 Assertion and associated + * {@link Authentication} token + * @param contextConverter the conversion strategy to use to generate a + * {@link ValidationContext} for each assertion being validated + * @return the default assertion validator strategy + */ + public static Converter createDefaultAssertionValidator( + Converter contextConverter) { + + return createAssertionValidator(Saml2ErrorCodes.INVALID_ASSERTION, + (assertionToken) -> SAML20AssertionValidators.attributeValidator, contextConverter); + } + + /** + * Construct a default strategy for converting a SAML 2.0 Response and + * {@link Authentication} token into a {@link Saml2Authentication} + * @return the default response authentication converter strategy + */ + public static Converter createDefaultResponseAuthenticationConverter() { + return (responseToken) -> { + Response response = responseToken.response; + Saml2AuthenticationToken token = responseToken.token; + Assertion assertion = CollectionUtils.firstElement(response.getAssertions()); + String username = assertion.getSubject().getNameID().getValue(); + Map> attributes = getAssertionAttributes(assertion); + return new Saml2Authentication(new DefaultSaml2AuthenticatedPrincipal(username, attributes), + token.getSaml2Response(), AuthorityUtils.createAuthorityList("ROLE_USER")); + }; + } + + /** + * @param authentication the authentication request object, must be of type + * {@link Saml2AuthenticationToken} + * @return {@link Saml2Authentication} if the assertion is valid + * @throws AuthenticationException if a validation exception occurs + */ + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + try { + Saml2AuthenticationToken token = (Saml2AuthenticationToken) authentication; + String serializedResponse = token.getSaml2Response(); + Response response = parse(serializedResponse); + process(token, response); + return this.responseAuthenticationConverter.convert(new ResponseToken(response, token)); + } + catch (Saml2AuthenticationException ex) { + throw ex; + } + catch (Exception ex) { + throw createAuthenticationException(Saml2ErrorCodes.INTERNAL_VALIDATION_ERROR, ex.getMessage(), ex); + } + } + + @Override + public boolean supports(Class authentication) { + return authentication != null && Saml2AuthenticationToken.class.isAssignableFrom(authentication); + } + + private Response parse(String response) throws Saml2Exception, Saml2AuthenticationException { + try { + Document document = this.parserPool + .parse(new ByteArrayInputStream(response.getBytes(StandardCharsets.UTF_8))); + Element element = document.getDocumentElement(); + return (Response) this.responseUnmarshaller.unmarshall(element); + } + catch (Exception ex) { + throw createAuthenticationException(Saml2ErrorCodes.MALFORMED_RESPONSE_DATA, ex.getMessage(), ex); + } + } + + private void process(Saml2AuthenticationToken token, Response response) { + String issuer = response.getIssuer().getValue(); + this.logger.debug(LogMessage.format("Processing SAML response from %s", issuer)); + boolean responseSigned = response.isSigned(); + + ResponseToken responseToken = new ResponseToken(response, token); + Saml2ResponseValidatorResult result = this.responseSignatureValidator.convert(responseToken); + if (responseSigned) { + this.responseElementsDecrypter.accept(responseToken); + } + result = result.concat(this.responseValidator.convert(responseToken)); + boolean allAssertionsSigned = true; + for (Assertion assertion : response.getAssertions()) { + AssertionToken assertionToken = new AssertionToken(assertion, token); + result = result.concat(this.assertionSignatureValidator.convert(assertionToken)); + allAssertionsSigned = allAssertionsSigned && assertion.isSigned(); + if (responseSigned || assertion.isSigned()) { + this.assertionElementsDecrypter.accept(new AssertionToken(assertion, token)); + } + result = result.concat(this.assertionValidator.convert(assertionToken)); + } + if (!responseSigned && !allAssertionsSigned) { + String description = "Either the response or one of the assertions is unsigned. " + + "Please either sign the response or all of the assertions."; + throw createAuthenticationException(Saml2ErrorCodes.INVALID_SIGNATURE, description, null); + } + Assertion firstAssertion = CollectionUtils.firstElement(response.getAssertions()); + if (!hasName(firstAssertion)) { + Saml2Error error = new Saml2Error(Saml2ErrorCodes.SUBJECT_NOT_FOUND, + "Assertion [" + firstAssertion.getID() + "] is missing a subject"); + result = result.concat(error); + } + + if (result.hasErrors()) { + Collection errors = result.getErrors(); + if (this.logger.isTraceEnabled()) { + this.logger.debug("Found " + errors.size() + " validation errors in SAML response [" + response.getID() + + "]: " + errors); + } + else if (this.logger.isDebugEnabled()) { + this.logger.debug( + "Found " + errors.size() + " validation errors in SAML response [" + response.getID() + "]"); + } + Saml2Error first = errors.iterator().next(); + throw createAuthenticationException(first.getErrorCode(), first.getDescription(), null); + } + else { + if (this.logger.isDebugEnabled()) { + this.logger.debug("Successfully processed SAML Response [" + response.getID() + "]"); + } + } + } + + private Converter createDefaultResponseSignatureValidator() { + return (responseToken) -> { + Response response = responseToken.getResponse(); + RelyingPartyRegistration registration = responseToken.getToken().getRelyingPartyRegistration(); + if (response.isSigned()) { + return OpenSamlVerificationUtils.verifySignature(response, registration).post(response.getSignature()); + } + return Saml2ResponseValidatorResult.success(); + }; + } + + private Consumer createDefaultResponseElementsDecrypter() { + return (responseToken) -> { + Response response = responseToken.getResponse(); + RelyingPartyRegistration registration = responseToken.getToken().getRelyingPartyRegistration(); + try { + OpenSamlDecryptionUtils.decryptResponseElements(response, registration); + } + catch (Exception ex) { + throw createAuthenticationException(Saml2ErrorCodes.DECRYPTION_ERROR, ex.getMessage(), ex); + } + }; + } + + private Converter createDefaultResponseValidator() { + return (responseToken) -> { + Response response = responseToken.getResponse(); + Saml2AuthenticationToken token = responseToken.getToken(); + Saml2ResponseValidatorResult result = Saml2ResponseValidatorResult.success(); + String issuer = response.getIssuer().getValue(); + String destination = response.getDestination(); + String location = token.getRelyingPartyRegistration().getAssertionConsumerServiceLocation(); + if (StringUtils.hasText(destination) && !destination.equals(location)) { + String message = "Invalid destination [" + destination + "] for SAML response [" + response.getID() + + "]"; + result = result.concat(new Saml2Error(Saml2ErrorCodes.INVALID_DESTINATION, message)); + } + String assertingPartyEntityId = token.getRelyingPartyRegistration().getAssertingPartyDetails() + .getEntityId(); + if (!StringUtils.hasText(issuer) || !issuer.equals(assertingPartyEntityId)) { + String message = String.format("Invalid issuer [%s] for SAML response [%s]", issuer, response.getID()); + result = result.concat(new Saml2Error(Saml2ErrorCodes.INVALID_ISSUER, message)); + } + if (response.getAssertions().isEmpty()) { + throw createAuthenticationException(Saml2ErrorCodes.MALFORMED_RESPONSE_DATA, + "No assertions found in response.", null); + } + return result; + }; + } + + private Converter createDefaultAssertionSignatureValidator() { + return createAssertionValidator(Saml2ErrorCodes.INVALID_SIGNATURE, (assertionToken) -> { + RelyingPartyRegistration registration = assertionToken.getToken().getRelyingPartyRegistration(); + SignatureTrustEngine engine = OpenSamlVerificationUtils.trustEngine(registration); + return SAML20AssertionValidators.createSignatureValidator(engine); + }, (assertionToken) -> new ValidationContext( + Collections.singletonMap(SAML2AssertionValidationParameters.SIGNATURE_REQUIRED, false))); + } + + private Consumer createDefaultAssertionElementsDecrypter() { + return (assertionToken) -> { + Assertion assertion = assertionToken.getAssertion(); + RelyingPartyRegistration registration = assertionToken.getToken().getRelyingPartyRegistration(); + try { + OpenSamlDecryptionUtils.decryptAssertionElements(assertion, registration); + } + catch (Exception ex) { + throw createAuthenticationException(Saml2ErrorCodes.DECRYPTION_ERROR, ex.getMessage(), ex); + } + }; + } + + private boolean hasName(Assertion assertion) { + if (assertion == null) { + return false; + } + if (assertion.getSubject() == null) { + return false; + } + if (assertion.getSubject().getNameID() == null) { + return false; + } + return assertion.getSubject().getNameID().getValue() != null; + } + + private static Map> getAssertionAttributes(Assertion assertion) { + Map> attributeMap = new LinkedHashMap<>(); + for (AttributeStatement attributeStatement : assertion.getAttributeStatements()) { + for (Attribute attribute : attributeStatement.getAttributes()) { + List attributeValues = new ArrayList<>(); + for (XMLObject xmlObject : attribute.getAttributeValues()) { + Object attributeValue = getXmlObjectValue(xmlObject); + if (attributeValue != null) { + attributeValues.add(attributeValue); + } + } + attributeMap.put(attribute.getName(), attributeValues); + } + } + return attributeMap; + } + + private static Object getXmlObjectValue(XMLObject xmlObject) { + if (xmlObject instanceof XSAny) { + return ((XSAny) xmlObject).getTextContent(); + } + if (xmlObject instanceof XSString) { + return ((XSString) xmlObject).getValue(); + } + if (xmlObject instanceof XSInteger) { + return ((XSInteger) xmlObject).getValue(); + } + if (xmlObject instanceof XSURI) { + return ((XSURI) xmlObject).getURI(); + } + if (xmlObject instanceof XSBoolean) { + XSBooleanValue xsBooleanValue = ((XSBoolean) xmlObject).getValue(); + return (xsBooleanValue != null) ? xsBooleanValue.getValue() : null; + } + if (xmlObject instanceof XSDateTime) { + return ((XSDateTime) xmlObject).getValue(); + } + return null; + } + + private static Saml2AuthenticationException createAuthenticationException(String code, String message, + Exception cause) { + return new Saml2AuthenticationException(new Saml2Error(code, message), cause); + } + + private static Converter createAssertionValidator(String errorCode, + Converter validatorConverter, + Converter contextConverter) { + + return (assertionToken) -> { + Assertion assertion = assertionToken.assertion; + SAML20AssertionValidator validator = validatorConverter.convert(assertionToken); + ValidationContext context = contextConverter.convert(assertionToken); + try { + ValidationResult result = validator.validate(assertion, context); + if (result == ValidationResult.VALID) { + return Saml2ResponseValidatorResult.success(); + } + } + catch (Exception ex) { + String message = String.format("Invalid assertion [%s] for SAML response [%s]: %s", assertion.getID(), + ((Response) assertion.getParent()).getID(), ex.getMessage()); + return Saml2ResponseValidatorResult.failure(new Saml2Error(errorCode, message)); + } + String message = String.format("Invalid assertion [%s] for SAML response [%s]: %s", assertion.getID(), + ((Response) assertion.getParent()).getID(), context.getValidationFailureMessage()); + return Saml2ResponseValidatorResult.failure(new Saml2Error(errorCode, message)); + }; + } + + private static ValidationContext createValidationContext(AssertionToken assertionToken, + Consumer> paramsConsumer) { + String audience = assertionToken.token.getRelyingPartyRegistration().getEntityId(); + String recipient = assertionToken.token.getRelyingPartyRegistration().getAssertionConsumerServiceLocation(); + Map params = new HashMap<>(); + params.put(SAML2AssertionValidationParameters.COND_VALID_AUDIENCES, Collections.singleton(audience)); + params.put(SAML2AssertionValidationParameters.SC_VALID_RECIPIENTS, Collections.singleton(recipient)); + paramsConsumer.accept(params); + return new ValidationContext(params); + } + + private static class SAML20AssertionValidators { + + private static final Collection conditions = new ArrayList<>(); + + private static final Collection subjects = new ArrayList<>(); + + private static final Collection statements = new ArrayList<>(); + + private static final SignaturePrevalidator validator = new SAMLSignatureProfileValidator(); + + static { + conditions.add(new AudienceRestrictionConditionValidator()); + conditions.add(new DelegationRestrictionConditionValidator()); + conditions.add(new ConditionValidator() { + @Nonnull + @Override + public QName getServicedCondition() { + return OneTimeUse.DEFAULT_ELEMENT_NAME; + } + + @Nonnull + @Override + public ValidationResult validate(Condition condition, Assertion assertion, ValidationContext context) { + // applications should validate their own OneTimeUse conditions + return ValidationResult.VALID; + } + }); + subjects.add(new BearerSubjectConfirmationValidator() { + @Override + protected ValidationResult validateAddress(SubjectConfirmation confirmation, Assertion assertion, + ValidationContext context, boolean required) { + // applications should validate their own addresses - gh-7514 + return ValidationResult.VALID; + } + + @Override + protected ValidationResult validateInResponseTo(SubjectConfirmation confirmation, Assertion assertion, + ValidationContext context, boolean required) { + // applications should validate their own in response to + return ValidationResult.VALID; + } + }); + } + + private static final SAML20AssertionValidator attributeValidator = new SAML20AssertionValidator(conditions, + subjects, statements, null, null, null) { + @Nonnull + @Override + protected ValidationResult validateSignature(Assertion token, ValidationContext context) { + return ValidationResult.VALID; + } + }; + + static SAML20AssertionValidator createSignatureValidator(SignatureTrustEngine engine) { + return new SAML20AssertionValidator(new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), null, engine, + validator) { + @Nonnull + @Override + protected ValidationResult validateConditions(Assertion assertion, ValidationContext context) { + return ValidationResult.VALID; + } + + @Nonnull + @Override + protected ValidationResult validateSubjectConfirmation(Assertion assertion, ValidationContext context) { + return ValidationResult.VALID; + } + + @Nonnull + @Override + protected ValidationResult validateStatements(Assertion assertion, ValidationContext context) { + return ValidationResult.VALID; + } + }; + + } + + } + + /** + * A tuple containing an OpenSAML {@link Response} and its associated authentication + * token. + * + * @since 5.4 + */ + public static class ResponseToken { + + private final Saml2AuthenticationToken token; + + private final Response response; + + ResponseToken(Response response, Saml2AuthenticationToken token) { + this.token = token; + this.response = response; + } + + public Response getResponse() { + return this.response; + } + + public Saml2AuthenticationToken getToken() { + return this.token; + } + + } + + /** + * A tuple containing an OpenSAML {@link Assertion} and its associated authentication + * token. + * + * @since 5.4 + */ + public static class AssertionToken { + + private final Saml2AuthenticationToken token; + + private final Assertion assertion; + + AssertionToken(Assertion assertion, Saml2AuthenticationToken token) { + this.token = token; + this.assertion = assertion; + } + + public Assertion getAssertion() { + return this.assertion; + } + + public Saml2AuthenticationToken getToken() { + return this.token; + } + + } + +} diff --git a/saml2/saml2-service-provider/opensaml4/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationRequestFactory.java b/saml2/saml2-service-provider/opensaml4/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationRequestFactory.java new file mode 100644 index 00000000000..30a3af18351 --- /dev/null +++ b/saml2/saml2-service-provider/opensaml4/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationRequestFactory.java @@ -0,0 +1,180 @@ +/* + * Copyright 2002-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.saml2.provider.service.authentication; + +import java.nio.charset.StandardCharsets; +import java.time.Clock; +import java.time.Instant; +import java.util.Map; +import java.util.UUID; + +import org.opensaml.core.config.ConfigurationService; +import org.opensaml.core.xml.config.XMLObjectProviderRegistry; +import org.opensaml.saml.common.xml.SAMLConstants; +import org.opensaml.saml.saml2.core.AuthnRequest; +import org.opensaml.saml.saml2.core.Issuer; +import org.opensaml.saml.saml2.core.impl.AuthnRequestBuilder; +import org.opensaml.saml.saml2.core.impl.IssuerBuilder; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.security.saml2.core.OpenSamlInitializationService; +import org.springframework.security.saml2.provider.service.authentication.OpenSamlSigningUtils.QueryParametersPartial; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; +import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * A {@link Saml2AuthenticationRequestFactory} that generates, signs, and serializes a + * SAML 2.0 AuthnRequest using OpenSAML 4 + * + * @author Josh Cummings + * @since 5.5 + */ +public final class OpenSaml4AuthenticationRequestFactory implements Saml2AuthenticationRequestFactory { + + static { + OpenSamlInitializationService.initialize(); + } + + private final AuthnRequestBuilder authnRequestBuilder; + + private final IssuerBuilder issuerBuilder; + + private Clock clock = Clock.systemUTC(); + + private Converter authenticationRequestContextConverter; + + /** + * Creates an {@link OpenSaml4AuthenticationRequestFactory} + */ + public OpenSaml4AuthenticationRequestFactory() { + this.authenticationRequestContextConverter = this::createAuthnRequest; + XMLObjectProviderRegistry registry = ConfigurationService.get(XMLObjectProviderRegistry.class); + this.authnRequestBuilder = (AuthnRequestBuilder) registry.getBuilderFactory() + .getBuilder(AuthnRequest.DEFAULT_ELEMENT_NAME); + this.issuerBuilder = (IssuerBuilder) registry.getBuilderFactory().getBuilder(Issuer.DEFAULT_ELEMENT_NAME); + } + + /** + * {@inheritDoc} + */ + @Override + @Deprecated + public String createAuthenticationRequest(Saml2AuthenticationRequest request) { + RelyingPartyRegistration registration = RelyingPartyRegistration.withRegistrationId("noId") + .assertionConsumerServiceBinding(Saml2MessageBinding.POST) + .assertionConsumerServiceLocation(request.getAssertionConsumerServiceUrl()) + .entityId(request.getIssuer()).remoteIdpEntityId("noIssuer").idpWebSsoUrl("noUrl") + .credentials((credentials) -> credentials.addAll(request.getCredentials())).build(); + Saml2AuthenticationRequestContext context = Saml2AuthenticationRequestContext.builder() + .relyingPartyRegistration(registration).issuer(request.getIssuer()) + .assertionConsumerServiceUrl(request.getAssertionConsumerServiceUrl()).build(); + AuthnRequest authnRequest = this.authenticationRequestContextConverter.convert(context); + return OpenSamlSigningUtils.serialize(OpenSamlSigningUtils.sign(authnRequest, registration)); + } + + /** + * {@inheritDoc} + */ + @Override + public Saml2PostAuthenticationRequest createPostAuthenticationRequest(Saml2AuthenticationRequestContext context) { + AuthnRequest authnRequest = this.authenticationRequestContextConverter.convert(context); + RelyingPartyRegistration registration = context.getRelyingPartyRegistration(); + if (registration.getAssertingPartyDetails().getWantAuthnRequestsSigned()) { + OpenSamlSigningUtils.sign(authnRequest, registration); + } + String xml = OpenSamlSigningUtils.serialize(authnRequest); + return Saml2PostAuthenticationRequest.withAuthenticationRequestContext(context) + .samlRequest(Saml2Utils.samlEncode(xml.getBytes(StandardCharsets.UTF_8))).build(); + } + + /** + * {@inheritDoc} + */ + @Override + public Saml2RedirectAuthenticationRequest createRedirectAuthenticationRequest( + Saml2AuthenticationRequestContext context) { + AuthnRequest authnRequest = this.authenticationRequestContextConverter.convert(context); + RelyingPartyRegistration registration = context.getRelyingPartyRegistration(); + String xml = OpenSamlSigningUtils.serialize(authnRequest); + Saml2RedirectAuthenticationRequest.Builder result = Saml2RedirectAuthenticationRequest + .withAuthenticationRequestContext(context); + String deflatedAndEncoded = Saml2Utils.samlEncode(Saml2Utils.samlDeflate(xml)); + result.samlRequest(deflatedAndEncoded).relayState(context.getRelayState()); + if (registration.getAssertingPartyDetails().getWantAuthnRequestsSigned()) { + QueryParametersPartial partial = OpenSamlSigningUtils.sign(registration).param("SAMLRequest", + deflatedAndEncoded); + if (StringUtils.hasText(context.getRelayState())) { + partial.param("RelayState", context.getRelayState()); + } + Map parameters = partial.parameters(); + return result.sigAlg(parameters.get("SigAlg")).signature(parameters.get("Signature")).build(); + } + return result.build(); + } + + private AuthnRequest createAuthnRequest(Saml2AuthenticationRequestContext context) { + String issuer = context.getIssuer(); + String destination = context.getDestination(); + String assertionConsumerServiceUrl = context.getAssertionConsumerServiceUrl(); + String protocolBinding = context.getRelyingPartyRegistration().getAssertionConsumerServiceBinding().getUrn(); + AuthnRequest auth = this.authnRequestBuilder.buildObject(); + if (auth.getID() == null) { + auth.setID("ARQ" + UUID.randomUUID().toString().substring(1)); + } + if (auth.getIssueInstant() == null) { + auth.setIssueInstant(Instant.now(this.clock)); + } + if (auth.isForceAuthn() == null) { + auth.setForceAuthn(Boolean.FALSE); + } + if (auth.isPassive() == null) { + auth.setIsPassive(Boolean.FALSE); + } + if (auth.getProtocolBinding() == null) { + auth.setProtocolBinding(SAMLConstants.SAML2_POST_BINDING_URI); + } + auth.setProtocolBinding(protocolBinding); + Issuer iss = this.issuerBuilder.buildObject(); + iss.setValue(issuer); + auth.setIssuer(iss); + auth.setDestination(destination); + auth.setAssertionConsumerServiceURL(assertionConsumerServiceUrl); + return auth; + } + + /** + * Set the strategy for building an {@link AuthnRequest} from a given context + * @param authenticationRequestContextConverter the conversion strategy to use + */ + public void setAuthenticationRequestContextConverter( + Converter authenticationRequestContextConverter) { + Assert.notNull(authenticationRequestContextConverter, "authenticationRequestContextConverter cannot be null"); + this.authenticationRequestContextConverter = authenticationRequestContextConverter; + } + + /** + * Use this {@link Clock} with {@link Instant#now()} for generating timestamps + * @param clock the {@link Clock} to use + */ + public void setClock(Clock clock) { + Assert.notNull(clock, "clock cannot be null"); + this.clock = clock; + } + +} diff --git a/saml2/saml2-service-provider/opensaml4/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationProviderTests.java b/saml2/saml2-service-provider/opensaml4/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationProviderTests.java new file mode 100644 index 00000000000..bc94bc3b2b8 --- /dev/null +++ b/saml2/saml2-service-provider/opensaml4/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationProviderTests.java @@ -0,0 +1,661 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.saml2.provider.service.authentication; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.time.Duration; +import java.time.Instant; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +import javax.xml.namespace.QName; + +import net.shibboleth.utilities.java.support.xml.SerializeSupport; +import org.junit.Test; +import org.opensaml.core.xml.XMLObject; +import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; +import org.opensaml.core.xml.io.Marshaller; +import org.opensaml.core.xml.io.MarshallingException; +import org.opensaml.core.xml.schema.XSDateTime; +import org.opensaml.core.xml.schema.impl.XSDateTimeBuilder; +import org.opensaml.saml.common.assertion.ValidationContext; +import org.opensaml.saml.saml2.assertion.SAML2AssertionValidationParameters; +import org.opensaml.saml.saml2.core.Assertion; +import org.opensaml.saml.saml2.core.Attribute; +import org.opensaml.saml.saml2.core.AttributeStatement; +import org.opensaml.saml.saml2.core.AttributeValue; +import org.opensaml.saml.saml2.core.Conditions; +import org.opensaml.saml.saml2.core.EncryptedAssertion; +import org.opensaml.saml.saml2.core.EncryptedAttribute; +import org.opensaml.saml.saml2.core.EncryptedID; +import org.opensaml.saml.saml2.core.NameID; +import org.opensaml.saml.saml2.core.OneTimeUse; +import org.opensaml.saml.saml2.core.Response; +import org.opensaml.saml.saml2.core.SubjectConfirmation; +import org.opensaml.saml.saml2.core.SubjectConfirmationData; +import org.opensaml.saml.saml2.core.impl.AttributeBuilder; +import org.opensaml.saml.saml2.core.impl.EncryptedAssertionBuilder; +import org.opensaml.saml.saml2.core.impl.EncryptedIDBuilder; +import org.opensaml.saml.saml2.core.impl.NameIDBuilder; +import org.opensaml.xmlsec.encryption.impl.EncryptedDataBuilder; +import org.opensaml.xmlsec.signature.support.SignatureConstants; +import org.w3c.dom.Element; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.security.core.Authentication; +import org.springframework.security.saml2.Saml2Exception; +import org.springframework.security.saml2.core.Saml2Error; +import org.springframework.security.saml2.core.Saml2ErrorCodes; +import org.springframework.security.saml2.core.Saml2ResponseValidatorResult; +import org.springframework.security.saml2.core.TestSaml2X509Credentials; +import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider.ResponseToken; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; +import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations; +import org.springframework.util.StringUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +/** + * Tests for {@link OpenSaml4AuthenticationProvider} + * + * @author Filip Hanik + * @author Josh Cummings + */ +public class OpenSaml4AuthenticationProviderTests { + + private static String DESTINATION = "https://localhost/login/saml2/sso/idp-alias"; + + private static String RELYING_PARTY_ENTITY_ID = "https://localhost/saml2/service-provider-metadata/idp-alias"; + + private static String ASSERTING_PARTY_ENTITY_ID = "https://some.idp.test/saml2/idp"; + + private OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider(); + + private Saml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal("name", + Collections.emptyMap()); + + private Saml2Authentication authentication = new Saml2Authentication(this.principal, "response", + Collections.emptyList()); + + @Test + public void supportsWhenSaml2AuthenticationTokenThenReturnTrue() { + assertThat(this.provider.supports(Saml2AuthenticationToken.class)) + .withFailMessage( + OpenSaml4AuthenticationProvider.class + "should support " + Saml2AuthenticationToken.class) + .isTrue(); + } + + @Test + public void supportsWhenNotSaml2AuthenticationTokenThenReturnFalse() { + assertThat(!this.provider.supports(Authentication.class)) + .withFailMessage(OpenSaml4AuthenticationProvider.class + "should not support " + Authentication.class) + .isTrue(); + } + + @Test + public void authenticateWhenUnknownDataClassThenThrowAuthenticationException() { + Assertion assertion = (Assertion) XMLObjectProviderRegistrySupport.getBuilderFactory() + .getBuilder(Assertion.DEFAULT_ELEMENT_NAME).buildObject(Assertion.DEFAULT_ELEMENT_NAME); + assertThatExceptionOfType(Saml2AuthenticationException.class) + .isThrownBy(() -> this.provider.authenticate( + new Saml2AuthenticationToken(verifying(registration()).build(), serialize(assertion)))) + .satisfies(errorOf(Saml2ErrorCodes.MALFORMED_RESPONSE_DATA)); + } + + @Test + public void authenticateWhenXmlErrorThenThrowAuthenticationException() { + Saml2AuthenticationToken token = new Saml2AuthenticationToken(verifying(registration()).build(), "invalid xml"); + assertThatExceptionOfType(Saml2AuthenticationException.class) + .isThrownBy(() -> this.provider.authenticate(token)) + .satisfies(errorOf(Saml2ErrorCodes.MALFORMED_RESPONSE_DATA)); + } + + @Test + public void authenticateWhenInvalidDestinationThenThrowAuthenticationException() { + Response response = response(DESTINATION + "invalid", ASSERTING_PARTY_ENTITY_ID); + response.getAssertions().add(assertion()); + TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(), + RELYING_PARTY_ENTITY_ID); + Saml2AuthenticationToken token = token(response, verifying(registration())); + assertThatExceptionOfType(Saml2AuthenticationException.class) + .isThrownBy(() -> this.provider.authenticate(token)) + .satisfies(errorOf(Saml2ErrorCodes.INVALID_DESTINATION)); + } + + @Test + public void authenticateWhenNoAssertionsPresentThenThrowAuthenticationException() { + Saml2AuthenticationToken token = token(); + assertThatExceptionOfType(Saml2AuthenticationException.class) + .isThrownBy(() -> this.provider.authenticate(token)) + .satisfies(errorOf(Saml2ErrorCodes.MALFORMED_RESPONSE_DATA, "No assertions found in response.")); + } + + @Test + public void authenticateWhenInvalidSignatureOnAssertionThenThrowAuthenticationException() { + Response response = response(); + response.getAssertions().add(assertion()); + Saml2AuthenticationToken token = token(response, verifying(registration())); + assertThatExceptionOfType(Saml2AuthenticationException.class) + .isThrownBy(() -> this.provider.authenticate(token)) + .satisfies(errorOf(Saml2ErrorCodes.INVALID_SIGNATURE)); + } + + @Test + public void authenticateWhenOpenSAMLValidationErrorThenThrowAuthenticationException() { + Response response = response(); + Assertion assertion = assertion(); + assertion.getSubject().getSubjectConfirmations().get(0).getSubjectConfirmationData() + .setNotOnOrAfter(Instant.now().minus(Duration.ofDays(3))); + TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.assertingPartySigningCredential(), + RELYING_PARTY_ENTITY_ID); + response.getAssertions().add(assertion); + Saml2AuthenticationToken token = token(response, verifying(registration())); + assertThatExceptionOfType(Saml2AuthenticationException.class) + .isThrownBy(() -> this.provider.authenticate(token)) + .satisfies(errorOf(Saml2ErrorCodes.INVALID_ASSERTION)); + } + + @Test + public void authenticateWhenMissingSubjectThenThrowAuthenticationException() { + Response response = response(); + Assertion assertion = assertion(); + assertion.setSubject(null); + TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.assertingPartySigningCredential(), + RELYING_PARTY_ENTITY_ID); + response.getAssertions().add(assertion); + Saml2AuthenticationToken token = token(response, verifying(registration())); + assertThatExceptionOfType(Saml2AuthenticationException.class) + .isThrownBy(() -> this.provider.authenticate(token)) + .satisfies(errorOf(Saml2ErrorCodes.SUBJECT_NOT_FOUND)); + } + + @Test + public void authenticateWhenUsernameMissingThenThrowAuthenticationException() { + Response response = response(); + Assertion assertion = assertion(); + assertion.getSubject().getNameID().setValue(null); + TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.assertingPartySigningCredential(), + RELYING_PARTY_ENTITY_ID); + response.getAssertions().add(assertion); + Saml2AuthenticationToken token = token(response, verifying(registration())); + assertThatExceptionOfType(Saml2AuthenticationException.class) + .isThrownBy(() -> this.provider.authenticate(token)) + .satisfies(errorOf(Saml2ErrorCodes.SUBJECT_NOT_FOUND)); + } + + @Test + public void authenticateWhenAssertionContainsValidationAddressThenItSucceeds() { + Response response = response(); + Assertion assertion = assertion(); + assertion.getSubject().getSubjectConfirmations() + .forEach((sc) -> sc.getSubjectConfirmationData().setAddress("10.10.10.10")); + TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.assertingPartySigningCredential(), + RELYING_PARTY_ENTITY_ID); + response.getAssertions().add(assertion); + Saml2AuthenticationToken token = token(response, verifying(registration())); + this.provider.authenticate(token); + } + + @Test + public void authenticateWhenAssertionContainsAttributesThenItSucceeds() { + Response response = response(); + Assertion assertion = assertion(); + List attributes = attributeStatements(); + assertion.getAttributeStatements().addAll(attributes); + TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.assertingPartySigningCredential(), + RELYING_PARTY_ENTITY_ID); + response.getAssertions().add(assertion); + Saml2AuthenticationToken token = token(response, verifying(registration())); + Authentication authentication = this.provider.authenticate(token); + Saml2AuthenticatedPrincipal principal = (Saml2AuthenticatedPrincipal) authentication.getPrincipal(); + Map expected = new LinkedHashMap<>(); + expected.put("email", Arrays.asList("john.doe@example.com", "doe.john@example.com")); + expected.put("name", Collections.singletonList("John Doe")); + expected.put("age", Collections.singletonList(21)); + expected.put("website", Collections.singletonList("https://johndoe.com/")); + expected.put("registered", Collections.singletonList(true)); + Instant registeredDate = Instant.parse("1970-01-01T00:00:00Z"); + expected.put("registeredDate", Collections.singletonList(registeredDate)); + assertThat((String) principal.getFirstAttribute("name")).isEqualTo("John Doe"); + assertThat(principal.getAttributes()).isEqualTo(expected); + } + + @Test + public void authenticateWhenEncryptedAssertionWithoutSignatureThenItFails() { + Response response = response(); + EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion(), + TestSaml2X509Credentials.assertingPartyEncryptingCredential()); + response.getEncryptedAssertions().add(encryptedAssertion); + TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(), + RELYING_PARTY_ENTITY_ID); + Saml2AuthenticationToken token = token(response, decrypting(registration())); + assertThatExceptionOfType(Saml2AuthenticationException.class) + .isThrownBy(() -> this.provider.authenticate(token)) + .satisfies(errorOf(Saml2ErrorCodes.INVALID_SIGNATURE)); + } + + @Test + public void authenticateWhenEncryptedAssertionWithSignatureThenItSucceeds() { + Response response = response(); + Assertion assertion = TestOpenSamlObjects.signed(assertion(), + TestSaml2X509Credentials.assertingPartySigningCredential(), RELYING_PARTY_ENTITY_ID); + EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion, + TestSaml2X509Credentials.assertingPartyEncryptingCredential()); + response.getEncryptedAssertions().add(encryptedAssertion); + TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(), + RELYING_PARTY_ENTITY_ID); + Saml2AuthenticationToken token = token(response, decrypting(verifying(registration()))); + this.provider.authenticate(token); + } + + @Test + public void authenticateWhenEncryptedAssertionWithResponseSignatureThenItSucceeds() { + Response response = response(); + EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion(), + TestSaml2X509Credentials.assertingPartyEncryptingCredential()); + response.getEncryptedAssertions().add(encryptedAssertion); + TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(), + RELYING_PARTY_ENTITY_ID); + Saml2AuthenticationToken token = token(response, decrypting(verifying(registration()))); + this.provider.authenticate(token); + } + + @Test + public void authenticateWhenEncryptedNameIdWithSignatureThenItSucceeds() { + Response response = response(); + Assertion assertion = assertion(); + NameID nameId = assertion.getSubject().getNameID(); + EncryptedID encryptedID = TestOpenSamlObjects.encrypted(nameId, + TestSaml2X509Credentials.assertingPartyEncryptingCredential()); + assertion.getSubject().setNameID(null); + assertion.getSubject().setEncryptedID(encryptedID); + response.getAssertions().add(assertion); + TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.assertingPartySigningCredential(), + RELYING_PARTY_ENTITY_ID); + Saml2AuthenticationToken token = token(response, decrypting(verifying(registration()))); + this.provider.authenticate(token); + } + + @Test + public void authenticateWhenEncryptedAttributeThenDecrypts() { + Response response = response(); + Assertion assertion = assertion(); + EncryptedAttribute attribute = TestOpenSamlObjects.encrypted("name", "value", + TestSaml2X509Credentials.assertingPartyEncryptingCredential()); + AttributeStatement statement = build(AttributeStatement.DEFAULT_ELEMENT_NAME); + statement.getEncryptedAttributes().add(attribute); + assertion.getAttributeStatements().add(statement); + response.getAssertions().add(assertion); + TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(), + RELYING_PARTY_ENTITY_ID); + Saml2AuthenticationToken token = token(response, decrypting(verifying(registration()))); + Saml2Authentication authentication = (Saml2Authentication) this.provider.authenticate(token); + Saml2AuthenticatedPrincipal principal = (Saml2AuthenticatedPrincipal) authentication.getPrincipal(); + assertThat(principal.getAttribute("name")).containsExactly("value"); + } + + @Test + public void authenticateWhenDecryptionKeysAreMissingThenThrowAuthenticationException() { + Response response = response(); + EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion(), + TestSaml2X509Credentials.assertingPartyEncryptingCredential()); + response.getEncryptedAssertions().add(encryptedAssertion); + TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(), + RELYING_PARTY_ENTITY_ID); + Saml2AuthenticationToken token = token(response, verifying(registration())); + assertThatExceptionOfType(Saml2AuthenticationException.class) + .isThrownBy(() -> this.provider.authenticate(token)) + .satisfies(errorOf(Saml2ErrorCodes.DECRYPTION_ERROR, "Failed to decrypt EncryptedData")); + } + + @Test + public void authenticateWhenDecryptionKeysAreWrongThenThrowAuthenticationException() { + Response response = response(); + EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion(), + TestSaml2X509Credentials.assertingPartyEncryptingCredential()); + response.getEncryptedAssertions().add(encryptedAssertion); + TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(), + RELYING_PARTY_ENTITY_ID); + Saml2AuthenticationToken token = token(response, registration() + .decryptionX509Credentials((c) -> c.add(TestSaml2X509Credentials.assertingPartyPrivateCredential()))); + assertThatExceptionOfType(Saml2AuthenticationException.class) + .isThrownBy(() -> this.provider.authenticate(token)) + .satisfies(errorOf(Saml2ErrorCodes.DECRYPTION_ERROR, "Failed to decrypt EncryptedData")); + } + + @Test + public void writeObjectWhenTypeIsSaml2AuthenticationThenNoException() throws IOException { + Response response = response(); + Assertion assertion = TestOpenSamlObjects.signed(assertion(), + TestSaml2X509Credentials.assertingPartySigningCredential(), RELYING_PARTY_ENTITY_ID); + EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion, + TestSaml2X509Credentials.assertingPartyEncryptingCredential()); + response.getEncryptedAssertions().add(encryptedAssertion); + TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(), + RELYING_PARTY_ENTITY_ID); + Saml2AuthenticationToken token = token(response, decrypting(verifying(registration()))); + Saml2Authentication authentication = (Saml2Authentication) this.provider.authenticate(token); + // the following code will throw an exception if authentication isn't serializable + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(1024); + ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteStream); + objectOutputStream.writeObject(authentication); + objectOutputStream.flush(); + } + + @Test + public void createDefaultAssertionValidatorWhenAssertionThenValidates() { + Response response = TestOpenSamlObjects.signedResponseWithOneAssertion(); + Assertion assertion = response.getAssertions().get(0); + OpenSaml4AuthenticationProvider.AssertionToken assertionToken = new OpenSaml4AuthenticationProvider.AssertionToken( + assertion, token()); + assertThat( + OpenSaml4AuthenticationProvider.createDefaultAssertionValidator().convert(assertionToken).hasErrors()) + .isFalse(); + } + + @Test + public void authenticateWhenDelegatingToDefaultAssertionValidatorThenUses() { + OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider(); + // @formatter:off + provider.setAssertionValidator((assertionToken) -> OpenSaml4AuthenticationProvider + .createDefaultAssertionValidator((token) -> new ValidationContext()) + .convert(assertionToken) + .concat(new Saml2Error("wrong error", "wrong error")) + ); + // @formatter:on + Response response = response(); + Assertion assertion = assertion(); + OneTimeUse oneTimeUse = build(OneTimeUse.DEFAULT_ELEMENT_NAME); + assertion.getConditions().getConditions().add(oneTimeUse); + response.getAssertions().add(assertion); + TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(), + ASSERTING_PARTY_ENTITY_ID); + Saml2AuthenticationToken token = token(response, verifying(registration())); + // @formatter:off + assertThatExceptionOfType(Saml2AuthenticationException.class) + .isThrownBy(() -> provider.authenticate(token)).isInstanceOf(Saml2AuthenticationException.class) + .satisfies((error) -> assertThat(error.getSaml2Error().getErrorCode()).isEqualTo(Saml2ErrorCodes.INVALID_ASSERTION)); + // @formatter:on + } + + @Test + public void authenticateWhenCustomAssertionValidatorThenUses() { + Converter validator = mock( + Converter.class); + OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider(); + // @formatter:off + provider.setAssertionValidator((assertionToken) -> OpenSaml4AuthenticationProvider.createDefaultAssertionValidator() + .convert(assertionToken) + .concat(validator.convert(assertionToken)) + ); + // @formatter:on + Response response = response(); + Assertion assertion = assertion(); + response.getAssertions().add(assertion); + TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(), + ASSERTING_PARTY_ENTITY_ID); + Saml2AuthenticationToken token = token(response, verifying(registration())); + given(validator.convert(any(OpenSaml4AuthenticationProvider.AssertionToken.class))) + .willReturn(Saml2ResponseValidatorResult.success()); + provider.authenticate(token); + verify(validator).convert(any(OpenSaml4AuthenticationProvider.AssertionToken.class)); + } + + @Test + public void authenticateWhenDefaultConditionValidatorNotUsedThenSignatureStillChecked() { + OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider(); + provider.setAssertionValidator((assertionToken) -> Saml2ResponseValidatorResult.success()); + Response response = response(); + Assertion assertion = assertion(); + TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.relyingPartyDecryptingCredential(), + RELYING_PARTY_ENTITY_ID); // broken + // signature + response.getAssertions().add(assertion); + TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(), + ASSERTING_PARTY_ENTITY_ID); + Saml2AuthenticationToken token = token(response, verifying(registration())); + // @formatter:off + assertThatExceptionOfType(Saml2AuthenticationException.class) + .isThrownBy(() -> provider.authenticate(token)) + .satisfies((error) -> assertThat(error.getSaml2Error().getErrorCode()).isEqualTo(Saml2ErrorCodes.INVALID_SIGNATURE)); + // @formatter:on + } + + @Test + public void authenticateWhenValidationContextCustomizedThenUsers() { + Map parameters = new HashMap<>(); + parameters.put(SAML2AssertionValidationParameters.SC_VALID_RECIPIENTS, Collections.singleton("blah")); + ValidationContext context = mock(ValidationContext.class); + given(context.getStaticParameters()).willReturn(parameters); + OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider(); + provider.setAssertionValidator( + OpenSaml4AuthenticationProvider.createDefaultAssertionValidator((assertionToken) -> context)); + Response response = response(); + Assertion assertion = assertion(); + response.getAssertions().add(assertion); + TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(), + ASSERTING_PARTY_ENTITY_ID); + Saml2AuthenticationToken token = token(response, verifying(registration())); + // @formatter:off + assertThatExceptionOfType(Saml2AuthenticationException.class) + .isThrownBy(() -> provider.authenticate(token)).isInstanceOf(Saml2AuthenticationException.class) + .satisfies((error) -> assertThat(error).hasMessageContaining("Invalid assertion")); + // @formatter:on + verify(context, atLeastOnce()).getStaticParameters(); + } + + @Test + public void authenticateWithSHA1SignatureThenItSucceeds() throws Exception { + Response response = response(); + Assertion assertion = TestOpenSamlObjects.signed(assertion(), + TestSaml2X509Credentials.assertingPartySigningCredential(), RELYING_PARTY_ENTITY_ID, + SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1); + response.getAssertions().add(assertion); + Saml2AuthenticationToken token = token(response, verifying(registration())); + this.provider.authenticate(token); + } + + @Test + public void setAssertionValidatorWhenNullThenIllegalArgument() { + // @formatter:off + assertThatIllegalArgumentException() + .isThrownBy(() -> this.provider.setAssertionValidator(null)); + // @formatter:on + } + + @Test + public void createDefaultResponseAuthenticationConverterWhenResponseThenConverts() { + Response response = TestOpenSamlObjects.signedResponseWithOneAssertion(); + Saml2AuthenticationToken token = token(response, verifying(registration())); + ResponseToken responseToken = new ResponseToken(response, token); + Saml2Authentication authentication = OpenSaml4AuthenticationProvider + .createDefaultResponseAuthenticationConverter().convert(responseToken); + assertThat(authentication.getName()).isEqualTo("test@saml.user"); + } + + @Test + public void authenticateWhenResponseAuthenticationConverterConfiguredThenUses() { + Converter authenticationConverter = mock(Converter.class); + OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider(); + provider.setResponseAuthenticationConverter(authenticationConverter); + Response response = TestOpenSamlObjects.signedResponseWithOneAssertion(); + Saml2AuthenticationToken token = token(response, verifying(registration())); + provider.authenticate(token); + verify(authenticationConverter).convert(any()); + } + + @Test + public void setResponseAuthenticationConverterWhenNullThenIllegalArgument() { + // @formatter:off + assertThatIllegalArgumentException() + .isThrownBy(() -> this.provider.setResponseAuthenticationConverter(null)); + // @formatter:on + } + + @Test + public void setResponseElementsDecrypterWhenNullThenIllegalArgument() { + assertThatIllegalArgumentException().isThrownBy(() -> this.provider.setResponseElementsDecrypter(null)); + } + + @Test + public void setAssertionElementsDecrypterWhenNullThenIllegalArgument() { + assertThatIllegalArgumentException().isThrownBy(() -> this.provider.setAssertionElementsDecrypter(null)); + } + + @Test + public void authenticateWhenCustomResponseElementsDecrypterThenDecryptsResponse() { + Response response = response(); + Assertion assertion = assertion(); + TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.assertingPartySigningCredential(), + RELYING_PARTY_ENTITY_ID); + response.getEncryptedAssertions().add(new EncryptedAssertionBuilder().buildObject()); + TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(), + RELYING_PARTY_ENTITY_ID); + Saml2AuthenticationToken token = token(response, verifying(registration())); + this.provider.setResponseElementsDecrypter((tuple) -> tuple.getResponse().getAssertions().add(assertion)); + Authentication authentication = this.provider.authenticate(token); + assertThat(authentication.getName()).isEqualTo("test@saml.user"); + } + + @Test + public void authenticateWhenCustomAssertionElementsDecrypterThenDecryptsAssertion() { + Response response = response(); + Assertion assertion = assertion(); + EncryptedID id = new EncryptedIDBuilder().buildObject(); + id.setEncryptedData(new EncryptedDataBuilder().buildObject()); + assertion.getSubject().setEncryptedID(id); + TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.assertingPartySigningCredential(), + RELYING_PARTY_ENTITY_ID); + response.getAssertions().add(assertion); + Saml2AuthenticationToken token = token(response, verifying(registration())); + this.provider.setAssertionElementsDecrypter((tuple) -> { + NameID name = new NameIDBuilder().buildObject(); + name.setValue("decrypted name"); + tuple.getAssertion().getSubject().setNameID(name); + }); + Authentication authentication = this.provider.authenticate(token); + assertThat(authentication.getName()).isEqualTo("decrypted name"); + } + + private T build(QName qName) { + return (T) XMLObjectProviderRegistrySupport.getBuilderFactory().getBuilder(qName).buildObject(qName); + } + + private String serialize(XMLObject object) { + try { + Marshaller marshaller = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(object); + Element element = marshaller.marshall(object); + return SerializeSupport.nodeToString(element); + } + catch (MarshallingException ex) { + throw new Saml2Exception(ex); + } + } + + private Consumer errorOf(String errorCode) { + return errorOf(errorCode, null); + } + + private Consumer errorOf(String errorCode, String description) { + return (ex) -> { + assertThat(ex.getSaml2Error().getErrorCode()).isEqualTo(errorCode); + if (StringUtils.hasText(description)) { + assertThat(ex.getSaml2Error().getDescription()).contains(description); + } + }; + } + + private Response response() { + Response response = TestOpenSamlObjects.response(); + response.setIssueInstant(Instant.now()); + return response; + } + + private Response response(String destination, String issuerEntityId) { + Response response = TestOpenSamlObjects.response(destination, issuerEntityId); + response.setIssueInstant(Instant.now()); + return response; + } + + private Assertion assertion() { + Assertion assertion = TestOpenSamlObjects.assertion(); + assertion.setIssueInstant(Instant.now()); + for (SubjectConfirmation confirmation : assertion.getSubject().getSubjectConfirmations()) { + SubjectConfirmationData data = confirmation.getSubjectConfirmationData(); + data.setNotBefore(Instant.now().minus(Duration.ofMillis(5 * 60 * 1000))); + data.setNotOnOrAfter(Instant.now().plus(Duration.ofMillis(5 * 60 * 1000))); + } + Conditions conditions = assertion.getConditions(); + conditions.setNotBefore(Instant.now().minus(Duration.ofMillis(5 * 60 * 1000))); + conditions.setNotOnOrAfter(Instant.now().plus(Duration.ofMillis(5 * 60 * 1000))); + return assertion; + } + + private List attributeStatements() { + List attributeStatements = TestOpenSamlObjects.attributeStatements(); + AttributeBuilder attributeBuilder = new AttributeBuilder(); + Attribute registeredDateAttr = attributeBuilder.buildObject(); + registeredDateAttr.setName("registeredDate"); + XSDateTime registeredDate = new XSDateTimeBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, + XSDateTime.TYPE_NAME); + registeredDate.setValue(Instant.parse("1970-01-01T00:00:00Z")); + registeredDateAttr.getAttributeValues().add(registeredDate); + attributeStatements.iterator().next().getAttributes().add(registeredDateAttr); + return attributeStatements; + } + + private Saml2AuthenticationToken token() { + Response response = response(); + RelyingPartyRegistration registration = verifying(registration()).build(); + return new Saml2AuthenticationToken(registration, serialize(response)); + } + + private Saml2AuthenticationToken token(Response response, RelyingPartyRegistration.Builder registration) { + return new Saml2AuthenticationToken(registration.build(), serialize(response)); + } + + private RelyingPartyRegistration.Builder registration() { + return TestRelyingPartyRegistrations.noCredentials().entityId(RELYING_PARTY_ENTITY_ID) + .assertionConsumerServiceLocation(DESTINATION) + .assertingPartyDetails((party) -> party.entityId(ASSERTING_PARTY_ENTITY_ID)); + } + + private RelyingPartyRegistration.Builder verifying(RelyingPartyRegistration.Builder builder) { + return builder.assertingPartyDetails((party) -> party + .verificationX509Credentials((c) -> c.add(TestSaml2X509Credentials.relyingPartyVerifyingCredential()))); + } + + private RelyingPartyRegistration.Builder decrypting(RelyingPartyRegistration.Builder builder) { + return builder + .decryptionX509Credentials((c) -> c.add(TestSaml2X509Credentials.relyingPartyDecryptingCredential())); + } + +} diff --git a/saml2/saml2-service-provider/opensaml4/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationRequestFactoryTests.java b/saml2/saml2-service-provider/opensaml4/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationRequestFactoryTests.java new file mode 100644 index 00000000000..0297d10f7fe --- /dev/null +++ b/saml2/saml2-service-provider/opensaml4/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationRequestFactoryTests.java @@ -0,0 +1,274 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.saml2.provider.service.authentication; + +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; +import java.time.Instant; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; +import org.opensaml.saml.common.xml.SAMLConstants; +import org.opensaml.saml.saml2.core.AuthnRequest; +import org.opensaml.saml.saml2.core.impl.AuthnRequestUnmarshaller; +import org.opensaml.xmlsec.signature.support.SignatureConstants; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.security.saml2.Saml2Exception; +import org.springframework.security.saml2.core.Saml2X509Credential; +import org.springframework.security.saml2.credentials.TestSaml2X509Credentials; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; +import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; +import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +/** + * Tests for {@link OpenSaml4AuthenticationRequestFactory} + */ +public class OpenSaml4AuthenticationRequestFactoryTests { + + private OpenSaml4AuthenticationRequestFactory factory; + + private Saml2AuthenticationRequestContext.Builder contextBuilder; + + private Saml2AuthenticationRequestContext context; + + private RelyingPartyRegistration.Builder relyingPartyRegistrationBuilder; + + private RelyingPartyRegistration relyingPartyRegistration; + + private AuthnRequestUnmarshaller unmarshaller; + + @Before + public void setUp() { + this.relyingPartyRegistrationBuilder = RelyingPartyRegistration.withRegistrationId("id") + .assertionConsumerServiceLocation("template") + .providerDetails((c) -> c.webSsoUrl("https://destination/sso")) + .providerDetails((c) -> c.entityId("remote-entity-id")).localEntityIdTemplate("local-entity-id") + .credentials((c) -> c.add(TestSaml2X509Credentials.relyingPartySigningCredential())); + this.relyingPartyRegistration = this.relyingPartyRegistrationBuilder.build(); + this.contextBuilder = Saml2AuthenticationRequestContext.builder().issuer("https://issuer") + .relyingPartyRegistration(this.relyingPartyRegistration) + .assertionConsumerServiceUrl("https://issuer/sso"); + this.context = this.contextBuilder.build(); + this.factory = new OpenSaml4AuthenticationRequestFactory(); + this.unmarshaller = (AuthnRequestUnmarshaller) XMLObjectProviderRegistrySupport.getUnmarshallerFactory() + .getUnmarshaller(AuthnRequest.DEFAULT_ELEMENT_NAME); + } + + @Test + public void createAuthenticationRequestWhenInvokingDeprecatedMethodThenReturnsXML() { + Saml2AuthenticationRequest request = Saml2AuthenticationRequest.withAuthenticationRequestContext(this.context) + .build(); + String result = this.factory.createAuthenticationRequest(request); + assertThat(result.replace("\n", "")) + .startsWith(" c.signAuthNRequest(false)).build()) + .build(); + Saml2RedirectAuthenticationRequest result = this.factory.createRedirectAuthenticationRequest(this.context); + assertThat(result.getSamlRequest()).isNotEmpty(); + assertThat(result.getRelayState()).isEqualTo("Relay State Value"); + assertThat(result.getSigAlg()).isNull(); + assertThat(result.getSignature()).isNull(); + assertThat(result.getBinding()).isEqualTo(Saml2MessageBinding.REDIRECT); + } + + @Test + public void createRedirectAuthenticationRequestWhenSignRequestThenSignatureIsPresent() { + this.context = this.contextBuilder.relayState("Relay State Value") + .relyingPartyRegistration(this.relyingPartyRegistration).build(); + Saml2RedirectAuthenticationRequest request = this.factory.createRedirectAuthenticationRequest(this.context); + assertThat(request.getRelayState()).isEqualTo("Relay State Value"); + assertThat(request.getSigAlg()).isEqualTo(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256); + assertThat(request.getSignature()).isNotNull(); + } + + @Test + public void createRedirectAuthenticationRequestWhenSignRequestThenCredentialIsRequired() { + Saml2X509Credential credential = org.springframework.security.saml2.core.TestSaml2X509Credentials + .relyingPartyVerifyingCredential(); + RelyingPartyRegistration registration = TestRelyingPartyRegistrations.noCredentials() + .assertingPartyDetails((party) -> party.verificationX509Credentials((c) -> c.add(credential))).build(); + this.context = this.contextBuilder.relayState("Relay State Value").relyingPartyRegistration(registration) + .build(); + assertThatExceptionOfType(Saml2Exception.class) + .isThrownBy(() -> this.factory.createPostAuthenticationRequest(this.context)); + } + + @Test + public void createPostAuthenticationRequestWhenNotSignRequestThenNoSignatureIsPresent() { + this.context = this.contextBuilder.relayState("Relay State Value") + .relyingPartyRegistration( + RelyingPartyRegistration.withRelyingPartyRegistration(this.relyingPartyRegistration) + .providerDetails((c) -> c.signAuthNRequest(false)).build()) + .build(); + Saml2PostAuthenticationRequest result = this.factory.createPostAuthenticationRequest(this.context); + assertThat(result.getSamlRequest()).isNotEmpty(); + assertThat(result.getRelayState()).isEqualTo("Relay State Value"); + assertThat(result.getBinding()).isEqualTo(Saml2MessageBinding.POST); + assertThat(new String(Saml2Utils.samlDecode(result.getSamlRequest()), StandardCharsets.UTF_8)) + .doesNotContain("ds:Signature"); + } + + @Test + public void createPostAuthenticationRequestWhenSignRequestThenSignatureIsPresent() { + this.context = this.contextBuilder.relayState("Relay State Value") + .relyingPartyRegistration( + RelyingPartyRegistration.withRelyingPartyRegistration(this.relyingPartyRegistration).build()) + .build(); + Saml2PostAuthenticationRequest result = this.factory.createPostAuthenticationRequest(this.context); + assertThat(result.getSamlRequest()).isNotEmpty(); + assertThat(result.getRelayState()).isEqualTo("Relay State Value"); + assertThat(result.getBinding()).isEqualTo(Saml2MessageBinding.POST); + assertThat(new String(Saml2Utils.samlDecode(result.getSamlRequest()), StandardCharsets.UTF_8)) + .contains("ds:Signature"); + } + + @Test + public void createPostAuthenticationRequestWhenSignRequestThenCredentialIsRequired() { + Saml2X509Credential credential = org.springframework.security.saml2.core.TestSaml2X509Credentials + .relyingPartyVerifyingCredential(); + RelyingPartyRegistration registration = TestRelyingPartyRegistrations.noCredentials() + .assertingPartyDetails((party) -> party.verificationX509Credentials((c) -> c.add(credential))).build(); + this.context = this.contextBuilder.relayState("Relay State Value").relyingPartyRegistration(registration) + .build(); + assertThatExceptionOfType(Saml2Exception.class) + .isThrownBy(() -> this.factory.createPostAuthenticationRequest(this.context)); + } + + @Test + public void createAuthenticationRequestWhenDefaultThenReturnsPostBinding() { + AuthnRequest authn = getAuthNRequest(Saml2MessageBinding.POST); + Assert.assertEquals(SAMLConstants.SAML2_POST_BINDING_URI, authn.getProtocolBinding()); + } + + @Test + public void createPostAuthenticationRequestWhenAuthnRequestConsumerThenUses() { + Converter authenticationRequestContextConverter = mock( + Converter.class); + given(authenticationRequestContextConverter.convert(this.context)).willReturn(authnRequest()); + this.factory.setAuthenticationRequestContextConverter(authenticationRequestContextConverter); + + this.factory.createPostAuthenticationRequest(this.context); + verify(authenticationRequestContextConverter).convert(this.context); + } + + @Test + public void createRedirectAuthenticationRequestWhenAuthnRequestConsumerThenUses() { + Converter authenticationRequestContextConverter = mock( + Converter.class); + given(authenticationRequestContextConverter.convert(this.context)).willReturn(authnRequest()); + this.factory.setAuthenticationRequestContextConverter(authenticationRequestContextConverter); + + this.factory.createRedirectAuthenticationRequest(this.context); + verify(authenticationRequestContextConverter).convert(this.context); + } + + @Test + public void setAuthenticationRequestContextConverterWhenNullThenException() { + // @formatter:off + assertThatIllegalArgumentException() + .isThrownBy(() -> this.factory.setAuthenticationRequestContextConverter(null)); + // @formatter:on + } + + @Test + public void createPostAuthenticationRequestWhenAssertionConsumerServiceBindingThenUses() { + RelyingPartyRegistration relyingPartyRegistration = this.relyingPartyRegistrationBuilder + .assertionConsumerServiceBinding(Saml2MessageBinding.REDIRECT).build(); + Saml2AuthenticationRequestContext context = this.contextBuilder + .relyingPartyRegistration(relyingPartyRegistration).build(); + Saml2PostAuthenticationRequest request = this.factory.createPostAuthenticationRequest(context); + String samlRequest = request.getSamlRequest(); + String inflated = new String(Saml2Utils.samlDecode(samlRequest)); + assertThat(inflated).contains("ProtocolBinding=\"" + SAMLConstants.SAML2_REDIRECT_BINDING_URI + "\""); + } + + @Test + public void createRedirectAuthenticationRequestWhenSHA1SignRequestThenSignatureIsPresent() { + RelyingPartyRegistration relyingPartyRegistration = this.relyingPartyRegistrationBuilder + .assertingPartyDetails( + (a) -> a.signingAlgorithms((algs) -> algs.add(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1))) + .build(); + Saml2AuthenticationRequestContext context = this.contextBuilder.relayState("Relay State Value") + .relyingPartyRegistration(relyingPartyRegistration).build(); + Saml2RedirectAuthenticationRequest result = this.factory.createRedirectAuthenticationRequest(context); + assertThat(result.getSamlRequest()).isNotEmpty(); + assertThat(result.getRelayState()).isEqualTo("Relay State Value"); + assertThat(result.getSigAlg()).isEqualTo(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1); + assertThat(result.getSignature()).isNotNull(); + assertThat(result.getBinding()).isEqualTo(Saml2MessageBinding.REDIRECT); + } + + private AuthnRequest authnRequest() { + AuthnRequest authnRequest = TestOpenSamlObjects.authnRequest(); + authnRequest.setIssueInstant(Instant.now()); + return authnRequest; + } + + private AuthnRequest getAuthNRequest(Saml2MessageBinding binding) { + AbstractSaml2AuthenticationRequest result = (binding == Saml2MessageBinding.REDIRECT) + ? this.factory.createRedirectAuthenticationRequest(this.context) + : this.factory.createPostAuthenticationRequest(this.context); + String samlRequest = result.getSamlRequest(); + assertThat(samlRequest).isNotEmpty(); + if (result.getBinding() == Saml2MessageBinding.REDIRECT) { + samlRequest = Saml2Utils.samlInflate(Saml2Utils.samlDecode(samlRequest)); + } + else { + samlRequest = new String(Saml2Utils.samlDecode(samlRequest), StandardCharsets.UTF_8); + } + try { + Document document = XMLObjectProviderRegistrySupport.getParserPool() + .parse(new ByteArrayInputStream(samlRequest.getBytes(StandardCharsets.UTF_8))); + Element element = document.getDocumentElement(); + return (AuthnRequest) this.unmarshaller.unmarshall(element); + } + catch (Exception ex) { + throw new Saml2Exception(ex); + } + } + +} diff --git a/saml2/saml2-service-provider/spring-security-saml2-service-provider.gradle b/saml2/saml2-service-provider/spring-security-saml2-service-provider.gradle index c10aadf7082..ae7fe74bc23 100644 --- a/saml2/saml2-service-provider/spring-security-saml2-service-provider.gradle +++ b/saml2/saml2-service-provider/spring-security-saml2-service-provider.gradle @@ -1,14 +1,87 @@ apply plugin: 'io.spring.convention.spring-module' +tasks.forEach({ task -> + if (project(":saml2-service-provider-core").tasks.findByName(task.name)) { + task.dependsOn(project(":saml2-service-provider-core").tasks[task.name]) + } + if (project(":saml2-service-provider-opensaml3").tasks.findByName(task.name)) { + task.dependsOn(project(":saml2-service-provider-opensaml3").tasks[task.name]) + } + if (project(":saml2-service-provider-opensaml4").tasks.findByName(task.name)) { + task.dependsOn(project(":saml2-service-provider-opensaml4").tasks[task.name]) + } +}) + +configurations { + coreSource { + canBeConsumed = false + canBeResolved = true + } + opensaml3Source { + canBeConsumed = false + canBeResolved = true + } + opensaml4Source { + canBeConsumed = false + canBeResolved = true + } + coreClasses { + canBeConsumed = false + canBeResolved = true + } + opensaml3Classes { + canBeConsumed = false + canBeResolved = true + } + opensaml4Classes { + canBeConsumed = false + canBeResolved = true + } + coreJavadoc { + canBeConsumed = false + canBeResolved = true + } + opensaml3Javadoc { + canBeConsumed = false + canBeResolved = true + } + opensaml4Javadoc { + canBeConsumed = false + canBeResolved = true + } +} + dependencies { - compile project(':spring-security-core') - compile project(':spring-security-web') + management platform(project(":spring-security-dependencies")) + api("org.opensaml:opensaml-core") + api("org.opensaml:opensaml-saml-api") + api("org.opensaml:opensaml-saml-impl") + coreSource(project(path: ":saml2-service-provider-core", configuration: 'sourceElements')) + opensaml3Source(project(path: ":saml2-service-provider-opensaml3", configuration: 'sourceElements')) + opensaml4Source(project(path: ":saml2-service-provider-opensaml4", configuration: 'sourceElements')) + coreClasses(project(path: ":saml2-service-provider-core", configuration: 'classesOnlyElements')) + opensaml3Classes(project(path: ":saml2-service-provider-opensaml3", configuration: 'classesOnlyElements')) + opensaml4Classes(project(path: ":saml2-service-provider-opensaml4", configuration: 'classesOnlyElements')) + coreJavadoc(project(path: ":saml2-service-provider-core", configuration: 'javadocElements')) + opensaml3Javadoc(project(path: ":saml2-service-provider-opensaml3", configuration: 'javadocElements')) + opensaml4Javadoc(project(path: ":saml2-service-provider-opensaml4", configuration: 'javadocElements')) +} - compile("org.opensaml:opensaml-core") - compile("org.opensaml:opensaml-saml-api") - compile("org.opensaml:opensaml-saml-impl") +jar { + from configurations.coreClasses + from configurations.opensaml3Classes + from configurations.opensaml4Classes +} - provided 'javax.servlet:javax.servlet-api' +javadocJar { + from configurations.coreJavadoc + from configurations.opensaml3Javadoc + from configurations.opensaml4Javadoc + duplicatesStrategy = DuplicatesStrategy.EXCLUDE +} - testCompile 'com.squareup.okhttp3:mockwebserver' +sourcesJar { + from configurations.coreSource + from configurations.opensaml3Source + from configurations.opensaml4Source } diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactory.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactory.java deleted file mode 100644 index 26bfd1a8489..00000000000 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactory.java +++ /dev/null @@ -1,311 +0,0 @@ -/* - * Copyright 2002-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.saml2.provider.service.authentication; - -import java.nio.charset.StandardCharsets; -import java.security.PrivateKey; -import java.security.cert.X509Certificate; -import java.time.Clock; -import java.time.Instant; -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -import net.shibboleth.utilities.java.support.resolver.CriteriaSet; -import net.shibboleth.utilities.java.support.xml.SerializeSupport; -import org.joda.time.DateTime; -import org.opensaml.core.config.ConfigurationService; -import org.opensaml.core.xml.config.XMLObjectProviderRegistry; -import org.opensaml.core.xml.io.MarshallingException; -import org.opensaml.saml.common.xml.SAMLConstants; -import org.opensaml.saml.saml2.core.AuthnRequest; -import org.opensaml.saml.saml2.core.Issuer; -import org.opensaml.saml.saml2.core.impl.AuthnRequestBuilder; -import org.opensaml.saml.saml2.core.impl.AuthnRequestMarshaller; -import org.opensaml.saml.saml2.core.impl.IssuerBuilder; -import org.opensaml.saml.security.impl.SAMLMetadataSignatureSigningParametersResolver; -import org.opensaml.security.SecurityException; -import org.opensaml.security.credential.BasicCredential; -import org.opensaml.security.credential.Credential; -import org.opensaml.security.credential.CredentialSupport; -import org.opensaml.security.credential.UsageType; -import org.opensaml.xmlsec.SignatureSigningParameters; -import org.opensaml.xmlsec.SignatureSigningParametersResolver; -import org.opensaml.xmlsec.criterion.SignatureSigningConfigurationCriterion; -import org.opensaml.xmlsec.crypto.XMLSigningUtil; -import org.opensaml.xmlsec.impl.BasicSignatureSigningConfiguration; -import org.opensaml.xmlsec.signature.support.SignatureConstants; -import org.opensaml.xmlsec.signature.support.SignatureSupport; -import org.w3c.dom.Element; - -import org.springframework.core.convert.converter.Converter; -import org.springframework.security.saml2.Saml2Exception; -import org.springframework.security.saml2.core.OpenSamlInitializationService; -import org.springframework.security.saml2.core.Saml2X509Credential; -import org.springframework.security.saml2.provider.service.authentication.Saml2RedirectAuthenticationRequest.Builder; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; -import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; -import org.springframework.web.util.UriComponentsBuilder; -import org.springframework.web.util.UriUtils; - -/** - * @since 5.2 - */ -public class OpenSamlAuthenticationRequestFactory implements Saml2AuthenticationRequestFactory { - - static { - OpenSamlInitializationService.initialize(); - } - - private Clock clock = Clock.systemUTC(); - - private AuthnRequestMarshaller marshaller; - - private AuthnRequestBuilder authnRequestBuilder; - - private IssuerBuilder issuerBuilder; - - private Converter protocolBindingResolver = (context) -> { - if (context == null) { - return SAMLConstants.SAML2_POST_BINDING_URI; - } - return context.getRelyingPartyRegistration().getAssertionConsumerServiceBinding().getUrn(); - }; - - private Converter authenticationRequestContextConverter = this::createAuthnRequest; - - /** - * Creates an {@link OpenSamlAuthenticationRequestFactory} - */ - public OpenSamlAuthenticationRequestFactory() { - XMLObjectProviderRegistry registry = ConfigurationService.get(XMLObjectProviderRegistry.class); - this.marshaller = (AuthnRequestMarshaller) registry.getMarshallerFactory() - .getMarshaller(AuthnRequest.DEFAULT_ELEMENT_NAME); - this.authnRequestBuilder = (AuthnRequestBuilder) registry.getBuilderFactory() - .getBuilder(AuthnRequest.DEFAULT_ELEMENT_NAME); - this.issuerBuilder = (IssuerBuilder) registry.getBuilderFactory().getBuilder(Issuer.DEFAULT_ELEMENT_NAME); - } - - @Override - @Deprecated - public String createAuthenticationRequest(Saml2AuthenticationRequest request) { - AuthnRequest authnRequest = createAuthnRequest(request.getIssuer(), request.getDestination(), - request.getAssertionConsumerServiceUrl(), this.protocolBindingResolver.convert(null)); - for (org.springframework.security.saml2.credentials.Saml2X509Credential credential : request.getCredentials()) { - if (credential.isSigningCredential()) { - X509Certificate certificate = credential.getCertificate(); - PrivateKey privateKey = credential.getPrivateKey(); - BasicCredential cred = CredentialSupport.getSimpleCredential(certificate, privateKey); - cred.setEntityId(request.getIssuer()); - cred.setUsageType(UsageType.SIGNING); - SignatureSigningParameters parameters = new SignatureSigningParameters(); - parameters.setSigningCredential(cred); - parameters.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256); - parameters.setSignatureReferenceDigestMethod(SignatureConstants.ALGO_ID_DIGEST_SHA256); - parameters.setSignatureCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS); - return serialize(sign(authnRequest, parameters)); - } - } - throw new IllegalArgumentException("No signing credential provided"); - } - - @Override - public Saml2PostAuthenticationRequest createPostAuthenticationRequest(Saml2AuthenticationRequestContext context) { - AuthnRequest authnRequest = this.authenticationRequestContextConverter.convert(context); - String xml = context.getRelyingPartyRegistration().getAssertingPartyDetails().getWantAuthnRequestsSigned() - ? serialize(sign(authnRequest, context.getRelyingPartyRegistration())) : serialize(authnRequest); - - return Saml2PostAuthenticationRequest.withAuthenticationRequestContext(context) - .samlRequest(Saml2Utils.samlEncode(xml.getBytes(StandardCharsets.UTF_8))).build(); - } - - @Override - public Saml2RedirectAuthenticationRequest createRedirectAuthenticationRequest( - Saml2AuthenticationRequestContext context) { - AuthnRequest authnRequest = this.authenticationRequestContextConverter.convert(context); - String xml = serialize(authnRequest); - Builder result = Saml2RedirectAuthenticationRequest.withAuthenticationRequestContext(context); - String deflatedAndEncoded = Saml2Utils.samlEncode(Saml2Utils.samlDeflate(xml)); - result.samlRequest(deflatedAndEncoded).relayState(context.getRelayState()); - if (context.getRelyingPartyRegistration().getAssertingPartyDetails().getWantAuthnRequestsSigned()) { - Map parameters = new LinkedHashMap<>(); - parameters.put("SAMLRequest", deflatedAndEncoded); - if (StringUtils.hasText(context.getRelayState())) { - parameters.put("RelayState", context.getRelayState()); - } - sign(parameters, context.getRelyingPartyRegistration()); - return result.sigAlg(parameters.get("SigAlg")).signature(parameters.get("Signature")).build(); - } - return result.build(); - } - - private AuthnRequest createAuthnRequest(Saml2AuthenticationRequestContext context) { - return createAuthnRequest(context.getIssuer(), context.getDestination(), - context.getAssertionConsumerServiceUrl(), this.protocolBindingResolver.convert(context)); - } - - private AuthnRequest createAuthnRequest(String issuer, String destination, String assertionConsumerServiceUrl, - String protocolBinding) { - AuthnRequest auth = this.authnRequestBuilder.buildObject(); - auth.setID("ARQ" + UUID.randomUUID().toString().substring(1)); - auth.setIssueInstant(new DateTime(this.clock.millis())); - auth.setForceAuthn(Boolean.FALSE); - auth.setIsPassive(Boolean.FALSE); - auth.setProtocolBinding(protocolBinding); - Issuer iss = this.issuerBuilder.buildObject(); - iss.setValue(issuer); - auth.setIssuer(iss); - auth.setDestination(destination); - auth.setAssertionConsumerServiceURL(assertionConsumerServiceUrl); - return auth; - } - - /** - * Set the {@link AuthnRequest} post-processor resolver - * @param authenticationRequestContextConverter - * @since 5.4 - */ - public void setAuthenticationRequestContextConverter( - Converter authenticationRequestContextConverter) { - Assert.notNull(authenticationRequestContextConverter, "authenticationRequestContextConverter cannot be null"); - this.authenticationRequestContextConverter = authenticationRequestContextConverter; - } - - /** - * ' Use this {@link Clock} with {@link Instant#now()} for generating timestamps - * @param clock - */ - public void setClock(Clock clock) { - Assert.notNull(clock, "clock cannot be null"); - this.clock = clock; - } - - /** - * Sets the {@code protocolBinding} to use when generating authentication requests. - * Acceptable values are {@link SAMLConstants#SAML2_POST_BINDING_URI} and - * {@link SAMLConstants#SAML2_REDIRECT_BINDING_URI} The IDP will be reading this value - * in the {@code AuthNRequest} to determine how to send the Response/Assertion to the - * ACS URL, assertion consumer service URL. - * @param protocolBinding either {@link SAMLConstants#SAML2_POST_BINDING_URI} or - * {@link SAMLConstants#SAML2_REDIRECT_BINDING_URI} - * @throws IllegalArgumentException if the protocolBinding is not valid - * @deprecated Use - * {@link org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration.Builder#assertionConsumerServiceBinding(Saml2MessageBinding)} - * instead - */ - @Deprecated - public void setProtocolBinding(String protocolBinding) { - boolean isAllowedBinding = SAMLConstants.SAML2_POST_BINDING_URI.equals(protocolBinding) - || SAMLConstants.SAML2_REDIRECT_BINDING_URI.equals(protocolBinding); - if (!isAllowedBinding) { - throw new IllegalArgumentException("Invalid protocol binding: " + protocolBinding); - } - this.protocolBindingResolver = (context) -> protocolBinding; - } - - private AuthnRequest sign(AuthnRequest authnRequest, RelyingPartyRegistration relyingPartyRegistration) { - SignatureSigningParameters parameters = resolveSigningParameters(relyingPartyRegistration); - return sign(authnRequest, parameters); - } - - private AuthnRequest sign(AuthnRequest authnRequest, SignatureSigningParameters parameters) { - try { - SignatureSupport.signObject(authnRequest, parameters); - return authnRequest; - } - catch (Exception ex) { - throw new Saml2Exception(ex); - } - } - - private void sign(Map components, RelyingPartyRegistration relyingPartyRegistration) { - SignatureSigningParameters parameters = resolveSigningParameters(relyingPartyRegistration); - sign(components, parameters); - } - - private void sign(Map components, SignatureSigningParameters parameters) { - Credential credential = parameters.getSigningCredential(); - String algorithmUri = parameters.getSignatureAlgorithm(); - components.put("SigAlg", algorithmUri); - UriComponentsBuilder builder = UriComponentsBuilder.newInstance(); - for (Map.Entry component : components.entrySet()) { - builder.queryParam(component.getKey(), UriUtils.encode(component.getValue(), StandardCharsets.ISO_8859_1)); - } - String queryString = builder.build(true).toString().substring(1); - try { - byte[] rawSignature = XMLSigningUtil.signWithURI(credential, algorithmUri, - queryString.getBytes(StandardCharsets.UTF_8)); - String b64Signature = Saml2Utils.samlEncode(rawSignature); - components.put("Signature", b64Signature); - } - catch (SecurityException ex) { - throw new Saml2Exception(ex); - } - } - - private String serialize(AuthnRequest authnRequest) { - try { - Element element = this.marshaller.marshall(authnRequest); - return SerializeSupport.nodeToString(element); - } - catch (MarshallingException ex) { - throw new Saml2Exception(ex); - } - } - - private SignatureSigningParameters resolveSigningParameters(RelyingPartyRegistration relyingPartyRegistration) { - List credentials = resolveSigningCredentials(relyingPartyRegistration); - List algorithms = relyingPartyRegistration.getAssertingPartyDetails().getSigningAlgorithms(); - List digests = Collections.singletonList(SignatureConstants.ALGO_ID_DIGEST_SHA256); - String canonicalization = SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS; - SignatureSigningParametersResolver resolver = new SAMLMetadataSignatureSigningParametersResolver(); - CriteriaSet criteria = new CriteriaSet(); - BasicSignatureSigningConfiguration signingConfiguration = new BasicSignatureSigningConfiguration(); - signingConfiguration.setSigningCredentials(credentials); - signingConfiguration.setSignatureAlgorithms(algorithms); - signingConfiguration.setSignatureReferenceDigestMethods(digests); - signingConfiguration.setSignatureCanonicalizationAlgorithm(canonicalization); - criteria.add(new SignatureSigningConfigurationCriterion(signingConfiguration)); - try { - SignatureSigningParameters parameters = resolver.resolveSingle(criteria); - Assert.notNull(parameters, "Failed to resolve any signing credential"); - return parameters; - } - catch (Exception ex) { - throw new Saml2Exception(ex); - } - } - - private List resolveSigningCredentials(RelyingPartyRegistration relyingPartyRegistration) { - List credentials = new ArrayList<>(); - for (Saml2X509Credential x509Credential : relyingPartyRegistration.getSigningX509Credentials()) { - X509Certificate certificate = x509Credential.getCertificate(); - PrivateKey privateKey = x509Credential.getPrivateKey(); - BasicCredential credential = CredentialSupport.getSimpleCredential(certificate, privateKey); - credential.setEntityId(relyingPartyRegistration.getEntityId()); - credential.setUsageType(UsageType.SIGNING); - credentials.add(credential); - } - return credentials; - } - -} diff --git a/samples/boot/hellorsocket/spring-security-samples-boot-hellorsocket.gradle b/samples/boot/hellorsocket/spring-security-samples-boot-hellorsocket.gradle deleted file mode 100644 index f2b788a0e27..00000000000 --- a/samples/boot/hellorsocket/spring-security-samples-boot-hellorsocket.gradle +++ /dev/null @@ -1,11 +0,0 @@ -apply plugin: 'io.spring.convention.spring-sample-boot' - -dependencies { - compile project(':spring-security-core') - compile project(':spring-security-config') - compile project(':spring-security-rsocket') - compile 'org.springframework.boot:spring-boot-starter-rsocket' - - testCompile project(':spring-security-test') - testCompile 'org.springframework.boot:spring-boot-starter-test' -} diff --git a/samples/boot/hellorsocket/src/integration-test/java/sample/HelloRSocketApplicationITests.java b/samples/boot/hellorsocket/src/integration-test/java/sample/HelloRSocketApplicationITests.java deleted file mode 100644 index 0709dd93bcf..00000000000 --- a/samples/boot/hellorsocket/src/integration-test/java/sample/HelloRSocketApplicationITests.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.rsocket.context.LocalRSocketServerPort; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.messaging.rsocket.RSocketRequester; -import org.springframework.security.rsocket.metadata.BasicAuthenticationEncoder; -import org.springframework.security.rsocket.metadata.UsernamePasswordMetadata; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit4.SpringRunner; - -import org.junit.Test; -import org.junit.runner.RunWith; -import reactor.core.publisher.Mono; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.springframework.security.rsocket.metadata.UsernamePasswordMetadata.BASIC_AUTHENTICATION_MIME_TYPE; - -/** - * @author Rob Winch - * @author Eddú Meléndez - * @since 5.0 - */ -@RunWith(SpringRunner.class) -@TestPropertySource(properties = "spring.rsocket.server.port=0") -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -public class HelloRSocketApplicationITests { - - @Autowired - RSocketRequester.Builder requester; - - @LocalRSocketServerPort - int port; - - @Test - public void messageWhenAuthenticatedThenSuccess() { - UsernamePasswordMetadata credentials = new UsernamePasswordMetadata("user", "password"); - RSocketRequester requester = this.requester - .rsocketStrategies((builder) -> builder.encoder(new BasicAuthenticationEncoder())) - .setupMetadata(credentials, BASIC_AUTHENTICATION_MIME_TYPE) - .connectTcp("localhost", this.port) - .block(); - - String message = requester.route("message") - .data(Mono.empty()) - .retrieveMono(String.class) - .block(); - - assertThat(message).isEqualTo("Hello"); - } - - @Test - public void messageWhenNotAuthenticatedThenError() { - RSocketRequester requester = this.requester - .connectTcp("localhost", this.port) - .block(); - - assertThatThrownBy(() -> requester.route("message") - .data(Mono.empty()) - .retrieveMono(String.class) - .block()) - .isNotNull(); - } - -} diff --git a/samples/boot/hellorsocket/src/main/java/sample/HelloRSocketApplication.java b/samples/boot/hellorsocket/src/main/java/sample/HelloRSocketApplication.java deleted file mode 100644 index fbbc6df27d7..00000000000 --- a/samples/boot/hellorsocket/src/main/java/sample/HelloRSocketApplication.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - * @author Rob Winch - * @since 5.2 - */ -@SpringBootApplication -public class HelloRSocketApplication { - - public static void main(String[] args) { - SpringApplication.run(HelloRSocketApplication.class, args); - } - -} diff --git a/samples/boot/hellorsocket/src/main/java/sample/HelloRSocketSecurityConfig.java b/samples/boot/hellorsocket/src/main/java/sample/HelloRSocketSecurityConfig.java deleted file mode 100644 index 9f469f08e7f..00000000000 --- a/samples/boot/hellorsocket/src/main/java/sample/HelloRSocketSecurityConfig.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2002-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.rsocket.EnableRSocketSecurity; -import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetails; - -/** - * @author Rob Winch - * @since 5.2 - */ -@Configuration -@EnableRSocketSecurity -public class HelloRSocketSecurityConfig { - - @Bean - MapReactiveUserDetailsService userDetailsService() { - UserDetails user = User.withDefaultPasswordEncoder() - .username("user") - .password("password") - .roles("SETUP") - .build(); - return new MapReactiveUserDetailsService(user); - } - -} diff --git a/samples/boot/hellorsocket/src/main/java/sample/MessageController.java b/samples/boot/hellorsocket/src/main/java/sample/MessageController.java deleted file mode 100644 index 0150b3f8213..00000000000 --- a/samples/boot/hellorsocket/src/main/java/sample/MessageController.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.springframework.messaging.handler.annotation.MessageMapping; -import org.springframework.stereotype.Controller; -import reactor.core.publisher.Mono; - -/** - * @author Rob Winch - * @since 5.2 - */ -@Controller -public class MessageController { - - @MessageMapping("message") - public Mono message() { - return Mono.just("Hello"); - } -} diff --git a/samples/boot/hellorsocket/src/main/resources/application.properties b/samples/boot/hellorsocket/src/main/resources/application.properties deleted file mode 100644 index 0d91b628f69..00000000000 --- a/samples/boot/hellorsocket/src/main/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ -spring.rsocket.server.port=8080 diff --git a/samples/boot/hellowebflux-method/spring-security-samples-boot-hellowebflux-method.gradle b/samples/boot/hellowebflux-method/spring-security-samples-boot-hellowebflux-method.gradle deleted file mode 100644 index c21781806f8..00000000000 --- a/samples/boot/hellowebflux-method/spring-security-samples-boot-hellowebflux-method.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'io.spring.convention.spring-sample-boot' - -dependencies { - compile project(':spring-security-core') - compile project(':spring-security-config') - compile project(':spring-security-web') - compile 'org.springframework.boot:spring-boot-starter-webflux' - - testCompile project(':spring-security-test') - testCompile 'io.projectreactor:reactor-test' - testCompile 'org.springframework.boot:spring-boot-starter-test' -} diff --git a/samples/boot/hellowebflux-method/src/integration-test/java/sample/HelloWebfluxMethodApplicationITests.java b/samples/boot/hellowebflux-method/src/integration-test/java/sample/HelloWebfluxMethodApplicationITests.java deleted file mode 100644 index d95956b2d62..00000000000 --- a/samples/boot/hellowebflux-method/src/integration-test/java/sample/HelloWebfluxMethodApplicationITests.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2002-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import java.util.function.Consumer; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.reactive.server.WebTestClient; - -/** - * @author Rob Winch - * @since 5.0 - */ -@RunWith(SpringRunner.class) -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -public class HelloWebfluxMethodApplicationITests { - - @Autowired - WebTestClient rest; - - - @Test - public void messageWhenNotAuthenticated() { - this.rest - .get() - .uri("/message") - .exchange() - .expectStatus().isUnauthorized(); - } - - @Test - public void messageWhenUserThenForbidden() { - this.rest - .get() - .uri("/message") - .headers(robsCredentials()) - .exchange() - .expectStatus().isEqualTo(HttpStatus.FORBIDDEN); - } - - @Test - public void messageWhenAdminThenOk() { - this.rest - .get() - .uri("/message") - .headers(adminCredentials()) - .exchange() - .expectStatus().isOk() - .expectBody(String.class).isEqualTo("Hello World!"); - } - - private Consumer robsCredentials() { - return (httpHeaders) -> httpHeaders.setBasicAuth("rob", "rob"); - } - - private Consumer adminCredentials() { - return (httpHeaders) -> httpHeaders.setBasicAuth("admin", "admin"); - } -} - diff --git a/samples/boot/hellowebflux-method/src/main/java/sample/HelloWebfluxMethodApplication.java b/samples/boot/hellowebflux-method/src/main/java/sample/HelloWebfluxMethodApplication.java deleted file mode 100644 index 37aec1d99f4..00000000000 --- a/samples/boot/hellowebflux-method/src/main/java/sample/HelloWebfluxMethodApplication.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2002-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - * @author Rob Winch - * @since 5.0 - */ -@SpringBootApplication -public class HelloWebfluxMethodApplication { - - public static void main(String[] args) { - SpringApplication.run(HelloWebfluxMethodApplication.class, args); - } -} diff --git a/samples/boot/hellowebflux-method/src/main/java/sample/HelloWorldMessageService.java b/samples/boot/hellowebflux-method/src/main/java/sample/HelloWorldMessageService.java deleted file mode 100644 index c6184c830d4..00000000000 --- a/samples/boot/hellowebflux-method/src/main/java/sample/HelloWorldMessageService.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2002-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.stereotype.Component; -import reactor.core.publisher.Mono; - -/** - * @author Rob Winch - * @since 5.0 - */ -@Component -public class HelloWorldMessageService { - @PreAuthorize("hasRole('ADMIN')") - public Mono findMessage() { - return Mono.just("Hello World!"); - } -} diff --git a/samples/boot/hellowebflux-method/src/main/java/sample/MessageController.java b/samples/boot/hellowebflux-method/src/main/java/sample/MessageController.java deleted file mode 100644 index b8e96e6a458..00000000000 --- a/samples/boot/hellowebflux-method/src/main/java/sample/MessageController.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2002-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; -import reactor.core.publisher.Mono; - -/** - * @author Rob Winch - * @since 5.0 - */ -@RestController -public class MessageController { - private final HelloWorldMessageService messages; - - public MessageController(HelloWorldMessageService messages) { - this.messages = messages; - } - - @GetMapping("/message") - public Mono message() { - return this.messages.findMessage(); - } -} diff --git a/samples/boot/hellowebflux-method/src/main/java/sample/SecurityConfig.java b/samples/boot/hellowebflux-method/src/main/java/sample/SecurityConfig.java deleted file mode 100644 index fbbcdc835e8..00000000000 --- a/samples/boot/hellowebflux-method/src/main/java/sample/SecurityConfig.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.springframework.context.annotation.Bean; -import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity; -import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; -import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; -import org.springframework.security.config.web.server.ServerHttpSecurity; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.web.server.SecurityWebFilterChain; - -import static org.springframework.security.config.Customizer.withDefaults; - -/** - * @author Rob Winch - * @since 5.0 - */ -@EnableWebFluxSecurity -@EnableReactiveMethodSecurity -public class SecurityConfig { - - @Bean - SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) { - return http - // Demonstrate that method security works - // Best practice to use both for defense in depth - .authorizeExchange((exchanges) -> exchanges - .anyExchange().permitAll() - ) - .httpBasic(withDefaults()) - .build(); - } - - @Bean - public MapReactiveUserDetailsService userDetailsService() { - User.UserBuilder userBuilder = User.withDefaultPasswordEncoder(); - UserDetails rob = userBuilder.username("rob").password("rob").roles("USER").build(); - UserDetails admin = userBuilder.username("admin").password("admin").roles("USER", "ADMIN").build(); - return new MapReactiveUserDetailsService(rob, admin); - } - -} diff --git a/samples/boot/hellowebflux-method/src/test/java/sample/HelloWebfluxMethodApplicationTests.java b/samples/boot/hellowebflux-method/src/test/java/sample/HelloWebfluxMethodApplicationTests.java deleted file mode 100644 index 6a2e2898eda..00000000000 --- a/samples/boot/hellowebflux-method/src/test/java/sample/HelloWebfluxMethodApplicationTests.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright 2002-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockUser; -import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.springSecurity; - -import java.util.function.Consumer; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.ApplicationContext; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.reactive.server.WebTestClient; - -/** - * @author Rob Winch - * @since 5.0 - */ -@RunWith(SpringRunner.class) -@SpringBootTest -public class HelloWebfluxMethodApplicationTests { - WebTestClient rest; - - @Autowired - public void setup(ApplicationContext context) { - this.rest = WebTestClient - .bindToApplicationContext(context) - .apply(springSecurity()) - .configureClient() - .build(); - } - - @Test - public void messageWhenNotAuthenticated() { - this.rest - .get() - .uri("/message") - .exchange() - .expectStatus().isUnauthorized(); - } - - @Test - public void messageWhenUserThenForbidden() { - this.rest - .get() - .uri("/message") - .headers(robsCredentials()) - .exchange() - .expectStatus().isEqualTo(HttpStatus.FORBIDDEN); - } - - @Test - public void messageWhenAdminThenOk() { - this.rest - .get() - .uri("/message") - .headers(adminCredentials()) - .exchange() - .expectStatus().isOk() - .expectBody(String.class).isEqualTo("Hello World!"); - } - - @Test - @WithMockUser - public void messageWhenWithMockUserThenForbidden() { - this.rest - .get() - .uri("/message") - .exchange() - .expectStatus().isEqualTo(HttpStatus.FORBIDDEN); - } - - @Test - @WithMockUser(roles = "ADMIN") - public void messageWhenWithMockAdminThenOk() { - this.rest - .get() - .uri("/message") - .exchange() - .expectStatus().isOk() - .expectBody(String.class).isEqualTo("Hello World!"); - } - - @Test - public void messageWhenMutateWithMockUserThenForbidden() { - this.rest - .mutateWith(mockUser()) - .get() - .uri("/message") - .exchange() - .expectStatus().isEqualTo(HttpStatus.FORBIDDEN); - } - - @Test - public void messageWhenMutateWithMockAdminThenOk() { - this.rest - .mutateWith(mockUser().roles("ADMIN")) - .get() - .uri("/message") - .exchange() - .expectStatus().isOk() - .expectBody(String.class).isEqualTo("Hello World!"); - } - - private Consumer robsCredentials() { - return (httpHeaders) -> httpHeaders.setBasicAuth("rob", "rob"); - } - - private Consumer adminCredentials() { - return (httpHeaders) -> httpHeaders.setBasicAuth("admin", "admin"); - } -} diff --git a/samples/boot/hellowebflux-method/src/test/java/sample/HelloWorldMessageServiceTests.java b/samples/boot/hellowebflux-method/src/test/java/sample/HelloWorldMessageServiceTests.java deleted file mode 100644 index d8f2aae925b..00000000000 --- a/samples/boot/hellowebflux-method/src/test/java/sample/HelloWorldMessageServiceTests.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2002-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.test.context.junit4.SpringRunner; -import reactor.test.StepVerifier; - -/** - * @author Rob Winch - * @since 5.0 - */ -@RunWith(SpringRunner.class) -@SpringBootTest -public class HelloWorldMessageServiceTests { - @Autowired - HelloWorldMessageService messages; - - @Test - public void messagesWhenNotAuthenticatedThenDenied() { - StepVerifier.create(this.messages.findMessage()) - .expectError(AccessDeniedException.class) - .verify(); - } - - @Test - @WithMockUser - public void messagesWhenUserThenDenied() { - StepVerifier.create(this.messages.findMessage()) - .expectError(AccessDeniedException.class) - .verify(); - } - - @Test - @WithMockUser(roles = "ADMIN") - public void messagesWhenAdminThenOk() { - StepVerifier.create(this.messages.findMessage()) - .expectNext("Hello World!") - .verifyComplete(); - } -} diff --git a/samples/boot/hellowebflux/spring-security-samples-boot-hellowebflux.gradle b/samples/boot/hellowebflux/spring-security-samples-boot-hellowebflux.gradle deleted file mode 100644 index d65d168ad11..00000000000 --- a/samples/boot/hellowebflux/spring-security-samples-boot-hellowebflux.gradle +++ /dev/null @@ -1,11 +0,0 @@ -apply plugin: 'io.spring.convention.spring-sample-boot' - -dependencies { - compile project(':spring-security-core') - compile project(':spring-security-config') - compile project(':spring-security-web') - compile 'org.springframework.boot:spring-boot-starter-webflux' - - testCompile project(':spring-security-test') - testCompile 'org.springframework.boot:spring-boot-starter-test' -} diff --git a/samples/boot/hellowebflux/src/integration-test/java/sample/HelloWebfluxApplicationITests.java b/samples/boot/hellowebflux/src/integration-test/java/sample/HelloWebfluxApplicationITests.java deleted file mode 100644 index 6d1dda2fac4..00000000000 --- a/samples/boot/hellowebflux/src/integration-test/java/sample/HelloWebfluxApplicationITests.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2002-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import java.util.function.Consumer; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.HttpHeaders; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.reactive.server.WebTestClient; - -/** - * @author Rob Winch - * @since 5.0 - */ -@RunWith(SpringRunner.class) -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -public class HelloWebfluxApplicationITests { - - @Autowired - WebTestClient rest; - - @Test - public void basicWhenNoCredentialsThenUnauthorized() { - this.rest - .get() - .uri("/") - .exchange() - .expectStatus().isUnauthorized(); - } - - @Test - public void basicWhenValidCredentialsThenOk() { - this.rest - .get() - .uri("/") - .headers(userCredentials()) - .exchange() - .expectStatus().isOk() - .expectBody().json("{\"message\":\"Hello user!\"}"); - } - - @Test - public void basicWhenInvalidCredentialsThenUnauthorized() { - this.rest - .get() - .uri("/") - .headers(invalidCredentials()) - .exchange() - .expectStatus().isUnauthorized() - .expectBody().isEmpty(); - } - - private Consumer userCredentials() { - return (httpHeaders) -> httpHeaders.setBasicAuth("user", "user"); - } - - private Consumer invalidCredentials() { - return (httpHeaders) -> httpHeaders.setBasicAuth("user", "INVALID"); - } -} diff --git a/samples/boot/hellowebflux/src/main/java/sample/HelloUserController.java b/samples/boot/hellowebflux/src/main/java/sample/HelloUserController.java deleted file mode 100644 index e06932f222d..00000000000 --- a/samples/boot/hellowebflux/src/main/java/sample/HelloUserController.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2002-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import java.security.Principal; -import java.util.Collections; -import java.util.Map; - -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; -import reactor.core.publisher.Mono; - -/** - * @author Rob Winch - * @since 5.0 - */ -@RestController -public class HelloUserController { - - @GetMapping("/") - public Mono> hello(Mono principal) { - return principal - .map(Principal::getName) - .map(this::helloMessage); - } - - private Map helloMessage(String username) { - return Collections.singletonMap("message", "Hello " + username + "!"); - } -} diff --git a/samples/boot/hellowebflux/src/main/java/sample/HelloWebfluxApplication.java b/samples/boot/hellowebflux/src/main/java/sample/HelloWebfluxApplication.java deleted file mode 100644 index b3b27fdf73a..00000000000 --- a/samples/boot/hellowebflux/src/main/java/sample/HelloWebfluxApplication.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2002-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - * @author Rob Winch - * @since 5.0 - */ -@SpringBootApplication -public class HelloWebfluxApplication { - - public static void main(String[] args) { - SpringApplication.run(HelloWebfluxApplication.class, args); - } - -} diff --git a/samples/boot/hellowebflux/src/main/java/sample/HelloWebfluxSecurityConfig.java b/samples/boot/hellowebflux/src/main/java/sample/HelloWebfluxSecurityConfig.java deleted file mode 100644 index e082133afdb..00000000000 --- a/samples/boot/hellowebflux/src/main/java/sample/HelloWebfluxSecurityConfig.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2002-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.springframework.context.annotation.Bean; -import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; -import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetails; - -/** - * @author Rob Winch - * @since 5.0 - */ -@EnableWebFluxSecurity -public class HelloWebfluxSecurityConfig { - - @Bean - public MapReactiveUserDetailsService userDetailsService() { - UserDetails user = User.withDefaultPasswordEncoder() - .username("user") - .password("user") - .roles("USER") - .build(); - return new MapReactiveUserDetailsService(user); - } -} diff --git a/samples/boot/hellowebflux/src/test/java/sample/HelloWebfluxApplicationTests.java b/samples/boot/hellowebflux/src/test/java/sample/HelloWebfluxApplicationTests.java deleted file mode 100644 index cc7892116f8..00000000000 --- a/samples/boot/hellowebflux/src/test/java/sample/HelloWebfluxApplicationTests.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2002-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockUser; -import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.springSecurity; - -import java.util.function.Consumer; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.ApplicationContext; -import org.springframework.http.HttpHeaders; -import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.reactive.server.WebTestClient; - -/** - * @author Rob Winch - * @since 5.0 - */ -@RunWith(SpringRunner.class) -@SpringBootTest -@AutoConfigureWebTestClient -public class HelloWebfluxApplicationTests { - WebTestClient rest; - - @Autowired - public void setup(ApplicationContext context) { - this.rest = WebTestClient - .bindToApplicationContext(context) - .apply(springSecurity()) - .configureClient() - .build(); - } - - @Test - public void basicWhenNoCredentialsThenUnauthorized() { - this.rest - .get() - .uri("/") - .exchange() - .expectStatus().isUnauthorized(); - } - - @Test - public void basicWhenValidCredentialsThenOk() { - this.rest - .get() - .uri("/") - .headers(userCredentials()) - .exchange() - .expectStatus().isOk() - .expectBody().json("{\"message\":\"Hello user!\"}"); - } - - @Test - public void basicWhenInvalidCredentialsThenUnauthorized() { - this.rest - .get() - .uri("/") - .headers(invalidCredentials()) - .exchange() - .expectStatus().isUnauthorized() - .expectBody().isEmpty(); - } - - @Test - public void mockSupportWhenMutateWithMockUserThenOk() { - this.rest - .mutateWith(mockUser()) - .get() - .uri("/") - .exchange() - .expectStatus().isOk() - .expectBody().json("{\"message\":\"Hello user!\"}"); - } - - @Test - @WithMockUser - public void mockSupportWhenWithMockUserThenOk() { - this.rest - .get() - .uri("/") - .exchange() - .expectStatus().isOk() - .expectBody().json("{\"message\":\"Hello user!\"}"); - } - - private Consumer userCredentials() { - return (httpHeaders) -> httpHeaders.setBasicAuth("user", "user"); - } - - private Consumer invalidCredentials() { - return (httpHeaders) -> httpHeaders.setBasicAuth("user", "INVALID"); - } -} diff --git a/samples/boot/hellowebfluxfn/spring-security-samples-boot-hellowebfluxfn.gradle b/samples/boot/hellowebfluxfn/spring-security-samples-boot-hellowebfluxfn.gradle deleted file mode 100644 index d65d168ad11..00000000000 --- a/samples/boot/hellowebfluxfn/spring-security-samples-boot-hellowebfluxfn.gradle +++ /dev/null @@ -1,11 +0,0 @@ -apply plugin: 'io.spring.convention.spring-sample-boot' - -dependencies { - compile project(':spring-security-core') - compile project(':spring-security-config') - compile project(':spring-security-web') - compile 'org.springframework.boot:spring-boot-starter-webflux' - - testCompile project(':spring-security-test') - testCompile 'org.springframework.boot:spring-boot-starter-test' -} diff --git a/samples/boot/hellowebfluxfn/src/integration-test/java/sample/HelloWebfluxFnApplicationITests.java b/samples/boot/hellowebfluxfn/src/integration-test/java/sample/HelloWebfluxFnApplicationITests.java deleted file mode 100644 index de42538a780..00000000000 --- a/samples/boot/hellowebfluxfn/src/integration-test/java/sample/HelloWebfluxFnApplicationITests.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2002-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import java.util.function.Consumer; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.HttpHeaders; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.reactive.server.WebTestClient; -import org.springframework.web.reactive.function.client.ExchangeFilterFunctions; - -/** - * @author Rob Winch - * @since 5.0 - */ -@RunWith(SpringRunner.class) -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -public class HelloWebfluxFnApplicationITests { - - WebTestClient rest; - - @Autowired - public void setRest(WebTestClient rest) { - this.rest = rest - .mutateWith((b, h, c) -> b.filter(ExchangeFilterFunctions.basicAuthentication())); - } - - @Test - public void basicWhenNoCredentialsThenUnauthorized() { - this.rest - .get() - .uri("/") - .exchange() - .expectStatus().isUnauthorized(); - } - - @Test - public void basicWhenValidCredentialsThenOk() { - this.rest - .get() - .uri("/") - .headers(userCredentials()) - .exchange() - .expectStatus().isOk() - .expectBody().json("{\"message\":\"Hello user!\"}"); - } - - @Test - public void basicWhenInvalidCredentialsThenUnauthorized() { - this.rest - .get() - .uri("/") - .headers(invalidCredentials()) - .exchange() - .expectStatus().isUnauthorized() - .expectBody().isEmpty(); - } - - private Consumer userCredentials() { - return (httpHeaders) -> httpHeaders.setBasicAuth("user", "user"); - } - - private Consumer invalidCredentials() { - return (httpHeaders) -> httpHeaders.setBasicAuth("user", "INVALID"); - } -} diff --git a/samples/boot/hellowebfluxfn/src/main/java/sample/HelloUserController.java b/samples/boot/hellowebfluxfn/src/main/java/sample/HelloUserController.java deleted file mode 100644 index e0c0810cee1..00000000000 --- a/samples/boot/hellowebfluxfn/src/main/java/sample/HelloUserController.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2002-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import java.security.Principal; -import java.util.Collections; - -import reactor.core.publisher.Mono; - -import org.springframework.http.MediaType; -import org.springframework.stereotype.Component; -import org.springframework.web.reactive.function.server.ServerRequest; -import org.springframework.web.reactive.function.server.ServerResponse; - -/** - * @author Rob Winch - * @since 5.0 - */ -@Component -public class HelloUserController { - - public Mono hello(ServerRequest serverRequest) { - return serverRequest.principal() - .map(Principal::getName) - .flatMap((username) -> - ServerResponse.ok() - .contentType(MediaType.APPLICATION_JSON) - .syncBody(Collections.singletonMap("message", "Hello " + username + "!")) - ); - } -} diff --git a/samples/boot/hellowebfluxfn/src/main/java/sample/HelloWebfluxFnApplication.java b/samples/boot/hellowebfluxfn/src/main/java/sample/HelloWebfluxFnApplication.java deleted file mode 100644 index c508e9f5547..00000000000 --- a/samples/boot/hellowebfluxfn/src/main/java/sample/HelloWebfluxFnApplication.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2002-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import static org.springframework.web.reactive.function.server.RequestPredicates.GET; -import static org.springframework.web.reactive.function.server.RouterFunctions.route; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Bean; -import org.springframework.web.reactive.function.server.RouterFunction; -import org.springframework.web.reactive.function.server.ServerResponse; - -/** - * @author Rob Winch - * @since 5.0 - */ -@SpringBootApplication -public class HelloWebfluxFnApplication { - - public static void main(String[] args) { - SpringApplication.run(HelloWebfluxFnApplication.class, args); - } - - @Bean - public RouterFunction routes(HelloUserController userController) { - return route( - GET("/"), userController::hello); - } -} diff --git a/samples/boot/hellowebfluxfn/src/main/java/sample/HelloWebfluxFnSecurityConfig.java b/samples/boot/hellowebfluxfn/src/main/java/sample/HelloWebfluxFnSecurityConfig.java deleted file mode 100644 index bd8c83c5532..00000000000 --- a/samples/boot/hellowebfluxfn/src/main/java/sample/HelloWebfluxFnSecurityConfig.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2002-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.springframework.context.annotation.Bean; -import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; -import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetails; - -/** - * @author Rob Winch - * @since 5.0 - */ -@EnableWebFluxSecurity -public class HelloWebfluxFnSecurityConfig { - - @Bean - public MapReactiveUserDetailsService userDetailsService() { - UserDetails user = User.withDefaultPasswordEncoder() - .username("user") - .password("user") - .roles("USER") - .build(); - return new MapReactiveUserDetailsService(user); - } -} diff --git a/samples/boot/hellowebfluxfn/src/test/java/sample/HelloWebfluxFnApplicationTests.java b/samples/boot/hellowebfluxfn/src/test/java/sample/HelloWebfluxFnApplicationTests.java deleted file mode 100644 index 826fe95cf35..00000000000 --- a/samples/boot/hellowebfluxfn/src/test/java/sample/HelloWebfluxFnApplicationTests.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2002-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockUser; -import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.springSecurity; - -import java.util.function.Consumer; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.ApplicationContext; -import org.springframework.http.HttpHeaders; -import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.reactive.server.WebTestClient; - -/** - * @author Rob Winch - * @since 5.0 - */ -@RunWith(SpringRunner.class) -@SpringBootTest -@AutoConfigureWebTestClient -public class HelloWebfluxFnApplicationTests { - - WebTestClient rest; - - @Autowired - public void setup(ApplicationContext context) { - this.rest = WebTestClient - .bindToApplicationContext(context) - .apply(springSecurity()) - .configureClient() - .build(); - } - - @Test - public void basicWhenNoCredentialsThenUnauthorized() { - this.rest - .get() - .uri("/") - .exchange() - .expectStatus().isUnauthorized(); - } - - @Test - public void basicWhenValidCredentialsThenOk() { - this.rest - .get() - .uri("/") - .headers(userCredentials()) - .exchange() - .expectStatus().isOk() - .expectBody().json("{\"message\":\"Hello user!\"}"); - } - - @Test - public void basicWhenInvalidCredentialsThenUnauthorized() { - this.rest - .get() - .uri("/") - .headers(invalidCredentials()) - .exchange() - .expectStatus().isUnauthorized() - .expectBody().isEmpty(); - } - - @Test - public void mockSupportWhenMutateWithMockUserThenOk() { - this.rest - .mutateWith(mockUser()) - .get() - .uri("/") - .exchange() - .expectStatus().isOk() - .expectBody().json("{\"message\":\"Hello user!\"}"); - } - - @Test - @WithMockUser - public void mockSupportWhenWithMockUserThenOk() { - this.rest - .get() - .uri("/") - .exchange() - .expectStatus().isOk() - .expectBody().json("{\"message\":\"Hello user!\"}"); - } - - private Consumer userCredentials() { - return (httpHeaders) -> httpHeaders.setBasicAuth("user", "user"); - } - - private Consumer invalidCredentials() { - return (httpHeaders) -> httpHeaders.setBasicAuth("user", "INVALID"); - } -} diff --git a/samples/boot/helloworld/spring-security-samples-boot-helloworld.gradle b/samples/boot/helloworld/spring-security-samples-boot-helloworld.gradle deleted file mode 100644 index a64426286bf..00000000000 --- a/samples/boot/helloworld/spring-security-samples-boot-helloworld.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'io.spring.convention.spring-sample-boot' - -dependencies { - compile project(':spring-security-config') - compile project(':spring-security-web') - compile 'org.springframework.boot:spring-boot-starter-thymeleaf' - compile 'org.springframework.boot:spring-boot-starter-web' - compile 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5' - - testCompile project(':spring-security-test') - testCompile 'org.springframework.boot:spring-boot-starter-test' -} diff --git a/samples/boot/helloworld/src/integration-test/java/org/springframework/security/samples/HelloWorldApplicationTests.java b/samples/boot/helloworld/src/integration-test/java/org/springframework/security/samples/HelloWorldApplicationTests.java deleted file mode 100644 index faeeb015a25..00000000000 --- a/samples/boot/helloworld/src/integration-test/java/org/springframework/security/samples/HelloWorldApplicationTests.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2012-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.samples; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.mock.web.MockHttpSession; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; -import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated; -import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * - * @author Joe Grandja - */ -@RunWith(SpringJUnit4ClassRunner.class) -@SpringBootTest -@AutoConfigureMockMvc -public class HelloWorldApplicationTests { - - @Autowired - private MockMvc mockMvc; - - @Test - public void accessUnprotected() throws Exception { - // @formatter:off - this.mockMvc.perform(get("/index")) - .andExpect(status().isOk()); - // @formatter:on - } - - @Test - public void accessProtectedRedirectsToLogin() throws Exception { - // @formatter:off - MvcResult mvcResult = this.mockMvc.perform(get("/user/index")) - .andExpect(status().is3xxRedirection()) - .andReturn(); - // @formatter:on - - assertThat(mvcResult.getResponse().getRedirectedUrl()).endsWith("/login"); - } - - @Test - public void loginUser() throws Exception { - // @formatter:off - this.mockMvc.perform(formLogin().user("user").password("password")) - .andExpect(authenticated()); - // @formatter:on - } - - @Test - public void loginInvalidUser() throws Exception { - // @formatter:off - this.mockMvc.perform(formLogin().user("invalid").password("invalid")) - .andExpect(unauthenticated()) - .andExpect(status().is3xxRedirection()); - // @formatter:on - } - - @Test - public void loginUserAccessProtected() throws Exception { - // @formatter:off - MvcResult mvcResult = this.mockMvc.perform(formLogin().user("user").password("password")) - .andExpect(authenticated()).andReturn(); - // @formatter:on - - MockHttpSession httpSession = (MockHttpSession) mvcResult.getRequest().getSession(false); - - // @formatter:off - this.mockMvc.perform(get("/user/index").session(httpSession)) - .andExpect(status().isOk()); - // @formatter:on - } - - @Test - public void loginUserValidateLogout() throws Exception { - // @formatter:off - MvcResult mvcResult = this.mockMvc.perform(formLogin().user("user").password("password")) - .andExpect(authenticated()).andReturn(); - // @formatter:on - - MockHttpSession httpSession = (MockHttpSession) mvcResult.getRequest().getSession(false); - - // @formatter:off - this.mockMvc.perform(post("/logout").with(csrf()).session(httpSession)) - .andExpect(unauthenticated()); - this.mockMvc.perform(get("/user/index").session(httpSession)) - .andExpect(unauthenticated()) - .andExpect(status().is3xxRedirection()); - // @formatter:on - } -} diff --git a/samples/boot/helloworld/src/main/java/org/springframework/security/samples/HelloWorldApplication.java b/samples/boot/helloworld/src/main/java/org/springframework/security/samples/HelloWorldApplication.java deleted file mode 100644 index 170bcadef31..00000000000 --- a/samples/boot/helloworld/src/main/java/org/springframework/security/samples/HelloWorldApplication.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2012-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.samples; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - * @author Joe Grandja - */ -@SpringBootApplication -public class HelloWorldApplication { - - public static void main(String[] args) { - SpringApplication.run(HelloWorldApplication.class, args); - } - - -} \ No newline at end of file diff --git a/samples/boot/helloworld/src/main/java/org/springframework/security/samples/config/SecurityConfig.java b/samples/boot/helloworld/src/main/java/org/springframework/security/samples/config/SecurityConfig.java deleted file mode 100644 index c3c71cf2fe5..00000000000 --- a/samples/boot/helloworld/src/main/java/org/springframework/security/samples/config/SecurityConfig.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.samples.config; - -import org.springframework.context.annotation.Bean; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.provisioning.InMemoryUserDetailsManager; - -/** - * @author Joe Grandja - */ -@EnableWebSecurity -public class SecurityConfig extends WebSecurityConfigurerAdapter { - - // @formatter:off - @Override - protected void configure(HttpSecurity http) throws Exception { - http - .authorizeRequests((authorize) -> authorize - .antMatchers("/css/**", "/index").permitAll() - .antMatchers("/user/**").hasRole("USER") - ) - .formLogin((formLogin) -> formLogin - .loginPage("/login") - .failureUrl("/login-error") - ); - } - // @formatter:on - - @Bean - public UserDetailsService userDetailsService() { - UserDetails userDetails = User.withDefaultPasswordEncoder() - .username("user") - .password("password") - .roles("USER") - .build(); - return new InMemoryUserDetailsManager(userDetails); - } -} diff --git a/samples/boot/helloworld/src/main/java/org/springframework/security/samples/web/MainController.java b/samples/boot/helloworld/src/main/java/org/springframework/security/samples/web/MainController.java deleted file mode 100644 index 793a8e97ebf..00000000000 --- a/samples/boot/helloworld/src/main/java/org/springframework/security/samples/web/MainController.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2002-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.samples.web; - -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.RequestMapping; - -/** - * @author Joe Grandja - */ -@Controller -public class MainController { - - @RequestMapping("/") - public String root() { - return "redirect:/index"; - } - - @RequestMapping("/index") - public String index() { - return "index"; - } - - @RequestMapping("/user/index") - public String userIndex() { - return "user/index"; - } - - @RequestMapping("/login") - public String login() { - return "login"; - } - - @RequestMapping("/login-error") - public String loginError(Model model) { - model.addAttribute("loginError", true); - return "login"; - } - -} diff --git a/samples/boot/helloworld/src/main/resources/application.yml b/samples/boot/helloworld/src/main/resources/application.yml deleted file mode 100644 index b59d86df49c..00000000000 --- a/samples/boot/helloworld/src/main/resources/application.yml +++ /dev/null @@ -1,12 +0,0 @@ -server: - port: 8080 - -logging: - level: - root: WARN - org.springframework.web: INFO - org.springframework.security: INFO - -spring: - thymeleaf: - cache: false diff --git a/samples/boot/helloworld/src/main/resources/static/css/main.css b/samples/boot/helloworld/src/main/resources/static/css/main.css deleted file mode 100644 index 5e6687a387c..00000000000 --- a/samples/boot/helloworld/src/main/resources/static/css/main.css +++ /dev/null @@ -1,13 +0,0 @@ -body { - font-family: sans; - font-size: 1em; -} - -p.error { - font-weight: bold; - color: red; -} - -div.logout { - float: right; -} \ No newline at end of file diff --git a/samples/boot/helloworld/src/main/resources/templates/index.html b/samples/boot/helloworld/src/main/resources/templates/index.html deleted file mode 100644 index 05fad120332..00000000000 --- a/samples/boot/helloworld/src/main/resources/templates/index.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - Hello Spring Security - - - - -
- Logged in user: | - Roles: -
- - - -
-
-

Hello Spring Security

-

This is an unsecured page, but you can access the secured pages after authenticating.

- - - diff --git a/samples/boot/helloworld/src/main/resources/templates/login.html b/samples/boot/helloworld/src/main/resources/templates/login.html deleted file mode 100644 index cec2b5b0daf..00000000000 --- a/samples/boot/helloworld/src/main/resources/templates/login.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - Login page - - - - -

Login page

-

Example user: user / password

-

Wrong user or password

-
- : -
- : -
- -
-

Back to home page

- - diff --git a/samples/boot/helloworld/src/main/resources/templates/user/index.html b/samples/boot/helloworld/src/main/resources/templates/user/index.html deleted file mode 100644 index 53dd9319a52..00000000000 --- a/samples/boot/helloworld/src/main/resources/templates/user/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - Hello Spring Security - - - - -
-

This is a secured page!

-

Back to home page

- - diff --git a/samples/boot/insecure/spring-security-samples-boot-insecure.gradle b/samples/boot/insecure/spring-security-samples-boot-insecure.gradle deleted file mode 100644 index 556ebbb0536..00000000000 --- a/samples/boot/insecure/spring-security-samples-boot-insecure.gradle +++ /dev/null @@ -1,8 +0,0 @@ -apply plugin: 'io.spring.convention.spring-sample-boot' - -dependencies { - compile 'org.springframework.boot:spring-boot-starter-thymeleaf' - compile 'org.springframework.boot:spring-boot-starter-web' - - testCompile 'org.springframework.boot:spring-boot-starter-test' -} diff --git a/samples/boot/insecure/src/integration-test/java/org/springframework/security/samples/InsecureApplicationTests.java b/samples/boot/insecure/src/integration-test/java/org/springframework/security/samples/InsecureApplicationTests.java deleted file mode 100644 index f2c7542e5b0..00000000000 --- a/samples/boot/insecure/src/integration-test/java/org/springframework/security/samples/InsecureApplicationTests.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2012-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.samples; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.web.servlet.MockMvc; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * - * @author Joe Grandja - */ -@RunWith(SpringJUnit4ClassRunner.class) -@SpringBootTest -@AutoConfigureMockMvc -public class InsecureApplicationTests { - - @Autowired - private MockMvc mockMvc; - - @Test - public void accessUnprotected() throws Exception { - this.mockMvc.perform(get("/index")).andExpect(status().isOk()); - } - -} diff --git a/samples/boot/insecure/src/main/java/org/springframework/security/samples/InsecureApplication.java b/samples/boot/insecure/src/main/java/org/springframework/security/samples/InsecureApplication.java deleted file mode 100644 index aab24fa9d36..00000000000 --- a/samples/boot/insecure/src/main/java/org/springframework/security/samples/InsecureApplication.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2012-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.samples; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - * @author Joe Grandja - */ -@SpringBootApplication -public class InsecureApplication { - - public static void main(String[] args) { - SpringApplication.run(InsecureApplication.class, args); - } - - -} diff --git a/samples/boot/insecure/src/main/java/org/springframework/security/samples/web/MainController.java b/samples/boot/insecure/src/main/java/org/springframework/security/samples/web/MainController.java deleted file mode 100644 index f7de44adcc9..00000000000 --- a/samples/boot/insecure/src/main/java/org/springframework/security/samples/web/MainController.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2002-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.samples.web; - -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; - -/** - * @author Joe Grandja - */ -@Controller -public class MainController { - - @RequestMapping("/") - public String root() { - return "redirect:/index"; - } - - @RequestMapping("/index") - public String index() { - return "index"; - } - - @RequestMapping("/user/index") - public String userIndex() { - return "user/index"; - } - - @RequestMapping(value = "/login") - public String login() { - return "login"; - } - - @RequestMapping(value = "/login", method = RequestMethod.POST) - public String postLogin() { - // TODO Enable form login with Spring Security (trigger error for now) - return "redirect:/login-error"; - } - - @RequestMapping("/login-error") - public String loginError(Model model) { - model.addAttribute("loginError", true); - return "login"; - } - -} diff --git a/samples/boot/insecure/src/main/resources/application.yml b/samples/boot/insecure/src/main/resources/application.yml deleted file mode 100644 index 02540888032..00000000000 --- a/samples/boot/insecure/src/main/resources/application.yml +++ /dev/null @@ -1,11 +0,0 @@ -server: - port: 8080 - -logging: - level: - root: WARN - org.springframework.web: INFO - -spring: - thymeleaf: - cache: false diff --git a/samples/boot/insecure/src/main/resources/static/css/main.css b/samples/boot/insecure/src/main/resources/static/css/main.css deleted file mode 100644 index 5e6687a387c..00000000000 --- a/samples/boot/insecure/src/main/resources/static/css/main.css +++ /dev/null @@ -1,13 +0,0 @@ -body { - font-family: sans; - font-size: 1em; -} - -p.error { - font-weight: bold; - color: red; -} - -div.logout { - float: right; -} \ No newline at end of file diff --git a/samples/boot/insecure/src/main/resources/templates/index.html b/samples/boot/insecure/src/main/resources/templates/index.html deleted file mode 100644 index ee9ccec6183..00000000000 --- a/samples/boot/insecure/src/main/resources/templates/index.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - Hello Spring Security - - - - -

Hello Spring Security

-

This is an unsecured page, but you can access the secured pages after authenticating.

- - - diff --git a/samples/boot/insecure/src/main/resources/templates/login.html b/samples/boot/insecure/src/main/resources/templates/login.html deleted file mode 100644 index cec2b5b0daf..00000000000 --- a/samples/boot/insecure/src/main/resources/templates/login.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - Login page - - - - -

Login page

-

Example user: user / password

-

Wrong user or password

-
- : -
- : -
- -
-

Back to home page

- - diff --git a/samples/boot/insecure/src/main/resources/templates/user/index.html b/samples/boot/insecure/src/main/resources/templates/user/index.html deleted file mode 100644 index 3fd4ccc1a35..00000000000 --- a/samples/boot/insecure/src/main/resources/templates/user/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - Hello Spring Security - - - - -

TODO Secure this

-

We would like to secure this page

-

Back to home page

- - diff --git a/samples/boot/kotlin-webflux/spring-security-samples-boot-kotlin-webflux.gradle.kts b/samples/boot/kotlin-webflux/spring-security-samples-boot-kotlin-webflux.gradle.kts deleted file mode 100644 index 20eb7b4060a..00000000000 --- a/samples/boot/kotlin-webflux/spring-security-samples-boot-kotlin-webflux.gradle.kts +++ /dev/null @@ -1,41 +0,0 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - -plugins { - id("io.spring.convention.spring-sample-boot") - kotlin("jvm") - kotlin("plugin.spring") version "1.3.71" -} - -repositories { - mavenCentral() -} - -dependencies { - implementation(project(":spring-security-core")) - implementation(project(":spring-security-config")) - implementation(project(":spring-security-web")) - implementation("org.springframework.boot:spring-boot-starter-webflux") - implementation("org.springframework.boot:spring-boot-starter-thymeleaf") - implementation("org.thymeleaf.extras:thymeleaf-extras-springsecurity5") - implementation("io.projectreactor.kotlin:reactor-kotlin-extensions") - implementation("org.jetbrains.kotlin:kotlin-reflect") - implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor") - - testImplementation(project(":spring-security-test")) - testImplementation("org.springframework.boot:spring-boot-starter-test") { - exclude(group = "org.junit.vintage", module = "junit-vintage-engine") - } - testImplementation("io.projectreactor:reactor-test") -} - -tasks.withType { - useJUnitPlatform() -} - -tasks.withType { - kotlinOptions { - freeCompilerArgs = listOf("-Xjsr305=strict") - jvmTarget = "1.8" - } -} diff --git a/samples/boot/kotlin-webflux/src/main/kotlin/org/springframework/security/samples/KotlinWebfluxApplication.kt b/samples/boot/kotlin-webflux/src/main/kotlin/org/springframework/security/samples/KotlinWebfluxApplication.kt deleted file mode 100644 index 572be2a6e3c..00000000000 --- a/samples/boot/kotlin-webflux/src/main/kotlin/org/springframework/security/samples/KotlinWebfluxApplication.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2002-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.samples - -import org.springframework.boot.autoconfigure.SpringBootApplication -import org.springframework.boot.runApplication - -@SpringBootApplication -class KotlinWebfluxApplication - -fun main(args: Array) { - runApplication(*args) -} diff --git a/samples/boot/kotlin-webflux/src/main/kotlin/org/springframework/security/samples/config/SecurityConfig.kt b/samples/boot/kotlin-webflux/src/main/kotlin/org/springframework/security/samples/config/SecurityConfig.kt deleted file mode 100644 index fcdb5fdcdee..00000000000 --- a/samples/boot/kotlin-webflux/src/main/kotlin/org/springframework/security/samples/config/SecurityConfig.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2002-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.samples.config - -import org.springframework.context.annotation.Bean -import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity -import org.springframework.security.config.web.server.ServerHttpSecurity -import org.springframework.security.config.web.server.invoke -import org.springframework.security.core.userdetails.MapReactiveUserDetailsService -import org.springframework.security.core.userdetails.ReactiveUserDetailsService -import org.springframework.security.core.userdetails.User -import org.springframework.security.web.server.SecurityWebFilterChain - -@EnableWebFluxSecurity -class SecurityConfig { - - @Bean - fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { - return http { - authorizeExchange { - authorize("/log-in", permitAll) - authorize("/", permitAll) - authorize("/css/**", permitAll) - authorize("/user/**", hasAuthority("ROLE_USER")) - } - formLogin { - loginPage = "/log-in" - } - } - } - - @Bean - fun userDetailsService(): ReactiveUserDetailsService { - val userDetails = User.withDefaultPasswordEncoder() - .username("user") - .password("password") - .roles("USER") - .build() - return MapReactiveUserDetailsService(userDetails) - } -} diff --git a/samples/boot/kotlin-webflux/src/main/kotlin/org/springframework/security/samples/web/MainController.kt b/samples/boot/kotlin-webflux/src/main/kotlin/org/springframework/security/samples/web/MainController.kt deleted file mode 100644 index 991c0195c06..00000000000 --- a/samples/boot/kotlin-webflux/src/main/kotlin/org/springframework/security/samples/web/MainController.kt +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2002-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.samples.web - -import org.springframework.stereotype.Controller -import org.springframework.web.bind.annotation.GetMapping - -@Controller -class MainController { - - @GetMapping("/") - fun index(): String { - return "index" - } - - @GetMapping("/user/index") - fun userIndex(): String { - return "user/index" - } - - @GetMapping("/log-in") - fun login(): String { - return "login" - } -} diff --git a/samples/boot/kotlin-webflux/src/main/resources/application.yml b/samples/boot/kotlin-webflux/src/main/resources/application.yml deleted file mode 100644 index 8c01e005bcd..00000000000 --- a/samples/boot/kotlin-webflux/src/main/resources/application.yml +++ /dev/null @@ -1,6 +0,0 @@ -server: - port: 8080 - -spring: - thymeleaf: - cache: false diff --git a/samples/boot/kotlin-webflux/src/main/resources/css/main.css b/samples/boot/kotlin-webflux/src/main/resources/css/main.css deleted file mode 100644 index de0941ecd58..00000000000 --- a/samples/boot/kotlin-webflux/src/main/resources/css/main.css +++ /dev/null @@ -1,8 +0,0 @@ -body { - font-family: sans; - font-size: 1em; -} - -div.logout { - float: right; -} diff --git a/samples/boot/kotlin-webflux/src/main/resources/templates/index.html b/samples/boot/kotlin-webflux/src/main/resources/templates/index.html deleted file mode 100644 index f637854f047..00000000000 --- a/samples/boot/kotlin-webflux/src/main/resources/templates/index.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - Hello Spring Security - - - - -
- Logged in user: | - Roles: -
-
- -
-
-
-

Hello Spring Security

-

This is an unsecured page, but you can access the secured pages after authenticating.

- - - diff --git a/samples/boot/kotlin-webflux/src/main/resources/templates/login.html b/samples/boot/kotlin-webflux/src/main/resources/templates/login.html deleted file mode 100644 index 2ee92169376..00000000000 --- a/samples/boot/kotlin-webflux/src/main/resources/templates/login.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - Login page - - - - -

Login page

-

Example user: user / password

-
- : -
- : -
- -
-

Back to home page

- - diff --git a/samples/boot/kotlin-webflux/src/main/resources/templates/user/index.html b/samples/boot/kotlin-webflux/src/main/resources/templates/user/index.html deleted file mode 100644 index 393f6d37051..00000000000 --- a/samples/boot/kotlin-webflux/src/main/resources/templates/user/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - Hello Spring Security - - - - -
-

This is a secured page!

-

Back to home page

- - diff --git a/samples/boot/kotlin-webflux/src/test/kotlin/org/springframework/security/samples/KotlinWebfluxApplicationTests.kt b/samples/boot/kotlin-webflux/src/test/kotlin/org/springframework/security/samples/KotlinWebfluxApplicationTests.kt deleted file mode 100644 index fc126e58b7f..00000000000 --- a/samples/boot/kotlin-webflux/src/test/kotlin/org/springframework/security/samples/KotlinWebfluxApplicationTests.kt +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2002-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.samples - -import org.junit.jupiter.api.Test -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.context.ApplicationContext -import org.springframework.test.web.reactive.server.WebTestClient -import org.springframework.security.test.context.support.WithMockUser - -@SpringBootTest -class KotlinWebfluxApplicationTests { - - lateinit var rest: WebTestClient - - @Autowired - fun setup(context: ApplicationContext) { - rest = WebTestClient - .bindToApplicationContext(context) - .configureClient() - .build() - } - - @Test - fun `index page is not protected`() { - rest - .get() - .uri("/") - .exchange() - .expectStatus().isOk - } - - @Test - fun `protected page when unauthenticated then redirects to login `() { - rest - .get() - .uri("/user/index") - .exchange() - .expectStatus().is3xxRedirection - .expectHeader().valueEquals("Location", "/log-in") - } - - @Test - @WithMockUser - fun `protected page can be accessed when authenticated`() { - rest - .get() - .uri("/user/index") - .exchange() - .expectStatus().isOk - } -} diff --git a/samples/boot/kotlin/spring-security-samples-boot-kotlin.gradle.kts b/samples/boot/kotlin/spring-security-samples-boot-kotlin.gradle.kts deleted file mode 100644 index bd841b8b710..00000000000 --- a/samples/boot/kotlin/spring-security-samples-boot-kotlin.gradle.kts +++ /dev/null @@ -1,31 +0,0 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - -plugins { - id("io.spring.convention.spring-sample-boot") - kotlin("jvm") - kotlin("plugin.spring") version "1.3.71" -} - -repositories { - mavenCentral() -} - -dependencies { - implementation(project(":spring-security-core")) - implementation(project(":spring-security-config")) - implementation(project(":spring-security-web")) - implementation("org.springframework.boot:spring-boot-starter-web") - implementation("org.springframework.boot:spring-boot-starter-thymeleaf") - implementation("org.thymeleaf.extras:thymeleaf-extras-springsecurity5") - implementation("org.jetbrains.kotlin:kotlin-reflect") - implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") - testImplementation(project(":spring-security-test")) - testImplementation("org.springframework.boot:spring-boot-starter-test") -} - -tasks.withType { - kotlinOptions { - freeCompilerArgs = listOf("-Xjsr305=strict") - jvmTarget = "1.8" - } -} diff --git a/samples/boot/kotlin/src/main/kotlin/org/springframework/security/samples/KotlinApplication.kt b/samples/boot/kotlin/src/main/kotlin/org/springframework/security/samples/KotlinApplication.kt deleted file mode 100644 index 1d7c7b28f2d..00000000000 --- a/samples/boot/kotlin/src/main/kotlin/org/springframework/security/samples/KotlinApplication.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.security.samples - -import org.springframework.boot.autoconfigure.SpringBootApplication -import org.springframework.boot.runApplication - -/** - * @author Eleftheria Stein - */ -@SpringBootApplication -class KotlinApplication - -fun main(args: Array) { - runApplication(*args) -} diff --git a/samples/boot/kotlin/src/main/kotlin/org/springframework/security/samples/config/SecurityConfig.kt b/samples/boot/kotlin/src/main/kotlin/org/springframework/security/samples/config/SecurityConfig.kt deleted file mode 100644 index 043f29ecae0..00000000000 --- a/samples/boot/kotlin/src/main/kotlin/org/springframework/security/samples/config/SecurityConfig.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.samples.config - -import org.springframework.context.annotation.Bean -import org.springframework.security.config.annotation.web.builders.HttpSecurity -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter -import org.springframework.security.config.web.servlet.invoke -import org.springframework.security.core.userdetails.User -import org.springframework.security.core.userdetails.UserDetailsService -import org.springframework.security.provisioning.InMemoryUserDetailsManager - -/** - * @author Eleftheria Stein - */ -@EnableWebSecurity -class SecurityConfig : WebSecurityConfigurerAdapter() { - - override fun configure(http: HttpSecurity) { - http { - authorizeRequests { - authorize("/css/**", permitAll) - authorize("/user/**", hasAuthority("ROLE_USER")) - } - formLogin { - loginPage = "/log-in" - } - } - } - - @Bean - public override fun userDetailsService(): UserDetailsService { - val userDetails = User.withDefaultPasswordEncoder() - .username("user") - .password("password") - .roles("USER") - .build() - return InMemoryUserDetailsManager(userDetails) - } -} diff --git a/samples/boot/kotlin/src/main/kotlin/org/springframework/security/samples/web/MainController.kt b/samples/boot/kotlin/src/main/kotlin/org/springframework/security/samples/web/MainController.kt deleted file mode 100644 index e8c8128ad8a..00000000000 --- a/samples/boot/kotlin/src/main/kotlin/org/springframework/security/samples/web/MainController.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.samples.web - -import org.springframework.stereotype.Controller -import org.springframework.web.bind.annotation.GetMapping - -/** - * @author Eleftheria Stein - */ -@Controller -class MainController { - - @GetMapping("/") - fun index(): String { - return "index" - } - - @GetMapping("/user/index") - fun userIndex(): String { - return "user/index" - } - - @GetMapping("/log-in") - fun login(): String { - return "login" - } -} diff --git a/samples/boot/kotlin/src/main/resources/application.yml b/samples/boot/kotlin/src/main/resources/application.yml deleted file mode 100644 index 8c01e005bcd..00000000000 --- a/samples/boot/kotlin/src/main/resources/application.yml +++ /dev/null @@ -1,6 +0,0 @@ -server: - port: 8080 - -spring: - thymeleaf: - cache: false diff --git a/samples/boot/kotlin/src/main/resources/static/css/main.css b/samples/boot/kotlin/src/main/resources/static/css/main.css deleted file mode 100644 index de0941ecd58..00000000000 --- a/samples/boot/kotlin/src/main/resources/static/css/main.css +++ /dev/null @@ -1,8 +0,0 @@ -body { - font-family: sans; - font-size: 1em; -} - -div.logout { - float: right; -} diff --git a/samples/boot/kotlin/src/main/resources/templates/index.html b/samples/boot/kotlin/src/main/resources/templates/index.html deleted file mode 100644 index c30f4a8292c..00000000000 --- a/samples/boot/kotlin/src/main/resources/templates/index.html +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - Hello Spring Security - - - - -
- Logged in user: | - Roles: -
-
- -
-
-
-

Hello Spring Security

-

This is an unsecured page, but you can access the secured pages after authenticating.

- - - diff --git a/samples/boot/kotlin/src/main/resources/templates/login.html b/samples/boot/kotlin/src/main/resources/templates/login.html deleted file mode 100644 index 2ee92169376..00000000000 --- a/samples/boot/kotlin/src/main/resources/templates/login.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - Login page - - - - -

Login page

-

Example user: user / password

-
- : -
- : -
- -
-

Back to home page

- - diff --git a/samples/boot/kotlin/src/main/resources/templates/user/index.html b/samples/boot/kotlin/src/main/resources/templates/user/index.html deleted file mode 100644 index b36caed0272..00000000000 --- a/samples/boot/kotlin/src/main/resources/templates/user/index.html +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - Hello Spring Security - - - - -
-

This is a secured page!

-

Back to home page

- - diff --git a/samples/boot/kotlin/src/test/kotlin/org/springframework/security/samples/KotlinApplicationTests.kt b/samples/boot/kotlin/src/test/kotlin/org/springframework/security/samples/KotlinApplicationTests.kt deleted file mode 100644 index 393bb0f2e20..00000000000 --- a/samples/boot/kotlin/src/test/kotlin/org/springframework/security/samples/KotlinApplicationTests.kt +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.samples - -import org.assertj.core.api.Assertions.assertThat -import org.junit.Test -import org.junit.runner.RunWith -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.mock.web.MockHttpSession -import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin -import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated -import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated -import org.springframework.test.context.junit4.SpringRunner -import org.springframework.test.web.servlet.MockMvc -import org.springframework.test.web.servlet.get -import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status - -@RunWith(SpringRunner::class) -@SpringBootTest -@AutoConfigureMockMvc -class KotlinApplicationTests { - - @Autowired - private lateinit var mockMvc: MockMvc - - @Test - fun `index page is not protected`() { - this.mockMvc.get("/") - .andExpect { - status { isOk() } - } - } - - @Test - fun `protected page redirects to login`() { - val mvcResult = this.mockMvc.get("/user/index") - .andExpect { status { is3xxRedirection() } } - .andReturn() - - assertThat(mvcResult.response.redirectedUrl).endsWith("/log-in") - } - - @Test - fun `valid user permitted to log in`() { - this.mockMvc.perform(formLogin("/log-in").user("user").password("password")) - .andExpect(authenticated()) - } - - @Test - fun `invalid user not permitted to log in`() { - this.mockMvc.perform(formLogin("/log-in").user("invalid").password("invalid")) - .andExpect(unauthenticated()) - .andExpect(status().is3xxRedirection) - } - - @Test - fun `logged in user can access protected page`() { - val mvcResult = this.mockMvc.perform(formLogin("/log-in").user("user").password("password")) - .andExpect(authenticated()).andReturn() - - val httpSession = mvcResult.request.getSession(false) as MockHttpSession - - this.mockMvc.get("/user/index") { - session = httpSession - }.andExpect { - status { isOk() } - } - } -} diff --git a/samples/boot/oauth2authorizationserver/README.adoc b/samples/boot/oauth2authorizationserver/README.adoc deleted file mode 100644 index e056a21b175..00000000000 --- a/samples/boot/oauth2authorizationserver/README.adoc +++ /dev/null @@ -1,39 +0,0 @@ -= OAuth 2.0 Authorization Server Sample - -This sample demonstrates an Authorization Server that supports a simple, static JWK Set. - -It's useful for working with the other samples in the library that want to point to an Authorization Server. - -== 1. Running the server - -To run the server, do: - -```bash -./gradlew bootRun -``` - -Or import the project into your IDE and run `OAuth2AuthorizationServerApplication` from there. - -Once it is up, this request asks for a token with the "message:read" scope: - -```bash -curl reader:secret@localhost:8081/oauth/token -d grant_type=password -d username=subject -d password=password -``` - -Which will respond with something like: - -```json -{ - "access_token":"eyJhbGciOiJSUzI1NiIsI...Fhq4RIVyA4ZAkC7T1aZbKAQ", - "token_type":"bearer", - "expires_in":599999999, - "scope":"message:read", - "jti":"8a425df7-f4c9-4ca4-be12-0136c3015da0" -} -``` - -You can also do the same with the `writer` client: - -```bash -curl writer:secret@localhost:8081/oauth/token -d grant_type=password -d username=subject -d password=password -``` diff --git a/samples/boot/oauth2authorizationserver/spring-security-samples-boot-oauth2authorizationserver.gradle b/samples/boot/oauth2authorizationserver/spring-security-samples-boot-oauth2authorizationserver.gradle deleted file mode 100644 index 3b2df4e605f..00000000000 --- a/samples/boot/oauth2authorizationserver/spring-security-samples-boot-oauth2authorizationserver.gradle +++ /dev/null @@ -1,14 +0,0 @@ -apply plugin: 'io.spring.convention.spring-sample-boot' - -dependencies { - compile 'org.springframework.boot:spring-boot-starter-web' - compile 'org.springframework.boot:spring-boot-starter-security' - compile "org.springframework.security.oauth.boot:spring-security-oauth2-autoconfigure:${springBootVersion}" - - compile 'javax.xml.bind:jaxb-api' - compile 'com.sun.xml.bind:jaxb-core' - compile 'com.sun.xml.bind:jaxb-impl' - compile 'com.nimbusds:nimbus-jose-jwt' - - testCompile 'org.springframework.boot:spring-boot-starter-test' -} diff --git a/samples/boot/oauth2authorizationserver/src/main/java/sample/AuthorizationServerConfiguration.java b/samples/boot/oauth2authorizationserver/src/main/java/sample/AuthorizationServerConfiguration.java deleted file mode 100644 index 4b20b0c88c5..00000000000 --- a/samples/boot/oauth2authorizationserver/src/main/java/sample/AuthorizationServerConfiguration.java +++ /dev/null @@ -1,284 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import java.math.BigInteger; -import java.security.KeyFactory; -import java.security.KeyPair; -import java.security.interfaces.RSAPublicKey; -import java.security.spec.RSAPrivateKeySpec; -import java.security.spec.RSAPublicKeySpec; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.stream.Collectors; - -import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.RSAKey; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.oauth2.common.OAuth2AccessToken; -import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; -import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; -import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; -import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; -import org.springframework.security.oauth2.provider.OAuth2Authentication; -import org.springframework.security.oauth2.provider.endpoint.FrameworkEndpoint; -import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter; -import org.springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter; -import org.springframework.security.oauth2.provider.token.TokenStore; -import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore; -import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; -import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; -import org.springframework.security.provisioning.InMemoryUserDetailsManager; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.ResponseBody; - -/** - * An instance of Legacy Authorization Server (spring-security-oauth2) that uses a single, - * not-rotating key and exposes a JWK endpoint. - * - * See - * - * Spring Security OAuth Autoconfig's documentation for additional detail - * - * @author Josh Cummings - * @since 5.1 - */ -@EnableAuthorizationServer -@Configuration -public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter { - - AuthenticationManager authenticationManager; - KeyPair keyPair; - boolean jwtEnabled; - - public AuthorizationServerConfiguration( - AuthenticationConfiguration authenticationConfiguration, - KeyPair keyPair, - @Value("${security.oauth2.authorizationserver.jwt.enabled:true}") boolean jwtEnabled) throws Exception { - - this.authenticationManager = authenticationConfiguration.getAuthenticationManager(); - this.keyPair = keyPair; - this.jwtEnabled = jwtEnabled; - } - - @Override - public void configure(ClientDetailsServiceConfigurer clients) - throws Exception { - // @formatter:off - clients.inMemory() - .withClient("reader") - .authorizedGrantTypes("password") - .secret("{noop}secret") - .scopes("message:read") - .accessTokenValiditySeconds(600_000_000) - .and() - .withClient("writer") - .authorizedGrantTypes("password") - .secret("{noop}secret") - .scopes("message:write") - .accessTokenValiditySeconds(600_000_000) - .and() - .withClient("noscopes") - .authorizedGrantTypes("password") - .secret("{noop}secret") - .scopes("none") - .accessTokenValiditySeconds(600_000_000); - // @formatter:on - } - - @Override - public void configure(AuthorizationServerEndpointsConfigurer endpoints) { - // @formatter:off - endpoints - .authenticationManager(this.authenticationManager) - .tokenStore(tokenStore()); - - if (this.jwtEnabled) { - endpoints - .accessTokenConverter(accessTokenConverter()); - } - // @formatter:on - } - - @Bean - public TokenStore tokenStore() { - if (this.jwtEnabled) { - return new JwtTokenStore(accessTokenConverter()); - } else { - return new InMemoryTokenStore(); - } - } - - @Bean - public JwtAccessTokenConverter accessTokenConverter() { - JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); - converter.setKeyPair(this.keyPair); - - DefaultAccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter(); - accessTokenConverter.setUserTokenConverter(new SubjectAttributeUserTokenConverter()); - converter.setAccessTokenConverter(accessTokenConverter); - - return converter; - } -} - -/** - * For configuring the end users recognized by this Authorization Server - */ -@Configuration -class UserConfig extends WebSecurityConfigurerAdapter { - - @Override - protected void configure(HttpSecurity http) throws Exception { - http - .authorizeRequests() - .mvcMatchers("/.well-known/jwks.json").permitAll() - .anyRequest().authenticated() - .and() - .httpBasic() - .and() - .csrf().ignoringRequestMatchers((request) -> "/introspect".equals(request.getRequestURI())); - } - - @Bean - @Override - public UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager( - User.withDefaultPasswordEncoder() - .username("subject") - .password("password") - .roles("USER") - .build()); - } -} - -/** - * Legacy Authorization Server (spring-security-oauth2) does not support any - * Token Introspection endpoint. - * - * This class adds ad-hoc support in order to better support the other samples in the repo. - */ -@FrameworkEndpoint -class IntrospectEndpoint { - TokenStore tokenStore; - - IntrospectEndpoint(TokenStore tokenStore) { - this.tokenStore = tokenStore; - } - - @PostMapping("/introspect") - @ResponseBody - public Map introspect(@RequestParam("token") String token) { - OAuth2AccessToken accessToken = this.tokenStore.readAccessToken(token); - Map attributes = new HashMap<>(); - if (accessToken == null || accessToken.isExpired()) { - attributes.put("active", false); - return attributes; - } - - OAuth2Authentication authentication = this.tokenStore.readAuthentication(token); - - attributes.put("active", true); - attributes.put("exp", accessToken.getExpiration().getTime()); - attributes.put("scope", accessToken.getScope().stream().collect(Collectors.joining(" "))); - attributes.put("sub", authentication.getName()); - - return attributes; - } -} - -/** - * Legacy Authorization Server (spring-security-oauth2) does not support any - * JWK Set endpoint. - * - * This class adds ad-hoc support in order to better support the other samples in the repo. - */ -@FrameworkEndpoint -class JwkSetEndpoint { - KeyPair keyPair; - - JwkSetEndpoint(KeyPair keyPair) { - this.keyPair = keyPair; - } - - @GetMapping("/.well-known/jwks.json") - @ResponseBody - public Map getKey() { - RSAPublicKey publicKey = (RSAPublicKey) this.keyPair.getPublic(); - RSAKey key = new RSAKey.Builder(publicKey).build(); - return new JWKSet(key).toJSONObject(); - } -} - -/** - * An Authorization Server will more typically have a key rotation strategy, and the keys will not - * be hard-coded into the application code. - * - * For simplicity, though, this sample doesn't demonstrate key rotation. - */ -@Configuration -class KeyConfig { - @Bean - KeyPair keyPair() { - try { - String privateExponent = "3851612021791312596791631935569878540203393691253311342052463788814433805390794604753109719790052408607029530149004451377846406736413270923596916756321977922303381344613407820854322190592787335193581632323728135479679928871596911841005827348430783250026013354350760878678723915119966019947072651782000702927096735228356171563532131162414366310012554312756036441054404004920678199077822575051043273088621405687950081861819700809912238863867947415641838115425624808671834312114785499017269379478439158796130804789241476050832773822038351367878951389438751088021113551495469440016698505614123035099067172660197922333993"; - String modulus = "18044398961479537755088511127417480155072543594514852056908450877656126120801808993616738273349107491806340290040410660515399239279742407357192875363433659810851147557504389760192273458065587503508596714389889971758652047927503525007076910925306186421971180013159326306810174367375596043267660331677530921991343349336096643043840224352451615452251387611820750171352353189973315443889352557807329336576421211370350554195530374360110583327093711721857129170040527236951522127488980970085401773781530555922385755722534685479501240842392531455355164896023070459024737908929308707435474197069199421373363801477026083786683"; - String exponent = "65537"; - - RSAPublicKeySpec publicSpec = new RSAPublicKeySpec(new BigInteger(modulus), new BigInteger(exponent)); - RSAPrivateKeySpec privateSpec = new RSAPrivateKeySpec(new BigInteger(modulus), new BigInteger(privateExponent)); - KeyFactory factory = KeyFactory.getInstance("RSA"); - return new KeyPair(factory.generatePublic(publicSpec), factory.generatePrivate(privateSpec)); - } catch ( Exception e ) { - throw new IllegalArgumentException(e); - } - } -} - -/** - * Legacy Authorization Server does not support a custom name for the user parameter, so we'll need - * to extend the default. By default, it uses the attribute {@code user_name}, though it would be - * better to adhere to the {@code sub} property defined in the - * JWT Specification. - */ -class SubjectAttributeUserTokenConverter extends DefaultUserAuthenticationConverter { - @Override - public Map convertUserAuthentication(Authentication authentication) { - Map response = new LinkedHashMap<>(); - response.put("sub", authentication.getName()); - if (authentication.getAuthorities() != null && !authentication.getAuthorities().isEmpty()) { - response.put(AUTHORITIES, AuthorityUtils.authorityListToSet(authentication.getAuthorities())); - } - return response; - } -} diff --git a/samples/boot/oauth2authorizationserver/src/main/java/sample/OAuth2AuthorizationServerApplication.java b/samples/boot/oauth2authorizationserver/src/main/java/sample/OAuth2AuthorizationServerApplication.java deleted file mode 100644 index 13602ec74e5..00000000000 --- a/samples/boot/oauth2authorizationserver/src/main/java/sample/OAuth2AuthorizationServerApplication.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2002-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - * @author Josh Cummings - */ -@SpringBootApplication -public class OAuth2AuthorizationServerApplication { - - public static void main(String[] args) { - SpringApplication.run(OAuth2AuthorizationServerApplication.class, args); - } -} diff --git a/samples/boot/oauth2authorizationserver/src/main/resources/application.yml b/samples/boot/oauth2authorizationserver/src/main/resources/application.yml deleted file mode 100644 index b0b10a294f5..00000000000 --- a/samples/boot/oauth2authorizationserver/src/main/resources/application.yml +++ /dev/null @@ -1,3 +0,0 @@ -server.port: 8081 - -# security.oauth2.authorizationserver.jwt.enabled: false diff --git a/samples/boot/oauth2authorizationserver/src/test/java/sample/OAuth2AuthorizationServerApplicationTests.java b/samples/boot/oauth2authorizationserver/src/test/java/sample/OAuth2AuthorizationServerApplicationTests.java deleted file mode 100644 index 05377bf3f86..00000000000 --- a/samples/boot/oauth2authorizationserver/src/test/java/sample/OAuth2AuthorizationServerApplicationTests.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2002-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests for {@link OAuth2AuthorizationServerApplication} - * - * @author Josh Cummings - */ -@RunWith(SpringRunner.class) -@SpringBootTest -@AutoConfigureMockMvc -public class OAuth2AuthorizationServerApplicationTests { - - @Autowired - MockMvc mvc; - - @Test - public void requestTokenWhenUsingPasswordGrantTypeThenOk() - throws Exception { - - this.mvc.perform(post("/oauth/token") - .param("grant_type", "password") - .param("username", "subject") - .param("password", "password") - .header("Authorization", "Basic cmVhZGVyOnNlY3JldA==")) - .andExpect(status().isOk()); - } - - @Test - public void requestJwkSetWhenUsingDefaultsThenOk() - throws Exception { - - this.mvc.perform(get("/.well-known/jwks.json")) - .andExpect(status().isOk()); - } - -} diff --git a/samples/boot/oauth2login-webflux/README.adoc b/samples/boot/oauth2login-webflux/README.adoc deleted file mode 100644 index a96fec395be..00000000000 --- a/samples/boot/oauth2login-webflux/README.adoc +++ /dev/null @@ -1,324 +0,0 @@ -NOTE: Spring Security Reactive OAuth only supports authentication using a user info endpoint. -Support for JWT validation will be added in https://github.com/spring-projects/spring-security/issues/5330[gh-5330]. - -= OAuth 2.0 Login Sample - -This guide provides instructions on setting up the sample application with OAuth 2.0 Login using an OAuth 2.0 Provider or OpenID Connect 1.0 Provider. -The sample application uses Spring Boot 2.0.0.M6 and the `spring-security-oauth2-client` module which is new in Spring Security 5.0. - -The following sections provide detailed steps for setting up OAuth 2.0 Login for these Providers: - -* <> -* <> -* <> -* <> - -[[google-login]] -== Login with Google - -This section shows how to configure the sample application using Google as the Authentication Provider and covers the following topics: - -* <> -* <> -* <> -* <> - -[[google-initial-setup]] -=== Initial setup - -To use Google's OAuth 2.0 authentication system for login, you must set up a project in the Google API Console to obtain OAuth 2.0 credentials. - -NOTE: https://developers.google.com/identity/protocols/OpenIDConnect[Google's OAuth 2.0 implementation] for authentication conforms to the - https://openid.net/connect/[OpenID Connect 1.0] specification and is https://openid.net/certification/[OpenID Certified]. - -Follow the instructions on the https://developers.google.com/identity/protocols/OpenIDConnect[OpenID Connect] page, starting in the section, "Setting up OAuth 2.0". - -After completing the "Obtain OAuth 2.0 credentials" instructions, you should have a new OAuth Client with credentials consisting of a Client ID and a Client Secret. - -[[google-redirect-uri]] -=== Setting the redirect URI - -The redirect URI is the path in the application that the end-user's user-agent is redirected back to after they have authenticated with Google -and have granted access to the OAuth Client _(created in the previous step)_ on the Consent page. - -In the "Set a redirect URI" sub-section, ensure that the *Authorized redirect URIs* field is set to `http://localhost:8080/login/oauth2/code/google`. - -TIP: The default redirect URI template is `{baseUrl}/login/oauth2/code/{registrationId}`. - The *_registrationId_* is a unique identifier for the `ClientRegistration`. - -IMPORTANT: If the application is running behind a proxy server, it is recommended to check https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#appendix-proxy-server[Proxy Server Configuration] to ensure the application is correctly configured. -Also, see the supported https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#oauth2Client-auth-code-redirect-uri[`URI` template variables] for `redirect-uri`. - -[[google-application-config]] -=== Configure application.yml - -Now that you have a new OAuth Client with Google, you need to configure the application to use the OAuth Client for the _authentication flow_. To do so: - -. Go to `application.yml` and set the following configuration: -+ -[source,yaml] ----- -spring: - security: - oauth2: - client: - registration: <1> - google: <2> - client-id: google-client-id - client-secret: google-client-secret ----- -+ -.OAuth Client properties -==== -<1> `spring.security.oauth2.client.registration` is the base property prefix for OAuth Client properties. -<2> Following the base property prefix is the ID for the `ClientRegistration`, such as google. -==== - -. Replace the values in the `client-id` and `client-secret` property with the OAuth 2.0 credentials you created earlier. - -[[google-boot-application]] -=== Boot up the application - -Launch the Spring Boot 2.0 sample and go to `http://localhost:8080`. -You are then redirected to the default _auto-generated_ login page, which displays a link for Google. - -Click on the Google link, and you are then redirected to Google for authentication. - -After authenticating with your Google account credentials, the next page presented to you is the Consent screen. -The Consent screen asks you to either allow or deny access to the OAuth Client you created earlier. -Click *Allow* to authorize the OAuth Client to access your email address and basic profile information. - -At this point, the OAuth Client retrieves your email address and basic profile information -from the https://openid.net/specs/openid-connect-core-1_0.html#UserInfo[UserInfo Endpoint] and establishes an authenticated session. - -[[github-login]] -== Login with GitHub - -This section shows how to configure the sample application using GitHub as the Authentication Provider and covers the following topics: - -* <> -* <> -* <> - -[[github-register-application]] -=== Register OAuth application - -To use GitHub's OAuth 2.0 authentication system for login, you must https://github.com/settings/applications/new[Register a new OAuth application]. - -When registering the OAuth application, ensure the *Authorization callback URL* is set to `http://localhost:8080/login/oauth2/code/github`. - -The Authorization callback URL (redirect URI) is the path in the application that the end-user's user-agent is redirected back to after they have authenticated with GitHub -and have granted access to the OAuth application on the _Authorize application_ page. - -TIP: The default redirect URI template is `{baseUrl}/login/oauth2/code/{registrationId}`. - The *_registrationId_* is a unique identifier for the `ClientRegistration`. - -IMPORTANT: If the application is running behind a proxy server, it is recommended to check https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#appendix-proxy-server[Proxy Server Configuration] to ensure the application is correctly configured. -Also, see the supported https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#oauth2Client-auth-code-redirect-uri[`URI` template variables] for `redirect-uri`. - -[[github-application-config]] -=== Configure application.yml - -Now that you have a new OAuth application with GitHub, you need to configure the application to use the OAuth application for the _authentication flow_. To do so: - -. Go to `application.yml` and set the following configuration: -+ -[source,yaml] ----- -spring: - security: - oauth2: - client: - registration: <1> - github: <2> - client-id: github-client-id - client-secret: github-client-secret ----- -+ -.OAuth Client properties -==== -<1> `spring.security.oauth2.client.registration` is the base property prefix for OAuth Client properties. -<2> Following the base property prefix is the ID for the `ClientRegistration`, such as github. -==== - -. Replace the values in the `client-id` and `client-secret` property with the OAuth 2.0 credentials you created earlier. - -[[github-boot-application]] -=== Boot up the application - -Launch the Spring Boot 2.0 sample and go to `http://localhost:8080`. -You are then redirected to the default _auto-generated_ login page, which displays a link for GitHub. - -Click on the GitHub link, and you are then redirected to GitHub for authentication. - -After authenticating with your GitHub credentials, the next page presented to you is "Authorize application". -This page will ask you to *Authorize* the application you created in the previous step. -Click _Authorize application_ to allow the OAuth application to access your personal user data information. - -At this point, the OAuth Client retrieves your personal user information -from the UserInfo Endpoint and establishes an authenticated session. - -[TIP] -For detailed information returned from the UserInfo Endpoint, see the API documentation -for https://developer.github.com/v3/users/#get-the-authenticated-user["Get the authenticated user"]. - -[[facebook-login]] -== Login with Facebook - -This section shows how to configure the sample application using Facebook as the Authentication Provider and covers the following topics: - -* <> -* <> -* <> - -[[facebook-register-application]] -=== Add a New App - -To use Facebook's OAuth 2.0 authentication system for login, you must first https://developers.facebook.com/apps[Add a New App]. - -Select "Create a New App" and then the "Create a New App ID" page is presented. Enter the Display Name, Contact Email, Category and then click "Create App ID". - -NOTE: The selection for the _Category_ field is not relevant but it's a required field - select "Local". - -The next page presented is "Product Setup". Click the "Get Started" button for the *Facebook Login* product. -In the left sidebar, under _Products -> Facebook Login_, select _Settings_. - -For the field *Valid OAuth redirect URIs*, enter `http://localhost:8080/login/oauth2/code/facebook` then click _Save Changes_. - -The OAuth redirect URI is the path in the application that the end-user's user-agent is redirected back to after they have authenticated with Facebook -and have granted access to the application on the _Authorize application_ page. - -TIP: The default redirect URI template is `{baseUrl}/login/oauth2/code/{registrationId}`. - The *_registrationId_* is a unique identifier for the `ClientRegistration`. - -IMPORTANT: If the application is running behind a proxy server, it is recommended to check https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#appendix-proxy-server[Proxy Server Configuration] to ensure the application is correctly configured. -Also, see the supported https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#oauth2Client-auth-code-redirect-uri[`URI` template variables] for `redirect-uri`. - -[[facebook-application-config]] -=== Configure application.yml - -Now that you have created a new application with Facebook, you need to configure the sample application to use the application for the _authentication flow_. To do so: - -. Go to `application.yml` and set the following configuration: -+ -[source,yaml] ----- -spring: - security: - oauth2: - client: - registration: <1> - facebook: <2> - client-id: facebook-client-id - client-secret: facebook-client-secret ----- -+ -.OAuth Client properties -==== -<1> `spring.security.oauth2.client.registration` is the base property prefix for OAuth Client properties. -<2> Following the base property prefix is the ID for the `ClientRegistration`, such as facebook. -==== - -. Replace the values in the `client-id` and `client-secret` property with the OAuth 2.0 credentials you created earlier. - -[[facebook-boot-application]] -=== Boot up the application - -Launch the Spring Boot 2.0 sample and go to `http://localhost:8080`. -You are then redirected to the default _auto-generated_ login page, which displays a link for Facebook. - -Click on the Facebook link, and you are then redirected to Facebook for authentication. - -After authenticating with your Facebook credentials, the next page presented to you is "Authorize application". -This page will ask you to *Authorize* the application you created in the previous step. -Click _Authorize application_ to allow the OAuth application to access your _public profile_ and _email address_ information. - -At this point, the OAuth Client retrieves your personal user information -from the UserInfo Endpoint and establishes an authenticated session. - -[[okta-login]] -== Login with Okta - -This section shows how to configure the sample application using Okta as the Authentication Provider and covers the following topics: - -* <> -* <> -* <> -* <> - -[[okta-register-application]] -=== Add Application - -To use Okta's OAuth 2.0 authentication system for login, you must first https://www.okta.com/developer/signup[create a developer account]. - -Sign in to your account sub-domain and navigate to _Applications -> Applications_ and then select the "Add Application" button. -From the "Add Application" page, select the "Create New App" button and enter the following: - -* *Platform:* Web -* *Sign on method:* OpenID Connect - -Select the _Create_ button. -On the "General Settings" page, enter the Application Name (for example, "Spring Security Okta Login") and then select the _Next_ button. -On the "Configure OpenID Connect" page, enter `http://localhost:8080/login/oauth2/code/okta` for the field *Redirect URIs* and then select _Finish_. - -The redirect URI is the path in the application that the end-user's user-agent is redirected back to after they have authenticated with Okta -and have granted access to the application on the _Authorize application_ page. - -TIP: The default redirect URI template is `{baseUrl}/login/oauth2/code/{registrationId}`. - The *_registrationId_* is a unique identifier for the `ClientRegistration`. - -IMPORTANT: If the application is running behind a proxy server, it is recommended to check https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#appendix-proxy-server[Proxy Server Configuration] to ensure the application is correctly configured. -Also, see the supported https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#oauth2Client-auth-code-redirect-uri[`URI` template variables] for `redirect-uri`. - -[[okta-assign-application-people]] -=== Assign Application to People - -From the "General" tab of the application, select the "Assignments" tab and then select the _Assign_ button. -Select _Assign to People_ and assign your account to the application. Then select the _Save and Go Back_ button. - -[[okta-application-config]] -=== Configure application.yml - -Now that you have created a new application with Okta, you need to configure the sample application to use the application for the _authentication flow_. To do so: - -. Go to `application.yml` and set the following configuration: -+ -[source,yaml] ----- -spring: - security: - oauth2: - client: - registration: <1> - okta: <2> - client-id: okta-client-id - client-secret: okta-client-secret - provider: <3> - okta: - authorization-uri: https://your-subdomain.oktapreview.com/oauth2/v1/authorize - token-uri: https://your-subdomain.oktapreview.com/oauth2/v1/token - user-info-uri: https://your-subdomain.oktapreview.com/oauth2/v1/userinfo - user-name-attribute: sub - jwk-set-uri: https://your-subdomain.oktapreview.com/oauth2/v1/keys ----- -+ -.OAuth Client properties -==== -<1> `spring.security.oauth2.client.registration` is the base property prefix for OAuth Client properties. -<2> Following the base property prefix is the ID for the `ClientRegistration`, such as okta. -<3> `spring.security.oauth2.client.provider` is the base property prefix for OAuth Provider properties. -==== - -. Replace the values in the `client-id` and `client-secret` property with the OAuth 2.0 credentials you created earlier. -As well, replace `https://your-subdomain.oktapreview.com` in `authorization-uri`, `token-uri`, `user-info-uri` and `jwk-set-uri` with the sub-domain assigned to your account during the registration process. - -[[okta-boot-application]] -=== Boot up the application - -Launch the Spring Boot 2.0 sample and go to `http://localhost:8080`. -You are then redirected to the default _auto-generated_ login page, which displays a link for Okta. - -Click on the Okta link, and you are then redirected to Okta for authentication. - -After authenticating with your Okta account credentials, the OAuth Client retrieves your email address and basic profile information -from the https://openid.net/specs/openid-connect-core-1_0.html#UserInfo[UserInfo Endpoint] and establishes an authenticated session. diff --git a/samples/boot/oauth2login-webflux/spring-security-samples-boot-oauth2login-webflux.gradle b/samples/boot/oauth2login-webflux/spring-security-samples-boot-oauth2login-webflux.gradle deleted file mode 100644 index 49099dbd7a8..00000000000 --- a/samples/boot/oauth2login-webflux/spring-security-samples-boot-oauth2login-webflux.gradle +++ /dev/null @@ -1,14 +0,0 @@ -apply plugin: 'io.spring.convention.spring-sample-boot' - -dependencies { - compile project(':spring-security-config') - compile project(':spring-security-oauth2-client') - compile project(':spring-security-oauth2-jose') - compile 'org.springframework.boot:spring-boot-starter-thymeleaf' - compile 'org.springframework.boot:spring-boot-starter-webflux' - compile 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5' - - testCompile project(':spring-security-test') - testCompile 'net.sourceforge.htmlunit:htmlunit' - testCompile 'org.springframework.boot:spring-boot-starter-test' -} diff --git a/samples/boot/oauth2login-webflux/src/integration-test/java/sample/OAuth2LoginApplicationTests.java b/samples/boot/oauth2login-webflux/src/integration-test/java/sample/OAuth2LoginApplicationTests.java deleted file mode 100644 index c3e2b63af6d..00000000000 --- a/samples/boot/oauth2login-webflux/src/integration-test/java/sample/OAuth2LoginApplicationTests.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2002-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.reactive.server.WebTestClient; - -import static org.hamcrest.core.StringContains.containsString; -import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockOAuth2Login; - -/** - * Tests for {@link ReactiveOAuth2LoginApplication} - */ -@RunWith(SpringRunner.class) -@SpringBootTest -@AutoConfigureWebTestClient -public class OAuth2LoginApplicationTests { - - @Autowired - WebTestClient test; - - @Autowired - ReactiveClientRegistrationRepository clientRegistrationRepository; - - @Test - public void requestWhenMockOidcLoginThenIndex() { - this.clientRegistrationRepository.findByRegistrationId("github") - .map((clientRegistration) -> - this.test.mutateWith(mockOAuth2Login().clientRegistration(clientRegistration)) - .get().uri("/") - .exchange() - .expectBody(String.class).value(containsString("GitHub")) - ).block(); - } -} diff --git a/samples/boot/oauth2login-webflux/src/main/java/sample/ReactiveOAuth2LoginApplication.java b/samples/boot/oauth2login-webflux/src/main/java/sample/ReactiveOAuth2LoginApplication.java deleted file mode 100644 index f6fd94e8490..00000000000 --- a/samples/boot/oauth2login-webflux/src/main/java/sample/ReactiveOAuth2LoginApplication.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2002-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - * @author Rob Winch - */ -@SpringBootApplication -public class ReactiveOAuth2LoginApplication { - - public static void main(String[] args) { - SpringApplication.run(ReactiveOAuth2LoginApplication.class, args); - } - -} diff --git a/samples/boot/oauth2login-webflux/src/main/java/sample/web/OAuth2LoginController.java b/samples/boot/oauth2login-webflux/src/main/java/sample/web/OAuth2LoginController.java deleted file mode 100644 index 2e03a6adcd4..00000000000 --- a/samples/boot/oauth2login-webflux/src/main/java/sample/web/OAuth2LoginController.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2002-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample.web; - -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; -import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient; -import org.springframework.security.oauth2.core.user.OAuth2User; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.GetMapping; - -/** - * @author Rob Winch - */ -@Controller -public class OAuth2LoginController { - - @GetMapping("/") - public String index(Model model, - @RegisteredOAuth2AuthorizedClient OAuth2AuthorizedClient authorizedClient, - @AuthenticationPrincipal OAuth2User oauth2User) { - model.addAttribute("userName", oauth2User.getName()); - model.addAttribute("clientName", authorizedClient.getClientRegistration().getClientName()); - model.addAttribute("userAttributes", oauth2User.getAttributes()); - return "index"; - } -} diff --git a/samples/boot/oauth2login-webflux/src/main/resources/application.yml b/samples/boot/oauth2login-webflux/src/main/resources/application.yml deleted file mode 100644 index 73b08fbd839..00000000000 --- a/samples/boot/oauth2login-webflux/src/main/resources/application.yml +++ /dev/null @@ -1,35 +0,0 @@ -server: - port: 8080 - -logging: - level: - root: INFO - org.springframework.web: INFO - org.springframework.security: INFO -# org.springframework.boot.autoconfigure: DEBUG - -spring: - thymeleaf: - cache: false - security: - oauth2: - client: - registration: - google: - client-id: your-app-client-id - client-secret: your-app-client-secret - github: - client-id: your-app-client-id - client-secret: your-app-client-secret - facebook: - client-id: your-app-client-id - client-secret: your-app-client-secret - okta: - client-id: your-app-client-id - client-secret: your-app-client-secret - provider: - okta: - authorization-uri: https://your-subdomain.oktapreview.com/oauth2/v1/authorize - token-uri: https://your-subdomain.oktapreview.com/oauth2/v1/token - user-info-uri: https://your-subdomain.oktapreview.com/oauth2/v1/userinfo - jwk-set-uri: https://your-subdomain.oktapreview.com/oauth2/v1/keys diff --git a/samples/boot/oauth2login-webflux/src/main/resources/templates/index.html b/samples/boot/oauth2login-webflux/src/main/resources/templates/index.html deleted file mode 100644 index ce8cdcde232..00000000000 --- a/samples/boot/oauth2login-webflux/src/main/resources/templates/index.html +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - Spring Security - OAuth 2.0 Login - - - -
-
- User: -
-
 
-
- Log Out -
-
-

OAuth 2.0 Login with Spring Security

-
- You are successfully logged in - via the OAuth 2.0 Client -
-
 
-
- User Attributes: -
    -
  • - : -
  • -
-
- - diff --git a/samples/boot/oauth2login-webflux/src/test/java/sample/OAuth2LoginControllerTests.java b/samples/boot/oauth2login-webflux/src/test/java/sample/OAuth2LoginControllerTests.java deleted file mode 100644 index 11ae5f8ba6f..00000000000 --- a/samples/boot/oauth2login-webflux/src/test/java/sample/OAuth2LoginControllerTests.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2002-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import sample.web.OAuth2LoginController; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest; -import org.springframework.core.ReactiveAdapterRegistry; -import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; -import org.springframework.security.oauth2.client.web.reactive.result.method.annotation.OAuth2AuthorizedClientArgumentResolver; -import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository; -import org.springframework.security.web.reactive.result.method.annotation.AuthenticationPrincipalArgumentResolver; -import org.springframework.security.web.server.context.SecurityContextServerWebExchangeWebFilter; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.reactive.server.WebTestClient; -import org.springframework.web.reactive.result.view.ViewResolver; - -import static org.hamcrest.Matchers.containsString; -import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockOAuth2Login; -import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.springSecurity; - -/** - * @author Josh Cummings - */ -@RunWith(SpringRunner.class) -@WebFluxTest(OAuth2LoginController.class) -public class OAuth2LoginControllerTests { - - @Autowired - OAuth2LoginController controller; - - @Autowired - ViewResolver viewResolver; - - @Mock - ReactiveClientRegistrationRepository clientRegistrationRepository; - - @Mock - ServerOAuth2AuthorizedClientRepository authorizedClientRepository; - - WebTestClient rest; - - @Before - public void setup() { - this.rest = WebTestClient - .bindToController(this.controller) - .apply(springSecurity()) - .webFilter(new SecurityContextServerWebExchangeWebFilter()) - .argumentResolvers((c) -> { - c.addCustomResolver(new AuthenticationPrincipalArgumentResolver(new ReactiveAdapterRegistry())); - c.addCustomResolver(new OAuth2AuthorizedClientArgumentResolver - (this.clientRegistrationRepository, this.authorizedClientRepository)); - }) - .viewResolvers((c) -> c.viewResolver(this.viewResolver)) - .build(); - } - - @Test - public void indexGreetsAuthenticatedUser() { - this.rest.mutateWith(mockOAuth2Login()) - .get().uri("/").exchange() - .expectBody(String.class).value(containsString("user")); - } -} diff --git a/samples/boot/oauth2login/README.adoc b/samples/boot/oauth2login/README.adoc deleted file mode 100644 index e949a7f84e1..00000000000 --- a/samples/boot/oauth2login/README.adoc +++ /dev/null @@ -1,321 +0,0 @@ -= OAuth 2.0 Login Sample - -This guide provides instructions on setting up the sample application with OAuth 2.0 Login using an OAuth 2.0 Provider or OpenID Connect 1.0 Provider. -The sample application uses Spring Boot 2.0.0.M6 and the `spring-security-oauth2-client` module which is new in Spring Security 5.0. - -The following sections provide detailed steps for setting up OAuth 2.0 Login for these Providers: - -* <> -* <> -* <> -* <> - -[[google-login]] -== Login with Google - -This section shows how to configure the sample application using Google as the Authentication Provider and covers the following topics: - -* <> -* <> -* <> -* <> - -[[google-initial-setup]] -=== Initial setup - -To use Google's OAuth 2.0 authentication system for login, you must set up a project in the Google API Console to obtain OAuth 2.0 credentials. - -NOTE: https://developers.google.com/identity/protocols/OpenIDConnect[Google's OAuth 2.0 implementation] for authentication conforms to the - https://openid.net/connect/[OpenID Connect 1.0] specification and is https://openid.net/certification/[OpenID Certified]. - -Follow the instructions on the https://developers.google.com/identity/protocols/OpenIDConnect[OpenID Connect] page, starting in the section, "Setting up OAuth 2.0". - -After completing the "Obtain OAuth 2.0 credentials" instructions, you should have a new OAuth Client with credentials consisting of a Client ID and a Client Secret. - -[[google-redirect-uri]] -=== Setting the redirect URI - -The redirect URI is the path in the application that the end-user's user-agent is redirected back to after they have authenticated with Google -and have granted access to the OAuth Client _(created in the previous step)_ on the Consent page. - -In the "Set a redirect URI" sub-section, ensure that the *Authorized redirect URIs* field is set to `http://localhost:8080/login/oauth2/code/google`. - -TIP: The default redirect URI template is `{baseUrl}/login/oauth2/code/{registrationId}`. - The *_registrationId_* is a unique identifier for the `ClientRegistration`. - -IMPORTANT: If the application is running behind a proxy server, it is recommended to check https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#appendix-proxy-server[Proxy Server Configuration] to ensure the application is correctly configured. -Also, see the supported https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#oauth2Client-auth-code-redirect-uri[`URI` template variables] for `redirect-uri`. - -[[google-application-config]] -=== Configure application.yml - -Now that you have a new OAuth Client with Google, you need to configure the application to use the OAuth Client for the _authentication flow_. To do so: - -. Go to `application.yml` and set the following configuration: -+ -[source,yaml] ----- -spring: - security: - oauth2: - client: - registration: <1> - google: <2> - client-id: google-client-id - client-secret: google-client-secret ----- -+ -.OAuth Client properties -==== -<1> `spring.security.oauth2.client.registration` is the base property prefix for OAuth Client properties. -<2> Following the base property prefix is the ID for the `ClientRegistration`, such as google. -==== - -. Replace the values in the `client-id` and `client-secret` property with the OAuth 2.0 credentials you created earlier. - -[[google-boot-application]] -=== Boot up the application - -Launch the Spring Boot 2.0 sample and go to `http://localhost:8080`. -You are then redirected to the default _auto-generated_ login page, which displays a link for Google. - -Click on the Google link, and you are then redirected to Google for authentication. - -After authenticating with your Google account credentials, the next page presented to you is the Consent screen. -The Consent screen asks you to either allow or deny access to the OAuth Client you created earlier. -Click *Allow* to authorize the OAuth Client to access your email address and basic profile information. - -At this point, the OAuth Client retrieves your email address and basic profile information -from the https://openid.net/specs/openid-connect-core-1_0.html#UserInfo[UserInfo Endpoint] and establishes an authenticated session. - -[[github-login]] -== Login with GitHub - -This section shows how to configure the sample application using GitHub as the Authentication Provider and covers the following topics: - -* <> -* <> -* <> - -[[github-register-application]] -=== Register OAuth application - -To use GitHub's OAuth 2.0 authentication system for login, you must https://github.com/settings/applications/new[Register a new OAuth application]. - -When registering the OAuth application, ensure the *Authorization callback URL* is set to `http://localhost:8080/login/oauth2/code/github`. - -The Authorization callback URL (redirect URI) is the path in the application that the end-user's user-agent is redirected back to after they have authenticated with GitHub -and have granted access to the OAuth application on the _Authorize application_ page. - -TIP: The default redirect URI template is `{baseUrl}/login/oauth2/code/{registrationId}`. - The *_registrationId_* is a unique identifier for the `ClientRegistration`. - -IMPORTANT: If the application is running behind a proxy server, it is recommended to check https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#appendix-proxy-server[Proxy Server Configuration] to ensure the application is correctly configured. -Also, see the supported https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#oauth2Client-auth-code-redirect-uri[`URI` template variables] for `redirect-uri`. - -[[github-application-config]] -=== Configure application.yml - -Now that you have a new OAuth application with GitHub, you need to configure the application to use the OAuth application for the _authentication flow_. To do so: - -. Go to `application.yml` and set the following configuration: -+ -[source,yaml] ----- -spring: - security: - oauth2: - client: - registration: <1> - github: <2> - client-id: github-client-id - client-secret: github-client-secret ----- -+ -.OAuth Client properties -==== -<1> `spring.security.oauth2.client.registration` is the base property prefix for OAuth Client properties. -<2> Following the base property prefix is the ID for the `ClientRegistration`, such as github. -==== - -. Replace the values in the `client-id` and `client-secret` property with the OAuth 2.0 credentials you created earlier. - -[[github-boot-application]] -=== Boot up the application - -Launch the Spring Boot 2.0 sample and go to `http://localhost:8080`. -You are then redirected to the default _auto-generated_ login page, which displays a link for GitHub. - -Click on the GitHub link, and you are then redirected to GitHub for authentication. - -After authenticating with your GitHub credentials, the next page presented to you is "Authorize application". -This page will ask you to *Authorize* the application you created in the previous step. -Click _Authorize application_ to allow the OAuth application to access your personal user data information. - -At this point, the OAuth Client retrieves your personal user information -from the UserInfo Endpoint and establishes an authenticated session. - -[TIP] -For detailed information returned from the UserInfo Endpoint, see the API documentation -for https://developer.github.com/v3/users/#get-the-authenticated-user["Get the authenticated user"]. - -[[facebook-login]] -== Login with Facebook - -This section shows how to configure the sample application using Facebook as the Authentication Provider and covers the following topics: - -* <> -* <> -* <> - -[[facebook-register-application]] -=== Add a New App - -To use Facebook's OAuth 2.0 authentication system for login, you must first https://developers.facebook.com/apps[Add a New App]. - -Select "Create a New App" and then the "Create a New App ID" page is presented. Enter the Display Name, Contact Email, Category and then click "Create App ID". - -NOTE: The selection for the _Category_ field is not relevant but it's a required field - select "Local". - -The next page presented is "Product Setup". Click the "Get Started" button for the *Facebook Login* product. -In the left sidebar, under _Products -> Facebook Login_, select _Settings_. - -For the field *Valid OAuth redirect URIs*, enter `http://localhost:8080/login/oauth2/code/facebook` then click _Save Changes_. - -The OAuth redirect URI is the path in the application that the end-user's user-agent is redirected back to after they have authenticated with Facebook -and have granted access to the application on the _Authorize application_ page. - -TIP: The default redirect URI template is `{baseUrl}/login/oauth2/code/{registrationId}`. - The *_registrationId_* is a unique identifier for the `ClientRegistration`. - -IMPORTANT: If the application is running behind a proxy server, it is recommended to check https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#appendix-proxy-server[Proxy Server Configuration] to ensure the application is correctly configured. -Also, see the supported https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#oauth2Client-auth-code-redirect-uri[`URI` template variables] for `redirect-uri`. - -[[facebook-application-config]] -=== Configure application.yml - -Now that you have created a new application with Facebook, you need to configure the sample application to use the application for the _authentication flow_. To do so: - -. Go to `application.yml` and set the following configuration: -+ -[source,yaml] ----- -spring: - security: - oauth2: - client: - registration: <1> - facebook: <2> - client-id: facebook-client-id - client-secret: facebook-client-secret ----- -+ -.OAuth Client properties -==== -<1> `spring.security.oauth2.client.registration` is the base property prefix for OAuth Client properties. -<2> Following the base property prefix is the ID for the `ClientRegistration`, such as facebook. -==== - -. Replace the values in the `client-id` and `client-secret` property with the OAuth 2.0 credentials you created earlier. - -[[facebook-boot-application]] -=== Boot up the application - -Launch the Spring Boot 2.0 sample and go to `http://localhost:8080`. -You are then redirected to the default _auto-generated_ login page, which displays a link for Facebook. - -Click on the Facebook link, and you are then redirected to Facebook for authentication. - -After authenticating with your Facebook credentials, the next page presented to you is "Authorize application". -This page will ask you to *Authorize* the application you created in the previous step. -Click _Authorize application_ to allow the OAuth application to access your _public profile_ and _email address_ information. - -At this point, the OAuth Client retrieves your personal user information -from the UserInfo Endpoint and establishes an authenticated session. - -[[okta-login]] -== Login with Okta - -This section shows how to configure the sample application using Okta as the Authentication Provider and covers the following topics: - -* <> -* <> -* <> -* <> - -[[okta-register-application]] -=== Add Application - -To use Okta's OAuth 2.0 authentication system for login, you must first https://www.okta.com/developer/signup[create a developer account]. - -Sign in to your account sub-domain and navigate to _Applications -> Applications_ and then select the "Add Application" button. -From the "Add Application" page, select the "Create New App" button and enter the following: - -* *Platform:* Web -* *Sign on method:* OpenID Connect - -Select the _Create_ button. -On the "General Settings" page, enter the Application Name (for example, "Spring Security Okta Login") and then select the _Next_ button. -On the "Configure OpenID Connect" page, enter `http://localhost:8080/login/oauth2/code/okta` for the field *Redirect URIs* and then select _Finish_. - -The redirect URI is the path in the application that the end-user's user-agent is redirected back to after they have authenticated with Okta -and have granted access to the application on the _Authorize application_ page. - -TIP: The default redirect URI template is `{baseUrl}/login/oauth2/code/{registrationId}`. - The *_registrationId_* is a unique identifier for the `ClientRegistration`. - -IMPORTANT: If the application is running behind a proxy server, it is recommended to check https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#appendix-proxy-server[Proxy Server Configuration] to ensure the application is correctly configured. -Also, see the supported https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#oauth2Client-auth-code-redirect-uri[`URI` template variables] for `redirect-uri`. - -[[okta-assign-application-people]] -=== Assign Application to People - -From the "General" tab of the application, select the "Assignments" tab and then select the _Assign_ button. -Select _Assign to People_ and assign your account to the application. Then select the _Save and Go Back_ button. - -[[okta-application-config]] -=== Configure application.yml - -Now that you have created a new application with Okta, you need to configure the sample application to use the application for the _authentication flow_. To do so: - -. Go to `application.yml` and set the following configuration: -+ -[source,yaml] ----- -spring: - security: - oauth2: - client: - registration: <1> - okta: <2> - client-id: okta-client-id - client-secret: okta-client-secret - provider: <3> - okta: - authorization-uri: https://your-subdomain.oktapreview.com/oauth2/v1/authorize - token-uri: https://your-subdomain.oktapreview.com/oauth2/v1/token - user-info-uri: https://your-subdomain.oktapreview.com/oauth2/v1/userinfo - user-name-attribute: sub - jwk-set-uri: https://your-subdomain.oktapreview.com/oauth2/v1/keys ----- -+ -.OAuth Client properties -==== -<1> `spring.security.oauth2.client.registration` is the base property prefix for OAuth Client properties. -<2> Following the base property prefix is the ID for the `ClientRegistration`, such as okta. -<3> `spring.security.oauth2.client.provider` is the base property prefix for OAuth Provider properties. -==== - -. Replace the values in the `client-id` and `client-secret` property with the OAuth 2.0 credentials you created earlier. -As well, replace `https://your-subdomain.oktapreview.com` in `authorization-uri`, `token-uri`, `user-info-uri` and `jwk-set-uri` with the sub-domain assigned to your account during the registration process. - -[[okta-boot-application]] -=== Boot up the application - -Launch the Spring Boot 2.0 sample and go to `http://localhost:8080`. -You are then redirected to the default _auto-generated_ login page, which displays a link for Okta. - -Click on the Okta link, and you are then redirected to Okta for authentication. - -After authenticating with your Okta account credentials, the OAuth Client retrieves your email address and basic profile information -from the https://openid.net/specs/openid-connect-core-1_0.html#UserInfo[UserInfo Endpoint] and establishes an authenticated session. diff --git a/samples/boot/oauth2login/spring-security-samples-boot-oauth2login.gradle b/samples/boot/oauth2login/spring-security-samples-boot-oauth2login.gradle deleted file mode 100644 index ef3a4e27b56..00000000000 --- a/samples/boot/oauth2login/spring-security-samples-boot-oauth2login.gradle +++ /dev/null @@ -1,14 +0,0 @@ -apply plugin: 'io.spring.convention.spring-sample-boot' - -dependencies { - compile project(':spring-security-config') - compile project(':spring-security-oauth2-client') - compile project(':spring-security-oauth2-jose') - compile 'org.springframework.boot:spring-boot-starter-thymeleaf' - compile 'org.springframework.boot:spring-boot-starter-web' - compile 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5' - - testCompile project(':spring-security-test') - testCompile 'net.sourceforge.htmlunit:htmlunit' - testCompile 'org.springframework.boot:spring-boot-starter-test' -} diff --git a/samples/boot/oauth2login/src/integration-test/java/sample/OAuth2LoginApplicationTests.java b/samples/boot/oauth2login/src/integration-test/java/sample/OAuth2LoginApplicationTests.java deleted file mode 100644 index 77867dbbc84..00000000000 --- a/samples/boot/oauth2login/src/integration-test/java/sample/OAuth2LoginApplicationTests.java +++ /dev/null @@ -1,385 +0,0 @@ -/* - * Copyright 2002-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import java.net.URI; -import java.net.URL; -import java.net.URLDecoder; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; - -import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException; -import com.gargoylesoftware.htmlunit.WebClient; -import com.gargoylesoftware.htmlunit.WebResponse; -import com.gargoylesoftware.htmlunit.html.DomNodeList; -import com.gargoylesoftware.htmlunit.html.HtmlAnchor; -import com.gargoylesoftware.htmlunit.html.HtmlElement; -import com.gargoylesoftware.htmlunit.html.HtmlPage; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.TestConfiguration; -import org.springframework.http.HttpStatus; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; -import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; -import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; -import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; -import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter; -import org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.user.DefaultOAuth2User; -import org.springframework.security.oauth2.core.user.OAuth2User; -import org.springframework.security.oauth2.core.user.OAuth2UserAuthority; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.web.util.UriComponents; -import org.springframework.web.util.UriComponentsBuilder; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.oauth2Login; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model; - -/** - * Integration tests for the OAuth 2.0 client filters {@link OAuth2AuthorizationRequestRedirectFilter} - * and {@link OAuth2LoginAuthenticationFilter}. These filters work together to realize - * OAuth 2.0 Login leveraging the Authorization Code Grant flow. - * - * @author Joe Grandja - * @since 5.0 - */ -@RunWith(SpringRunner.class) -@SpringBootTest -@AutoConfigureMockMvc -public class OAuth2LoginApplicationTests { - private static final String AUTHORIZATION_BASE_URI = "/oauth2/authorization"; - private static final String AUTHORIZE_BASE_URL = "http://localhost:8080/login/oauth2/code"; - - @Autowired - private WebClient webClient; - - @Autowired - private MockMvc mvc; - - @Autowired - private ClientRegistrationRepository clientRegistrationRepository; - - @Before - public void setup() { - this.webClient.getCookieManager().clearCookies(); - } - - @Test - public void requestIndexPageWhenNotAuthenticatedThenDisplayLoginPage() throws Exception { - HtmlPage page = this.webClient.getPage("/"); - this.assertLoginPage(page); - } - - @Test - public void requestOtherPageWhenNotAuthenticatedThenDisplayLoginPage() throws Exception { - HtmlPage page = this.webClient.getPage("/other-page"); - this.assertLoginPage(page); - } - - @Test - public void requestAuthorizeGitHubClientWhenLinkClickedThenStatusRedirectForAuthorization() throws Exception { - HtmlPage page = this.webClient.getPage("/"); - - ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("github"); - - HtmlAnchor clientAnchorElement = this.getClientAnchorElement(page, clientRegistration); - assertThat(clientAnchorElement).isNotNull(); - - WebResponse response = this.followLinkDisableRedirects(clientAnchorElement); - - assertThat(response.getStatusCode()).isEqualTo(HttpStatus.MOVED_PERMANENTLY.value()); - - String authorizeRedirectUri = response.getResponseHeaderValue("Location"); - assertThat(authorizeRedirectUri).isNotNull(); - - UriComponents uriComponents = UriComponentsBuilder.fromUri(URI.create(authorizeRedirectUri)).build(); - - String requestUri = uriComponents.getScheme() + "://" + uriComponents.getHost() + uriComponents.getPath(); - assertThat(requestUri).isEqualTo(clientRegistration.getProviderDetails().getAuthorizationUri()); - - Map params = uriComponents.getQueryParams().toSingleValueMap(); - - assertThat(params.get(OAuth2ParameterNames.RESPONSE_TYPE)).isEqualTo(OAuth2AuthorizationResponseType.CODE.getValue()); - assertThat(params.get(OAuth2ParameterNames.CLIENT_ID)).isEqualTo(clientRegistration.getClientId()); - String redirectUri = AUTHORIZE_BASE_URL + "/" + clientRegistration.getRegistrationId(); - assertThat(URLDecoder.decode(params.get(OAuth2ParameterNames.REDIRECT_URI), "UTF-8")).isEqualTo(redirectUri); - assertThat(URLDecoder.decode(params.get(OAuth2ParameterNames.SCOPE), "UTF-8")) - .isEqualTo(clientRegistration.getScopes().stream().collect(Collectors.joining(" "))); - assertThat(params.get(OAuth2ParameterNames.STATE)).isNotNull(); - } - - @Test - public void requestAuthorizeClientWhenInvalidClientThenStatusInternalServerError() throws Exception { - HtmlPage page = this.webClient.getPage("/"); - - ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("google"); - - HtmlAnchor clientAnchorElement = this.getClientAnchorElement(page, clientRegistration); - assertThat(clientAnchorElement).isNotNull(); - clientAnchorElement.setAttribute("href", clientAnchorElement.getHrefAttribute() + "-invalid"); - - WebResponse response = null; - try { - clientAnchorElement.click(); - } catch (FailingHttpStatusCodeException ex) { - response = ex.getResponse(); - } - - assertThat(response.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR.value()); - } - - @Test - public void requestAuthorizationCodeGrantWhenValidAuthorizationResponseThenDisplayIndexPage() throws Exception { - HtmlPage page = this.webClient.getPage("/"); - - ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("github"); - - HtmlAnchor clientAnchorElement = this.getClientAnchorElement(page, clientRegistration); - assertThat(clientAnchorElement).isNotNull(); - - WebResponse response = this.followLinkDisableRedirects(clientAnchorElement); - - UriComponents authorizeRequestUriComponents = UriComponentsBuilder.fromUri( - URI.create(response.getResponseHeaderValue("Location"))).build(); - - Map params = authorizeRequestUriComponents.getQueryParams().toSingleValueMap(); - String code = "auth-code"; - String state = URLDecoder.decode(params.get(OAuth2ParameterNames.STATE), "UTF-8"); - String redirectUri = URLDecoder.decode(params.get(OAuth2ParameterNames.REDIRECT_URI), "UTF-8"); - - String authorizationResponseUri = - UriComponentsBuilder.fromHttpUrl(redirectUri) - .queryParam(OAuth2ParameterNames.CODE, code) - .queryParam(OAuth2ParameterNames.STATE, state) - .build().encode().toUriString(); - - page = this.webClient.getPage(new URL(authorizationResponseUri)); - this.assertIndexPage(page); - } - - @Test - public void requestAuthorizationCodeGrantWhenNoMatchingAuthorizationRequestThenDisplayLoginPageWithError() throws Exception { - HtmlPage page = this.webClient.getPage("/"); - URL loginPageUrl = page.getBaseURL(); - URL loginErrorPageUrl = new URL(loginPageUrl.toString() + "?error"); - - ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("google"); - - String code = "auth-code"; - String state = "state"; - String redirectUri = AUTHORIZE_BASE_URL + "/" + clientRegistration.getRegistrationId(); - - String authorizationResponseUri = - UriComponentsBuilder.fromHttpUrl(redirectUri) - .queryParam(OAuth2ParameterNames.CODE, code) - .queryParam(OAuth2ParameterNames.STATE, state) - .build().encode().toUriString(); - - // Clear session cookie will ensure the 'session-saved' - // Authorization Request (from previous request) is not found - this.webClient.getCookieManager().clearCookies(); - - page = this.webClient.getPage(new URL(authorizationResponseUri)); - assertThat(page.getBaseURL()).isEqualTo(loginErrorPageUrl); - - HtmlElement errorElement = page.getBody().getFirstByXPath("div"); - assertThat(errorElement).isNotNull(); - assertThat(errorElement.asText()).contains("authorization_request_not_found"); - } - - @Test - public void requestAuthorizationCodeGrantWhenInvalidStateParamThenDisplayLoginPageWithError() throws Exception { - HtmlPage page = this.webClient.getPage("/"); - URL loginPageUrl = page.getBaseURL(); - URL loginErrorPageUrl = new URL(loginPageUrl.toString() + "?error"); - - ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("google"); - - HtmlAnchor clientAnchorElement = this.getClientAnchorElement(page, clientRegistration); - assertThat(clientAnchorElement).isNotNull(); - this.followLinkDisableRedirects(clientAnchorElement); - - String code = "auth-code"; - String state = "invalid-state"; - String redirectUri = AUTHORIZE_BASE_URL + "/" + clientRegistration.getRegistrationId(); - - String authorizationResponseUri = - UriComponentsBuilder.fromHttpUrl(redirectUri) - .queryParam(OAuth2ParameterNames.CODE, code) - .queryParam(OAuth2ParameterNames.STATE, state) - .build().encode().toUriString(); - - page = this.webClient.getPage(new URL(authorizationResponseUri)); - assertThat(page.getBaseURL()).isEqualTo(loginErrorPageUrl); - - HtmlElement errorElement = page.getBody().getFirstByXPath("div"); - assertThat(errorElement).isNotNull(); - assertThat(errorElement.asText()).contains("authorization_request_not_found"); - } - - @Test - public void requestWhenMockOAuth2LoginThenIndex() throws Exception { - ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("github"); - this.mvc.perform(get("/").with(oauth2Login().clientRegistration(clientRegistration))) - .andExpect(model().attribute("userName", "user")) - .andExpect(model().attribute("clientName", "GitHub")) - .andExpect(model().attribute("userAttributes", Collections.singletonMap("sub", "user"))); - } - - private void assertLoginPage(HtmlPage page) { - assertThat(page.getTitleText()).isEqualTo("Please sign in"); - - int expectedClients = 4; - - List clientAnchorElements = page.getAnchors(); - assertThat(clientAnchorElements.size()).isEqualTo(expectedClients); - - ClientRegistration googleClientRegistration = this.clientRegistrationRepository.findByRegistrationId("google"); - ClientRegistration githubClientRegistration = this.clientRegistrationRepository.findByRegistrationId("github"); - ClientRegistration facebookClientRegistration = this.clientRegistrationRepository.findByRegistrationId("facebook"); - ClientRegistration oktaClientRegistration = this.clientRegistrationRepository.findByRegistrationId("okta"); - - String baseAuthorizeUri = AUTHORIZATION_BASE_URI + "/"; - String googleClientAuthorizeUri = baseAuthorizeUri + googleClientRegistration.getRegistrationId(); - String githubClientAuthorizeUri = baseAuthorizeUri + githubClientRegistration.getRegistrationId(); - String facebookClientAuthorizeUri = baseAuthorizeUri + facebookClientRegistration.getRegistrationId(); - String oktaClientAuthorizeUri = baseAuthorizeUri + oktaClientRegistration.getRegistrationId(); - - for (int i=0; i divElements = page.getBody().getElementsByTagName("div"); - assertThat(divElements.get(1).asText()).contains("User: joeg@springsecurity.io"); - assertThat(divElements.get(4).asText()).contains("You are successfully logged in joeg@springsecurity.io"); - } - - private HtmlAnchor getClientAnchorElement(HtmlPage page, ClientRegistration clientRegistration) { - Optional clientAnchorElement = page.getAnchors().stream() - .filter((e) -> e.asText().equals(clientRegistration.getClientName())).findFirst(); - - return (clientAnchorElement.orElse(null)); - } - - private WebResponse followLinkDisableRedirects(HtmlAnchor anchorElement) throws Exception { - WebResponse response = null; - try { - // Disable the automatic redirection (which will trigger - // an exception) so that we can capture the response - this.webClient.getOptions().setRedirectEnabled(false); - anchorElement.click(); - } catch (FailingHttpStatusCodeException ex) { - response = ex.getResponse(); - this.webClient.getOptions().setRedirectEnabled(true); - } - return response; - } - - @EnableWebSecurity - @TestConfiguration - public static class SecurityTestConfig extends WebSecurityConfigurerAdapter { - - // @formatter:off - @Override - protected void configure(HttpSecurity http) throws Exception { - http - .authorizeRequests((authorizeRequests) -> - authorizeRequests - .anyRequest().authenticated() - ) - .oauth2Login((oauth2Login) -> - oauth2Login - .tokenEndpoint((tokenEndpoint) -> - tokenEndpoint - .accessTokenResponseClient(this.mockAccessTokenResponseClient()) - ) - .userInfoEndpoint((userInfoEndpoint) -> - userInfoEndpoint - .userService(this.mockUserService()) - ) - ); - } - // @formatter:on - - private OAuth2AccessTokenResponseClient mockAccessTokenResponseClient() { - OAuth2AccessTokenResponse accessTokenResponse = OAuth2AccessTokenResponse.withToken("access-token-1234") - .tokenType(OAuth2AccessToken.TokenType.BEARER) - .expiresIn(60 * 1000) - .build(); - - OAuth2AccessTokenResponseClient tokenResponseClient = mock(OAuth2AccessTokenResponseClient.class); - when(tokenResponseClient.getTokenResponse(any())).thenReturn(accessTokenResponse); - return tokenResponseClient; - } - - private OAuth2UserService mockUserService() { - Map attributes = new HashMap<>(); - attributes.put("id", "joeg"); - attributes.put("first-name", "Joe"); - attributes.put("last-name", "Grandja"); - attributes.put("email", "joeg@springsecurity.io"); - - GrantedAuthority authority = new OAuth2UserAuthority(attributes); - Set authorities = new HashSet<>(); - authorities.add(authority); - - DefaultOAuth2User user = new DefaultOAuth2User(authorities, attributes, "email"); - - OAuth2UserService userService = mock(OAuth2UserService.class); - when(userService.loadUser(any())).thenReturn(user); - return userService; - } - } -} diff --git a/samples/boot/oauth2login/src/main/java/sample/OAuth2LoginApplication.java b/samples/boot/oauth2login/src/main/java/sample/OAuth2LoginApplication.java deleted file mode 100644 index dc69b3f9ec3..00000000000 --- a/samples/boot/oauth2login/src/main/java/sample/OAuth2LoginApplication.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2002-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - * @author Joe Grandja - */ -@SpringBootApplication -public class OAuth2LoginApplication { - - public static void main(String[] args) { - SpringApplication.run(OAuth2LoginApplication.class, args); - } -} diff --git a/samples/boot/oauth2login/src/main/java/sample/web/OAuth2LoginController.java b/samples/boot/oauth2login/src/main/java/sample/web/OAuth2LoginController.java deleted file mode 100644 index fa46a60ad79..00000000000 --- a/samples/boot/oauth2login/src/main/java/sample/web/OAuth2LoginController.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2002-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample.web; - -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; -import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient; -import org.springframework.security.oauth2.core.user.OAuth2User; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.GetMapping; - -/** - * @author Joe Grandja - * @author Rob Winch - */ -@Controller -public class OAuth2LoginController { - - @GetMapping("/") - public String index(Model model, - @RegisteredOAuth2AuthorizedClient OAuth2AuthorizedClient authorizedClient, - @AuthenticationPrincipal OAuth2User oauth2User) { - model.addAttribute("userName", oauth2User.getName()); - model.addAttribute("clientName", authorizedClient.getClientRegistration().getClientName()); - model.addAttribute("userAttributes", oauth2User.getAttributes()); - return "index"; - } -} diff --git a/samples/boot/oauth2login/src/main/resources/application.yml b/samples/boot/oauth2login/src/main/resources/application.yml deleted file mode 100644 index 73b08fbd839..00000000000 --- a/samples/boot/oauth2login/src/main/resources/application.yml +++ /dev/null @@ -1,35 +0,0 @@ -server: - port: 8080 - -logging: - level: - root: INFO - org.springframework.web: INFO - org.springframework.security: INFO -# org.springframework.boot.autoconfigure: DEBUG - -spring: - thymeleaf: - cache: false - security: - oauth2: - client: - registration: - google: - client-id: your-app-client-id - client-secret: your-app-client-secret - github: - client-id: your-app-client-id - client-secret: your-app-client-secret - facebook: - client-id: your-app-client-id - client-secret: your-app-client-secret - okta: - client-id: your-app-client-id - client-secret: your-app-client-secret - provider: - okta: - authorization-uri: https://your-subdomain.oktapreview.com/oauth2/v1/authorize - token-uri: https://your-subdomain.oktapreview.com/oauth2/v1/token - user-info-uri: https://your-subdomain.oktapreview.com/oauth2/v1/userinfo - jwk-set-uri: https://your-subdomain.oktapreview.com/oauth2/v1/keys diff --git a/samples/boot/oauth2login/src/main/resources/templates/index.html b/samples/boot/oauth2login/src/main/resources/templates/index.html deleted file mode 100644 index 629d8ac8ee5..00000000000 --- a/samples/boot/oauth2login/src/main/resources/templates/index.html +++ /dev/null @@ -1,34 +0,0 @@ - - - - Spring Security - OAuth 2.0 Login - - - -
-
- User: -
-
 
-
-
- -
-
-
-

OAuth 2.0 Login with Spring Security

-
- You are successfully logged in - via the OAuth 2.0 Client -
-
 
-
- User Attributes: -
    -
  • - : -
  • -
-
- - diff --git a/samples/boot/oauth2login/src/test/java/sample/web/OAuth2LoginControllerTests.java b/samples/boot/oauth2login/src/test/java/sample/web/OAuth2LoginControllerTests.java deleted file mode 100644 index 6a3af42ff7b..00000000000 --- a/samples/boot/oauth2login/src/test/java/sample/web/OAuth2LoginControllerTests.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2002-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample.web; - -import java.util.Collections; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; - -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.oauth2Login; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model; - -/** - * Tests for {@link OAuth2LoginController} - * - * @author Josh Cummings - */ -@RunWith(SpringRunner.class) -@WebMvcTest(OAuth2LoginController.class) -public class OAuth2LoginControllerTests { - - @Autowired - MockMvc mvc; - - @Test - public void rootWhenAuthenticatedReturnsUserAndClient() throws Exception { - this.mvc.perform(get("/").with(oauth2Login())) - .andExpect(model().attribute("userName", "user")) - .andExpect(model().attribute("clientName", "test")) - .andExpect(model().attribute("userAttributes", Collections.singletonMap("sub", "user"))); - } - - @Test - public void rootWhenOverridingClientRegistrationReturnsAccordingly() throws Exception { - ClientRegistration clientRegistration = ClientRegistration.withRegistrationId("test") - .authorizationGrantType(AuthorizationGrantType.PASSWORD) - .clientId("my-client-id") - .clientName("my-client-name") - .tokenUri("https://token-uri.example.org") - .build(); - - this.mvc.perform(get("/").with(oauth2Login() - .clientRegistration(clientRegistration) - .attributes((a) -> a.put("sub", "spring-security")))) - .andExpect(model().attribute("userName", "spring-security")) - .andExpect(model().attribute("clientName", "my-client-name")) - .andExpect(model().attribute("userAttributes", Collections.singletonMap("sub", "spring-security"))); - } -} diff --git a/samples/boot/oauth2resourceserver-jwe/README.adoc b/samples/boot/oauth2resourceserver-jwe/README.adoc deleted file mode 100644 index 84c64f6f587..00000000000 --- a/samples/boot/oauth2resourceserver-jwe/README.adoc +++ /dev/null @@ -1,119 +0,0 @@ -= OAuth 2.0 Resource Server Sample - -This sample demonstrates integrating Resource Server with a mock Authorization Server, though it can be modified to integrate -with your favorite Authorization Server. This resource server is configured to accept JWE-encrypted tokens. - -With it, you can run the integration tests or run the application as a stand-alone service to explore how you can -secure your own service with OAuth 2.0 Bearer Tokens using Spring Security. - -== 1. Running the tests - -To run the tests, do: - -```bash -./gradlew integrationTest -``` - -Or import the project into your IDE and run `OAuth2ResourceServerApplicationTests` from there. - -=== What is it doing? - -By default, the tests are pointing at a mock Authorization Server instance. - -The tests are configured with a set of hard-coded tokens originally obtained from the mock Authorization Server, -and each makes a query to the Resource Server with their corresponding token. - -The Resource Server decrypts the token and subsquently verifies it with the Authorization Server and authorizes the request, returning the phrase - -```bash -Hello, subject! -``` - -where "subject" is the value of the `sub` field in the JWT returned by the Authorization Server. - -== 2. Running the app - -To run as a stand-alone application, do: - -```bash -./gradlew bootRun -``` - -Or import the project into your IDE and run `OAuth2ResourceServerApplication` from there. - -Once it is up, you can use the following token: - -```bash -export TOKEN=eyJjdHkiOiJKV1QiLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVAtMjU2In0.IyeWtsTonaiWJdoT13B0M7gpqVxAirVGlfqFI4TOmTRcVHICs_ESezS7fa0ODS9XYdwklTtG7hH39yeeMzr2Zo1Ghh-m36fdoqQrV1Do04rUvuTjqbgyNffeZEGB6rquJ-cyAVjp_Oljy10-Bbnw7CeVGwNBSVo9UCB5j49OlNWhLxFpYARFmOlYpXj-s4Q4JiqV6EvjDAYeohAR4QQmND3qoxR-s2I6SLcIho0sSSpUlhrRiqu2uvWefHDcZJdW2WYWnxLHxhzNu3CfnLiqhhaA_YA_iWXR9FYnPDCf_4q3FgSXcgttXzomFKAx5DwnE_dXvsCvpWxslZMU1UIiLA.MHOrrza2GQ9_5PIv.zU4tfhxT6apWBC3stBwQmGlCQBltWVQe4dFIykybWWBFqxo1bf2BZ37twzoEIFXG9jSYEMH4mvBXPWSvn66t-_jnqLnKTJst2plBjhagGCAoLNWXVKeYNp67o-lKOD_JJQFqsRw4oE05VSgRr14MZeaUBFcU3A_kKxMXOu899DKfXBGJvj75H7lDyd8RUXTb-OSWWfUiJc6Y5AUy1zCZCN9yfDsCXt9heTsZANy92Oou9sMFaXkYzyums5OtkBtLFzyuNMEoNioRehTV-FTuL8tDRB1mNhHObwsBfFbR6M1jJK37pHUXGtko-yZ6NGwxyLtwGh4uU2jzE614rQzuzR8aHaHxOkUs1pBTZ8AcRt41snByOe-KU0adthHxedobFiQQBoQ05DgSU7DO6hsK0uVBDF3eG2KjH4L2lZy-WugloLHhdguUoO9F0zUx15-XZO4EVzmhy8xfH2tSXz98eKzz9Dv0DdGnrBL9cK2MM88N1zoq5u4NdlnE12HvuesB7GKdMwZx1-gTw_pzP81TzcctJWl6ETK09Uc.jk0O8qc4Fvip856stDz05Q -``` - -And then make this request: - -```bash -curl -H "Authorization: Bearer $TOKEN" localhost:8080 -``` - -Which will respond with the phrase: - -```bash -Hello, subject! -``` - -where `subject` is the value of the `sub` field in the JWT returned by the Authorization Server. - -Or this: - -```bash -export TOKEN=eyJjdHkiOiJKV1QiLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVAtMjU2In0.CRBAEgvQhpB6pPQhpkTAKpsDai1FDcvkDSRig1R3OI-g18JdTe-qDhzWwP-hV3aCwFwHxQ_g8Z8OIZvhyKpQaPwBb72UeLqqhzSIkm0gEsmmjYg1vEGOrDH5_Fqlm0LnAnXTmsbOIWYIj11ZuenI2lEmMCkVwqth0RlzakdcHRXiuDTEln_trhZpE2j80X-9rS2gZy9Raa9VLir3-F3wC0GKPEL6e3x1OygC03ix9uyXS3vpTsU9zlgoYADZyaLeOF1mCG4mQhvXs7IPmPbwNsElJwKh0xSQCHvNOQShprlvd3cHiUFKYw9fXphY1O-AUYcRzHk4DjoBdkGNQMy_Kw.KtC_z674rYBtDgkN.e8QU50Iq1JHkn-1USSxpjEkbrukb4cobvlQRK40iXGAKVIuOod4bSq5fDpIAPHugqIf-_zGsvr-2OCOdzhtBikL46wU7UdZppxPWtk-X6kl33zH_XObRMaGfe-hLxt3RPxRVn7I1Hp6tGW1Rkxyf_ESq4XlcbbrkhDoIz_G_LKXJhvQ-xahW2e0AUc7RZSucns4XUeq9xX_dd7Ht-o1TmQI9WFoFc1l7oh9GtQ6GZMsghnZ1VrbIS2L7jSYiSsD2JqSv1LLtOGj_FBA0ufhqM3LloGiwflEwAryMD10oNb73WonKEycEj1rBsTIKW7SHkI-VkrQA4-8N-aLWgHwDnzyPZmyNyKpqUMvhjIE_0w6oqU4HpN7J5nfBEIAtpPZ_pDkwAdxCQ7JV3zfiUnF7ZQ3q1PnSId315si02ZN9-wRSrMHcHnboQN1Hs4xCAfGyClVyLpCzfa_fAehjt6v1DjgjbzwSjr_LdNmWTvXYBhNO8Jq9Vb7axksrdwksD3pYNMY8cRZxP-LO0V5Sv1_kT_Hf2yLo2iTwB8n8szzGrJ4QQLb5Znu7Sv-M2x52cnIDMiorP3LNpFk.G85FuMSm-8bGumFAStiFQA - -curl -H "Authorization: Bearer $TOKEN" localhost:8080/message -``` - -Will respond with: - -```bash -secret message -``` - -== 2. Testing against other Authorization Servers - -_In order to use this sample, your Authorization Server must encrypt using the public key available in the sample. -Also it must support JWTs that either use the "scope" or "scp" attribute._ - -To change the sample to point at your Authorization Server, simply find this property in the `application.yml`: - -```yaml -spring: - security: - oauth2: - resourceserver: - jwt: - jwk-set-uri: ${mockwebserver.url}/.well-known/jwks.json -``` - -And change the property to your Authorization Server's JWK set endpoint: - -```yaml -spring: - security: - oauth2: - resourceserver: - jwt: - jwk-set-uri: https://localhost:9031/pf/JWKS -``` - -If your Authorization Server does not support RSA_OAEP_256 or AESGCM, then you can change these values in `OAuth2ResourceServerSecurityConfiguration`: - -```java - -``` - -And then you can run the app the same as before: - -```bash -./gradlew bootRun -``` - -Make sure to obtain valid tokens from your Authorization Server in order to play with the sample Resource Server. -To use the `/` endpoint, any valid token from your Authorization Server will do. -To use the `/message` endpoint, the token should have the `message:read` scope. diff --git a/samples/boot/oauth2resourceserver-jwe/spring-security-samples-boot-oauth2resourceserver-jwe.gradle b/samples/boot/oauth2resourceserver-jwe/spring-security-samples-boot-oauth2resourceserver-jwe.gradle deleted file mode 100644 index 2135bb0af66..00000000000 --- a/samples/boot/oauth2resourceserver-jwe/spring-security-samples-boot-oauth2resourceserver-jwe.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'io.spring.convention.spring-sample-boot' - -dependencies { - compile project(':spring-security-config') - compile project(':spring-security-oauth2-jose') - compile project(':spring-security-oauth2-resource-server') - - compile 'org.springframework.boot:spring-boot-starter-web' - compile 'com.squareup.okhttp3:mockwebserver' - - testCompile project(':spring-security-test') - testCompile 'org.springframework.boot:spring-boot-starter-test' -} diff --git a/samples/boot/oauth2resourceserver-jwe/src/integration-test/java/sample/OAuth2ResourceServerApplicationITests.java b/samples/boot/oauth2resourceserver-jwe/src/integration-test/java/sample/OAuth2ResourceServerApplicationITests.java deleted file mode 100644 index cd25346ce8c..00000000000 --- a/samples/boot/oauth2resourceserver-jwe/src/integration-test/java/sample/OAuth2ResourceServerApplicationITests.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2002-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.HttpHeaders; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.request.RequestPostProcessor; - -import static org.hamcrest.Matchers.containsString; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Integration tests for {@link OAuth2ResourceServerApplication} - * - * @author Josh Cummings - */ -@RunWith(SpringRunner.class) -@SpringBootTest -@AutoConfigureMockMvc -@ActiveProfiles("test") -public class OAuth2ResourceServerApplicationITests { - - String noScopesToken = "eyJjdHkiOiJKV1QiLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVAtMjU2In0.IyeWtsTonaiWJdoT13B0M7gpqVxAirVGlfqFI4TOmTRcVHICs_ESezS7fa0ODS9XYdwklTtG7hH39yeeMzr2Zo1Ghh-m36fdoqQrV1Do04rUvuTjqbgyNffeZEGB6rquJ-cyAVjp_Oljy10-Bbnw7CeVGwNBSVo9UCB5j49OlNWhLxFpYARFmOlYpXj-s4Q4JiqV6EvjDAYeohAR4QQmND3qoxR-s2I6SLcIho0sSSpUlhrRiqu2uvWefHDcZJdW2WYWnxLHxhzNu3CfnLiqhhaA_YA_iWXR9FYnPDCf_4q3FgSXcgttXzomFKAx5DwnE_dXvsCvpWxslZMU1UIiLA.MHOrrza2GQ9_5PIv.zU4tfhxT6apWBC3stBwQmGlCQBltWVQe4dFIykybWWBFqxo1bf2BZ37twzoEIFXG9jSYEMH4mvBXPWSvn66t-_jnqLnKTJst2plBjhagGCAoLNWXVKeYNp67o-lKOD_JJQFqsRw4oE05VSgRr14MZeaUBFcU3A_kKxMXOu899DKfXBGJvj75H7lDyd8RUXTb-OSWWfUiJc6Y5AUy1zCZCN9yfDsCXt9heTsZANy92Oou9sMFaXkYzyums5OtkBtLFzyuNMEoNioRehTV-FTuL8tDRB1mNhHObwsBfFbR6M1jJK37pHUXGtko-yZ6NGwxyLtwGh4uU2jzE614rQzuzR8aHaHxOkUs1pBTZ8AcRt41snByOe-KU0adthHxedobFiQQBoQ05DgSU7DO6hsK0uVBDF3eG2KjH4L2lZy-WugloLHhdguUoO9F0zUx15-XZO4EVzmhy8xfH2tSXz98eKzz9Dv0DdGnrBL9cK2MM88N1zoq5u4NdlnE12HvuesB7GKdMwZx1-gTw_pzP81TzcctJWl6ETK09Uc.jk0O8qc4Fvip856stDz05Q"; - String messageReadToken = "eyJjdHkiOiJKV1QiLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVAtMjU2In0.CRBAEgvQhpB6pPQhpkTAKpsDai1FDcvkDSRig1R3OI-g18JdTe-qDhzWwP-hV3aCwFwHxQ_g8Z8OIZvhyKpQaPwBb72UeLqqhzSIkm0gEsmmjYg1vEGOrDH5_Fqlm0LnAnXTmsbOIWYIj11ZuenI2lEmMCkVwqth0RlzakdcHRXiuDTEln_trhZpE2j80X-9rS2gZy9Raa9VLir3-F3wC0GKPEL6e3x1OygC03ix9uyXS3vpTsU9zlgoYADZyaLeOF1mCG4mQhvXs7IPmPbwNsElJwKh0xSQCHvNOQShprlvd3cHiUFKYw9fXphY1O-AUYcRzHk4DjoBdkGNQMy_Kw.KtC_z674rYBtDgkN.e8QU50Iq1JHkn-1USSxpjEkbrukb4cobvlQRK40iXGAKVIuOod4bSq5fDpIAPHugqIf-_zGsvr-2OCOdzhtBikL46wU7UdZppxPWtk-X6kl33zH_XObRMaGfe-hLxt3RPxRVn7I1Hp6tGW1Rkxyf_ESq4XlcbbrkhDoIz_G_LKXJhvQ-xahW2e0AUc7RZSucns4XUeq9xX_dd7Ht-o1TmQI9WFoFc1l7oh9GtQ6GZMsghnZ1VrbIS2L7jSYiSsD2JqSv1LLtOGj_FBA0ufhqM3LloGiwflEwAryMD10oNb73WonKEycEj1rBsTIKW7SHkI-VkrQA4-8N-aLWgHwDnzyPZmyNyKpqUMvhjIE_0w6oqU4HpN7J5nfBEIAtpPZ_pDkwAdxCQ7JV3zfiUnF7ZQ3q1PnSId315si02ZN9-wRSrMHcHnboQN1Hs4xCAfGyClVyLpCzfa_fAehjt6v1DjgjbzwSjr_LdNmWTvXYBhNO8Jq9Vb7axksrdwksD3pYNMY8cRZxP-LO0V5Sv1_kT_Hf2yLo2iTwB8n8szzGrJ4QQLb5Znu7Sv-M2x52cnIDMiorP3LNpFk.G85FuMSm-8bGumFAStiFQA"; - - @Autowired - MockMvc mvc; - - @Test - public void performWhenValidBearerTokenThenAllows() - throws Exception { - - this.mvc.perform(get("/").with(bearerToken(this.noScopesToken))) - .andExpect(status().isOk()) - .andExpect(content().string(containsString("Hello, subject!"))); - } - - @Test - public void performWhenValidBearerTokenThenScopedRequestsAlsoWork() - throws Exception { - - this.mvc.perform(get("/message").with(bearerToken(this.messageReadToken))) - .andExpect(status().isOk()) - .andExpect(content().string(containsString("secret message"))); - } - - @Test - public void performWhenInsufficientlyScopedBearerTokenThenDeniesScopedMethodAccess() - throws Exception { - - this.mvc.perform(get("/message").with(bearerToken(this.noScopesToken))) - .andExpect(status().isForbidden()) - .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, - containsString("Bearer error=\"insufficient_scope\""))); - } - - private static class BearerTokenRequestPostProcessor implements RequestPostProcessor { - private String token; - - BearerTokenRequestPostProcessor(String token) { - this.token = token; - } - - @Override - public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { - request.addHeader("Authorization", "Bearer " + this.token); - return request; - } - } - - private static BearerTokenRequestPostProcessor bearerToken(String token) { - return new BearerTokenRequestPostProcessor(token); - } -} diff --git a/samples/boot/oauth2resourceserver-jwe/src/main/java/org/springframework/boot/env/MockWebServerEnvironmentPostProcessor.java b/samples/boot/oauth2resourceserver-jwe/src/main/java/org/springframework/boot/env/MockWebServerEnvironmentPostProcessor.java deleted file mode 100644 index 62f91d1a1fc..00000000000 --- a/samples/boot/oauth2resourceserver-jwe/src/main/java/org/springframework/boot/env/MockWebServerEnvironmentPostProcessor.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2002-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.env; - -import org.springframework.beans.factory.DisposableBean; -import org.springframework.boot.SpringApplication; -import org.springframework.core.env.ConfigurableEnvironment; - -/** - * @author Rob Winch - */ -public class MockWebServerEnvironmentPostProcessor - implements EnvironmentPostProcessor, DisposableBean { - - private final MockWebServerPropertySource propertySource = new MockWebServerPropertySource(); - - @Override - public void postProcessEnvironment(ConfigurableEnvironment environment, - SpringApplication application) { - environment.getPropertySources().addFirst(this.propertySource); - } - - @Override - public void destroy() throws Exception { - this.propertySource.destroy(); - } -} diff --git a/samples/boot/oauth2resourceserver-jwe/src/main/java/org/springframework/boot/env/MockWebServerPropertySource.java b/samples/boot/oauth2resourceserver-jwe/src/main/java/org/springframework/boot/env/MockWebServerPropertySource.java deleted file mode 100644 index 5e9df1d836f..00000000000 --- a/samples/boot/oauth2resourceserver-jwe/src/main/java/org/springframework/boot/env/MockWebServerPropertySource.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2002-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.env; - -import java.io.IOException; - -import okhttp3.mockwebserver.Dispatcher; -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.MockWebServer; -import okhttp3.mockwebserver.RecordedRequest; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.beans.factory.DisposableBean; -import org.springframework.core.env.PropertySource; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; - -/** - * @author Rob Winch - */ -public class MockWebServerPropertySource extends PropertySource implements - DisposableBean { - - private static final MockResponse JWKS_RESPONSE = response( - "{\"keys\":[{\"kty\":\"RSA\",\"e\":\"AQAB\",\"use\":\"sig\",\"kid\":\"one\",\"n\":\"i7H90yfquGVhXxekdzXkMaxlIg67Q_ofd7iuFHtgeUx-Iye2QjukuULhl774oITYnZIZsh2UHxRYG8nFypcYZfHJMQes_OYFTkTvRroKll5p3wxSkhpARbkEPFMyMJ5WIm3MeNO2ermMhDWVVeI2xQH-tW6w-C6b5d_F6lrIwCnpZwSv6PQ3kef-rcObp_PZANIo232bvpwyC6uW1W2kpjAvYJhQ8NrkG2oO0ynqEJW2UyoCWRdsT2BLZcFMAcxG3Iw9b9__IbvNoUBwr596JYfzrX0atiKyk4Yg8dJ1wBjHFN2fkHTlzn6HDwTJkj4VNDQvKu4P2zhKn1gmWuxhuQ\"}]}", - 200 - ); - - private static final MockResponse NOT_FOUND_RESPONSE = response( - "{ \"message\" : \"This mock authorization server responds to just one request: GET /.well-known/jwks.json.\" }", - 404 - ); - - /** - * Name of the random {@link PropertySource}. - */ - public static final String MOCK_WEB_SERVER_PROPERTY_SOURCE_NAME = "mockwebserver"; - - private static final String NAME = "mockwebserver.url"; - - private static final Log logger = LogFactory.getLog(MockWebServerPropertySource.class); - - private boolean started; - - public MockWebServerPropertySource() { - super(MOCK_WEB_SERVER_PROPERTY_SOURCE_NAME, new MockWebServer()); - } - - @Override - public Object getProperty(String name) { - if (!name.equals(NAME)) { - return null; - } - if (logger.isTraceEnabled()) { - logger.trace("Looking up the url for '" + name + "'"); - } - String url = getUrl(); - return url; - } - - @Override - public void destroy() throws Exception { - getSource().shutdown(); - } - - /** - * Get's the URL (i.e. "http://localhost:123456") - * @return - */ - private String getUrl() { - MockWebServer mockWebServer = getSource(); - if (!this.started) { - intializeMockWebServer(mockWebServer); - } - String url = mockWebServer.url("").url().toExternalForm(); - return url.substring(0, url.length() - 1); - } - - private void intializeMockWebServer(MockWebServer mockWebServer) { - Dispatcher dispatcher = new Dispatcher() { - @Override - public MockResponse dispatch(RecordedRequest request) { - if ("/.well-known/jwks.json".equals(request.getPath())) { - return JWKS_RESPONSE; - } - - return NOT_FOUND_RESPONSE; - } - }; - - mockWebServer.setDispatcher(dispatcher); - try { - mockWebServer.start(); - this.started = true; - } catch (IOException e) { - throw new RuntimeException("Could not start " + mockWebServer, e); - } - } - - private static MockResponse response(String body, int status) { - return new MockResponse() - .setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .setResponseCode(status) - .setBody(body); - } - -} diff --git a/samples/boot/oauth2resourceserver-jwe/src/main/java/org/springframework/boot/env/package-info.java b/samples/boot/oauth2resourceserver-jwe/src/main/java/org/springframework/boot/env/package-info.java deleted file mode 100644 index 02260378222..00000000000 --- a/samples/boot/oauth2resourceserver-jwe/src/main/java/org/springframework/boot/env/package-info.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2002-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * This provides integration of a {@link okhttp3.mockwebserver.MockWebServer} and the - * {@link org.springframework.core.env.Environment} - * @author Rob Winch - */ -package org.springframework.boot.env; diff --git a/samples/boot/oauth2resourceserver-jwe/src/main/java/sample/OAuth2ResourceServerApplication.java b/samples/boot/oauth2resourceserver-jwe/src/main/java/sample/OAuth2ResourceServerApplication.java deleted file mode 100644 index a0841c00f32..00000000000 --- a/samples/boot/oauth2resourceserver-jwe/src/main/java/sample/OAuth2ResourceServerApplication.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2002-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - * @author Josh Cummings - */ -@SpringBootApplication -public class OAuth2ResourceServerApplication { - - public static void main(String[] args) { - SpringApplication.run(OAuth2ResourceServerApplication.class, args); - } -} diff --git a/samples/boot/oauth2resourceserver-jwe/src/main/java/sample/OAuth2ResourceServerController.java b/samples/boot/oauth2resourceserver-jwe/src/main/java/sample/OAuth2ResourceServerController.java deleted file mode 100644 index f0bcdbe64f5..00000000000 --- a/samples/boot/oauth2resourceserver-jwe/src/main/java/sample/OAuth2ResourceServerController.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2002-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - * @author Josh Cummings - */ -@RestController -public class OAuth2ResourceServerController { - - @GetMapping("/") - public String index(@AuthenticationPrincipal Jwt jwt) { - return String.format("Hello, %s!", jwt.getSubject()); - } - - @GetMapping("/message") - public String message() { - return "secret message"; - } -} diff --git a/samples/boot/oauth2resourceserver-jwe/src/main/java/sample/OAuth2ResourceServerSecurityConfiguration.java b/samples/boot/oauth2resourceserver-jwe/src/main/java/sample/OAuth2ResourceServerSecurityConfiguration.java deleted file mode 100644 index dfe4135d4bc..00000000000 --- a/samples/boot/oauth2resourceserver-jwe/src/main/java/sample/OAuth2ResourceServerSecurityConfiguration.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import java.net.URL; -import java.security.interfaces.RSAPrivateCrtKey; -import java.security.interfaces.RSAPrivateKey; - -import com.nimbusds.jose.EncryptionMethod; -import com.nimbusds.jose.JWEAlgorithm; -import com.nimbusds.jose.JWSAlgorithm; -import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.KeyUse; -import com.nimbusds.jose.jwk.RSAKey; -import com.nimbusds.jose.jwk.source.ImmutableJWKSet; -import com.nimbusds.jose.jwk.source.JWKSource; -import com.nimbusds.jose.jwk.source.RemoteJWKSet; -import com.nimbusds.jose.proc.JWEDecryptionKeySelector; -import com.nimbusds.jose.proc.JWEKeySelector; -import com.nimbusds.jose.proc.JWSKeySelector; -import com.nimbusds.jose.proc.JWSVerificationKeySelector; -import com.nimbusds.jose.proc.SecurityContext; -import com.nimbusds.jose.util.Base64URL; -import com.nimbusds.jwt.proc.ConfigurableJWTProcessor; -import com.nimbusds.jwt.proc.DefaultJWTProcessor; -import com.nimbusds.jwt.proc.JWTProcessor; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; -import org.springframework.security.oauth2.jwt.JwtDecoder; -import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; - -import static org.springframework.security.config.Customizer.withDefaults; - -/** - * @author Josh Cummings - */ -@EnableWebSecurity -public class OAuth2ResourceServerSecurityConfiguration extends WebSecurityConfigurerAdapter { - - private final JWSAlgorithm jwsAlgorithm = JWSAlgorithm.RS256; - private final JWEAlgorithm jweAlgorithm = JWEAlgorithm.RSA_OAEP_256; - private final EncryptionMethod encryptionMethod = EncryptionMethod.A256GCM; - - @Value("${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}") - URL jwkSetUri; - - @Value("${sample.jwe-key-value}") - RSAPrivateKey key; - - @Override - protected void configure(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeRequests((authorizeRequests) -> - authorizeRequests - .antMatchers("/message/**").hasAuthority("SCOPE_message:read") - .anyRequest().authenticated() - ) - .oauth2ResourceServer((oauth2ResourceServer) -> - oauth2ResourceServer - .jwt(withDefaults()) - ); - // @formatter:on - } - - @Bean - JwtDecoder jwtDecoder() { - return new NimbusJwtDecoder(jwtProcessor()); - } - - private JWTProcessor jwtProcessor() { - JWKSource jwsJwkSource = new RemoteJWKSet<>(this.jwkSetUri); - JWSKeySelector jwsKeySelector = - new JWSVerificationKeySelector<>(this.jwsAlgorithm, jwsJwkSource); - - JWKSource jweJwkSource = new ImmutableJWKSet<>(new JWKSet(rsaKey())); - JWEKeySelector jweKeySelector = - new JWEDecryptionKeySelector<>(this.jweAlgorithm, this.encryptionMethod, jweJwkSource); - - ConfigurableJWTProcessor jwtProcessor = new DefaultJWTProcessor<>(); - jwtProcessor.setJWSKeySelector(jwsKeySelector); - jwtProcessor.setJWEKeySelector(jweKeySelector); - - return jwtProcessor; - } - - private RSAKey rsaKey() { - RSAPrivateCrtKey crtKey = (RSAPrivateCrtKey) this.key; - Base64URL n = Base64URL.encode(crtKey.getModulus()); - Base64URL e = Base64URL.encode(crtKey.getPublicExponent()); - return new RSAKey.Builder(n, e) - .privateKey(this.key) - .keyUse(KeyUse.ENCRYPTION) - .build(); - } -} diff --git a/samples/boot/oauth2resourceserver-jwe/src/main/resources/META-INF/spring.factories b/samples/boot/oauth2resourceserver-jwe/src/main/resources/META-INF/spring.factories deleted file mode 100644 index 37b447c9702..00000000000 --- a/samples/boot/oauth2resourceserver-jwe/src/main/resources/META-INF/spring.factories +++ /dev/null @@ -1 +0,0 @@ -org.springframework.boot.env.EnvironmentPostProcessor=org.springframework.boot.env.MockWebServerEnvironmentPostProcessor diff --git a/samples/boot/oauth2resourceserver-jwe/src/main/resources/application.yml b/samples/boot/oauth2resourceserver-jwe/src/main/resources/application.yml deleted file mode 100644 index 8573fda8f66..00000000000 --- a/samples/boot/oauth2resourceserver-jwe/src/main/resources/application.yml +++ /dev/null @@ -1,9 +0,0 @@ -spring: - security: - oauth2: - resourceserver: - jwt: - jwk-set-uri: ${mockwebserver.url}/.well-known/jwks.json - -sample: - jwe-key-value: classpath:simple.priv diff --git a/samples/boot/oauth2resourceserver-jwe/src/main/resources/simple.priv b/samples/boot/oauth2resourceserver-jwe/src/main/resources/simple.priv deleted file mode 100644 index 53510079ace..00000000000 --- a/samples/boot/oauth2resourceserver-jwe/src/main/resources/simple.priv +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDcWWomvlNGyQhA -iB0TcN3sP2VuhZ1xNRPxr58lHswC9Cbtdc2hiSbe/sxAvU1i0O8vaXwICdzRZ1JM -g1TohG9zkqqjZDhyw1f1Ic6YR/OhE6NCpqERy97WMFeW6gJd1i5inHj/W19GAbqK -LhSHGHqIjyo0wlBf58t+qFt9h/EFBVE/LAGQBsg/jHUQCxsLoVI2aSELGIw2oSDF -oiljwLaQl0n9khX5ZbiegN3OkqodzCYHwWyu6aVVj8M1W9RIMiKmKr09s/gf31Nc -3WjvjqhFo1rTuurWGgKAxJLL7zlJqAKjGWbIT4P6h/1Kwxjw6X23St3OmhsG6HIn -+jl1++MrAgMBAAECggEBAMf820wop3pyUOwI3aLcaH7YFx5VZMzvqJdNlvpg1jbE -E2Sn66b1zPLNfOIxLcBG8x8r9Ody1Bi2Vsqc0/5o3KKfdgHvnxAB3Z3dPh2WCDek -lCOVClEVoLzziTuuTdGO5/CWJXdWHcVzIjPxmK34eJXioiLaTYqN3XKqKMdpD0ZG -mtNTGvGf+9fQ4i94t0WqIxpMpGt7NM4RHy3+Onggev0zLiDANC23mWrTsUgect/7 -62TYg8g1bKwLAb9wCBT+BiOuCc2wrArRLOJgUkj/F4/gtrR9ima34SvWUyoUaKA0 -bi4YBX9l8oJwFGHbU9uFGEMnH0T/V0KtIB7qetReywkCgYEA9cFyfBIQrYISV/OA -+Z0bo3vh2aL0QgKrSXZ924cLt7itQAHNZ2ya+e3JRlTczi5mnWfjPWZ6eJB/8MlH -Gpn12o/POEkU+XjZZSPe1RWGt5g0S3lWqyx9toCS9ACXcN9tGbaqcFSVI73zVTRA -8J9grR0fbGn7jaTlTX2tnlOTQ60CgYEA5YjYpEq4L8UUMFkuj+BsS3u0oEBnzuHd -I9LEHmN+CMPosvabQu5wkJXLuqo2TxRnAznsA8R3pCLkdPGoWMCiWRAsCn979TdY -QbqO2qvBAD2Q19GtY7lIu6C35/enQWzJUMQE3WW0OvjLzZ0l/9mA2FBRR+3F9A1d -rBdnmv0c3TcCgYEAi2i+ggVZcqPbtgrLOk5WVGo9F1GqUBvlgNn30WWNTx4zIaEk -HSxtyaOLTxtq2odV7Kr3LGiKxwPpn/T+Ief+oIp92YcTn+VfJVGw4Z3BezqbR8lA -Uf/+HF5ZfpMrVXtZD4Igs3I33Duv4sCuqhEvLWTc44pHifVloozNxYfRfU0CgYBN -HXa7a6cJ1Yp829l62QlJKtx6Ymj95oAnQu5Ez2ROiZMqXRO4nucOjGUP55Orac1a -FiGm+mC/skFS0MWgW8evaHGDbWU180wheQ35hW6oKAb7myRHtr4q20ouEtQMdQIF -snV39G1iyqeeAsf7dxWElydXpRi2b68i3BIgzhzebQKBgQCdUQuTsqV9y/JFpu6H -c5TVvhG/ubfBspI5DhQqIGijnVBzFT//UfIYMSKJo75qqBEyP2EJSmCsunWsAFsM -TszuiGTkrKcZy9G0wJqPztZZl2F2+bJgnA6nBEV7g5PA4Af+QSmaIhRwqGDAuROR -47jndeyIaMTNETEmOnms+as17g== ------END PRIVATE KEY----- \ No newline at end of file diff --git a/samples/boot/oauth2resourceserver-jwe/src/main/resources/simple.pub b/samples/boot/oauth2resourceserver-jwe/src/main/resources/simple.pub deleted file mode 100644 index 0b2ee7b336c..00000000000 --- a/samples/boot/oauth2resourceserver-jwe/src/main/resources/simple.pub +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3FlqJr5TRskIQIgdE3Dd -7D9lboWdcTUT8a+fJR7MAvQm7XXNoYkm3v7MQL1NYtDvL2l8CAnc0WdSTINU6IRv -c5Kqo2Q4csNX9SHOmEfzoROjQqahEcve1jBXluoCXdYuYpx4/1tfRgG6ii4Uhxh6 -iI8qNMJQX+fLfqhbfYfxBQVRPywBkAbIP4x1EAsbC6FSNmkhCxiMNqEgxaIpY8C2 -kJdJ/ZIV+WW4noDdzpKqHcwmB8FsrumlVY/DNVvUSDIipiq9PbP4H99TXN1o746o -RaNa07rq1hoCgMSSy+85SagCoxlmyE+D+of9SsMY8Ol9t0rdzpobBuhyJ/o5dfvj -KwIDAQAB ------END PUBLIC KEY----- \ No newline at end of file diff --git a/samples/boot/oauth2resourceserver-multitenancy/README.adoc b/samples/boot/oauth2resourceserver-multitenancy/README.adoc deleted file mode 100644 index 97674479bde..00000000000 --- a/samples/boot/oauth2resourceserver-multitenancy/README.adoc +++ /dev/null @@ -1,155 +0,0 @@ -= OAuth 2.0 Resource Server Sample - -This sample demonstrates integrating Resource Server with a mock Authorization Server, though it can be modified to integrate -with your favorite Authorization Server. - -With it, you can run the integration tests or run the application as a stand-alone service to explore how you can -secure your own service with OAuth 2.0 Bearer Tokens using Spring Security. - -== 1. Running the tests - -To run the tests, do: - -```bash -./gradlew integrationTest -``` - -Or import the project into your IDE and run `OAuth2ResourceServerApplicationTests` from there. - -=== What is it doing? - -By default, the tests are pointing at a mock Authorization Server instance. - -The tests are configured with a set of hard-coded tokens originally obtained from the mock Authorization Server, -and each makes a query to the Resource Server with their corresponding token. - -The Resource Server subsequently verifies with the Authorization Server and authorizes the request, returning either the -phrase - -```bash -Hello, subject for tenant one! -``` - -where "subject" is the value of the `sub` field in the JWT sent in the `Authorization` header, - -or the phrase -```bash -Hello, subject for tenant two! -``` -where "subject" is the value of the `sub` field in the Introspection response from the Authorization Server. - - -== 2. Running the app - -To run as a stand-alone application, do: - -```bash -./gradlew bootRun -``` - -Or import the project into your IDE and run `OAuth2ResourceServerApplication` from there. - -=== Authorizing with tenantOne (JWT) - -Once it is up, you can use the following token: - -```bash -export TOKEN=eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJzdWJqZWN0IiwiZXhwIjo0NjgzODA1MTI4fQ.ULEPdHG-MK5GlrTQMhgqcyug2brTIZaJIrahUeq9zaiwUSdW83fJ7W1IDd2Z3n4a25JY2uhEcoV95lMfccHR6y_2DLrNvfta22SumY9PEDF2pido54LXG6edIGgarnUbJdR4rpRe_5oRGVa8gDx8FnuZsNv6StSZHAzw5OsuevSTJ1UbJm4UfX3wiahFOQ2OI6G-r5TB2rQNdiPHuNyzG5yznUqRIZ7-GCoMqHMaC-1epKxiX8gYXRROuUYTtcMNa86wh7OVDmvwVmFioRcR58UWBRoO1XQexTtOQq_t8KYsrPZhb9gkyW8x2bAQF-d0J0EJY8JslaH6n4RBaZISww -``` - -And then make this request: - -```bash -curl -H "tenant: one" -H "Authorization: Bearer $TOKEN" localhost:8080 -``` - -Which will respond with the phrase: - -```bash -Hello, subject for tenant one! -``` - -where `subject` is the value of the `sub` field in the JWT sent in the `Authorization` header. - -Or this: - -```bash -export TOKEN=eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJzdWJqZWN0Iiwic2NvcGUiOiJtZXNzYWdlOnJlYWQiLCJleHAiOjQ2ODM4MDUxNDF9.h-j6FKRFdnTdmAueTZCdep45e6DPwqM68ZQ8doIJ1exi9YxAlbWzOwId6Bd0L5YmCmp63gGQgsBUBLzwnZQ8kLUgUOBEC3UzSWGRqMskCY9_k9pX0iomX6IfF3N0PaYs0WPC4hO1s8wfZQ-6hKQ4KigFi13G9LMLdH58PRMK0pKEvs3gCbHJuEPw-K5ORlpdnleUTQIwINafU57cmK3KocTeknPAM_L716sCuSYGvDl6xUTXO7oPdrXhS_EhxLP6KxrpI1uD4Ea_5OWTh7S0Wx5LLDfU6wBG1DowN20d374zepOIEkR-Jnmr_QlR44vmRqS5ncrF-1R0EGcPX49U6A - -curl -H "tenant: one" -H "Authorization: Bearer $TOKEN" localhost:8080/message -``` - -Will respond with: - -```bash -secret message for tenant one -``` - -=== Authorizing with tenantTwo (Opaque token) - -Once it is up, you can use the following token: - -```bash -export TOKEN=00ed5855-1869-47a0-b0c9-0f3ce520aee7 -``` - -And then make this request: - -```bash -curl -H "tenant: two" -H "Authorization: Bearer $TOKEN" localhost:8080 -``` - -Which will respond with the phrase: - -```bash -Hello, subject for tenant two! -``` - -where `subject` is the value of the `sub` field in the Introspection response from the Authorization Server. - -Or this: - -```bash -export TOKEN=b43d1500-c405-4dc9-b9c9-6cfd966c34c9 - -curl -H "tenant: two" -H "Authorization: Bearer $TOKEN" localhost:8080/message -``` - -Will respond with: - -```bash -secret message for tenant two -``` - -== 2. Testing against other Authorization Servers - -_In order to use this sample, your Authorization Server must support JWTs that either use the "scope" or "scp" attribute._ - -To change the sample to point at your Authorization Server, simply find these properties in the `application.yml`: - -```yaml -tenantOne.jwk-set-uri: ${mockwebserver.url}/.well-known/jwks.json -tenantTwo.introspection-uri: ${mockwebserver.url}/introspect -tenantTwo.introspection-client-id: client -tenantTwo.introspection-client-secret: secret -``` - -And change the properties to your Authorization Server's JWK set endpoint and -introspection endpoint, including its client id and secret - -```yaml -tenantOne.jwk-set-uri: https://dev-123456.oktapreview.com/oauth2/default/v1/keys -tenantTwo.introspection-uri: https://dev-123456.oktapreview.com/oauth2/default/v1/introspect -tenantTwo.introspection-client-id: client -tenantTwo.introspection-client-secret: secret -``` - -And then you can run the app the same as before: - -```bash -./gradlew bootRun -``` - -Make sure to obtain valid tokens from your Authorization Server in order to play with the sample Resource Server. -To use the `/` endpoint, any valid token from your Authorization Server will do. -To use the `/message` endpoint, the token should have the `message:read` scope. diff --git a/samples/boot/oauth2resourceserver-multitenancy/spring-security-samples-boot-oauth2resourceserver-multitenancy.gradle b/samples/boot/oauth2resourceserver-multitenancy/spring-security-samples-boot-oauth2resourceserver-multitenancy.gradle deleted file mode 100644 index 9074842b18a..00000000000 --- a/samples/boot/oauth2resourceserver-multitenancy/spring-security-samples-boot-oauth2resourceserver-multitenancy.gradle +++ /dev/null @@ -1,14 +0,0 @@ -apply plugin: 'io.spring.convention.spring-sample-boot' - -dependencies { - compile project(':spring-security-config') - compile project(':spring-security-oauth2-jose') - compile project(':spring-security-oauth2-resource-server') - - compile 'org.springframework.boot:spring-boot-starter-web' - compile 'com.nimbusds:oauth2-oidc-sdk' - compile 'com.squareup.okhttp3:mockwebserver' - - testCompile project(':spring-security-test') - testCompile 'org.springframework.boot:spring-boot-starter-test' -} diff --git a/samples/boot/oauth2resourceserver-multitenancy/src/integration-test/java/sample/OAuth2ResourceServerApplicationITests.java b/samples/boot/oauth2resourceserver-multitenancy/src/integration-test/java/sample/OAuth2ResourceServerApplicationITests.java deleted file mode 100644 index be6d3f559ca..00000000000 --- a/samples/boot/oauth2resourceserver-multitenancy/src/integration-test/java/sample/OAuth2ResourceServerApplicationITests.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright 2002-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.HttpHeaders; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; - -import static org.hamcrest.Matchers.containsString; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Integration tests for {@link OAuth2ResourceServerApplication} - * - * @author Josh Cummings - */ -@RunWith(SpringRunner.class) -@SpringBootTest -@AutoConfigureMockMvc -public class OAuth2ResourceServerApplicationITests { - - String tenantOneNoScopesToken = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJzdWJqZWN0IiwiZXhwIjo0NjgzODA1MTI4fQ.ULEPdHG-MK5GlrTQMhgqcyug2brTIZaJIrahUeq9zaiwUSdW83fJ7W1IDd2Z3n4a25JY2uhEcoV95lMfccHR6y_2DLrNvfta22SumY9PEDF2pido54LXG6edIGgarnUbJdR4rpRe_5oRGVa8gDx8FnuZsNv6StSZHAzw5OsuevSTJ1UbJm4UfX3wiahFOQ2OI6G-r5TB2rQNdiPHuNyzG5yznUqRIZ7-GCoMqHMaC-1epKxiX8gYXRROuUYTtcMNa86wh7OVDmvwVmFioRcR58UWBRoO1XQexTtOQq_t8KYsrPZhb9gkyW8x2bAQF-d0J0EJY8JslaH6n4RBaZISww"; - String tenantOneMessageReadToken = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJzdWJqZWN0Iiwic2NvcGUiOiJtZXNzYWdlOnJlYWQiLCJleHAiOjQ2ODM4MDUxNDF9.h-j6FKRFdnTdmAueTZCdep45e6DPwqM68ZQ8doIJ1exi9YxAlbWzOwId6Bd0L5YmCmp63gGQgsBUBLzwnZQ8kLUgUOBEC3UzSWGRqMskCY9_k9pX0iomX6IfF3N0PaYs0WPC4hO1s8wfZQ-6hKQ4KigFi13G9LMLdH58PRMK0pKEvs3gCbHJuEPw-K5ORlpdnleUTQIwINafU57cmK3KocTeknPAM_L716sCuSYGvDl6xUTXO7oPdrXhS_EhxLP6KxrpI1uD4Ea_5OWTh7S0Wx5LLDfU6wBG1DowN20d374zepOIEkR-Jnmr_QlR44vmRqS5ncrF-1R0EGcPX49U6A"; - String tenantTwoNoScopesToken = "00ed5855-1869-47a0-b0c9-0f3ce520aee7"; - String tenantTwoMessageReadToken = "b43d1500-c405-4dc9-b9c9-6cfd966c34c9"; - - @Autowired - MockMvc mvc; - - @Test - public void tenantOnePerformWhenValidBearerTokenThenAllows() - throws Exception { - - this.mvc.perform(get("/") - .header("tenant", "one") - .header("Authorization", "Bearer " + this.tenantOneNoScopesToken)) - .andExpect(status().isOk()) - .andExpect(content().string(containsString("Hello, subject for tenant one!"))); - } - - @Test - public void tenantOnePerformWhenValidBearerTokenThenScopedRequestsAlsoWork() - throws Exception { - - this.mvc.perform(get("/message") - .header("tenant", "one") - .header("Authorization", "Bearer " + this.tenantOneMessageReadToken)) - .andExpect(status().isOk()) - .andExpect(content().string(containsString("secret message for tenant one"))); - } - - @Test - public void tenantOnePerformWhenInsufficientlyScopedBearerTokenThenDeniesScopedMethodAccess() - throws Exception { - - this.mvc.perform(get("/message") - .header("tenant", "one") - .header("Authorization", "Bearer " + this.tenantOneNoScopesToken)) - .andExpect(status().isForbidden()) - .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, - containsString("Bearer error=\"insufficient_scope\""))); - } - - @Test - public void tenantTwoPerformWhenValidBearerTokenThenAllows() - throws Exception { - - this.mvc.perform(get("/") - .header("tenant", "two") - .header("Authorization", "Bearer " + this.tenantTwoNoScopesToken)) - .andExpect(status().isOk()) - .andExpect(content().string(containsString("Hello, subject for tenant two!"))); - } - - @Test - public void tenantTwoPerformWhenValidBearerTokenThenScopedRequestsAlsoWork() - throws Exception { - - this.mvc.perform(get("/message") - .header("tenant", "two") - .header("Authorization", "Bearer " + this.tenantTwoMessageReadToken)) - .andExpect(status().isOk()) - .andExpect(content().string(containsString("secret message for tenant two"))); - } - - @Test - public void tenantTwoPerformWhenInsufficientlyScopedBearerTokenThenDeniesScopedMethodAccess() - throws Exception { - - this.mvc.perform(get("/message") - .header("tenant", "two") - .header("Authorization", "Bearer " + this.tenantTwoNoScopesToken)) - .andExpect(status().isForbidden()) - .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, - containsString("Bearer error=\"insufficient_scope\""))); - } - - @Test(expected = IllegalArgumentException.class) - public void invalidTenantPerformWhenValidBearerTokenThenThrowsException() - throws Exception { - - this.mvc.perform(get("/") - .header("tenant", "three") - .header("Authorization", "Bearer " + this.tenantOneNoScopesToken)); - } -} diff --git a/samples/boot/oauth2resourceserver-multitenancy/src/main/java/org/springframework/boot/env/MockWebServerEnvironmentPostProcessor.java b/samples/boot/oauth2resourceserver-multitenancy/src/main/java/org/springframework/boot/env/MockWebServerEnvironmentPostProcessor.java deleted file mode 100644 index f6f664891be..00000000000 --- a/samples/boot/oauth2resourceserver-multitenancy/src/main/java/org/springframework/boot/env/MockWebServerEnvironmentPostProcessor.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.env; - -import org.springframework.beans.factory.DisposableBean; -import org.springframework.boot.SpringApplication; -import org.springframework.core.env.ConfigurableEnvironment; - -/** - * @author Rob Winch - */ -public class MockWebServerEnvironmentPostProcessor - implements EnvironmentPostProcessor, DisposableBean { - - private final MockWebServerPropertySource propertySource = new MockWebServerPropertySource(); - - @Override - public void postProcessEnvironment(ConfigurableEnvironment environment, - SpringApplication application) { - environment.getPropertySources().addFirst(this.propertySource); - } - - @Override - public void destroy() throws Exception { - this.propertySource.destroy(); - } -} diff --git a/samples/boot/oauth2resourceserver-multitenancy/src/main/java/org/springframework/boot/env/MockWebServerPropertySource.java b/samples/boot/oauth2resourceserver-multitenancy/src/main/java/org/springframework/boot/env/MockWebServerPropertySource.java deleted file mode 100644 index 9e3f58e628d..00000000000 --- a/samples/boot/oauth2resourceserver-multitenancy/src/main/java/org/springframework/boot/env/MockWebServerPropertySource.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.env; - -import java.io.IOException; -import java.util.Base64; -import java.util.Map; -import java.util.Optional; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import okhttp3.mockwebserver.Dispatcher; -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.MockWebServer; -import okhttp3.mockwebserver.RecordedRequest; -import okio.Buffer; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.beans.factory.DisposableBean; -import org.springframework.core.env.PropertySource; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; - -/** - * @author Rob Winch - */ -public class MockWebServerPropertySource extends PropertySource implements - DisposableBean { - - // introspection endpoint - - private static final MockResponse NO_SCOPES_RESPONSE = response( - "{\n" + - " \"active\": true,\n" + - " \"sub\": \"subject\"\n" + - " }", - 200 - ); - - private static final MockResponse MESSASGE_READ_SCOPE_RESPONSE = response( - "{\n" + - " \"active\": true,\n" + - " \"scope\" : \"message:read\"," + - " \"sub\": \"subject\"\n" + - " }", - 200 - ); - - private static final MockResponse INACTIVE_RESPONSE = response( - "{\n" + - " \"active\": false,\n" + - " }", - 200 - ); - - private static final MockResponse BAD_REQUEST_RESPONSE = response( - "{ \"message\" : \"This mock authorization server requires a username and password of " + - "client/secret and a POST body of token=${token}\" }", - 400 - ); - - private static final MockResponse NOT_FOUND_RESPONSE = response( - "{ \"message\" : \"This mock authorization server responds to just two requests: POST /introspect" + - " and GET /.well-known/jwks.json.\" }", - 404 - ); - - // jwks endpoint - - private static final MockResponse JWKS_RESPONSE = response( - "{\"keys\":[{\"p\":\"2p-ViY7DE9ZrdWQb544m0Jp7Cv03YCSljqfim9pD4ALhObX0OrAznOiowTjwBky9JGffMwDBVSfJSD9TSU7aH2sbbfi0bZLMdekKAuimudXwUqPDxrrg0BCyvCYgLmKjbVT3zcdylWSog93CNTxGDPzauu-oc0XPNKCXnaDpNvE\",\"kty\":\"RSA\",\"q\":\"sP_QYavrpBvSJ86uoKVGj2AGl78CSsAtpf1ybSY5TwUlorXSdqapRbY69Y271b0aMLzlleUn9ZTBO1dlKV2_dw_lPADHVia8z3pxL-8sUhIXLsgj4acchMk4c9YX-sFh07xENnyZ-_TXm3llPLuL67HUfBC2eKe800TmCYVWc9U\",\"d\":\"bn1nFxCQT4KLTHqo8mo9HvHD0cRNRNdWcKNnnEQkCF6tKbt-ILRyQGP8O40axLd7CoNVG9c9p_-g4-2kwCtLJNv_STLtwfpCY7VN5o6-ZIpfTjiW6duoPrLWq64Hm_4LOBQTiZfUPcLhsuJRHbWqakj-kV_YbUyC2Ocf_dd8IAQcSrAU2SCcDebhDCWwRUFvaa9V5eq0851S9goaA-AJz-JXyePH6ZFr8JxmWkWxYZ5kdcMD-sm9ZbxE0CaEk32l4fE4hR-L8x2dDtjWA-ahKCZ091z-gV3HWtR2JOjvxoNRjxUo3UxaGiFJHWNIl0EYUJZu1Cb-5wIlEI7wPx5mwQ\",\"e\":\"AQAB\",\"use\":\"sig\",\"kid\":\"one\",\"qi\":\"qS0OK48M2CIAA6_4Wdw4EbCaAfcTLf5Oy9t5BOF_PFUKqoSpZ6JsT5H0a_4zkjt-oI969v78OTlvBKbmEyKO-KeytzHBAA5CsLmVcz0THrMSg6oXZqu66MPnvWoZN9FEN5TklPOvBFm8Bg1QZ3k-YMVaM--DLvhaYR95_mqaz50\",\"dp\":\"Too2NozLGD1XrXyhabZvy1E0EuaVFj0UHQPDLSpkZ_2g3BK6Art6T0xmE8RYtmqrKIEIdlI3IliAvyvAx_1D7zWTTRaj-xlZyqJFrnXWL7zj8UxT8PkB-r2E-ILZ3NAi1gxIWezlBTZ8M6NfObDFmbTc_3tJkN_raISo8z_ziIE\",\"dq\":\"U0yhSkY5yOsa9YcMoigGVBWSJLpNHtbg5NypjHrPv8OhWbkOSq7WvSstBkFk5AtyFvvfZLMLIkWWxxGzV0t6f1MoxBtttLrYYyCxwihiiGFhLbAdSuZ1wnxcqA9bC7UVECvrQmVTpsMs8UupfHKbQBpZ8OWAqrnuYNNtG4_4Bt0\",\"n\":\"lygtuZj0lJjqOqIWocF8Bb583QDdq-aaFg8PesOp2-EDda6GqCpL-_NZVOflNGX7XIgjsWHcPsQHsV9gWuOzSJ0iEuWvtQ6eGBP5M6m7pccLNZfwUse8Cb4Ngx3XiTlyuqM7pv0LPyppZusfEHVEdeelou7Dy9k0OQ_nJTI3b2E1WBoHC58CJ453lo4gcBm1efURN3LIVc1V9NQY_ESBKVdwqYyoJPEanURLVGRd6cQKn6YrCbbIRHjqAyqOE-z3KmgDJnPriljfR5XhSGyM9eqD9Xpy6zu_MAeMJJfSArp857zLPk-Wf5VP9STAcjyfdBIybMKnwBYr2qHMT675hQ\"}]}", - 200 - ); - - /** - * Name of the random {@link PropertySource}. - */ - public static final String MOCK_WEB_SERVER_PROPERTY_SOURCE_NAME = "mockwebserver"; - - private static final String NAME = "mockwebserver.url"; - - private static final Log logger = LogFactory.getLog(MockWebServerPropertySource.class); - - private boolean started; - - public MockWebServerPropertySource() { - super(MOCK_WEB_SERVER_PROPERTY_SOURCE_NAME, new MockWebServer()); - } - - @Override - public Object getProperty(String name) { - if (!name.equals(NAME)) { - return null; - } - if (logger.isTraceEnabled()) { - logger.trace("Looking up the url for '" + name + "'"); - } - String url = getUrl(); - return url; - } - - @Override - public void destroy() throws Exception { - getSource().shutdown(); - } - - /** - * Get's the URL (i.e. "http://localhost:123456") - * @return - */ - private String getUrl() { - MockWebServer mockWebServer = getSource(); - if (!this.started) { - intializeMockWebServer(mockWebServer); - } - String url = mockWebServer.url("").url().toExternalForm(); - return url.substring(0, url.length() - 1); - } - - private void intializeMockWebServer(MockWebServer mockWebServer) { - Dispatcher dispatcher = new Dispatcher() { - @Override - public MockResponse dispatch(RecordedRequest request) { - return doDispatch(request); - } - }; - - mockWebServer.setDispatcher(dispatcher); - try { - mockWebServer.start(); - this.started = true; - } catch (IOException e) { - throw new RuntimeException("Could not start " + mockWebServer, e); - } - } - - private MockResponse doDispatch(RecordedRequest request) { - if ("/.well-known/jwks.json".equals(request.getPath())) { - return JWKS_RESPONSE; - } - - if ("/introspect".equals(request.getPath())) { - return Optional.ofNullable(request.getHeader(HttpHeaders.AUTHORIZATION)) - .filter((authorization) -> isAuthorized(authorization, "client", "secret")) - .map((authorization) -> parseBody(request.getBody())) - .map((parameters) -> parameters.get("token")) - .map((token) -> { - if ("00ed5855-1869-47a0-b0c9-0f3ce520aee7".equals(token)) { - return NO_SCOPES_RESPONSE; - } else if ("b43d1500-c405-4dc9-b9c9-6cfd966c34c9".equals(token)) { - return MESSASGE_READ_SCOPE_RESPONSE; - } else { - return INACTIVE_RESPONSE; - } - }) - .orElse(BAD_REQUEST_RESPONSE); - } - - return NOT_FOUND_RESPONSE; - } - - private boolean isAuthorized(String authorization, String username, String password) { - String[] values = new String(Base64.getDecoder().decode(authorization.substring(6))).split(":"); - return username.equals(values[0]) && password.equals(values[1]); - } - - private Map parseBody(Buffer body) { - return Stream.of(body.readUtf8().split("&")) - .map((parameter) -> parameter.split("=")) - .collect(Collectors.toMap((parts) -> parts[0], (parts) -> parts[1])); - } - - private static MockResponse response(String body, int status) { - return new MockResponse() - .setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .setResponseCode(status) - .setBody(body); - } - -} diff --git a/samples/boot/oauth2resourceserver-multitenancy/src/main/java/org/springframework/boot/env/package-info.java b/samples/boot/oauth2resourceserver-multitenancy/src/main/java/org/springframework/boot/env/package-info.java deleted file mode 100644 index 4db05821da1..00000000000 --- a/samples/boot/oauth2resourceserver-multitenancy/src/main/java/org/springframework/boot/env/package-info.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * This provides integration of a {@link okhttp3.mockwebserver.MockWebServer} and the - * {@link org.springframework.core.env.Environment} - * @author Rob Winch - */ -package org.springframework.boot.env; diff --git a/samples/boot/oauth2resourceserver-multitenancy/src/main/java/sample/OAuth2ResourceServerApplication.java b/samples/boot/oauth2resourceserver-multitenancy/src/main/java/sample/OAuth2ResourceServerApplication.java deleted file mode 100644 index d5c70cc70da..00000000000 --- a/samples/boot/oauth2resourceserver-multitenancy/src/main/java/sample/OAuth2ResourceServerApplication.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - * @author Josh Cummings - */ -@SpringBootApplication -public class OAuth2ResourceServerApplication { - - public static void main(String[] args) { - SpringApplication.run(OAuth2ResourceServerApplication.class, args); - } -} diff --git a/samples/boot/oauth2resourceserver-multitenancy/src/main/java/sample/OAuth2ResourceServerController.java b/samples/boot/oauth2resourceserver-multitenancy/src/main/java/sample/OAuth2ResourceServerController.java deleted file mode 100644 index 1dce6e718a5..00000000000 --- a/samples/boot/oauth2resourceserver-multitenancy/src/main/java/sample/OAuth2ResourceServerController.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2002-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestHeader; -import org.springframework.web.bind.annotation.RestController; - -/** - * @author Josh Cummings - */ -@RestController -public class OAuth2ResourceServerController { - - @GetMapping("/") - public String index(@AuthenticationPrincipal OAuth2AuthenticatedPrincipal token, @RequestHeader("tenant") String tenant) { - String subject = token.getAttribute("sub"); - return String.format("Hello, %s for tenant %s!", subject, tenant); - } - - @GetMapping("/message") - public String message(@RequestHeader("tenant") String tenant) { - return String.format("secret message for tenant %s", tenant); - } -} diff --git a/samples/boot/oauth2resourceserver-multitenancy/src/main/java/sample/OAuth2ResourceServerSecurityConfiguration.java b/samples/boot/oauth2resourceserver-multitenancy/src/main/java/sample/OAuth2ResourceServerSecurityConfiguration.java deleted file mode 100644 index 75bf3865b92..00000000000 --- a/samples/boot/oauth2resourceserver-multitenancy/src/main/java/sample/OAuth2ResourceServerSecurityConfiguration.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2002-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import javax.servlet.http.HttpServletRequest; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.authentication.AuthenticationManagerResolver; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; - -/** - * @author Josh Cummings - */ -@EnableWebSecurity -public class OAuth2ResourceServerSecurityConfiguration extends WebSecurityConfigurerAdapter { - - @Autowired - AuthenticationManagerResolver authenticationManagerResolver; - - @Override - protected void configure(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeRequests((authz) -> authz - .antMatchers("/message/**").hasAuthority("SCOPE_message:read") - .anyRequest().authenticated() - ) - .oauth2ResourceServer((oauth2) -> oauth2 - .authenticationManagerResolver(this.authenticationManagerResolver) - ); - // @formatter:on - } -} diff --git a/samples/boot/oauth2resourceserver-multitenancy/src/main/java/sample/TenantAuthenticationManagerResolver.java b/samples/boot/oauth2resourceserver-multitenancy/src/main/java/sample/TenantAuthenticationManagerResolver.java deleted file mode 100644 index 939bfc0b8b5..00000000000 --- a/samples/boot/oauth2resourceserver-multitenancy/src/main/java/sample/TenantAuthenticationManagerResolver.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2002-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import javax.servlet.http.HttpServletRequest; - -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.AuthenticationManagerResolver; -import org.springframework.security.authentication.ProviderManager; -import org.springframework.security.oauth2.jwt.JwtDecoder; -import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider; -import org.springframework.security.oauth2.server.resource.authentication.JwtBearerTokenAuthenticationConverter; -import org.springframework.security.oauth2.server.resource.authentication.OpaqueTokenAuthenticationProvider; -import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector; -import org.springframework.stereotype.Component; - -@Component -public class TenantAuthenticationManagerResolver - implements AuthenticationManagerResolver { - - private AuthenticationManager jwt; - private AuthenticationManager opaqueToken; - - public TenantAuthenticationManagerResolver( - JwtDecoder jwtDecoder, OpaqueTokenIntrospector opaqueTokenIntrospector) { - - JwtAuthenticationProvider jwtAuthenticationProvider = new JwtAuthenticationProvider(jwtDecoder); - jwtAuthenticationProvider.setJwtAuthenticationConverter(new JwtBearerTokenAuthenticationConverter()); - this.jwt = new ProviderManager(jwtAuthenticationProvider); - this.opaqueToken = new ProviderManager(new OpaqueTokenAuthenticationProvider(opaqueTokenIntrospector)); - } - - @Override - public AuthenticationManager resolve(HttpServletRequest request) { - String tenant = request.getHeader("tenant"); - if ("one".equals(tenant)) { - return this.jwt; - } - if ("two".equals(tenant)) { - return this.opaqueToken; - } - throw new IllegalArgumentException("unknown tenant"); - } -} diff --git a/samples/boot/oauth2resourceserver-multitenancy/src/main/resources/META-INF/spring.factories b/samples/boot/oauth2resourceserver-multitenancy/src/main/resources/META-INF/spring.factories deleted file mode 100644 index 37b447c9702..00000000000 --- a/samples/boot/oauth2resourceserver-multitenancy/src/main/resources/META-INF/spring.factories +++ /dev/null @@ -1 +0,0 @@ -org.springframework.boot.env.EnvironmentPostProcessor=org.springframework.boot.env.MockWebServerEnvironmentPostProcessor diff --git a/samples/boot/oauth2resourceserver-multitenancy/src/main/resources/application.yml b/samples/boot/oauth2resourceserver-multitenancy/src/main/resources/application.yml deleted file mode 100644 index 6447ad0d940..00000000000 --- a/samples/boot/oauth2resourceserver-multitenancy/src/main/resources/application.yml +++ /dev/null @@ -1,10 +0,0 @@ -spring: - security: - oauth2: - resourceserver: - jwt: - jwk-set-uri: ${mockwebserver.url}/.well-known/jwks.json - opaquetoken: - introspection-uri: ${mockwebserver.url}/introspect - client-id: client - client-secret: secret diff --git a/samples/boot/oauth2resourceserver-opaque/README.adoc b/samples/boot/oauth2resourceserver-opaque/README.adoc deleted file mode 100644 index fc6add9a1f3..00000000000 --- a/samples/boot/oauth2resourceserver-opaque/README.adoc +++ /dev/null @@ -1,114 +0,0 @@ -= OAuth 2.0 Resource Server Sample - -This sample demonstrates integrating Resource Server with a mock Authorization Server, though it can be modified to integrate -with your favorite Authorization Server. - -With it, you can run the integration tests or run the application as a stand-alone service to explore how you can -secure your own service with OAuth 2.0 Opaque Bearer Tokens using Spring Security. - -== 1. Running the tests - -To run the tests, do: - -```bash -./gradlew integrationTest -``` - -Or import the project into your IDE and run `OAuth2ResourceServerApplicationTests` from there. - -=== What is it doing? - -By default, the tests are pointing at a mock Authorization Server instance. - -The tests are configured with a set of hard-coded tokens originally obtained from the mock Authorization Server, -and each makes a query to the Resource Server with their corresponding token. - -The Resource Server subsquently verifies with the Authorization Server and authorizes the request, returning the phrase - -```bash -Hello, subject! -``` - -where "subject" is the value of the `sub` field in the JWT returned by the Authorization Server. - -== 2. Running the app - -To run as a stand-alone application, do: - -```bash -./gradlew bootRun -``` - -Or import the project into your IDE and run `OAuth2ResourceServerApplication` from there. - -Once it is up, you can use the following token: - -```bash -export TOKEN=00ed5855-1869-47a0-b0c9-0f3ce520aee7 -``` - -And then make this request: - -```bash -curl -H "Authorization: Bearer $TOKEN" localhost:8080 -``` - -Which will respond with the phrase: - -```bash -Hello, subject! -``` - -where `subject` is the value of the `sub` field in the JWT returned by the Authorization Server. - -Or this: - -```bash -export TOKEN=b43d1500-c405-4dc9-b9c9-6cfd966c34c9 - -curl -H "Authorization: Bearer $TOKEN" localhost:8080/message -``` - -Will respond with: - -```bash -secret message -``` - -== 2. Testing against other Authorization Servers - -_In order to use this sample, your Authorization Server must support Opaque Tokens and the Introspection Endpoint. - -To change the sample to point at your Authorization Server, simply find this property in the `application.yml`: - -```yaml -spring: - security: - oauth2: - resourceserver: - opaque: - introspection-uri: ${mockwebserver.url}/introspect - introspection-client-id: client - introspection-client-secret: secret -``` - -And change the property to your Authorization Server's Introspection endpoint, including its client id and secret: - -```yaml -spring: - security: - oauth2: - resourceserver: - opaque: - introspection-uri: ${mockwebserver.url}/introspect -``` - -And then you can run the app the same as before: - -```bash -./gradlew bootRun -``` - -Make sure to obtain valid tokens from your Authorization Server in order to play with the sample Resource Server. -To use the `/` endpoint, any valid token from your Authorization Server will do. -To use the `/message` endpoint, the token should have the `message:read` scope. diff --git a/samples/boot/oauth2resourceserver-opaque/spring-security-samples-boot-oauth2resourceserver-opaque.gradle b/samples/boot/oauth2resourceserver-opaque/spring-security-samples-boot-oauth2resourceserver-opaque.gradle deleted file mode 100644 index 9074842b18a..00000000000 --- a/samples/boot/oauth2resourceserver-opaque/spring-security-samples-boot-oauth2resourceserver-opaque.gradle +++ /dev/null @@ -1,14 +0,0 @@ -apply plugin: 'io.spring.convention.spring-sample-boot' - -dependencies { - compile project(':spring-security-config') - compile project(':spring-security-oauth2-jose') - compile project(':spring-security-oauth2-resource-server') - - compile 'org.springframework.boot:spring-boot-starter-web' - compile 'com.nimbusds:oauth2-oidc-sdk' - compile 'com.squareup.okhttp3:mockwebserver' - - testCompile project(':spring-security-test') - testCompile 'org.springframework.boot:spring-boot-starter-test' -} diff --git a/samples/boot/oauth2resourceserver-opaque/src/integration-test/java/sample/OAuth2ResourceServerApplicationITests.java b/samples/boot/oauth2resourceserver-opaque/src/integration-test/java/sample/OAuth2ResourceServerApplicationITests.java deleted file mode 100644 index 10bd3fc7ec1..00000000000 --- a/samples/boot/oauth2resourceserver-opaque/src/integration-test/java/sample/OAuth2ResourceServerApplicationITests.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.HttpHeaders; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.request.RequestPostProcessor; - -import static org.hamcrest.Matchers.containsString; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Integration tests for {@link OAuth2ResourceServerApplication} - * - * @author Josh Cummings - */ -@RunWith(SpringRunner.class) -@SpringBootTest -@AutoConfigureMockMvc -@ActiveProfiles("test") -public class OAuth2ResourceServerApplicationITests { - - String noScopesToken = "00ed5855-1869-47a0-b0c9-0f3ce520aee7"; - String messageReadToken = "b43d1500-c405-4dc9-b9c9-6cfd966c34c9"; - - @Autowired - MockMvc mvc; - - @Test - public void performWhenValidBearerTokenThenAllows() - throws Exception { - - this.mvc.perform(get("/").with(bearerToken(this.noScopesToken))) - .andExpect(status().isOk()) - .andExpect(content().string(containsString("Hello, subject!"))); - } - - @Test - public void performWhenValidBearerTokenThenScopedRequestsAlsoWork() - throws Exception { - - this.mvc.perform(get("/message").with(bearerToken(this.messageReadToken))) - .andExpect(status().isOk()) - .andExpect(content().string(containsString("secret message"))); - } - - @Test - public void performWhenInsufficientlyScopedBearerTokenThenDeniesScopedMethodAccess() - throws Exception { - - this.mvc.perform(get("/message").with(bearerToken(this.noScopesToken))) - .andExpect(status().isForbidden()) - .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, - containsString("Bearer error=\"insufficient_scope\""))); - } - - private static class BearerTokenRequestPostProcessor implements RequestPostProcessor { - private String token; - - BearerTokenRequestPostProcessor(String token) { - this.token = token; - } - - @Override - public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { - request.addHeader("Authorization", "Bearer " + this.token); - return request; - } - } - - private static BearerTokenRequestPostProcessor bearerToken(String token) { - return new BearerTokenRequestPostProcessor(token); - } -} diff --git a/samples/boot/oauth2resourceserver-opaque/src/main/java/org/springframework/boot/env/MockWebServerEnvironmentPostProcessor.java b/samples/boot/oauth2resourceserver-opaque/src/main/java/org/springframework/boot/env/MockWebServerEnvironmentPostProcessor.java deleted file mode 100644 index f6f664891be..00000000000 --- a/samples/boot/oauth2resourceserver-opaque/src/main/java/org/springframework/boot/env/MockWebServerEnvironmentPostProcessor.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.env; - -import org.springframework.beans.factory.DisposableBean; -import org.springframework.boot.SpringApplication; -import org.springframework.core.env.ConfigurableEnvironment; - -/** - * @author Rob Winch - */ -public class MockWebServerEnvironmentPostProcessor - implements EnvironmentPostProcessor, DisposableBean { - - private final MockWebServerPropertySource propertySource = new MockWebServerPropertySource(); - - @Override - public void postProcessEnvironment(ConfigurableEnvironment environment, - SpringApplication application) { - environment.getPropertySources().addFirst(this.propertySource); - } - - @Override - public void destroy() throws Exception { - this.propertySource.destroy(); - } -} diff --git a/samples/boot/oauth2resourceserver-opaque/src/main/java/org/springframework/boot/env/MockWebServerPropertySource.java b/samples/boot/oauth2resourceserver-opaque/src/main/java/org/springframework/boot/env/MockWebServerPropertySource.java deleted file mode 100644 index 0bc6b575586..00000000000 --- a/samples/boot/oauth2resourceserver-opaque/src/main/java/org/springframework/boot/env/MockWebServerPropertySource.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.env; - -import java.io.IOException; -import java.util.Base64; -import java.util.Map; -import java.util.Optional; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import okhttp3.mockwebserver.Dispatcher; -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.MockWebServer; -import okhttp3.mockwebserver.RecordedRequest; -import okio.Buffer; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.beans.factory.DisposableBean; -import org.springframework.core.env.PropertySource; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; - -/** - * @author Rob Winch - */ -public class MockWebServerPropertySource extends PropertySource implements - DisposableBean { - - private static final MockResponse NO_SCOPES_RESPONSE = response( - "{\n" + - " \"active\": true,\n" + - " \"sub\": \"subject\"\n" + - " }", - 200 - ); - - private static final MockResponse MESSASGE_READ_SCOPE_RESPONSE = response( - "{\n" + - " \"active\": true,\n" + - " \"scope\" : \"message:read\"," + - " \"sub\": \"subject\"\n" + - " }", - 200 - ); - - private static final MockResponse INACTIVE_RESPONSE = response( - "{\n" + - " \"active\": false,\n" + - " }", - 200 - ); - - private static final MockResponse BAD_REQUEST_RESPONSE = response( - "{ \"message\" : \"This mock authorization server requires a username and password of " + - "client/secret and a POST body of token=${token}\" }", - 400 - ); - - private static final MockResponse NOT_FOUND_RESPONSE = response( - "{ \"message\" : \"This mock authorization server responds to just one request: POST /introspect.\" }", - 404 - ); - - /** - * Name of the random {@link PropertySource}. - */ - public static final String MOCK_WEB_SERVER_PROPERTY_SOURCE_NAME = "mockwebserver"; - - private static final String NAME = "mockwebserver.url"; - - private static final Log logger = LogFactory.getLog(MockWebServerPropertySource.class); - - private boolean started; - - public MockWebServerPropertySource() { - super(MOCK_WEB_SERVER_PROPERTY_SOURCE_NAME, new MockWebServer()); - } - - @Override - public Object getProperty(String name) { - if (!name.equals(NAME)) { - return null; - } - if (logger.isTraceEnabled()) { - logger.trace("Looking up the url for '" + name + "'"); - } - String url = getUrl(); - return url; - } - - @Override - public void destroy() throws Exception { - getSource().shutdown(); - } - - /** - * Get's the URL (e.g. "http://localhost:123456") - * @return - */ - private String getUrl() { - MockWebServer mockWebServer = getSource(); - if (!this.started) { - initializeMockWebServer(mockWebServer); - } - String url = mockWebServer.url("").url().toExternalForm(); - return url.substring(0, url.length() - 1); - } - - private void initializeMockWebServer(MockWebServer mockWebServer) { - Dispatcher dispatcher = new Dispatcher() { - @Override - public MockResponse dispatch(RecordedRequest request) { - return doDispatch(request); - } - }; - - mockWebServer.setDispatcher(dispatcher); - try { - mockWebServer.start(); - this.started = true; - } catch (IOException e) { - throw new RuntimeException("Could not start " + mockWebServer, e); - } - } - - private MockResponse doDispatch(RecordedRequest request) { - if ("/introspect".equals(request.getPath())) { - return Optional.ofNullable(request.getHeader(HttpHeaders.AUTHORIZATION)) - .filter((authorization) -> isAuthorized(authorization, "client", "secret")) - .map((authorization) -> parseBody(request.getBody())) - .map((parameters) -> parameters.get("token")) - .map((token) -> { - if ("00ed5855-1869-47a0-b0c9-0f3ce520aee7".equals(token)) { - return NO_SCOPES_RESPONSE; - } else if ("b43d1500-c405-4dc9-b9c9-6cfd966c34c9".equals(token)) { - return MESSASGE_READ_SCOPE_RESPONSE; - } else { - return INACTIVE_RESPONSE; - } - }) - .orElse(BAD_REQUEST_RESPONSE); - } - - return NOT_FOUND_RESPONSE; - } - - private boolean isAuthorized(String authorization, String username, String password) { - String[] values = new String(Base64.getDecoder().decode(authorization.substring(6))).split(":"); - return username.equals(values[0]) && password.equals(values[1]); - } - - private Map parseBody(Buffer body) { - return Stream.of(body.readUtf8().split("&")) - .map((parameter) -> parameter.split("=")) - .collect(Collectors.toMap((parts) -> parts[0], (parts) -> parts[1])); - } - - private static MockResponse response(String body, int status) { - return new MockResponse() - .setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .setResponseCode(status) - .setBody(body); - } - -} diff --git a/samples/boot/oauth2resourceserver-opaque/src/main/java/org/springframework/boot/env/package-info.java b/samples/boot/oauth2resourceserver-opaque/src/main/java/org/springframework/boot/env/package-info.java deleted file mode 100644 index 22b7245ebba..00000000000 --- a/samples/boot/oauth2resourceserver-opaque/src/main/java/org/springframework/boot/env/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * This provides integration of a {@link okhttp3.mockwebserver.MockWebServer} and the - * {@link org.springframework.core.env.Environment} - * - * @author Rob Winch - */ -package org.springframework.boot.env; diff --git a/samples/boot/oauth2resourceserver-opaque/src/main/java/sample/OAuth2ResourceServerApplication.java b/samples/boot/oauth2resourceserver-opaque/src/main/java/sample/OAuth2ResourceServerApplication.java deleted file mode 100644 index d5c70cc70da..00000000000 --- a/samples/boot/oauth2resourceserver-opaque/src/main/java/sample/OAuth2ResourceServerApplication.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - * @author Josh Cummings - */ -@SpringBootApplication -public class OAuth2ResourceServerApplication { - - public static void main(String[] args) { - SpringApplication.run(OAuth2ResourceServerApplication.class, args); - } -} diff --git a/samples/boot/oauth2resourceserver-opaque/src/main/java/sample/OAuth2ResourceServerController.java b/samples/boot/oauth2resourceserver-opaque/src/main/java/sample/OAuth2ResourceServerController.java deleted file mode 100644 index 857d29be66f..00000000000 --- a/samples/boot/oauth2resourceserver-opaque/src/main/java/sample/OAuth2ResourceServerController.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RestController; - -/** - * @author Josh Cummings - */ -@RestController -public class OAuth2ResourceServerController { - - @GetMapping("/") - public String index(@AuthenticationPrincipal(expression="subject") String subject) { - return String.format("Hello, %s!", subject); - } - - @GetMapping("/message") - public String message() { - return "secret message"; - } - - @PostMapping("/message") - public String createMessage(@RequestBody String message) { - return String.format("Message was created. Content: %s", message); - } -} diff --git a/samples/boot/oauth2resourceserver-opaque/src/main/java/sample/OAuth2ResourceServerSecurityConfiguration.java b/samples/boot/oauth2resourceserver-opaque/src/main/java/sample/OAuth2ResourceServerSecurityConfiguration.java deleted file mode 100644 index 6fc3444c731..00000000000 --- a/samples/boot/oauth2resourceserver-opaque/src/main/java/sample/OAuth2ResourceServerSecurityConfiguration.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.HttpMethod; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; - -/** - * @author Josh Cummings - */ -@EnableWebSecurity -public class OAuth2ResourceServerSecurityConfiguration extends WebSecurityConfigurerAdapter { - - @Value("${spring.security.oauth2.resourceserver.opaque.introspection-uri}") String introspectionUri; - @Value("${spring.security.oauth2.resourceserver.opaque.introspection-client-id}") String clientId; - @Value("${spring.security.oauth2.resourceserver.opaque.introspection-client-secret}") String clientSecret; - - @Override - protected void configure(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeRequests((authorizeRequests) -> - authorizeRequests - .antMatchers(HttpMethod.GET, "/message/**").hasAuthority("SCOPE_message:read") - .antMatchers(HttpMethod.POST, "/message/**").hasAuthority("SCOPE_message:write") - .anyRequest().authenticated() - ) - .oauth2ResourceServer((oauth2ResourceServer) -> - oauth2ResourceServer - .opaqueToken((opaqueToken) -> - opaqueToken - .introspectionUri(this.introspectionUri) - .introspectionClientCredentials(this.clientId, this.clientSecret) - ) - ); - // @formatter:on - } -} diff --git a/samples/boot/oauth2resourceserver-opaque/src/main/resources/META-INF/spring.factories b/samples/boot/oauth2resourceserver-opaque/src/main/resources/META-INF/spring.factories deleted file mode 100644 index 37b447c9702..00000000000 --- a/samples/boot/oauth2resourceserver-opaque/src/main/resources/META-INF/spring.factories +++ /dev/null @@ -1 +0,0 @@ -org.springframework.boot.env.EnvironmentPostProcessor=org.springframework.boot.env.MockWebServerEnvironmentPostProcessor diff --git a/samples/boot/oauth2resourceserver-opaque/src/main/resources/application.yml b/samples/boot/oauth2resourceserver-opaque/src/main/resources/application.yml deleted file mode 100644 index a7dcfead944..00000000000 --- a/samples/boot/oauth2resourceserver-opaque/src/main/resources/application.yml +++ /dev/null @@ -1,8 +0,0 @@ -spring: - security: - oauth2: - resourceserver: - opaque: - introspection-uri: ${mockwebserver.url}/introspect - introspection-client-id: client - introspection-client-secret: secret diff --git a/samples/boot/oauth2resourceserver-opaque/src/test/java/sample/OAuth2ResourceServerControllerTests.java b/samples/boot/oauth2resourceserver-opaque/src/test/java/sample/OAuth2ResourceServerControllerTests.java deleted file mode 100644 index ee3318f2469..00000000000 --- a/samples/boot/oauth2resourceserver-opaque/src/test/java/sample/OAuth2ResourceServerControllerTests.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; - -import static org.hamcrest.CoreMatchers.is; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.opaqueToken; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * @author Josh Cummings - * @since 5.3 - */ -@RunWith(SpringRunner.class) -@WebMvcTest(OAuth2ResourceServerController.class) -public class OAuth2ResourceServerControllerTests { - - @Autowired - MockMvc mvc; - - @Test - public void indexGreetsAuthenticatedUser() throws Exception { - this.mvc.perform(get("/").with(opaqueToken().attributes((a) -> a.put("sub", "ch4mpy")))) - .andExpect(content().string(is("Hello, ch4mpy!"))); - } - - @Test - public void messageCanBeReadWithScopeMessageReadAuthority() throws Exception { - this.mvc.perform(get("/message").with(opaqueToken().attributes((a) -> a.put("scope", "message:read")))) - .andExpect(content().string(is("secret message"))); - - this.mvc.perform(get("/message") - .with(jwt().authorities(new SimpleGrantedAuthority(("SCOPE_message:read"))))) - .andExpect(content().string(is("secret message"))); - } - - @Test - public void messageCanNotBeReadWithoutScopeMessageReadAuthority() throws Exception { - this.mvc.perform(get("/message").with(opaqueToken())) - .andExpect(status().isForbidden()); - } - - @Test - public void messageCanNotBeCreatedWithoutAnyScope() throws Exception { - this.mvc.perform(post("/message") - .content("Hello message") - .with(opaqueToken())) - .andExpect(status().isForbidden()); - } - - @Test - public void messageCanNotBeCreatedWithScopeMessageReadAuthority() throws Exception { - this.mvc.perform(post("/message") - .content("Hello message") - .with(opaqueToken().authorities(new SimpleGrantedAuthority("SCOPE_message:read")))) - .andExpect(status().isForbidden()); - } - - @Test - public void messageCanBeCreatedWithScopeMessageWriteAuthority() throws Exception { - this.mvc.perform(post("/message") - .content("Hello message") - .with(opaqueToken().authorities(new SimpleGrantedAuthority("SCOPE_message:write")))) - .andExpect(status().isOk()) - .andExpect(content().string(is("Message was created. Content: Hello message"))); - } -} diff --git a/samples/boot/oauth2resourceserver-static/README.adoc b/samples/boot/oauth2resourceserver-static/README.adoc deleted file mode 100644 index ecc530d7242..00000000000 --- a/samples/boot/oauth2resourceserver-static/README.adoc +++ /dev/null @@ -1,82 +0,0 @@ -= OAuth 2.0 Resource Server Sample - -This sample demonstrates integrating Resource Server with a pre-configured key. - -With it, you can run the integration tests or run the application as a stand-alone service to explore how you can -secure your own service with OAuth 2.0 Bearer Tokens using Spring Security. - -== 1. Running the tests - -To run the tests, do: - -```bash -./gradlew integrationTest -``` - -Or import the project into your IDE and run `OAuth2ResourceServerApplicationITests` from there. - -=== What is it doing? - -By default, the application is configured with an RSA public key that is available in the sample. - -The tests are configured with a set of hard-coded tokens that are signed with the corresponding RSA private key. -Each test makes a query to the Resource Server with their corresponding token. - -The Resource Server subsequently verifies the token against the public key and authorizes the request, returning the phrase - -```bash -Hello, subject! -``` - -where "subject" is the value of the `sub` field in the token. - -== 2. Running the app - -To run as a stand-alone application, do: - -```bash -./gradlew bootRun -``` - -Or import the project into your IDE and run `OAuth2ResourceServerApplication` from there. - -Once it is up, you can use the following token: - -```bash -export TOKEN=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJzdWJqZWN0IiwiaWF0IjoxNTE2MjM5MDIyfQ.eB2c9xtg5wcCZxZ-o-sH4Mx1JGkqAZwH4_WS0UcDbj_nen0NPBj6CqOEPhr_LZDagb4mM6HoAPJywWWG8b_Ylnn5r2gWDzib2mb0kxIuAjnvVBrpzusw4ItTVvP_srv2DrwcisKYiKqU5X_3ka7MSVvKtswdLY3RXeCJ_S2W9go -``` - -And then make this request: - -```bash -curl -H "Authorization: Bearer $TOKEN" localhost:8080 -``` - -Which will respond with the phrase: - -```bash -Hello, subject! -``` - -where `subject` is the value of the `sub` field in the token. - -Or this: - -```bash -export TOKEN=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJzdWJqZWN0IiwiaWF0IjoxNTE2MjM5MDIyLCJzY29wZSI6Im1lc3NhZ2U6cmVhZCJ9.bsRCpUEaiWnzX4OqNxTBqwUD4vxxtPp-CHKTw7XcrglrvZ2lvYXaiZZbCp-hcPhuzMEzEAFuH6s4GZZOWVIX-wT47GdTz9cfA-Z4QPjS2RxePKphFXgBI3jHEpQo94Qya2fJdV4LvgBmA1uM_RTnYY1UbmeYuHKnXrZoGyV8QQQ - -curl -H "Authorization: Bearer $TOKEN" localhost:8080/message -``` - -Will respond with: - -```bash -secret message -``` - -== 3. Testing with Other Tokens - -You can create your own tokens. Simply edit the public key in `OAuth2ResourceServerSecurityConfiguration` to match the private key you use. - -To use the `/` endpoint, any valid token will do. -To use the `/message` endpoint, the token should have the `message:read` scope. diff --git a/samples/boot/oauth2resourceserver-static/spring-security-samples-boot-oauth2resourceserver-static.gradle b/samples/boot/oauth2resourceserver-static/spring-security-samples-boot-oauth2resourceserver-static.gradle deleted file mode 100644 index faba0d5f463..00000000000 --- a/samples/boot/oauth2resourceserver-static/spring-security-samples-boot-oauth2resourceserver-static.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'io.spring.convention.spring-sample-boot' - -dependencies { - compile project(':spring-security-config') - compile project(':spring-security-oauth2-jose') - compile project(':spring-security-oauth2-resource-server') - - compile 'org.springframework.boot:spring-boot-starter-web' - - testCompile project(':spring-security-test') - testCompile 'org.springframework.boot:spring-boot-starter-test' -} diff --git a/samples/boot/oauth2resourceserver-static/src/integration-test/java/sample/OAuth2ResourceServerApplicationITests.java b/samples/boot/oauth2resourceserver-static/src/integration-test/java/sample/OAuth2ResourceServerApplicationITests.java deleted file mode 100644 index 851d3bd242a..00000000000 --- a/samples/boot/oauth2resourceserver-static/src/integration-test/java/sample/OAuth2ResourceServerApplicationITests.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2002-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.HttpHeaders; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.request.RequestPostProcessor; - -import static org.hamcrest.Matchers.containsString; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Integration tests for {@link OAuth2ResourceServerApplication} - * - * @author Josh Cummings - */ -@RunWith(SpringRunner.class) -@SpringBootTest -@AutoConfigureMockMvc -@ActiveProfiles("test") -public class OAuth2ResourceServerApplicationITests { - - String noScopesToken = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJzdWJqZWN0IiwiaWF0IjoxNTE2MjM5MDIyfQ.eB2c9xtg5wcCZxZ-o-sH4Mx1JGkqAZwH4_WS0UcDbj_nen0NPBj6CqOEPhr_LZDagb4mM6HoAPJywWWG8b_Ylnn5r2gWDzib2mb0kxIuAjnvVBrpzusw4ItTVvP_srv2DrwcisKYiKqU5X_3ka7MSVvKtswdLY3RXeCJ_S2W9go"; - String messageReadToken = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJzdWJqZWN0IiwiaWF0IjoxNTE2MjM5MDIyLCJzY29wZSI6Im1lc3NhZ2U6cmVhZCJ9.bsRCpUEaiWnzX4OqNxTBqwUD4vxxtPp-CHKTw7XcrglrvZ2lvYXaiZZbCp-hcPhuzMEzEAFuH6s4GZZOWVIX-wT47GdTz9cfA-Z4QPjS2RxePKphFXgBI3jHEpQo94Qya2fJdV4LvgBmA1uM_RTnYY1UbmeYuHKnXrZoGyV8QQQ"; - - @Autowired - MockMvc mvc; - - @Test - public void performWhenValidBearerTokenThenAllows() - throws Exception { - - this.mvc.perform(get("/").with(bearerToken(this.noScopesToken))) - .andExpect(status().isOk()) - .andExpect(content().string(containsString("Hello, subject!"))); - } - - @Test - public void performWhenValidBearerTokenThenScopedRequestsAlsoWork() - throws Exception { - - this.mvc.perform(get("/message").with(bearerToken(this.messageReadToken))) - .andExpect(status().isOk()) - .andExpect(content().string(containsString("secret message"))); - } - - @Test - public void performWhenInsufficientlyScopedBearerTokenThenDeniesScopedMethodAccess() - throws Exception { - - this.mvc.perform(get("/message").with(bearerToken(this.noScopesToken))) - .andExpect(status().isForbidden()) - .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, - containsString("Bearer error=\"insufficient_scope\""))); - } - - private static class BearerTokenRequestPostProcessor implements RequestPostProcessor { - private String token; - - BearerTokenRequestPostProcessor(String token) { - this.token = token; - } - - @Override - public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { - request.addHeader("Authorization", "Bearer " + this.token); - return request; - } - } - - private static BearerTokenRequestPostProcessor bearerToken(String token) { - return new BearerTokenRequestPostProcessor(token); - } -} diff --git a/samples/boot/oauth2resourceserver-static/src/main/java/sample/OAuth2ResourceServerApplication.java b/samples/boot/oauth2resourceserver-static/src/main/java/sample/OAuth2ResourceServerApplication.java deleted file mode 100644 index a0841c00f32..00000000000 --- a/samples/boot/oauth2resourceserver-static/src/main/java/sample/OAuth2ResourceServerApplication.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2002-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - * @author Josh Cummings - */ -@SpringBootApplication -public class OAuth2ResourceServerApplication { - - public static void main(String[] args) { - SpringApplication.run(OAuth2ResourceServerApplication.class, args); - } -} diff --git a/samples/boot/oauth2resourceserver-static/src/main/java/sample/OAuth2ResourceServerController.java b/samples/boot/oauth2resourceserver-static/src/main/java/sample/OAuth2ResourceServerController.java deleted file mode 100644 index f0bcdbe64f5..00000000000 --- a/samples/boot/oauth2resourceserver-static/src/main/java/sample/OAuth2ResourceServerController.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2002-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - * @author Josh Cummings - */ -@RestController -public class OAuth2ResourceServerController { - - @GetMapping("/") - public String index(@AuthenticationPrincipal Jwt jwt) { - return String.format("Hello, %s!", jwt.getSubject()); - } - - @GetMapping("/message") - public String message() { - return "secret message"; - } -} diff --git a/samples/boot/oauth2resourceserver-static/src/main/java/sample/OAuth2ResourceServerSecurityConfiguration.java b/samples/boot/oauth2resourceserver-static/src/main/java/sample/OAuth2ResourceServerSecurityConfiguration.java deleted file mode 100644 index a57bed2d51b..00000000000 --- a/samples/boot/oauth2resourceserver-static/src/main/java/sample/OAuth2ResourceServerSecurityConfiguration.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import java.security.interfaces.RSAPublicKey; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; -import org.springframework.security.oauth2.jwt.JwtDecoder; -import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; - -/** - * @author Josh Cummings - */ -@EnableWebSecurity -public class OAuth2ResourceServerSecurityConfiguration extends WebSecurityConfigurerAdapter { - - @Value("${spring.security.oauth2.resourceserver.jwt.key-value}") - RSAPublicKey key; - - @Override - protected void configure(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeRequests((authorizeRequests) -> - authorizeRequests - .antMatchers("/message/**").hasAuthority("SCOPE_message:read") - .anyRequest().authenticated() - ) - .oauth2ResourceServer((oauth2ResourceServer) -> - oauth2ResourceServer - .jwt((jwt) -> - jwt.decoder(jwtDecoder()) - ) - ); - // @formatter:on - } - - @Bean - JwtDecoder jwtDecoder() { - return NimbusJwtDecoder.withPublicKey(this.key).build(); - } -} diff --git a/samples/boot/oauth2resourceserver-static/src/main/resources/application.yml b/samples/boot/oauth2resourceserver-static/src/main/resources/application.yml deleted file mode 100644 index 123d342ead4..00000000000 --- a/samples/boot/oauth2resourceserver-static/src/main/resources/application.yml +++ /dev/null @@ -1,6 +0,0 @@ -spring: - security: - oauth2: - resourceserver: - jwt: - key-value: classpath:simple.pub diff --git a/samples/boot/oauth2resourceserver-static/src/main/resources/simple.pub b/samples/boot/oauth2resourceserver-static/src/main/resources/simple.pub deleted file mode 100644 index a25c08779e4..00000000000 --- a/samples/boot/oauth2resourceserver-static/src/main/resources/simple.pub +++ /dev/null @@ -1,7 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdlatRjRjogo3WojgGHFHYLugd -UWAY9iR3fy4arWNA1KoS8kVw33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQs -HUfQrSDv+MuSUMAe8jzKE4qW+jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5D -o2kQ+X5xK9cipRgEKwIDAQAB ------END PUBLIC KEY----- - diff --git a/samples/boot/oauth2resourceserver-webflux/README.adoc b/samples/boot/oauth2resourceserver-webflux/README.adoc deleted file mode 100644 index 75c3fd7b419..00000000000 --- a/samples/boot/oauth2resourceserver-webflux/README.adoc +++ /dev/null @@ -1,112 +0,0 @@ -= OAuth 2.0 Resource Server Sample - -This sample demonstrates integrating Resource Server with a mock Authorization Server, though it can be modified to integrate -with your favorite Authorization Server. - -With it, you can run the integration tests or run the application as a stand-alone service to explore how you can -secure your own service with OAuth 2.0 Bearer Tokens using Spring Security. - -== 1. Running the tests - -To run the tests, do: - -```bash -./gradlew integrationTest -``` - -Or import the project into your IDE and run `ServerOAuth2ResourceServerApplicationTests` from there. - -=== What is it doing? - -By default, the tests are pointing at a mock Authorization Server instance. - -The tests are configured with a set of hard-coded tokens originally obtained from the mock Authorization Server, -and each makes a query to the Resource Server with their corresponding token. - -The Resource Server subsquently verifies with the Authorization Server and authorizes the request, returning the phrase - -```bash -Hello, subject! -``` - -where "subject" is the value of the `sub` field in the JWT returned by the Authorization Server. - -== 2. Running the app - -To run as a stand-alone application, do: - -```bash -./gradlew bootRun -``` - -Or import the project into your IDE and run `ServerOAuth2ResourceServerApplication` from there. - -Once it is up, you can use the following token: - -```bash -export TOKEN=eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJzdWJqZWN0IiwiZXhwIjo0NjgzODA1MTI4fQ.ULEPdHG-MK5GlrTQMhgqcyug2brTIZaJIrahUeq9zaiwUSdW83fJ7W1IDd2Z3n4a25JY2uhEcoV95lMfccHR6y_2DLrNvfta22SumY9PEDF2pido54LXG6edIGgarnUbJdR4rpRe_5oRGVa8gDx8FnuZsNv6StSZHAzw5OsuevSTJ1UbJm4UfX3wiahFOQ2OI6G-r5TB2rQNdiPHuNyzG5yznUqRIZ7-GCoMqHMaC-1epKxiX8gYXRROuUYTtcMNa86wh7OVDmvwVmFioRcR58UWBRoO1XQexTtOQq_t8KYsrPZhb9gkyW8x2bAQF-d0J0EJY8JslaH6n4RBaZISww -``` - -And then make this request: - -```bash -curl -H "Authorization: Bearer $TOKEN" localhost:8080 -``` - -Which will respond with the phrase: - -```bash -Hello, subject! -``` - -where `subject` is the value of the `sub` field in the JWT returned by the Authorization Server. - -Or this: - -```bash -export TOKEN=eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJzdWJqZWN0Iiwic2NvcGUiOiJtZXNzYWdlOnJlYWQiLCJleHAiOjQ2ODM4MDUxNDF9.h-j6FKRFdnTdmAueTZCdep45e6DPwqM68ZQ8doIJ1exi9YxAlbWzOwId6Bd0L5YmCmp63gGQgsBUBLzwnZQ8kLUgUOBEC3UzSWGRqMskCY9_k9pX0iomX6IfF3N0PaYs0WPC4hO1s8wfZQ-6hKQ4KigFi13G9LMLdH58PRMK0pKEvs3gCbHJuEPw-K5ORlpdnleUTQIwINafU57cmK3KocTeknPAM_L716sCuSYGvDl6xUTXO7oPdrXhS_EhxLP6KxrpI1uD4Ea_5OWTh7S0Wx5LLDfU6wBG1DowN20d374zepOIEkR-Jnmr_QlR44vmRqS5ncrF-1R0EGcPX49U6A - -curl -H "Authorization: Bearer $TOKEN" localhost:8080/message -``` - -Will respond with: - -```bash -secret message -``` - -== 2. Testing against other Authorization Servers - -_In order to use this sample, your Authorization Server must support JWTs that either use the "scope" or "scp" attribute._ - -To change the sample to point at your Authorization Server, simply find this property in the `application.yml`: - -```yaml -spring: - security: - oauth2: - resourceserver: - jwt: - jwk-set-uri: ${mockwebserver.url}/.well-known/jwks.json -``` - -And change the property to your Authorization Server's JWK set endpoint: - -```yaml -spring: - security: - oauth2: - resourceserver: - jwt: - jwk-set-uri: https://dev-123456.oktapreview.com/oauth2/default/v1/keys -``` - -And then you can run the app the same as before: - -```bash -./gradlew bootRun -``` - -Make sure to obtain valid tokens from your Authorization Server in order to play with the sample Resource Server. -To use the `/` endpoint, any valid token from your Authorization Server will do. -To use the `/message` endpoint, the token should have the `message:read` scope. diff --git a/samples/boot/oauth2resourceserver-webflux/spring-security-samples-boot-oauth2resourceserver-webflux.gradle b/samples/boot/oauth2resourceserver-webflux/spring-security-samples-boot-oauth2resourceserver-webflux.gradle deleted file mode 100644 index 0d92aef8de6..00000000000 --- a/samples/boot/oauth2resourceserver-webflux/spring-security-samples-boot-oauth2resourceserver-webflux.gradle +++ /dev/null @@ -1,16 +0,0 @@ -apply plugin: 'io.spring.convention.spring-sample-boot' - -dependencies { - compile project(':spring-security-config') - compile project(':spring-security-oauth2-jose') - compile project(':spring-security-oauth2-client') - compile project(':spring-security-oauth2-resource-server') - - compile 'org.springframework.boot:spring-boot-starter-webflux' - compile 'com.squareup.okhttp3:mockwebserver' - - testCompile project(':spring-security-test') - testCompile 'org.springframework.boot:spring-boot-starter-test' - - testCompile 'com.squareup.okhttp3:mockwebserver' -} diff --git a/samples/boot/oauth2resourceserver-webflux/src/integration-test/java/sample/ServerOAuth2ResourceServerApplicationITests.java b/samples/boot/oauth2resourceserver-webflux/src/integration-test/java/sample/ServerOAuth2ResourceServerApplicationITests.java deleted file mode 100644 index 9cbdcf2b36b..00000000000 --- a/samples/boot/oauth2resourceserver-webflux/src/integration-test/java/sample/ServerOAuth2ResourceServerApplicationITests.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2002-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.HttpHeaders; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.web.reactive.server.WebTestClient; - -import java.util.function.Consumer; - -import static org.hamcrest.Matchers.containsString; - -/** - * @author Rob Winch - * @since 5.1 - */ -@SpringBootTest -@AutoConfigureWebTestClient -@RunWith(SpringJUnit4ClassRunner.class) -public class ServerOAuth2ResourceServerApplicationITests { - - Consumer noScopesToken = (http) -> http.setBearerAuth("eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJzdWJqZWN0IiwiZXhwIjo0NjgzODA1MTI4fQ.ULEPdHG-MK5GlrTQMhgqcyug2brTIZaJIrahUeq9zaiwUSdW83fJ7W1IDd2Z3n4a25JY2uhEcoV95lMfccHR6y_2DLrNvfta22SumY9PEDF2pido54LXG6edIGgarnUbJdR4rpRe_5oRGVa8gDx8FnuZsNv6StSZHAzw5OsuevSTJ1UbJm4UfX3wiahFOQ2OI6G-r5TB2rQNdiPHuNyzG5yznUqRIZ7-GCoMqHMaC-1epKxiX8gYXRROuUYTtcMNa86wh7OVDmvwVmFioRcR58UWBRoO1XQexTtOQq_t8KYsrPZhb9gkyW8x2bAQF-d0J0EJY8JslaH6n4RBaZISww"); - Consumer messageReadToken = (http) -> http.setBearerAuth("eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJzdWJqZWN0Iiwic2NvcGUiOiJtZXNzYWdlOnJlYWQiLCJleHAiOjQ2ODM4MDUxNDF9.h-j6FKRFdnTdmAueTZCdep45e6DPwqM68ZQ8doIJ1exi9YxAlbWzOwId6Bd0L5YmCmp63gGQgsBUBLzwnZQ8kLUgUOBEC3UzSWGRqMskCY9_k9pX0iomX6IfF3N0PaYs0WPC4hO1s8wfZQ-6hKQ4KigFi13G9LMLdH58PRMK0pKEvs3gCbHJuEPw-K5ORlpdnleUTQIwINafU57cmK3KocTeknPAM_L716sCuSYGvDl6xUTXO7oPdrXhS_EhxLP6KxrpI1uD4Ea_5OWTh7S0Wx5LLDfU6wBG1DowN20d374zepOIEkR-Jnmr_QlR44vmRqS5ncrF-1R0EGcPX49U6A"); - - @Autowired - private WebTestClient rest; - - - @Test - public void getWhenValidBearerTokenThenAllows() { - - this.rest.get().uri("/") - .headers(this.noScopesToken) - .exchange() - .expectStatus().isOk() - .expectBody(String.class).isEqualTo("Hello, subject!"); - } - - @Test - public void getWhenValidBearerTokenThenScopedRequestsAlsoWork() { - - this.rest.get().uri("/message") - .headers(this.messageReadToken) - .exchange() - .expectStatus().isOk() - .expectBody(String.class).isEqualTo("secret message"); - } - - @Test - public void getWhenInsufficientlyScopedBearerTokenThenDeniesScopedMethodAccess() { - - this.rest.get().uri("/message") - .headers(this.noScopesToken) - .exchange() - .expectStatus().isForbidden() - .expectHeader().value(HttpHeaders.WWW_AUTHENTICATE, containsString("Bearer error=\"insufficient_scope\"")); - } -} diff --git a/samples/boot/oauth2resourceserver-webflux/src/main/java/org/springframework/boot/env/MockWebServerEnvironmentPostProcessor.java b/samples/boot/oauth2resourceserver-webflux/src/main/java/org/springframework/boot/env/MockWebServerEnvironmentPostProcessor.java deleted file mode 100644 index 62f91d1a1fc..00000000000 --- a/samples/boot/oauth2resourceserver-webflux/src/main/java/org/springframework/boot/env/MockWebServerEnvironmentPostProcessor.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2002-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.env; - -import org.springframework.beans.factory.DisposableBean; -import org.springframework.boot.SpringApplication; -import org.springframework.core.env.ConfigurableEnvironment; - -/** - * @author Rob Winch - */ -public class MockWebServerEnvironmentPostProcessor - implements EnvironmentPostProcessor, DisposableBean { - - private final MockWebServerPropertySource propertySource = new MockWebServerPropertySource(); - - @Override - public void postProcessEnvironment(ConfigurableEnvironment environment, - SpringApplication application) { - environment.getPropertySources().addFirst(this.propertySource); - } - - @Override - public void destroy() throws Exception { - this.propertySource.destroy(); - } -} diff --git a/samples/boot/oauth2resourceserver-webflux/src/main/java/org/springframework/boot/env/MockWebServerPropertySource.java b/samples/boot/oauth2resourceserver-webflux/src/main/java/org/springframework/boot/env/MockWebServerPropertySource.java deleted file mode 100644 index 54dc2ca1430..00000000000 --- a/samples/boot/oauth2resourceserver-webflux/src/main/java/org/springframework/boot/env/MockWebServerPropertySource.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2002-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.env; - -import okhttp3.mockwebserver.Dispatcher; -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.MockWebServer; -import okhttp3.mockwebserver.RecordedRequest; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.beans.factory.DisposableBean; -import org.springframework.core.env.PropertySource; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; - -import java.io.IOException; - -/** - * @author Rob Winch - */ -public class MockWebServerPropertySource extends PropertySource implements - DisposableBean { - - private static final MockResponse JWKS_RESPONSE = response( - "{\"keys\":[{\"p\":\"2p-ViY7DE9ZrdWQb544m0Jp7Cv03YCSljqfim9pD4ALhObX0OrAznOiowTjwBky9JGffMwDBVSfJSD9TSU7aH2sbbfi0bZLMdekKAuimudXwUqPDxrrg0BCyvCYgLmKjbVT3zcdylWSog93CNTxGDPzauu-oc0XPNKCXnaDpNvE\",\"kty\":\"RSA\",\"q\":\"sP_QYavrpBvSJ86uoKVGj2AGl78CSsAtpf1ybSY5TwUlorXSdqapRbY69Y271b0aMLzlleUn9ZTBO1dlKV2_dw_lPADHVia8z3pxL-8sUhIXLsgj4acchMk4c9YX-sFh07xENnyZ-_TXm3llPLuL67HUfBC2eKe800TmCYVWc9U\",\"d\":\"bn1nFxCQT4KLTHqo8mo9HvHD0cRNRNdWcKNnnEQkCF6tKbt-ILRyQGP8O40axLd7CoNVG9c9p_-g4-2kwCtLJNv_STLtwfpCY7VN5o6-ZIpfTjiW6duoPrLWq64Hm_4LOBQTiZfUPcLhsuJRHbWqakj-kV_YbUyC2Ocf_dd8IAQcSrAU2SCcDebhDCWwRUFvaa9V5eq0851S9goaA-AJz-JXyePH6ZFr8JxmWkWxYZ5kdcMD-sm9ZbxE0CaEk32l4fE4hR-L8x2dDtjWA-ahKCZ091z-gV3HWtR2JOjvxoNRjxUo3UxaGiFJHWNIl0EYUJZu1Cb-5wIlEI7wPx5mwQ\",\"e\":\"AQAB\",\"use\":\"sig\",\"kid\":\"one\",\"qi\":\"qS0OK48M2CIAA6_4Wdw4EbCaAfcTLf5Oy9t5BOF_PFUKqoSpZ6JsT5H0a_4zkjt-oI969v78OTlvBKbmEyKO-KeytzHBAA5CsLmVcz0THrMSg6oXZqu66MPnvWoZN9FEN5TklPOvBFm8Bg1QZ3k-YMVaM--DLvhaYR95_mqaz50\",\"dp\":\"Too2NozLGD1XrXyhabZvy1E0EuaVFj0UHQPDLSpkZ_2g3BK6Art6T0xmE8RYtmqrKIEIdlI3IliAvyvAx_1D7zWTTRaj-xlZyqJFrnXWL7zj8UxT8PkB-r2E-ILZ3NAi1gxIWezlBTZ8M6NfObDFmbTc_3tJkN_raISo8z_ziIE\",\"dq\":\"U0yhSkY5yOsa9YcMoigGVBWSJLpNHtbg5NypjHrPv8OhWbkOSq7WvSstBkFk5AtyFvvfZLMLIkWWxxGzV0t6f1MoxBtttLrYYyCxwihiiGFhLbAdSuZ1wnxcqA9bC7UVECvrQmVTpsMs8UupfHKbQBpZ8OWAqrnuYNNtG4_4Bt0\",\"n\":\"lygtuZj0lJjqOqIWocF8Bb583QDdq-aaFg8PesOp2-EDda6GqCpL-_NZVOflNGX7XIgjsWHcPsQHsV9gWuOzSJ0iEuWvtQ6eGBP5M6m7pccLNZfwUse8Cb4Ngx3XiTlyuqM7pv0LPyppZusfEHVEdeelou7Dy9k0OQ_nJTI3b2E1WBoHC58CJ453lo4gcBm1efURN3LIVc1V9NQY_ESBKVdwqYyoJPEanURLVGRd6cQKn6YrCbbIRHjqAyqOE-z3KmgDJnPriljfR5XhSGyM9eqD9Xpy6zu_MAeMJJfSArp857zLPk-Wf5VP9STAcjyfdBIybMKnwBYr2qHMT675hQ\"}]}", - 200 - ); - - private static final MockResponse NOT_FOUND_RESPONSE = response( - "{ \"message\" : \"This mock authorization server responds to just one request: GET /.well-known/jwks.json.\" }", - 404 - ); - - /** - * Name of the random {@link PropertySource}. - */ - public static final String MOCK_WEB_SERVER_PROPERTY_SOURCE_NAME = "mockwebserver"; - - private static final String NAME = "mockwebserver.url"; - - private static final Log logger = LogFactory.getLog(MockWebServerPropertySource.class); - - private boolean started; - - public MockWebServerPropertySource() { - super(MOCK_WEB_SERVER_PROPERTY_SOURCE_NAME, new MockWebServer()); - } - - @Override - public Object getProperty(String name) { - if (!name.equals(NAME)) { - return null; - } - if (logger.isTraceEnabled()) { - logger.trace("Looking up the url for '" + name + "'"); - } - String url = getUrl(); - return url; - } - - @Override - public void destroy() throws Exception { - getSource().shutdown(); - } - - /** - * Get's the URL (i.e. "http://localhost:123456") - * @return - */ - private String getUrl() { - MockWebServer mockWebServer = getSource(); - if (!this.started) { - intializeMockWebServer(mockWebServer); - } - String url = mockWebServer.url("").url().toExternalForm(); - return url.substring(0, url.length() - 1); - } - - private void intializeMockWebServer(MockWebServer mockWebServer) { - Dispatcher dispatcher = new Dispatcher() { - @Override - public MockResponse dispatch(RecordedRequest request) { - if ("/.well-known/jwks.json".equals(request.getPath())) { - return JWKS_RESPONSE; - } - - return NOT_FOUND_RESPONSE; - } - }; - - mockWebServer.setDispatcher(dispatcher); - try { - mockWebServer.start(); - this.started = true; - } catch (IOException e) { - throw new RuntimeException("Could not start " + mockWebServer, e); - } - } - - private static MockResponse response(String body, int status) { - return new MockResponse() - .setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .setResponseCode(status) - .setBody(body); - } - -} diff --git a/samples/boot/oauth2resourceserver-webflux/src/main/java/org/springframework/boot/env/package-info.java b/samples/boot/oauth2resourceserver-webflux/src/main/java/org/springframework/boot/env/package-info.java deleted file mode 100644 index 02260378222..00000000000 --- a/samples/boot/oauth2resourceserver-webflux/src/main/java/org/springframework/boot/env/package-info.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2002-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * This provides integration of a {@link okhttp3.mockwebserver.MockWebServer} and the - * {@link org.springframework.core.env.Environment} - * @author Rob Winch - */ -package org.springframework.boot.env; diff --git a/samples/boot/oauth2resourceserver-webflux/src/main/java/sample/OAuth2ResourceServerController.java b/samples/boot/oauth2resourceserver-webflux/src/main/java/sample/OAuth2ResourceServerController.java deleted file mode 100644 index ea34b99e202..00000000000 --- a/samples/boot/oauth2resourceserver-webflux/src/main/java/sample/OAuth2ResourceServerController.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2002-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RestController; - -/** - * @author Josh Cummings - */ -@RestController -public class OAuth2ResourceServerController { - - @GetMapping("/") - public String index(@AuthenticationPrincipal Jwt jwt) { - return String.format("Hello, %s!", jwt.getSubject()); - } - - @GetMapping("/message") - public String message() { - return "secret message"; - } - - @PostMapping("/message") - public String createMessage(@RequestBody String message) { - return String.format("Message was created. Content: %s", message); - } -} diff --git a/samples/boot/oauth2resourceserver-webflux/src/main/java/sample/SecurityConfig.java b/samples/boot/oauth2resourceserver-webflux/src/main/java/sample/SecurityConfig.java deleted file mode 100644 index d4f6429b51a..00000000000 --- a/samples/boot/oauth2resourceserver-webflux/src/main/java/sample/SecurityConfig.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.springframework.context.annotation.Bean; -import org.springframework.http.HttpMethod; -import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; -import org.springframework.security.config.web.server.ServerHttpSecurity; -import org.springframework.security.web.server.SecurityWebFilterChain; - -import static org.springframework.security.config.Customizer.withDefaults; - -/** - * @author Rob Winch - * @since 5.1 - */ -@EnableWebFluxSecurity -public class SecurityConfig { - - @Bean - SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { - http - .authorizeExchange((exchanges) -> - exchanges - .pathMatchers(HttpMethod.GET, "/message/**").hasAuthority("SCOPE_message:read") - .pathMatchers(HttpMethod.POST, "/message/**").hasAuthority("SCOPE_message:write") - .anyExchange().authenticated() - ) - .oauth2ResourceServer((oauth2ResourceServer) -> - oauth2ResourceServer - .jwt(withDefaults()) - ); - return http.build(); - } -} diff --git a/samples/boot/oauth2resourceserver-webflux/src/main/java/sample/ServerOAuth2ResourceServerApplication.java b/samples/boot/oauth2resourceserver-webflux/src/main/java/sample/ServerOAuth2ResourceServerApplication.java deleted file mode 100644 index c3e3575e26d..00000000000 --- a/samples/boot/oauth2resourceserver-webflux/src/main/java/sample/ServerOAuth2ResourceServerApplication.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2002-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - * @author Rob Winch - */ -@SpringBootApplication -public class ServerOAuth2ResourceServerApplication { - - public static void main(String[] args) { - SpringApplication.run(ServerOAuth2ResourceServerApplication.class, args); - } -} diff --git a/samples/boot/oauth2resourceserver-webflux/src/main/resources/META-INF/spring.factories b/samples/boot/oauth2resourceserver-webflux/src/main/resources/META-INF/spring.factories deleted file mode 100644 index 37b447c9702..00000000000 --- a/samples/boot/oauth2resourceserver-webflux/src/main/resources/META-INF/spring.factories +++ /dev/null @@ -1 +0,0 @@ -org.springframework.boot.env.EnvironmentPostProcessor=org.springframework.boot.env.MockWebServerEnvironmentPostProcessor diff --git a/samples/boot/oauth2resourceserver-webflux/src/main/resources/application.yml b/samples/boot/oauth2resourceserver-webflux/src/main/resources/application.yml deleted file mode 100644 index 2a6d127d3fe..00000000000 --- a/samples/boot/oauth2resourceserver-webflux/src/main/resources/application.yml +++ /dev/null @@ -1,6 +0,0 @@ -spring: - security: - oauth2: - resourceserver: - jwt: - jwk-set-uri: ${mockwebserver.url}/.well-known/jwks.json diff --git a/samples/boot/oauth2resourceserver-webflux/src/test/java/sample/OAuth2ResourceServerControllerTests.java b/samples/boot/oauth2resourceserver-webflux/src/test/java/sample/OAuth2ResourceServerControllerTests.java deleted file mode 100644 index 8fc72574f19..00000000000 --- a/samples/boot/oauth2resourceserver-webflux/src/test/java/sample/OAuth2ResourceServerControllerTests.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.junit.Test; -import org.junit.runner.RunWith; -import reactor.core.publisher.Mono; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.annotation.Import; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.reactive.server.WebTestClient; - -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.when; -import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockJwt; - -/** - * @author Josh Cummings - */ -@RunWith(SpringRunner.class) -@WebFluxTest(OAuth2ResourceServerController.class) -@Import(SecurityConfig.class) -public class OAuth2ResourceServerControllerTests { - - @Autowired - WebTestClient rest; - - @MockBean - ReactiveJwtDecoder jwtDecoder; - - @Test - public void indexGreetsAuthenticatedUser() { - this.rest.mutateWith(mockJwt().jwt((jwt) -> jwt.subject("test-subject"))) - .get().uri("/").exchange() - .expectBody(String.class).isEqualTo("Hello, test-subject!"); - } - - @Test - public void messageCanBeReadWithScopeMessageReadAuthority() { - this.rest.mutateWith(mockJwt().jwt((jwt) -> jwt.claim("scope", "message:read"))) - .get().uri("/message").exchange() - .expectBody(String.class).isEqualTo("secret message"); - - this.rest.mutateWith(mockJwt().authorities(new SimpleGrantedAuthority("SCOPE_message:read"))) - .get().uri("/message").exchange() - .expectBody(String.class).isEqualTo("secret message"); - } - - @Test - public void messageCanNotBeReadWithoutScopeMessageReadAuthority() { - this.rest.mutateWith(mockJwt()) - .get().uri("/message").exchange() - .expectStatus().isForbidden(); - } - - @Test - public void messageCanNotBeCreatedWithoutAnyScope() { - Jwt jwt = jwt().claim("scope", "").build(); - when(this.jwtDecoder.decode(anyString())).thenReturn(Mono.just(jwt)); - this.rest.post().uri("/message") - .headers((headers) -> headers.setBearerAuth(jwt.getTokenValue())) - .syncBody("Hello message").exchange() - .expectStatus().isForbidden(); - } - - @Test - public void messageCanNotBeCreatedWithScopeMessageReadAuthority() { - Jwt jwt = jwt().claim("scope", "message:read").build(); - when(this.jwtDecoder.decode(anyString())).thenReturn(Mono.just(jwt)); - this.rest.post().uri("/message") - .headers((headers) -> headers.setBearerAuth(jwt.getTokenValue())) - .syncBody("Hello message").exchange() - .expectStatus().isForbidden(); - } - - @Test - public void messageCanBeCreatedWithScopeMessageWriteAuthority() { - Jwt jwt = jwt().claim("scope", "message:write").build(); - when(this.jwtDecoder.decode(anyString())).thenReturn(Mono.just(jwt)); - this.rest.post().uri("/message") - .headers((headers) -> headers.setBearerAuth(jwt.getTokenValue())) - .syncBody("Hello message").exchange() - .expectStatus().isOk() - .expectBody(String.class).isEqualTo("Message was created. Content: Hello message"); - } - - private Jwt.Builder jwt() { - return Jwt.withTokenValue("token").header("alg", "none"); - } -} diff --git a/samples/boot/oauth2resourceserver/README.adoc b/samples/boot/oauth2resourceserver/README.adoc deleted file mode 100644 index a82747004f8..00000000000 --- a/samples/boot/oauth2resourceserver/README.adoc +++ /dev/null @@ -1,126 +0,0 @@ -= OAuth 2.0 Resource Server Sample - -This sample demonstrates integrating Resource Server with a mock Authorization Server, though it can be modified to integrate -with your favorite Authorization Server. - -With it, you can run the integration tests or run the application as a stand-alone service to explore how you can -secure your own service with OAuth 2.0 Bearer Tokens using Spring Security. - -== 1. Running the tests - -To run the tests, do: - -```bash -./gradlew integrationTest -``` - -Or import the project into your IDE and run `OAuth2ResourceServerApplicationTests` from there. - -=== What is it doing? - -By default, the tests are pointing at a mock Authorization Server instance. - -The tests are configured with a set of hard-coded tokens originally obtained from the mock Authorization Server, -and each makes a query to the Resource Server with their corresponding token. - -The Resource Server subsquently verifies with the Authorization Server and authorizes the request, returning the phrase - -```bash -Hello, subject! -``` - -where "subject" is the value of the `sub` field in the JWT returned by the Authorization Server. - -== 2. Running the app - -To run as a stand-alone application, do: - -```bash -./gradlew bootRun -``` - -Or import the project into your IDE and run `OAuth2ResourceServerApplication` from there. - -Once it is up, you can use the following token: - -```bash -export TOKEN=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJzdWJqZWN0IiwiZXhwIjoyMTY0MjQ1ODgwLCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiMDFkOThlZWEtNjc0MC00OGRlLTk4ODAtYzM5ZjgyMGZiNzVlIiwiY2xpZW50X2lkIjoibm9zY29wZXMiLCJzY29wZSI6WyJub25lIl19.VOzgGLOUuQ_R2Ur1Ke41VaobddhKgUZgto7Y3AGxst7SuxLQ4LgWwdSSDRx-jRvypjsCgYPbjAYLhn9nCbfwtCitkymUKUNKdebvVAI0y8YvliWTL5S-GiJD9dN8SSsXUla9A4xB_9Mt5JAlRpQotQSCLojVSKQmjhMpQWmYAlKVjnlImoRwQFPI4w3Ijn4G4EMTKWUYRfrD0-WNT9ZYWBeza6QgV6sraP7ToRB3eQLy2p04cU40X-RHLeYCsMBfxsMMh89CJff-9tn7VDKi1hAGc_Lp9yS9ZaItJuFJTjf8S_vsjVB1nBhvdS_6IED_m_fOU52KiGSO2qL6shxHvg -``` - -And then make this request: - -```bash -curl -H "Authorization: Bearer $TOKEN" localhost:8080 -``` - -Which will respond with the phrase: - -```bash -Hello, subject! -``` - -where `subject` is the value of the `sub` field in the JWT returned by the Authorization Server. - -Or this to make a GET request to /messages: - -```bash -export TOKEN=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJzdWJqZWN0IiwiZXhwIjoyMTY0MjQ1NjQ4LCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiY2I1ZGMwNDYtMDkyMi00ZGJmLWE5MzAtOGI2M2FhZTYzZjk2IiwiY2xpZW50X2lkIjoicmVhZGVyIiwic2NvcGUiOlsibWVzc2FnZTpyZWFkIl19.Pre2ksnMiOGYWQtuIgHB0i3uTnNzD0SMFM34iyQJHK5RLlSjge08s9qHdx6uv5cZ4gZm_cB1D6f4-fLx76bCblK6mVcabbR74w_eCdSBXNXuqG-HNrOYYmmx5iJtdwx5fXPmF8TyVzsq_LvRm_LN4lWNYquT4y36Tox6ZD3feYxXvHQ3XyZn9mVKnlzv-GCwkBohCR3yPow5uVmr04qh_al52VIwKMrvJBr44igr4fTZmzwRAZmQw5rZeyep0b4nsCjadNcndHtMtYKNVuG5zbDLsB7GGvilcI9TDDnUXtwthB_3iq32DAd9x8wJmJ5K8gmX6GjZFtYzKk_zEboXoQ - -curl -H "Authorization: Bearer $TOKEN" localhost:8080/message -``` - -Will respond with: - -```bash -secret message -``` - -In order to make a POST request to /message, you can use the following request: - -```bash -export TOKEN=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJzdWJqZWN0IiwiZXhwIjoyMTY0MjQzOTA0LCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiZGI4ZjgwMzQtM2VlNy00NjBjLTk3NTEtMDJiMDA1OWI5NzA4IiwiY2xpZW50X2lkIjoid3JpdGVyIiwic2NvcGUiOlsibWVzc2FnZTp3cml0ZSJdfQ.USvpx_ntKXtchLmc93auJq0qSav6vLm4B7ItPzhrDH2xmogBP35eKeklwXK5GCb7ck1aKJV5SpguBlTCz0bZC1zAWKB6gyFIqedALPAran5QR-8WpGfl0wFqds7d8Jw3xmpUUBduRLab9hkeAhgoVgxevc8d6ITM7kRnHo5wT3VzvBU8DquedVXm5fbBnRPgG4_jOWJKbqYpqaR2z2TnZRWh3CqL82Orh1Ww1dJYF_fae1dTVV4tvN5iSndYcGxMoBaiw3kRRi6EyNxnXnt1pFtZqc1f6D9x4AHiri8_vpBp2vwG5OfQD5-rrleP_XlIB3rNQT7tu3fiqu4vUzQaEg - -curl -H "Authorization: Bearer $TOKEN" -d "my message" localhost:8080/message -``` - -Will respond this: - -```bash -Message was created. Content: my message -``` - -== 2. Testing against other Authorization Servers - -_In order to use this sample, your Authorization Server must support JWTs that either use the "scope" or "scp" attribute._ - -To change the sample to point at your Authorization Server, simply find this property in the `application.yml`: - -```yaml -spring: - security: - oauth2: - resourceserver: - jwt: - jwk-set-uri: ${mockwebserver.url}/.well-known/jwks.json -``` - -And change the property to your Authorization Server's JWK set endpoint: - -```yaml -spring: - security: - oauth2: - resourceserver: - jwt: - jwk-set-uri: https://dev-123456.oktapreview.com/oauth2/default/v1/keys -``` - -And then you can run the app the same as before: - -```bash -./gradlew bootRun -``` - -Make sure to obtain valid tokens from your Authorization Server in order to play with the sample Resource Server. -To use the `/` endpoint, any valid token from your Authorization Server will do. -To use the `/message` endpoint, the token should have the `message:read` scope. diff --git a/samples/boot/oauth2resourceserver/spring-security-samples-boot-oauth2resourceserver.gradle b/samples/boot/oauth2resourceserver/spring-security-samples-boot-oauth2resourceserver.gradle deleted file mode 100644 index 2135bb0af66..00000000000 --- a/samples/boot/oauth2resourceserver/spring-security-samples-boot-oauth2resourceserver.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'io.spring.convention.spring-sample-boot' - -dependencies { - compile project(':spring-security-config') - compile project(':spring-security-oauth2-jose') - compile project(':spring-security-oauth2-resource-server') - - compile 'org.springframework.boot:spring-boot-starter-web' - compile 'com.squareup.okhttp3:mockwebserver' - - testCompile project(':spring-security-test') - testCompile 'org.springframework.boot:spring-boot-starter-test' -} diff --git a/samples/boot/oauth2resourceserver/src/integration-test/java/sample/OAuth2ResourceServerApplicationITests.java b/samples/boot/oauth2resourceserver/src/integration-test/java/sample/OAuth2ResourceServerApplicationITests.java deleted file mode 100644 index bb7de586653..00000000000 --- a/samples/boot/oauth2resourceserver/src/integration-test/java/sample/OAuth2ResourceServerApplicationITests.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.HttpHeaders; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.request.RequestPostProcessor; - -import static org.hamcrest.Matchers.containsString; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Integration tests for {@link OAuth2ResourceServerApplication} - * - * @author Josh Cummings - */ -@RunWith(SpringRunner.class) -@SpringBootTest -@AutoConfigureMockMvc -@ActiveProfiles("test") -public class OAuth2ResourceServerApplicationITests { - - String noScopesToken = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJzdWJqZWN0IiwiZXhwIjoyMTY0MjQ1ODgwLCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiMDFkOThlZWEtNjc0MC00OGRlLTk4ODAtYzM5ZjgyMGZiNzVlIiwiY2xpZW50X2lkIjoibm9zY29wZXMiLCJzY29wZSI6WyJub25lIl19.VOzgGLOUuQ_R2Ur1Ke41VaobddhKgUZgto7Y3AGxst7SuxLQ4LgWwdSSDRx-jRvypjsCgYPbjAYLhn9nCbfwtCitkymUKUNKdebvVAI0y8YvliWTL5S-GiJD9dN8SSsXUla9A4xB_9Mt5JAlRpQotQSCLojVSKQmjhMpQWmYAlKVjnlImoRwQFPI4w3Ijn4G4EMTKWUYRfrD0-WNT9ZYWBeza6QgV6sraP7ToRB3eQLy2p04cU40X-RHLeYCsMBfxsMMh89CJff-9tn7VDKi1hAGc_Lp9yS9ZaItJuFJTjf8S_vsjVB1nBhvdS_6IED_m_fOU52KiGSO2qL6shxHvg"; - String messageReadToken = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJzdWJqZWN0IiwiZXhwIjoyMTY0MjQ1NjQ4LCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiY2I1ZGMwNDYtMDkyMi00ZGJmLWE5MzAtOGI2M2FhZTYzZjk2IiwiY2xpZW50X2lkIjoicmVhZGVyIiwic2NvcGUiOlsibWVzc2FnZTpyZWFkIl19.Pre2ksnMiOGYWQtuIgHB0i3uTnNzD0SMFM34iyQJHK5RLlSjge08s9qHdx6uv5cZ4gZm_cB1D6f4-fLx76bCblK6mVcabbR74w_eCdSBXNXuqG-HNrOYYmmx5iJtdwx5fXPmF8TyVzsq_LvRm_LN4lWNYquT4y36Tox6ZD3feYxXvHQ3XyZn9mVKnlzv-GCwkBohCR3yPow5uVmr04qh_al52VIwKMrvJBr44igr4fTZmzwRAZmQw5rZeyep0b4nsCjadNcndHtMtYKNVuG5zbDLsB7GGvilcI9TDDnUXtwthB_3iq32DAd9x8wJmJ5K8gmX6GjZFtYzKk_zEboXoQ"; - String messageWriteToken = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJzdWJqZWN0IiwiZXhwIjoyMTY0MjQzOTA0LCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiZGI4ZjgwMzQtM2VlNy00NjBjLTk3NTEtMDJiMDA1OWI5NzA4IiwiY2xpZW50X2lkIjoid3JpdGVyIiwic2NvcGUiOlsibWVzc2FnZTp3cml0ZSJdfQ.USvpx_ntKXtchLmc93auJq0qSav6vLm4B7ItPzhrDH2xmogBP35eKeklwXK5GCb7ck1aKJV5SpguBlTCz0bZC1zAWKB6gyFIqedALPAran5QR-8WpGfl0wFqds7d8Jw3xmpUUBduRLab9hkeAhgoVgxevc8d6ITM7kRnHo5wT3VzvBU8DquedVXm5fbBnRPgG4_jOWJKbqYpqaR2z2TnZRWh3CqL82Orh1Ww1dJYF_fae1dTVV4tvN5iSndYcGxMoBaiw3kRRi6EyNxnXnt1pFtZqc1f6D9x4AHiri8_vpBp2vwG5OfQD5-rrleP_XlIB3rNQT7tu3fiqu4vUzQaEg"; - - @Autowired - MockMvc mvc; - - @Test - public void performWhenValidBearerTokenThenAllows() - throws Exception { - - this.mvc.perform(get("/").with(bearerToken(this.noScopesToken))) - .andExpect(status().isOk()) - .andExpect(content().string(containsString("Hello, subject!"))); - } - - @Test - public void performWhenValidBearerTokenThenScopedRequestsAlsoWork() - throws Exception { - - this.mvc.perform(get("/message").with(bearerToken(this.messageReadToken))) - .andExpect(status().isOk()) - .andExpect(content().string(containsString("secret message"))); - } - - @Test - public void performWhenInsufficientlyScopedBearerTokenThenDeniesScopedMethodAccess() - throws Exception { - - this.mvc.perform(get("/message").with(bearerToken(this.noScopesToken))) - .andExpect(status().isForbidden()) - .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, - containsString("Bearer error=\"insufficient_scope\""))); - } - - @Test - public void performPostWhenValidBearerTokenThenScopedRequestsAlsoWork() - throws Exception { - - this.mvc.perform(post("/message").content("example message") - .with(bearerToken(this.messageWriteToken))) - .andExpect(status().isOk()) - .andExpect(content().string(containsString("Message was created"))); - } - - @Test - public void performPostWhenInsufficientlyScopedBearerTokenThenDeniesScopedMethodAccess() - throws Exception { - - this.mvc.perform(post("/message").content("Example message") - .with(bearerToken(this.messageReadToken))) - .andExpect(status().isForbidden()) - .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, - containsString("Bearer error=\"insufficient_scope\""))); - } - - private static class BearerTokenRequestPostProcessor implements RequestPostProcessor { - private String token; - - BearerTokenRequestPostProcessor(String token) { - this.token = token; - } - - @Override - public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { - request.addHeader("Authorization", "Bearer " + this.token); - return request; - } - } - - private static BearerTokenRequestPostProcessor bearerToken(String token) { - return new BearerTokenRequestPostProcessor(token); - } -} diff --git a/samples/boot/oauth2resourceserver/src/main/java/org/springframework/boot/env/MockWebServerEnvironmentPostProcessor.java b/samples/boot/oauth2resourceserver/src/main/java/org/springframework/boot/env/MockWebServerEnvironmentPostProcessor.java deleted file mode 100644 index 62f91d1a1fc..00000000000 --- a/samples/boot/oauth2resourceserver/src/main/java/org/springframework/boot/env/MockWebServerEnvironmentPostProcessor.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2002-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.env; - -import org.springframework.beans.factory.DisposableBean; -import org.springframework.boot.SpringApplication; -import org.springframework.core.env.ConfigurableEnvironment; - -/** - * @author Rob Winch - */ -public class MockWebServerEnvironmentPostProcessor - implements EnvironmentPostProcessor, DisposableBean { - - private final MockWebServerPropertySource propertySource = new MockWebServerPropertySource(); - - @Override - public void postProcessEnvironment(ConfigurableEnvironment environment, - SpringApplication application) { - environment.getPropertySources().addFirst(this.propertySource); - } - - @Override - public void destroy() throws Exception { - this.propertySource.destroy(); - } -} diff --git a/samples/boot/oauth2resourceserver/src/main/java/org/springframework/boot/env/MockWebServerPropertySource.java b/samples/boot/oauth2resourceserver/src/main/java/org/springframework/boot/env/MockWebServerPropertySource.java deleted file mode 100644 index b90ad29b91c..00000000000 --- a/samples/boot/oauth2resourceserver/src/main/java/org/springframework/boot/env/MockWebServerPropertySource.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.env; - -import okhttp3.mockwebserver.Dispatcher; -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.MockWebServer; -import okhttp3.mockwebserver.RecordedRequest; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.beans.factory.DisposableBean; -import org.springframework.core.env.PropertySource; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; - -import java.io.IOException; - -/** - * @author Rob Winch - */ -public class MockWebServerPropertySource extends PropertySource implements - DisposableBean { - - private static final MockResponse JWKS_RESPONSE = response( - "{ \"keys\": [ { \"kty\": \"RSA\", \"e\": \"AQAB\", \"n\": \"jvBtqsGCOmnYzwe_-HvgOqlKk6HPiLEzS6uCCcnVkFXrhnkPMZ-uQXTR0u-7ZklF0XC7-AMW8FQDOJS1T7IyJpCyeU4lS8RIf_Z8RX51gPGnQWkRvNw61RfiSuSA45LR5NrFTAAGoXUca_lZnbqnl0td-6hBDVeHYkkpAsSck1NPhlcsn-Pvc2Vleui_Iy1U2mzZCM1Vx6Dy7x9IeP_rTNtDhULDMFbB_JYs-Dg6Zd5Ounb3mP57tBGhLYN7zJkN1AAaBYkElsc4GUsGsUWKqgteQSXZorpf6HdSJsQMZBDd7xG8zDDJ28hGjJSgWBndRGSzQEYU09Xbtzk-8khPuw\" } ] }", - 200 - ); - - private static final MockResponse NOT_FOUND_RESPONSE = response( - "{ \"message\" : \"This mock authorization server responds to just one request: GET /.well-known/jwks.json.\" }", - 404 - ); - - /** - * Name of the random {@link PropertySource}. - */ - public static final String MOCK_WEB_SERVER_PROPERTY_SOURCE_NAME = "mockwebserver"; - - private static final String NAME = "mockwebserver.url"; - - private static final Log logger = LogFactory.getLog(MockWebServerPropertySource.class); - - private boolean started; - - public MockWebServerPropertySource() { - super(MOCK_WEB_SERVER_PROPERTY_SOURCE_NAME, new MockWebServer()); - } - - @Override - public Object getProperty(String name) { - if (!name.equals(NAME)) { - return null; - } - if (logger.isTraceEnabled()) { - logger.trace("Looking up the url for '" + name + "'"); - } - String url = getUrl(); - return url; - } - - @Override - public void destroy() throws Exception { - getSource().shutdown(); - } - - /** - * Get's the URL (i.e. "http://localhost:123456") - * @return - */ - private String getUrl() { - MockWebServer mockWebServer = getSource(); - if (!this.started) { - intializeMockWebServer(mockWebServer); - } - String url = mockWebServer.url("").url().toExternalForm(); - return url.substring(0, url.length() - 1); - } - - private void intializeMockWebServer(MockWebServer mockWebServer) { - Dispatcher dispatcher = new Dispatcher() { - @Override - public MockResponse dispatch(RecordedRequest request) { - if ("/.well-known/jwks.json".equals(request.getPath())) { - return JWKS_RESPONSE; - } - - return NOT_FOUND_RESPONSE; - } - }; - - mockWebServer.setDispatcher(dispatcher); - try { - mockWebServer.start(); - this.started = true; - } catch (IOException e) { - throw new RuntimeException("Could not start " + mockWebServer, e); - } - } - - private static MockResponse response(String body, int status) { - return new MockResponse() - .setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .setResponseCode(status) - .setBody(body); - } - -} diff --git a/samples/boot/oauth2resourceserver/src/main/java/org/springframework/boot/env/package-info.java b/samples/boot/oauth2resourceserver/src/main/java/org/springframework/boot/env/package-info.java deleted file mode 100644 index 02260378222..00000000000 --- a/samples/boot/oauth2resourceserver/src/main/java/org/springframework/boot/env/package-info.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2002-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * This provides integration of a {@link okhttp3.mockwebserver.MockWebServer} and the - * {@link org.springframework.core.env.Environment} - * @author Rob Winch - */ -package org.springframework.boot.env; diff --git a/samples/boot/oauth2resourceserver/src/main/java/sample/OAuth2ResourceServerApplication.java b/samples/boot/oauth2resourceserver/src/main/java/sample/OAuth2ResourceServerApplication.java deleted file mode 100644 index a0841c00f32..00000000000 --- a/samples/boot/oauth2resourceserver/src/main/java/sample/OAuth2ResourceServerApplication.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2002-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - * @author Josh Cummings - */ -@SpringBootApplication -public class OAuth2ResourceServerApplication { - - public static void main(String[] args) { - SpringApplication.run(OAuth2ResourceServerApplication.class, args); - } -} diff --git a/samples/boot/oauth2resourceserver/src/main/java/sample/OAuth2ResourceServerController.java b/samples/boot/oauth2resourceserver/src/main/java/sample/OAuth2ResourceServerController.java deleted file mode 100644 index c761078dd8e..00000000000 --- a/samples/boot/oauth2resourceserver/src/main/java/sample/OAuth2ResourceServerController.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RestController; - -/** - * @author Josh Cummings - */ -@RestController -public class OAuth2ResourceServerController { - - @GetMapping("/") - public String index(@AuthenticationPrincipal Jwt jwt) { - return String.format("Hello, %s!", jwt.getSubject()); - } - - @GetMapping("/message") - public String message() { - return "secret message"; - } - - @PostMapping("/message") - public String createMessage(@RequestBody String message) { - return String.format("Message was created. Content: %s", message); - } -} diff --git a/samples/boot/oauth2resourceserver/src/main/java/sample/OAuth2ResourceServerSecurityConfiguration.java b/samples/boot/oauth2resourceserver/src/main/java/sample/OAuth2ResourceServerSecurityConfiguration.java deleted file mode 100644 index d7e157cfc65..00000000000 --- a/samples/boot/oauth2resourceserver/src/main/java/sample/OAuth2ResourceServerSecurityConfiguration.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.http.HttpMethod; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; -import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer; -import org.springframework.security.oauth2.jwt.JwtDecoder; -import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; - -/** - * @author Josh Cummings - */ -@EnableWebSecurity -public class OAuth2ResourceServerSecurityConfiguration extends WebSecurityConfigurerAdapter { - - @Value("${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}") String jwkSetUri; - - @Override - protected void configure(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeRequests((authorizeRequests) -> - authorizeRequests - .antMatchers(HttpMethod.GET, "/message/**").hasAuthority("SCOPE_message:read") - .antMatchers(HttpMethod.POST, "/message/**").hasAuthority("SCOPE_message:write") - .anyRequest().authenticated() - ) - .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt); - // @formatter:on - } - - @Bean - JwtDecoder jwtDecoder() { - return NimbusJwtDecoder.withJwkSetUri(this.jwkSetUri).build(); - } -} diff --git a/samples/boot/oauth2resourceserver/src/main/resources/META-INF/spring.factories b/samples/boot/oauth2resourceserver/src/main/resources/META-INF/spring.factories deleted file mode 100644 index 37b447c9702..00000000000 --- a/samples/boot/oauth2resourceserver/src/main/resources/META-INF/spring.factories +++ /dev/null @@ -1 +0,0 @@ -org.springframework.boot.env.EnvironmentPostProcessor=org.springframework.boot.env.MockWebServerEnvironmentPostProcessor diff --git a/samples/boot/oauth2resourceserver/src/main/resources/application.yml b/samples/boot/oauth2resourceserver/src/main/resources/application.yml deleted file mode 100644 index 2a6d127d3fe..00000000000 --- a/samples/boot/oauth2resourceserver/src/main/resources/application.yml +++ /dev/null @@ -1,6 +0,0 @@ -spring: - security: - oauth2: - resourceserver: - jwt: - jwk-set-uri: ${mockwebserver.url}/.well-known/jwks.json diff --git a/samples/boot/oauth2resourceserver/src/test/java/sample/OAuth2ResourceServerControllerTests.java b/samples/boot/oauth2resourceserver/src/test/java/sample/OAuth2ResourceServerControllerTests.java deleted file mode 100644 index 565212b0738..00000000000 --- a/samples/boot/oauth2resourceserver/src/test/java/sample/OAuth2ResourceServerControllerTests.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; - -import static org.hamcrest.CoreMatchers.is; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * - * @author Jérôme Wacongne <ch4mp@c4-soft.com> - * @author Josh Cummings - * @since 5.2.0 - * - */ -@RunWith(SpringRunner.class) -@WebMvcTest(OAuth2ResourceServerController.class) -public class OAuth2ResourceServerControllerTests { - - @Autowired - MockMvc mockMvc; - - @Test - public void indexGreetsAuthenticatedUser() throws Exception { - mockMvc.perform(get("/").with(jwt().jwt((jwt) -> jwt.subject("ch4mpy")))) - .andExpect(content().string(is("Hello, ch4mpy!"))); - } - - @Test - public void messageCanBeReadWithScopeMessageReadAuthority() throws Exception { - mockMvc.perform(get("/message").with(jwt().jwt((jwt) -> jwt.claim("scope", "message:read")))) - .andExpect(content().string(is("secret message"))); - - mockMvc.perform(get("/message") - .with(jwt().authorities(new SimpleGrantedAuthority(("SCOPE_message:read"))))) - .andExpect(content().string(is("secret message"))); - } - - @Test - public void messageCanNotBeReadWithoutScopeMessageReadAuthority() throws Exception { - mockMvc.perform(get("/message").with(jwt())) - .andExpect(status().isForbidden()); - } - - @Test - public void messageCanNotBeCreatedWithoutAnyScope() throws Exception { - mockMvc.perform(post("/message") - .content("Hello message") - .with(jwt())) - .andExpect(status().isForbidden()); - } - - @Test - public void messageCanNotBeCreatedWithScopeMessageReadAuthority() throws Exception { - mockMvc.perform(post("/message") - .content("Hello message") - .with(jwt().jwt((jwt) -> jwt.claim("scope", "message:read")))) - .andExpect(status().isForbidden()); - } - - @Test - public void messageCanBeCreatedWithScopeMessageWriteAuthority() - throws Exception { - mockMvc.perform(post("/message") - .content("Hello message") - .with(jwt().jwt((jwt) -> jwt.claim("scope", "message:write")))) - .andExpect(status().isOk()) - .andExpect(content().string(is("Message was created. Content: Hello message"))); - } -} diff --git a/samples/boot/oauth2webclient-webflux/README.adoc b/samples/boot/oauth2webclient-webflux/README.adoc deleted file mode 100644 index ea984aa684a..00000000000 --- a/samples/boot/oauth2webclient-webflux/README.adoc +++ /dev/null @@ -1,63 +0,0 @@ -= OAuth 2.0 WebClient (WebFlux) Sample - -== GitHub Repositories - -This guide provides instructions on setting up the sample application, which leverages WebClient OAuth2 integration to display a list of public GitHub repositories that are accessible to the authenticated user. - -This includes repositories owned by the authenticated user, repositories where the authenticated user is a collaborator, and repositories that the authenticated user has access to through an organization membership. - -The following sections provide detailed steps for setting up the sample and covers the following topics: - -* <> -* <> -* <> - -[[github-register-application]] -=== Register OAuth application - -To use GitHub's OAuth 2.0 authorization system, you must https://github.com/settings/applications/new[Register a new OAuth application]. - -When registering the OAuth application, ensure the *Authorization callback URL* is set to `http://localhost:8080/login/oauth2/code/client-id`. - -The Authorization callback URL (redirect URI) is the path in the application that the end-user's user-agent is redirected back to after they have authenticated with GitHub and have granted access to the OAuth application on the _Authorize application_ page. - -[[github-application-config]] -=== Configure application.yml - -Now that you have a new OAuth application with GitHub, you need to configure the sample to use the OAuth application for the _authorization code grant flow_. -To do so: - -. Go to `application.yml` and set the following configuration: -+ -[source,yaml] ----- -spring: - security: - oauth2: - client: - registration: <1> - client-id: <2> - client-id: replace-with-client-id - client-secret: replace-with-client-secret - provider: github - scope: read:user,public_repo ----- -+ -.OAuth Client properties -==== -<1> `spring.security.oauth2.client.registration` is the base property prefix for OAuth Client properties. -<2> Following the base property prefix is the ID for the `ClientRegistration`, which is github. -==== - -. Replace the values in the `client-id` and `client-secret` property with the OAuth 2.0 credentials you created earlier. - -[[github-boot-application]] -=== Boot up the application - -Launch the Spring Boot 2.0 sample and go to `http://localhost:8080`. -You are then redirected to the default _auto-generated_ form login page. -Log in using *'user'* (username) and *'password'* (password) or click the link to authenticate with GitHub and then you'll be redirected to GitHub for authentication. - -After authenticating with your GitHub credentials, the next page presented to you is "Authorize application". -This page will ask you to *Authorize* the application you created in the previous step. -Click _Authorize application_ to allow the OAuth application to access and display your public repository information. diff --git a/samples/boot/oauth2webclient-webflux/spring-security-samples-boot-oauth2webclient-webflux.gradle b/samples/boot/oauth2webclient-webflux/spring-security-samples-boot-oauth2webclient-webflux.gradle deleted file mode 100644 index 286f80a5818..00000000000 --- a/samples/boot/oauth2webclient-webflux/spring-security-samples-boot-oauth2webclient-webflux.gradle +++ /dev/null @@ -1,15 +0,0 @@ -apply plugin: 'io.spring.convention.spring-sample-boot' - -dependencies { - compile project(':spring-security-config') - compile project(':spring-security-oauth2-client') - compile project(':spring-security-oauth2-jose') - compile 'org.springframework.boot:spring-boot-starter-thymeleaf' - compile 'org.springframework.boot:spring-boot-starter-webflux' - compile 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5' - compile 'io.projectreactor.netty:reactor-netty' - - testCompile project(':spring-security-test') - testCompile 'org.springframework.boot:spring-boot-starter-test' - testCompile 'com.squareup.okhttp3:mockwebserver' -} diff --git a/samples/boot/oauth2webclient-webflux/src/main/java/sample/OAuth2WebClientWebFluxApplication.java b/samples/boot/oauth2webclient-webflux/src/main/java/sample/OAuth2WebClientWebFluxApplication.java deleted file mode 100644 index cdd6416f937..00000000000 --- a/samples/boot/oauth2webclient-webflux/src/main/java/sample/OAuth2WebClientWebFluxApplication.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2002-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - * @author Joe Grandja - */ -@SpringBootApplication -public class OAuth2WebClientWebFluxApplication { - - public static void main(String[] args) { - SpringApplication.run(OAuth2WebClientWebFluxApplication.class, args); - } -} diff --git a/samples/boot/oauth2webclient-webflux/src/main/java/sample/config/SecurityConfig.java b/samples/boot/oauth2webclient-webflux/src/main/java/sample/config/SecurityConfig.java deleted file mode 100644 index 33fb8b78676..00000000000 --- a/samples/boot/oauth2webclient-webflux/src/main/java/sample/config/SecurityConfig.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample.config; - -import org.springframework.context.annotation.Bean; -import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; -import org.springframework.security.config.web.server.ServerHttpSecurity; -import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.web.server.SecurityWebFilterChain; - -import static org.springframework.security.config.Customizer.withDefaults; - -/** - * @author Rob Winch - */ -@EnableWebFluxSecurity -public class SecurityConfig { - - @Bean - SecurityWebFilterChain configure(ServerHttpSecurity http) { - http - .authorizeExchange((exchanges) -> - exchanges - .pathMatchers("/", "/public/**").permitAll() - .anyExchange().authenticated() - ) - .oauth2Login(withDefaults()) - .formLogin(withDefaults()) - .oauth2Client(withDefaults()); - return http.build(); - } - - @Bean - MapReactiveUserDetailsService userDetailsService() { - UserDetails userDetails = User.withDefaultPasswordEncoder() - .username("user") - .password("password") - .roles("USER") - .build(); - return new MapReactiveUserDetailsService(userDetails); - } -} diff --git a/samples/boot/oauth2webclient-webflux/src/main/java/sample/config/WebClientConfig.java b/samples/boot/oauth2webclient-webflux/src/main/java/sample/config/WebClientConfig.java deleted file mode 100644 index 6016aa2ef59..00000000000 --- a/samples/boot/oauth2webclient-webflux/src/main/java/sample/config/WebClientConfig.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample.config; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager; -import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProvider; -import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProviderBuilder; -import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; -import org.springframework.security.oauth2.client.web.DefaultReactiveOAuth2AuthorizedClientManager; -import org.springframework.security.oauth2.client.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction; -import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository; -import org.springframework.web.reactive.function.client.WebClient; - -/** - * @author Rob Winch - * @since 5.1 - */ -@Configuration -public class WebClientConfig { - - @Value("${resource-uri}") String uri; - - @Bean - WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) { - ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = - new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager); - oauth.setDefaultOAuth2AuthorizedClient(true); - return WebClient.builder() - .baseUrl(this.uri) - .filter(oauth) - .build(); - } - - @Bean - ReactiveOAuth2AuthorizedClientManager authorizedClientManager( - ReactiveClientRegistrationRepository clientRegistrationRepository, - ServerOAuth2AuthorizedClientRepository authorizedClientRepository) { - - ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = - ReactiveOAuth2AuthorizedClientProviderBuilder.builder() - .authorizationCode() - .refreshToken() - .clientCredentials() - .password() - .build(); - DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager = - new DefaultReactiveOAuth2AuthorizedClientManager( - clientRegistrationRepository, authorizedClientRepository); - authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); - - return authorizedClientManager; - } -} diff --git a/samples/boot/oauth2webclient-webflux/src/main/java/sample/web/IndexController.java b/samples/boot/oauth2webclient-webflux/src/main/java/sample/web/IndexController.java deleted file mode 100644 index e1e0035ef13..00000000000 --- a/samples/boot/oauth2webclient-webflux/src/main/java/sample/web/IndexController.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2002-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample.web; - -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; - -/** - * @author Rob Winch - */ -@Controller -public class IndexController { - @GetMapping("/") - String index() { - return "index"; - } -} diff --git a/samples/boot/oauth2webclient-webflux/src/main/java/sample/web/OAuth2WebClientController.java b/samples/boot/oauth2webclient-webflux/src/main/java/sample/web/OAuth2WebClientController.java deleted file mode 100644 index a1ddae90aaa..00000000000 --- a/samples/boot/oauth2webclient-webflux/src/main/java/sample/web/OAuth2WebClientController.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2002-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample.web; - -import reactor.core.publisher.Mono; - -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.reactive.function.client.WebClient; - -import static org.springframework.security.oauth2.client.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId; - -/** - * @author Joe Grandja - * @author Rob Winch - */ -@Controller -@RequestMapping(path = {"/webclient", "/public/webclient"}) -public class OAuth2WebClientController { - private final WebClient webClient; - - public OAuth2WebClientController(WebClient webClient) { - this.webClient = webClient; - } - - @GetMapping("/explicit") - String explicit(Model model) { - Mono body = this.webClient - .get() - .attributes(clientRegistrationId("client-id")) - .retrieve() - .bodyToMono(String.class); - model.addAttribute("body", body); - return "response"; - } - - @GetMapping("/implicit") - String implicit(Model model) { - Mono body = this.webClient - .get() - .retrieve() - .bodyToMono(String.class); - model.addAttribute("body", body); - return "response"; - } -} diff --git a/samples/boot/oauth2webclient-webflux/src/main/java/sample/web/RegisteredOAuth2AuthorizedClientController.java b/samples/boot/oauth2webclient-webflux/src/main/java/sample/web/RegisteredOAuth2AuthorizedClientController.java deleted file mode 100644 index 558277394f9..00000000000 --- a/samples/boot/oauth2webclient-webflux/src/main/java/sample/web/RegisteredOAuth2AuthorizedClientController.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2002-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample.web; - -import reactor.core.publisher.Mono; - -import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; -import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.reactive.function.client.WebClient; - -import static org.springframework.security.oauth2.client.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction.oauth2AuthorizedClient; - -/** - * @author Joe Grandja - * @author Rob Winch - */ -@Controller -@RequestMapping(path = {"/annotation", "/public/annotation"}) -public class RegisteredOAuth2AuthorizedClientController { - - private final WebClient webClient; - - public RegisteredOAuth2AuthorizedClientController(WebClient webClient) { - this.webClient = webClient; - } - - @GetMapping("/explicit") - String explicit(Model model, @RegisteredOAuth2AuthorizedClient("client-id") OAuth2AuthorizedClient authorizedClient) { - Mono body = this.webClient - .get() - .attributes(oauth2AuthorizedClient(authorizedClient)) - .retrieve() - .bodyToMono(String.class); - model.addAttribute("body", body); - return "response"; - } - - @GetMapping("/implicit") - String implicit(Model model, @RegisteredOAuth2AuthorizedClient OAuth2AuthorizedClient authorizedClient) { - Mono body = this.webClient - .get() - .attributes(oauth2AuthorizedClient(authorizedClient)) - .retrieve() - .bodyToMono(String.class); - model.addAttribute("body", body); - return "response"; - } -} diff --git a/samples/boot/oauth2webclient-webflux/src/main/resources/application.yml b/samples/boot/oauth2webclient-webflux/src/main/resources/application.yml deleted file mode 100644 index 8528a06d0da..00000000000 --- a/samples/boot/oauth2webclient-webflux/src/main/resources/application.yml +++ /dev/null @@ -1,21 +0,0 @@ -logging: - level: - root: INFO - org.springframework.web: INFO - org.springframework.security: INFO -# org.springframework.boot.autoconfigure: DEBUG - -spring: - thymeleaf: - cache: false - security: - oauth2: - client: - registration: - client-id: - client-id: replace-with-client-id - client-secret: replace-with-client-secret - provider: github - scope: read:user,public_repo - -resource-uri: https://api.github.com/user/repos diff --git a/samples/boot/oauth2webclient-webflux/src/main/resources/templates/index.html b/samples/boot/oauth2webclient-webflux/src/main/resources/templates/index.html deleted file mode 100644 index 7787a75dd7b..00000000000 --- a/samples/boot/oauth2webclient-webflux/src/main/resources/templates/index.html +++ /dev/null @@ -1,51 +0,0 @@ - - - - OAuth2 WebClient Showcase - - - -Log Out -

Examples

- -

@RegisteredOAuth2AuthorizedClient

-

-Examples on RegisteredOAuth2AuthorizedClientController -

Authenticated

-
    -
  • Explicit - Explicitly provide a Client Registration Id
  • -
  • - Implicit - Use the currently logged in user's OAuth Token. This will - only work if the user authenticates with oauth2Login and the token provided is the correct token provided at - log in is authorized.
  • -
-

Public

-
    -
  • Explicit - Explicitly provide a Client Registration Id
  • -
  • - Implicit - This will fail if the user is not authenticated. - Since it is mapped to permitAll, it is going to fail unless the user already took an action to log in and then - authenticates with oauth2Login()
  • -
- -

ServerOAuth2AuthorizedClientExchangeFilterFunction

-

- Examples on OAuth2WebClientController that demonstrate how to use ServerOAuth2AuthorizedClientExchangeFilterFunction -

Authenticated

-
    -
  • Explicit - Explicitly provide a Client Registration Id
  • -
  • - Implicit - Use the currently logged in user's OAuth Token. This will - only work if the user authenticates with oauth2Login and the token provided is the correct token provided at - log in is authorized.
  • -
-

Public

-
    -
  • Explicit - Explicitly provide a Client Registration Id
  • -
  • - Implicit - This will fail if the user is not authenticated. - Since it is mapped to permitAll, it is going to fail unless the user already took an action to log in and then - authenticates with oauth2Login()
  • -
- - diff --git a/samples/boot/oauth2webclient-webflux/src/main/resources/templates/response.html b/samples/boot/oauth2webclient-webflux/src/main/resources/templates/response.html deleted file mode 100644 index 210c18c29ae..00000000000 --- a/samples/boot/oauth2webclient-webflux/src/main/resources/templates/response.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - OAuth2 WebClient Showcase - - - -Back -

Response

-
- - - diff --git a/samples/boot/oauth2webclient-webflux/src/test/java/sample/OAuth2WebClientControllerTests.java b/samples/boot/oauth2webclient-webflux/src/test/java/sample/OAuth2WebClientControllerTests.java deleted file mode 100644 index 52445dd2128..00000000000 --- a/samples/boot/oauth2webclient-webflux/src/test/java/sample/OAuth2WebClientControllerTests.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2002-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.MockWebServer; -import org.junit.AfterClass; -import org.junit.Test; -import org.junit.runner.RunWith; -import sample.config.SecurityConfig; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; -import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest; -import org.springframework.boot.test.context.TestConfiguration; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.reactive.server.WebTestClient; -import org.springframework.web.reactive.function.client.WebClient; - -import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockOAuth2Client; -import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockOAuth2Login; - -@WebFluxTest -@Import(SecurityConfig.class) -@AutoConfigureWebTestClient -@RunWith(SpringRunner.class) -public class OAuth2WebClientControllerTests { - private static MockWebServer web = new MockWebServer(); - - @Autowired - private WebTestClient client; - - @AfterClass - public static void shutdown() throws Exception { - web.shutdown(); - } - - @Test - public void explicitWhenAuthenticatedThenUsesClientIdRegistration() throws Exception { - web.enqueue(new MockResponse().setBody("body").setResponseCode(200)); - this.client.mutateWith(mockOAuth2Login()) - .mutateWith(mockOAuth2Client("client-id")) - .get().uri("/webclient/explicit") - .exchange() - .expectStatus().isOk(); - } - - @Test - public void implicitWhenAuthenticatedThenUsesDefaultRegistration() throws Exception { - web.enqueue(new MockResponse().setBody("body").setResponseCode(200)); - this.client.mutateWith(mockOAuth2Login()) - .get().uri("/webclient/implicit") - .exchange() - .expectStatus().isOk(); - } - - @Test - public void publicExplicitWhenAuthenticatedThenUsesClientIdRegistration() throws Exception { - web.enqueue(new MockResponse().setBody("body").setResponseCode(200)); - this.client.mutateWith(mockOAuth2Client("client-id")) - .get().uri("/public/webclient/explicit") - .exchange() - .expectStatus().isOk(); - } - - @Test - public void publicImplicitWhenAuthenticatedThenUsesDefaultRegistration() throws Exception { - web.enqueue(new MockResponse().setBody("body").setResponseCode(200)); - this.client.mutateWith(mockOAuth2Login()) - .get().uri("/public/webclient/implicit") - .exchange() - .expectStatus().isOk(); - } - - @TestConfiguration - static class WebClientConfig { - @Bean - WebClient web() { - return WebClient.create(web.url("/").toString()); - } - } -} diff --git a/samples/boot/oauth2webclient-webflux/src/test/java/sample/OAuth2WebClientWebFluxApplicationTests.java b/samples/boot/oauth2webclient-webflux/src/test/java/sample/OAuth2WebClientWebFluxApplicationTests.java deleted file mode 100644 index 657a6f7a163..00000000000 --- a/samples/boot/oauth2webclient-webflux/src/test/java/sample/OAuth2WebClientWebFluxApplicationTests.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2002-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.reactive.server.WebTestClient; - -/** - * @author Rob Winch - */ -@SpringBootTest -@AutoConfigureWebTestClient -@RunWith(SpringRunner.class) -public class OAuth2WebClientWebFluxApplicationTests { - @Autowired - private WebTestClient client; - - @Test - public void annotationExplicitWhenNotAuthenticatedThenLoginRequested() { - this.client.get().uri("/annotation/explicit") - .exchange() - .expectStatus().is3xxRedirection(); - } -} diff --git a/samples/boot/oauth2webclient-webflux/src/test/java/sample/RegisteredOAuth2AuthorizedClientControllerTests.java b/samples/boot/oauth2webclient-webflux/src/test/java/sample/RegisteredOAuth2AuthorizedClientControllerTests.java deleted file mode 100644 index 32fc9095b7c..00000000000 --- a/samples/boot/oauth2webclient-webflux/src/test/java/sample/RegisteredOAuth2AuthorizedClientControllerTests.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2002-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.MockWebServer; -import org.junit.AfterClass; -import org.junit.Test; -import org.junit.runner.RunWith; -import sample.config.SecurityConfig; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; -import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest; -import org.springframework.boot.test.context.TestConfiguration; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.reactive.server.WebTestClient; -import org.springframework.web.reactive.function.client.WebClient; - -import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockOAuth2Client; -import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockOAuth2Login; - -@WebFluxTest -@Import(SecurityConfig.class) -@AutoConfigureWebTestClient -@RunWith(SpringRunner.class) -public class RegisteredOAuth2AuthorizedClientControllerTests { - private static MockWebServer web = new MockWebServer(); - - @Autowired - private WebTestClient client; - - @AfterClass - public static void shutdown() throws Exception { - web.shutdown(); - } - - @Test - public void annotationExplicitWhenAuthenticatedThenUsesClientIdRegistration() throws Exception { - web.enqueue(new MockResponse().setBody("body").setResponseCode(200)); - this.client.mutateWith(mockOAuth2Login()) - .mutateWith(mockOAuth2Client("client-id")) - .get().uri("/annotation/explicit") - .exchange() - .expectStatus().isOk(); - } - - @Test - public void annotationImplicitWhenAuthenticatedThenUsesDefaultRegistration() throws Exception { - web.enqueue(new MockResponse().setBody("body").setResponseCode(200)); - this.client.mutateWith(mockOAuth2Login()) - .get().uri("/annotation/implicit") - .exchange() - .expectStatus().isOk(); - } - - @Test - public void publicAnnotationExplicitWhenAuthenticatedThenUsesClientIdRegistration() throws Exception { - web.enqueue(new MockResponse().setBody("body").setResponseCode(200)); - this.client.mutateWith(mockOAuth2Client("client-id")) - .get().uri("/public/annotation/explicit") - .exchange() - .expectStatus().isOk(); - } - - @Test - public void publicAnnotationImplicitWhenAuthenticatedThenUsesDefaultRegistration() throws Exception { - web.enqueue(new MockResponse().setBody("body").setResponseCode(200)); - this.client.mutateWith(mockOAuth2Login()) - .get().uri("/public/annotation/implicit") - .exchange() - .expectStatus().isOk(); - } - - @TestConfiguration - static class WebClientConfig { - @Bean - WebClient web() { - return WebClient.create(web.url("/").toString()); - } - } -} diff --git a/samples/boot/oauth2webclient/README.adoc b/samples/boot/oauth2webclient/README.adoc deleted file mode 100644 index b234f34f7e8..00000000000 --- a/samples/boot/oauth2webclient/README.adoc +++ /dev/null @@ -1,63 +0,0 @@ -= OAuth 2.0 WebClient (Servlet) Sample - -== GitHub Repositories - -This guide provides instructions on setting up the sample application, which leverages WebClient OAuth2 integration to display a list of public GitHub repositories that are accessible to the authenticated user. - -This includes repositories owned by the authenticated user, repositories where the authenticated user is a collaborator, and repositories that the authenticated user has access to through an organization membership. - -The following sections provide detailed steps for setting up the sample and covers the following topics: - -* <> -* <> -* <> - -[[github-register-application]] -=== Register OAuth application - -To use GitHub's OAuth 2.0 authorization system, you must https://github.com/settings/applications/new[Register a new OAuth application]. - -When registering the OAuth application, ensure the *Authorization callback URL* is set to `http://localhost:8080/login/oauth2/code/client-id`. - -The Authorization callback URL (redirect URI) is the path in the application that the end-user's user-agent is redirected back to after they have authenticated with GitHub and have granted access to the OAuth application on the _Authorize application_ page. - -[[github-application-config]] -=== Configure application.yml - -Now that you have a new OAuth application with GitHub, you need to configure the sample to use the OAuth application for the _authorization code grant flow_. -To do so: - -. Go to `application.yml` and set the following configuration: -+ -[source,yaml] ----- -spring: - security: - oauth2: - client: - registration: <1> - client-id: <2> - client-id: replace-with-client-id - client-secret: replace-with-client-secret - provider: github - scope: read:user,public_repo ----- -+ -.OAuth Client properties -==== -<1> `spring.security.oauth2.client.registration` is the base property prefix for OAuth Client properties. -<2> Following the base property prefix is the ID for the `ClientRegistration`, which is github. -==== - -. Replace the values in the `client-id` and `client-secret` property with the OAuth 2.0 credentials you created earlier. - -[[github-boot-application]] -=== Boot up the application - -Launch the Spring Boot 2.0 sample and go to `http://localhost:8080`. -You are then redirected to the default _auto-generated_ form login page. -Log in using *'user'* (username) and *'password'* (password) or click the link to authenticate with GitHub and then you'll be redirected to GitHub for authentication. - -After authenticating with your GitHub credentials, the next page presented to you is "Authorize application". -This page will ask you to *Authorize* the application you created in the previous step. -Click _Authorize application_ to allow the OAuth application to access and display your public repository information. diff --git a/samples/boot/oauth2webclient/spring-security-samples-boot-oauth2webclient.gradle b/samples/boot/oauth2webclient/spring-security-samples-boot-oauth2webclient.gradle deleted file mode 100644 index f65d82709c4..00000000000 --- a/samples/boot/oauth2webclient/spring-security-samples-boot-oauth2webclient.gradle +++ /dev/null @@ -1,16 +0,0 @@ -apply plugin: 'io.spring.convention.spring-sample-boot' - -dependencies { - compile project(':spring-security-config') - compile project(':spring-security-oauth2-client') - compile project(':spring-security-oauth2-jose') - compile 'org.springframework:spring-webflux' - compile 'org.springframework.boot:spring-boot-starter-thymeleaf' - compile 'org.springframework.boot:spring-boot-starter-web' - compile 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5' - compile 'io.projectreactor.netty:reactor-netty' - - testCompile project(':spring-security-test') - testCompile 'org.springframework.boot:spring-boot-starter-test' - testCompile 'com.squareup.okhttp3:mockwebserver' -} diff --git a/samples/boot/oauth2webclient/src/main/java/sample/OAuth2WebClientApplication.java b/samples/boot/oauth2webclient/src/main/java/sample/OAuth2WebClientApplication.java deleted file mode 100644 index 4c4a6b7de15..00000000000 --- a/samples/boot/oauth2webclient/src/main/java/sample/OAuth2WebClientApplication.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2002-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration; - -/** - * @author Joe Grandja - */ -// FIXME: Work around https://github.com/spring-projects/spring-boot/issues/14463 -@SpringBootApplication(exclude = ReactiveOAuth2ClientAutoConfiguration.class) -public class OAuth2WebClientApplication { - - public static void main(String[] args) { - SpringApplication.run(OAuth2WebClientApplication.class, args); - } -} diff --git a/samples/boot/oauth2webclient/src/main/java/sample/config/SecurityConfig.java b/samples/boot/oauth2webclient/src/main/java/sample/config/SecurityConfig.java deleted file mode 100644 index e5602d812d7..00000000000 --- a/samples/boot/oauth2webclient/src/main/java/sample/config/SecurityConfig.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample.config; - -import org.springframework.context.annotation.Bean; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.provisioning.InMemoryUserDetailsManager; - -import static org.springframework.security.config.Customizer.withDefaults; - -/** - * @author Joe Grandja - */ -@EnableWebSecurity -public class SecurityConfig extends WebSecurityConfigurerAdapter { - - @Override - protected void configure(HttpSecurity http) throws Exception { - http - .authorizeRequests((authorizeRequests) -> - authorizeRequests - .mvcMatchers("/", "/public/**").permitAll() - .anyRequest().authenticated() - ) - .formLogin(withDefaults()) - .oauth2Login(withDefaults()) - .oauth2Client(withDefaults()); - } - - @Bean - public UserDetailsService userDetailsService() { - UserDetails userDetails = User.withDefaultPasswordEncoder() - .username("user") - .password("password") - .roles("USER") - .build(); - return new InMemoryUserDetailsManager(userDetails); - } -} diff --git a/samples/boot/oauth2webclient/src/main/java/sample/config/WebClientConfig.java b/samples/boot/oauth2webclient/src/main/java/sample/config/WebClientConfig.java deleted file mode 100644 index da9510602bd..00000000000 --- a/samples/boot/oauth2webclient/src/main/java/sample/config/WebClientConfig.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2002-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample.config; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder; -import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; -import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager; -import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; -import org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction; -import org.springframework.web.reactive.function.client.WebClient; - -/** - * @author Rob Winch - * @since 5.1 - */ -@Configuration -public class WebClientConfig { - - @Value("${resource-uri}") String resourceUri; - - @Bean - WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) { - ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2 = - new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager); - oauth2.setDefaultOAuth2AuthorizedClient(true); - return WebClient.builder() - .baseUrl(this.resourceUri) - .apply(oauth2.oauth2Configuration()) - .build(); - } - - @Bean - OAuth2AuthorizedClientManager authorizedClientManager(ClientRegistrationRepository clientRegistrationRepository, - OAuth2AuthorizedClientRepository authorizedClientRepository) { - OAuth2AuthorizedClientProvider authorizedClientProvider = - OAuth2AuthorizedClientProviderBuilder.builder() - .authorizationCode() - .refreshToken() - .clientCredentials() - .password() - .build(); - DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager( - clientRegistrationRepository, authorizedClientRepository); - authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); - - return authorizedClientManager; - } -} diff --git a/samples/boot/oauth2webclient/src/main/java/sample/web/IndexController.java b/samples/boot/oauth2webclient/src/main/java/sample/web/IndexController.java deleted file mode 100644 index e1e0035ef13..00000000000 --- a/samples/boot/oauth2webclient/src/main/java/sample/web/IndexController.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2002-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample.web; - -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; - -/** - * @author Rob Winch - */ -@Controller -public class IndexController { - @GetMapping("/") - String index() { - return "index"; - } -} diff --git a/samples/boot/oauth2webclient/src/main/java/sample/web/OAuth2WebClientController.java b/samples/boot/oauth2webclient/src/main/java/sample/web/OAuth2WebClientController.java deleted file mode 100644 index f20b479d1fd..00000000000 --- a/samples/boot/oauth2webclient/src/main/java/sample/web/OAuth2WebClientController.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2002-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample.web; - -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.reactive.function.client.WebClient; - -import static org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId; - -/** - * @author Joe Grandja - * @author Rob Winch - */ -@Controller -@RequestMapping(path = {"/webclient", "/public/webclient"}) -public class OAuth2WebClientController { - private final WebClient webClient; - - public OAuth2WebClientController(WebClient webClient) { - this.webClient = webClient; - } - - @GetMapping("/explicit") - String explicit(Model model) { - String body = this.webClient - .get() - .attributes(clientRegistrationId("client-id")) - .retrieve() - .bodyToMono(String.class) - .block(); - model.addAttribute("body", body); - return "response"; - } - - @GetMapping("/implicit") - String implicit(Model model) { - String body = this.webClient - .get() - .retrieve() - .bodyToMono(String.class) - .block(); - model.addAttribute("body", body); - return "response"; - } -} diff --git a/samples/boot/oauth2webclient/src/main/java/sample/web/RegisteredOAuth2AuthorizedClientController.java b/samples/boot/oauth2webclient/src/main/java/sample/web/RegisteredOAuth2AuthorizedClientController.java deleted file mode 100644 index 883db445d06..00000000000 --- a/samples/boot/oauth2webclient/src/main/java/sample/web/RegisteredOAuth2AuthorizedClientController.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2002-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample.web; - -import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; -import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.reactive.function.client.WebClient; - -import static org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction.oauth2AuthorizedClient; - -/** - * @author Joe Grandja - * @author Rob Winch - */ -@Controller -@RequestMapping(path = {"/annotation", "/public/annotation"}) -public class RegisteredOAuth2AuthorizedClientController { - private final WebClient webClient; - - public RegisteredOAuth2AuthorizedClientController(WebClient webClient) { - this.webClient = webClient; - } - - @GetMapping("/explicit") - String explicit(Model model, @RegisteredOAuth2AuthorizedClient("client-id") OAuth2AuthorizedClient authorizedClient) { - String body = this.webClient - .get() - .attributes(oauth2AuthorizedClient(authorizedClient)) - .retrieve() - .bodyToMono(String.class) - .block(); - model.addAttribute("body", body); - return "response"; - } - - @GetMapping("/implicit") - String implicit(Model model, @RegisteredOAuth2AuthorizedClient OAuth2AuthorizedClient authorizedClient) { - String body = this.webClient - .get() - .attributes(oauth2AuthorizedClient(authorizedClient)) - .retrieve() - .bodyToMono(String.class) - .block(); - model.addAttribute("body", body); - return "response"; - } -} diff --git a/samples/boot/oauth2webclient/src/main/resources/application.yml b/samples/boot/oauth2webclient/src/main/resources/application.yml deleted file mode 100644 index 8528a06d0da..00000000000 --- a/samples/boot/oauth2webclient/src/main/resources/application.yml +++ /dev/null @@ -1,21 +0,0 @@ -logging: - level: - root: INFO - org.springframework.web: INFO - org.springframework.security: INFO -# org.springframework.boot.autoconfigure: DEBUG - -spring: - thymeleaf: - cache: false - security: - oauth2: - client: - registration: - client-id: - client-id: replace-with-client-id - client-secret: replace-with-client-secret - provider: github - scope: read:user,public_repo - -resource-uri: https://api.github.com/user/repos diff --git a/samples/boot/oauth2webclient/src/main/resources/templates/index.html b/samples/boot/oauth2webclient/src/main/resources/templates/index.html deleted file mode 100644 index 07a8e680352..00000000000 --- a/samples/boot/oauth2webclient/src/main/resources/templates/index.html +++ /dev/null @@ -1,51 +0,0 @@ - - - - OAuth2 WebClient Showcase - - - -Log Out -

Examples

- -

@RegisteredOAuth2AuthorizedClient

-

-Examples on RegisteredOAuth2AuthorizedClientController -

Authenticated

-
    -
  • Explicit - Explicitly provide a Client Registration Id
  • -
  • - Implicit - Use the currently logged in user's OAuth Token. This will - only work if the user authenticates with oauth2Login and the token provided is the correct token provided at - log in is authorized.
  • -
-

Public

-
    -
  • Explicit - Explicitly provide a Client Registration Id
  • -
  • - Implicit - This will fail if the user is not authenticated. - Since it is mapped to permitAll, it is going to fail unless the user already took an action to log in and then - authenticates with oauth2Login()
  • -
- -

ServletOAuth2AuthorizedClientExchangeFilterFunction

-

- Examples on OAuth2WebClientController that demonstrate how to use ServletOAuth2AuthorizedClientExchangeFilterFunction -

Authenticated

-
    -
  • Explicit - Explicitly provide a Client Registration Id
  • -
  • - Implicit - Use the currently logged in user's OAuth Token. This will - only work if the user authenticates with oauth2Login and the token provided is the correct token provided at - log in is authorized.
  • -
-

Public

-
    -
  • Explicit - Explicitly provide a Client Registration Id
  • -
  • - Implicit - This will fail if the user is not authenticated. - Since it is mapped to permitAll, it is going to fail unless the user already took an action to log in and then - authenticates with oauth2Login()
  • -
- - diff --git a/samples/boot/oauth2webclient/src/main/resources/templates/response.html b/samples/boot/oauth2webclient/src/main/resources/templates/response.html deleted file mode 100644 index 210c18c29ae..00000000000 --- a/samples/boot/oauth2webclient/src/main/resources/templates/response.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - OAuth2 WebClient Showcase - - - -Back -

Response

-
- - - diff --git a/samples/boot/oauth2webclient/src/test/java/sample/OAuth2WebClientApplicationTests.java b/samples/boot/oauth2webclient/src/test/java/sample/OAuth2WebClientApplicationTests.java deleted file mode 100644 index 4e153b9961c..00000000000 --- a/samples/boot/oauth2webclient/src/test/java/sample/OAuth2WebClientApplicationTests.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2002-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * @author Rob Winch - */ -@SpringBootTest -@AutoConfigureMockMvc -@RunWith(SpringRunner.class) -public class OAuth2WebClientApplicationTests { - @Autowired - private MockMvc mockMvc; - - @Test - public void annotationExplicitWhenNotAuthenticatedThenLoginRequested() throws Exception { - this.mockMvc.perform(get("/annotation/explicit")) - .andExpect(status().is3xxRedirection()); - } -} diff --git a/samples/boot/oauth2webclient/src/test/java/sample/OAuth2WebClientControllerTests.java b/samples/boot/oauth2webclient/src/test/java/sample/OAuth2WebClientControllerTests.java deleted file mode 100644 index 8829c004715..00000000000 --- a/samples/boot/oauth2webclient/src/test/java/sample/OAuth2WebClientControllerTests.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2002-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.MockWebServer; -import org.junit.AfterClass; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.context.TestConfiguration; -import org.springframework.context.annotation.Bean; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.web.reactive.function.client.WebClient; - -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.oauth2Client; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.oauth2Login; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -@WebMvcTest -@AutoConfigureMockMvc -@RunWith(SpringRunner.class) -public class OAuth2WebClientControllerTests { - private static MockWebServer web = new MockWebServer(); - - @Autowired - private MockMvc mockMvc; - - @AfterClass - public static void shutdown() throws Exception { - web.shutdown(); - } - - @Test - public void explicitWhenAuthenticatedThenUsesClientIdRegistration() throws Exception { - web.enqueue(new MockResponse().setBody("body").setResponseCode(200)); - this.mockMvc.perform(get("/webclient/explicit") - .with(oauth2Login()) - .with(oauth2Client("client-id"))) - .andExpect(status().isOk()); - } - - @Test - public void implicitWhenAuthenticatedThenUsesDefaultRegistration() throws Exception { - web.enqueue(new MockResponse().setBody("body").setResponseCode(200)); - this.mockMvc.perform(get("/webclient/implicit") - .with(oauth2Login())) - .andExpect(status().isOk()); - } - - @Test - public void publicExplicitWhenAuthenticatedThenUsesClientIdRegistration() throws Exception { - web.enqueue(new MockResponse().setBody("body").setResponseCode(200)); - this.mockMvc.perform(get("/public/webclient/explicit") - .with(oauth2Client("client-id"))) - .andExpect(status().isOk()); - } - - @Test - public void publicImplicitWhenAuthenticatedThenUsesDefaultRegistration() throws Exception { - web.enqueue(new MockResponse().setBody("body").setResponseCode(200)); - this.mockMvc.perform(get("/public/webclient/implicit") - .with(oauth2Login())) - .andExpect(status().isOk()); - } - - @TestConfiguration - static class WebClientConfig { - @Bean - WebClient web() { - return WebClient.create(web.url("/").toString()); - } - } -} diff --git a/samples/boot/oauth2webclient/src/test/java/sample/RegisteredOAuth2AuthorizedClientControllerTests.java b/samples/boot/oauth2webclient/src/test/java/sample/RegisteredOAuth2AuthorizedClientControllerTests.java deleted file mode 100644 index 91e3a2ec913..00000000000 --- a/samples/boot/oauth2webclient/src/test/java/sample/RegisteredOAuth2AuthorizedClientControllerTests.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2002-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.MockWebServer; -import org.junit.AfterClass; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.context.TestConfiguration; -import org.springframework.context.annotation.Bean; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.web.reactive.function.client.WebClient; - -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.oauth2Client; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.oauth2Login; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -@WebMvcTest -@AutoConfigureMockMvc -@RunWith(SpringRunner.class) -public class RegisteredOAuth2AuthorizedClientControllerTests { - private static MockWebServer web = new MockWebServer(); - - @Autowired - private MockMvc mockMvc; - - @AfterClass - public static void shutdown() throws Exception { - web.shutdown(); - } - - @Test - public void annotationExplicitWhenAuthenticatedThenUsesClientIdRegistration() throws Exception { - web.enqueue(new MockResponse().setBody("body").setResponseCode(200)); - this.mockMvc.perform(get("/annotation/explicit") - .with(oauth2Login()) - .with(oauth2Client("client-id"))) - .andExpect(status().isOk()); - } - - @Test - public void annotationImplicitWhenAuthenticatedThenUsesDefaultRegistration() throws Exception { - web.enqueue(new MockResponse().setBody("body").setResponseCode(200)); - this.mockMvc.perform(get("/annotation/implicit") - .with(oauth2Login())) - .andExpect(status().isOk()); - } - - @Test - public void publicAnnotationExplicitWhenAuthenticatedThenUsesClientIdRegistration() throws Exception { - web.enqueue(new MockResponse().setBody("body").setResponseCode(200)); - this.mockMvc.perform(get("/public/annotation/explicit") - .with(oauth2Client("client-id"))) - .andExpect(status().isOk()); - } - - @Test - public void publicAnnotationImplicitWhenAuthenticatedThenUsesDefaultRegistration() throws Exception { - web.enqueue(new MockResponse().setBody("body").setResponseCode(200)); - this.mockMvc.perform(get("/public/annotation/implicit") - .with(oauth2Login())) - .andExpect(status().isOk()); - } - - @TestConfiguration - static class WebClientConfig { - @Bean - WebClient web() { - return WebClient.create(web.url("/").toString()); - } - } -} diff --git a/samples/boot/saml2login/README.adoc b/samples/boot/saml2login/README.adoc deleted file mode 100644 index d51b1bba4be..00000000000 --- a/samples/boot/saml2login/README.adoc +++ /dev/null @@ -1,38 +0,0 @@ -= SAML 2.0 Login Sample - -This guide provides instructions on setting up this SAML 2.0 Login sample application. - -The sample application uses Spring Boot and the `spring-security-saml2-service-provider` -module which is new in Spring Security 5.2. - -== Goals - -`saml2Login()` provides a very simple implementation of a Service Provider that can receive a SAML 2.0 Response via the HTTP-POST and HTTP-REDIRECT bindings against the SimpleSAMLphp SAML 2.0 reference implementation. - -The following features are implemented in the MVP: - -1. Receive and validate a SAML 2.0 Response containing an assertion, and create a corresponding authentication in Spring Security -2. Send a SAML 2.0 AuthNRequest to an Identity Provider -3. Provide a framework for components used in SAML 2.0 authentication that can be swapped by configuration -4. Work against the SimpleSAMLphp reference implementation - -== Run the Sample - -=== Start up the Sample Boot Application -``` - ./gradlew :spring-security-samples-boot-saml2login:bootRun -``` - -=== Open a Browser - -http://localhost:8080/ - -You will be redirect to the SimpleSAMLphp IDP - -=== Type in your credentials - -``` -User: user -Password: password -``` - diff --git a/samples/boot/saml2login/spring-security-samples-boot-saml2login.gradle b/samples/boot/saml2login/spring-security-samples-boot-saml2login.gradle deleted file mode 100644 index f46ce4a8d98..00000000000 --- a/samples/boot/saml2login/spring-security-samples-boot-saml2login.gradle +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -apply plugin: 'io.spring.convention.spring-sample-boot' - -dependencies { - compile project(':spring-security-config') - compile project(':spring-security-saml2-service-provider') - compile 'org.springframework.boot:spring-boot-starter-thymeleaf' - compile 'org.springframework.boot:spring-boot-starter-web' - compile 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5' - - testCompile project(':spring-security-test') - testCompile 'net.sourceforge.htmlunit:htmlunit' - testCompile 'org.springframework.boot:spring-boot-starter-test' -} diff --git a/samples/boot/saml2login/src/integration-test/java/sample/Saml2LoginApplicationITests.java b/samples/boot/saml2login/src/integration-test/java/sample/Saml2LoginApplicationITests.java deleted file mode 100644 index c963ab3b2b6..00000000000 --- a/samples/boot/saml2login/src/integration-test/java/sample/Saml2LoginApplicationITests.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2002-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import javax.servlet.http.HttpSession; - -import com.gargoylesoftware.htmlunit.WebClient; -import com.gargoylesoftware.htmlunit.html.HtmlForm; -import com.gargoylesoftware.htmlunit.html.HtmlInput; -import com.gargoylesoftware.htmlunit.html.HtmlPage; -import com.gargoylesoftware.htmlunit.html.HtmlSubmitInput; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.mock.web.MockHttpSession; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -@RunWith(SpringRunner.class) -@SpringBootTest -@AutoConfigureMockMvc -public class Saml2LoginApplicationITests { - static final String SIGNED_RESPONSE = "PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIElEPSJfY2UyYjU4NTVjZjU5YmEyNDc4OTUyOGRkOGQzZDcyOGRiMGViZjNlNzNiIiBWZXJzaW9uPSIyLjAiIElzc3VlSW5zdGFudD0iMjAyMS0wMS0yMFQwMTowMzoyNFoiIERlc3RpbmF0aW9uPSJodHRwOi8vbG9jYWxob3N0OjgwODAvbG9naW4vc2FtbDIvc3NvL29uZSIgSW5SZXNwb25zZVRvPSJBUlFjOThmMjAwLWRjZjctNGRmNC1hNTIyLTA3MjA2MjA4YjA3ZCI+PHNhbWw6SXNzdWVyPmh0dHBzOi8vc2ltcGxlc2FtbC1mb3Itc3ByaW5nLXNhbWwuYXBwcy5wY2ZvbmUuaW8vc2FtbDIvaWRwL21ldGFkYXRhLnBocDwvc2FtbDpJc3N1ZXI+PGRzOlNpZ25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+CiAgPGRzOlNpZ25lZEluZm8+PGRzOkNhbm9uaWNhbGl6YXRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz4KICAgIDxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNyc2Etc2hhMjU2Ii8+CiAgPGRzOlJlZmVyZW5jZSBVUkk9IiNfY2UyYjU4NTVjZjU5YmEyNDc4OTUyOGRkOGQzZDcyOGRiMGViZjNlNzNiIj48ZHM6VHJhbnNmb3Jtcz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI2VudmVsb3BlZC1zaWduYXR1cmUiLz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+PC9kczpUcmFuc2Zvcm1zPjxkczpEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNzaGEyNTYiLz48ZHM6RGlnZXN0VmFsdWU+TXJUNS8wdTRScTl3QmMvejFQd2FrNURXZm1xOGlOVk52NldHZWFnZUVzUT08L2RzOkRpZ2VzdFZhbHVlPjwvZHM6UmVmZXJlbmNlPjwvZHM6U2lnbmVkSW5mbz48ZHM6U2lnbmF0dXJlVmFsdWU+WWg1UFE5cFVBbkkvOW5tNzgxZ040bThTS0U4T1VRTDVOMlI3ZklPZmtHVmMzRjhzNlJlR3hRNWFneUFXYnQzUDRwQ3RWeGtqbk4rMk5KeUw4QmhRMHN0dEovb2JFTHJGUldLemYyYUJaS2NCN0JHTFNtRXdoUFE3N3BHL0psMjBhaDQyaGRyWFc3TE9Ob0VZOHMyY093dm16NkQ2bW9YQWprMHV2UEVTNjhUVndxU2VmT3JwNXV0QmRSQUt6cUJRQ2NQWFJCdnB5NWJ3QkpDL2RKNE5QLzJpalE3N2I3eWhvVDQ0R21hSUduSGo0YVFaeG9kY1JuNU9oQ2hYRk4ydUk2YW1mT0ZYOThjUXZ5KzhDWm9YYUZRMnJmT2dPbGdzbmNGWGMwaXhYK05MSjlvSlJWT2hxRVpjY2JoZ3hPM2hpQ2UwemRuZHloVWxpaGMwdFU2OVlBPT08L2RzOlNpZ25hdHVyZVZhbHVlPgo8ZHM6S2V5SW5mbz48ZHM6WDUwOURhdGE+PGRzOlg1MDlDZXJ0aWZpY2F0ZT5NSUlFRXpDQ0F2dWdBd0lCQWdJSkFJYzFxekxydis1bk1BMEdDU3FHU0liM0RRRUJDd1VBTUlHZk1Rc3dDUVlEVlFRR0V3SlZVekVMTUFrR0ExVUVDQXdDUTA4eEZEQVNCZ05WQkFjTUMwTmhjM1JzWlNCU2IyTnJNUnd3R2dZRFZRUUtEQk5UWVcxc0lGUmxjM1JwYm1jZ1UyVnlkbVZ5TVFzd0NRWURWUVFMREFKSlZERWdNQjRHQTFVRUF3d1hjMmx0Y0d4bGMyRnRiSEJvY0M1alptRndjSE11YVc4eElEQWVCZ2txaGtpRzl3MEJDUUVXRVdab1lXNXBhMEJ3YVhadmRHRnNMbWx2TUI0WERURTFNREl5TXpJeU5EVXdNMW9YRFRJMU1ESXlNakl5TkRVd00xb3dnWjh4Q3pBSkJnTlZCQVlUQWxWVE1Rc3dDUVlEVlFRSURBSkRUekVVTUJJR0ExVUVCd3dMUTJGemRHeGxJRkp2WTJzeEhEQWFCZ05WQkFvTUUxTmhiV3dnVkdWemRHbHVaeUJUWlhKMlpYSXhDekFKQmdOVkJBc01Ba2xVTVNBd0hnWURWUVFEREJkemFXMXdiR1Z6WVcxc2NHaHdMbU5tWVhCd2N5NXBiekVnTUI0R0NTcUdTSWIzRFFFSkFSWVJabWhoYm1sclFIQnBkbTkwWVd3dWFXOHdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFDNGNuNjJFMXhMcXBOMzRQbWJyS0Jia09YRmp6V2dKOWIrcFh1YVJmdDZBMzM5dXVJUWVvZUg1cWVTS1JWVGwzMkwwZ2R6Mlppdkx3WlhXK2NxdmZ0VlcxdHZFSHZ6SkZ5eGVUVzNmQ1VlQ1FzZWJMbkEycVJhMDdSa3hUbzZOZjI0NG1XV1JEb2Rjb0hFZkRVU2J4ZlRaNklFeFNvalNJVTJSbkQ2V2xsWVdGZEQxR0ZwQkpPbVFCOHJBYzh3SklCZEhGZFFuWDhUdGw3aFo2cnRncUVZTXpZVk11SjJGMnIxSFNVMXpTQXZ3cGRZUDZyUkdGUkpFZmRBOW1tM1dLZk5MU2M1Y2xqejBYL1RYeTB2VmxBVjk1bDlxY2ZGelBtcmtOSXN0OUZaU3dwdkI0OUx5QVZrZTA0RlFQUHdMZ1ZINGdwaGlKSDNqdlo3SStKNWxTOFZBZ01CQUFHalVEQk9NQjBHQTFVZERnUVdCQlRUeVA2Q2M1SGxCSjUrdWNWQ3dHYzVvZ0tOR3pBZkJnTlZIU01FR0RBV2dCVFR5UDZDYzVIbEJKNSt1Y1ZDd0djNW9nS05HekFNQmdOVkhSTUVCVEFEQVFIL01BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQXZNUzRFUWVQL2lwVjRqT0c1bE82L3RZQ2IvaUplQWR1T25SaGtKazBEYlgzMjlsRExaaFRUTC94L3cvOW11Q1ZjdkxyekVwNlBOK1ZXZnc1RTVGV3RaTjB5aEd0UDlSK3ZabnJWK29jMnpHRCtubzEveVNGT2UzRWlKQ081ZGVoeEtqWUVtQlJ2NXNVL0xaRktacG96S04vQk1FYTZDcUx1eGJ6Yjd5a3hWcjdFVkZYd2x0UHh6RTlUbUw5T0FDTk55RjVlSkhXTVJNbGxhclV2a2NYbGg0cHV4NGtzOWU2elY5RFFCeTJ6ZHM5ZjFJM3F4ZzBlWDZKbkdyWGkvWmlDVCtsSmdWZTNaRlhpZWppTEFpS0IwNHNYVzN0aTBMVzNseDEzWTFZbFE0L3RscGdUZ2ZJSnhLVjZueVBpTG9LMG55d2JNZCt2cEFpckR0Mk9jK2hrPC9kczpYNTA5Q2VydGlmaWNhdGU+PC9kczpYNTA5RGF0YT48L2RzOktleUluZm8+PC9kczpTaWduYXR1cmU+PHNhbWxwOlN0YXR1cz48c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+PC9zYW1scDpTdGF0dXM+PHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9Il9jYjYzYmMzNmMyYzAzYjRlMWJjZDViMWIwY2MyZTE2NWQwNDQ1NDZlODgiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDIxLTAxLTIwVDAxOjAzOjI0WiI+PHNhbWw6SXNzdWVyPmh0dHBzOi8vc2ltcGxlc2FtbC1mb3Itc3ByaW5nLXNhbWwuYXBwcy5wY2ZvbmUuaW8vc2FtbDIvaWRwL21ldGFkYXRhLnBocDwvc2FtbDpJc3N1ZXI+PGRzOlNpZ25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+CiAgPGRzOlNpZ25lZEluZm8+PGRzOkNhbm9uaWNhbGl6YXRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz4KICAgIDxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNyc2Etc2hhMjU2Ii8+CiAgPGRzOlJlZmVyZW5jZSBVUkk9IiNfY2I2M2JjMzZjMmMwM2I0ZTFiY2Q1YjFiMGNjMmUxNjVkMDQ0NTQ2ZTg4Ij48ZHM6VHJhbnNmb3Jtcz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI2VudmVsb3BlZC1zaWduYXR1cmUiLz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+PC9kczpUcmFuc2Zvcm1zPjxkczpEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNzaGEyNTYiLz48ZHM6RGlnZXN0VmFsdWU+VWFtN2NHVGlCd2xuRDBJdGd5aU5KVjN2Z0NPNytZZkRxSWJrWERkR3hrQT08L2RzOkRpZ2VzdFZhbHVlPjwvZHM6UmVmZXJlbmNlPjwvZHM6U2lnbmVkSW5mbz48ZHM6U2lnbmF0dXJlVmFsdWU+RkhpWUpETDlKTXM1Y2V5WXhUVVgrUndEQm45RFYzVE81dDFham4raGFtb1c2MUpBY0JaNjEwUHpYMzN3alA3Mk1kYmdDWnR5ZmNrSktZUUpPT0szRkxLTkJLQkphOTNsSS9rZWZjTXRTUGxBU2hESm9ydmU0U0tWa29WbzZLVnB0eC9OTnowRkhJNURFZTZiUUVjZWFiNERVNDFVdEpQMHUyWm16ejVjNC83VzhLdmt6MkxMbXhWZlE3Q2todmgvNzBhWHlkWVBVRml3bE4vV1lTV3JYVU9oOXNFTDFiZGVlQzFkYnpaeVdNNldnSkdRMUpJblBnSGd0YTlxMU96eGliOFlLRXpQSUMzVEZldkU1Y0phMFQvd1NzOVIxN0JSR09OclhTTWQvRCt4YkY0Z3lIYW5EZFlOYVN2TzdIS2p4bzRwYk1aY05peDhMTkVYZGtiZEx3PT08L2RzOlNpZ25hdHVyZVZhbHVlPgo8ZHM6S2V5SW5mbz48ZHM6WDUwOURhdGE+PGRzOlg1MDlDZXJ0aWZpY2F0ZT5NSUlFRXpDQ0F2dWdBd0lCQWdJSkFJYzFxekxydis1bk1BMEdDU3FHU0liM0RRRUJDd1VBTUlHZk1Rc3dDUVlEVlFRR0V3SlZVekVMTUFrR0ExVUVDQXdDUTA4eEZEQVNCZ05WQkFjTUMwTmhjM1JzWlNCU2IyTnJNUnd3R2dZRFZRUUtEQk5UWVcxc0lGUmxjM1JwYm1jZ1UyVnlkbVZ5TVFzd0NRWURWUVFMREFKSlZERWdNQjRHQTFVRUF3d1hjMmx0Y0d4bGMyRnRiSEJvY0M1alptRndjSE11YVc4eElEQWVCZ2txaGtpRzl3MEJDUUVXRVdab1lXNXBhMEJ3YVhadmRHRnNMbWx2TUI0WERURTFNREl5TXpJeU5EVXdNMW9YRFRJMU1ESXlNakl5TkRVd00xb3dnWjh4Q3pBSkJnTlZCQVlUQWxWVE1Rc3dDUVlEVlFRSURBSkRUekVVTUJJR0ExVUVCd3dMUTJGemRHeGxJRkp2WTJzeEhEQWFCZ05WQkFvTUUxTmhiV3dnVkdWemRHbHVaeUJUWlhKMlpYSXhDekFKQmdOVkJBc01Ba2xVTVNBd0hnWURWUVFEREJkemFXMXdiR1Z6WVcxc2NHaHdMbU5tWVhCd2N5NXBiekVnTUI0R0NTcUdTSWIzRFFFSkFSWVJabWhoYm1sclFIQnBkbTkwWVd3dWFXOHdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFDNGNuNjJFMXhMcXBOMzRQbWJyS0Jia09YRmp6V2dKOWIrcFh1YVJmdDZBMzM5dXVJUWVvZUg1cWVTS1JWVGwzMkwwZ2R6Mlppdkx3WlhXK2NxdmZ0VlcxdHZFSHZ6SkZ5eGVUVzNmQ1VlQ1FzZWJMbkEycVJhMDdSa3hUbzZOZjI0NG1XV1JEb2Rjb0hFZkRVU2J4ZlRaNklFeFNvalNJVTJSbkQ2V2xsWVdGZEQxR0ZwQkpPbVFCOHJBYzh3SklCZEhGZFFuWDhUdGw3aFo2cnRncUVZTXpZVk11SjJGMnIxSFNVMXpTQXZ3cGRZUDZyUkdGUkpFZmRBOW1tM1dLZk5MU2M1Y2xqejBYL1RYeTB2VmxBVjk1bDlxY2ZGelBtcmtOSXN0OUZaU3dwdkI0OUx5QVZrZTA0RlFQUHdMZ1ZINGdwaGlKSDNqdlo3SStKNWxTOFZBZ01CQUFHalVEQk9NQjBHQTFVZERnUVdCQlRUeVA2Q2M1SGxCSjUrdWNWQ3dHYzVvZ0tOR3pBZkJnTlZIU01FR0RBV2dCVFR5UDZDYzVIbEJKNSt1Y1ZDd0djNW9nS05HekFNQmdOVkhSTUVCVEFEQVFIL01BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQXZNUzRFUWVQL2lwVjRqT0c1bE82L3RZQ2IvaUplQWR1T25SaGtKazBEYlgzMjlsRExaaFRUTC94L3cvOW11Q1ZjdkxyekVwNlBOK1ZXZnc1RTVGV3RaTjB5aEd0UDlSK3ZabnJWK29jMnpHRCtubzEveVNGT2UzRWlKQ081ZGVoeEtqWUVtQlJ2NXNVL0xaRktacG96S04vQk1FYTZDcUx1eGJ6Yjd5a3hWcjdFVkZYd2x0UHh6RTlUbUw5T0FDTk55RjVlSkhXTVJNbGxhclV2a2NYbGg0cHV4NGtzOWU2elY5RFFCeTJ6ZHM5ZjFJM3F4ZzBlWDZKbkdyWGkvWmlDVCtsSmdWZTNaRlhpZWppTEFpS0IwNHNYVzN0aTBMVzNseDEzWTFZbFE0L3RscGdUZ2ZJSnhLVjZueVBpTG9LMG55d2JNZCt2cEFpckR0Mk9jK2hrPC9kczpYNTA5Q2VydGlmaWNhdGU+PC9kczpYNTA5RGF0YT48L2RzOktleUluZm8+PC9kczpTaWduYXR1cmU+PHNhbWw6U3ViamVjdD48c2FtbDpOYW1lSUQgU1BOYW1lUXVhbGlmaWVyPSJodHRwOi8vbG9jYWxob3N0OjgwODAvc2FtbDIvc2VydmljZS1wcm92aWRlci1tZXRhZGF0YS9vbmUiIEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6MS4xOm5hbWVpZC1mb3JtYXQ6ZW1haWxBZGRyZXNzIj50ZXN0dXNlckBzcHJpbmcuc2VjdXJpdHkuc2FtbDwvc2FtbDpOYW1lSUQ+PHNhbWw6U3ViamVjdENvbmZpcm1hdGlvbiBNZXRob2Q9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpjbTpiZWFyZXIiPjxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj0iMjA1Mi0wOS0yOFQwMjo1MDowNFoiIFJlY2lwaWVudD0iaHR0cDovL2xvY2FsaG9zdDo4MDgwL2xvZ2luL3NhbWwyL3Nzby9vbmUiIEluUmVzcG9uc2VUbz0iQVJRYzk4ZjIwMC1kY2Y3LTRkZjQtYTUyMi0wNzIwNjIwOGIwN2QiLz48L3NhbWw6U3ViamVjdENvbmZpcm1hdGlvbj48L3NhbWw6U3ViamVjdD48c2FtbDpDb25kaXRpb25zIE5vdEJlZm9yZT0iMjAyMS0wMS0yMFQwMTowMjo1NFoiIE5vdE9uT3JBZnRlcj0iMjA1Mi0wOS0yOFQwMjo1MDowNFoiPjxzYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+PHNhbWw6QXVkaWVuY2U+aHR0cDovL2xvY2FsaG9zdDo4MDgwL3NhbWwyL3NlcnZpY2UtcHJvdmlkZXItbWV0YWRhdGEvb25lPC9zYW1sOkF1ZGllbmNlPjwvc2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPjwvc2FtbDpDb25kaXRpb25zPjxzYW1sOkF1dGhuU3RhdGVtZW50IEF1dGhuSW5zdGFudD0iMjAyMS0wMS0yMFQwMDo0ODoyOVoiIFNlc3Npb25Ob3RPbk9yQWZ0ZXI9IjIwMjEtMDEtMjBUMDg6NDg6MjlaIiBTZXNzaW9uSW5kZXg9Il9lN2ExYTllNDk1YmZlMjI2NjQ5ZThkY2MzN2UxNDE1NDQ5NTIxNWQ2ZWIiPjxzYW1sOkF1dGhuQ29udGV4dD48c2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj51cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YWM6Y2xhc3NlczpQYXNzd29yZFByb3RlY3RlZFRyYW5zcG9ydDwvc2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj48L3NhbWw6QXV0aG5Db250ZXh0Pjwvc2FtbDpBdXRoblN0YXRlbWVudD48c2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+PHNhbWw6QXR0cmlidXRlIE5hbWU9InVpZCIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+dGVzdHVzZXJAc3ByaW5nLnNlY3VyaXR5LnNhbWw8L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48c2FtbDpBdHRyaWJ1dGUgTmFtZT0iZWR1UGVyc29uQWZmaWxpYXRpb24iIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPm1lbWJlcjwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj51c2VyPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU+PHNhbWw6QXR0cmlidXRlIE5hbWU9ImVtYWlsQWRkcmVzcyIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+dGVzdHVzZXJAc3ByaW5nLnNlY3VyaXR5LnNhbWw8L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48L3NhbWw6QXR0cmlidXRlU3RhdGVtZW50Pjwvc2FtbDpBc3NlcnRpb24+PC9zYW1scDpSZXNwb25zZT4="; - - static final Map> USER_ATTRIBUTES = new LinkedHashMap<>(); - - static { - USER_ATTRIBUTES.put("uid", Arrays.asList("testuser@spring.security.saml")); - USER_ATTRIBUTES.put("eduPersonAffiliation", Arrays.asList("member", "user")); - USER_ATTRIBUTES.put("emailAddress", Arrays.asList("testuser@spring.security.saml")); - } - - @Autowired - MockMvc mvc; - - @Autowired - WebClient webClient; - - @Test - public void indexWhenSamlResponseThenShowsUserInformation() throws Exception { - HttpSession session = this.mvc.perform(get("http://localhost:8080/")) - .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("http://localhost:8080/saml2/authenticate/one")) - .andReturn() - .getRequest().getSession(); - - this.mvc.perform(post("http://localhost:8080/login/saml2/sso/one") - .param("SAMLResponse", SIGNED_RESPONSE) - .session((MockHttpSession) session)) - .andExpect(redirectedUrl("http://localhost:8080/")); - - this.mvc.perform(get("http://localhost:8080/") - .session((MockHttpSession) session)) - .andExpect(model().attribute("emailAddress", "testuser@spring.security.saml")) - .andExpect(model().attribute("userAttributes", USER_ATTRIBUTES)); - } - - @Test - public void authenticationAttemptWhenValidThenShowsUserEmailAddress() throws Exception { - HtmlPage assertingParty = this.webClient.getPage("/"); - HtmlForm form = assertingParty.getFormByName("f"); - HtmlInput username = form.getInputByName("username"); - HtmlInput password = form.getInputByName("password"); - HtmlSubmitInput submit = assertingParty.getHtmlElementById("submit_button"); - username.setValueAttribute("user"); - password.setValueAttribute("password"); - HtmlPage relyingParty = submit.click(); - assertThat(relyingParty.asText()) - .contains("You're email address is testuser@spring.security.saml"); - } -} diff --git a/samples/boot/saml2login/src/main/java/sample/IndexController.java b/samples/boot/saml2login/src/main/java/sample/IndexController.java deleted file mode 100644 index 8da3c251eb8..00000000000 --- a/samples/boot/saml2login/src/main/java/sample/IndexController.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2002-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.GetMapping; - -@Controller -public class IndexController { - - @GetMapping("/") - public String index(Model model, - @AuthenticationPrincipal Saml2AuthenticatedPrincipal principal) { - String emailAddress = principal.getFirstAttribute("emailAddress"); - model.addAttribute("emailAddress", emailAddress); - model.addAttribute("userAttributes", principal.getAttributes()); - return "index"; - } -} diff --git a/samples/boot/saml2login/src/main/java/sample/Saml2LoginApplication.java b/samples/boot/saml2login/src/main/java/sample/Saml2LoginApplication.java deleted file mode 100644 index 08202d28def..00000000000 --- a/samples/boot/saml2login/src/main/java/sample/Saml2LoginApplication.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class Saml2LoginApplication { - - public static void main(String[] args) { - SpringApplication.run(Saml2LoginApplication.class, args); - } - -} diff --git a/samples/boot/saml2login/src/main/java/sample/SecurityConfig.java b/samples/boot/saml2login/src/main/java/sample/SecurityConfig.java deleted file mode 100644 index 434cf366681..00000000000 --- a/samples/boot/saml2login/src/main/java/sample/SecurityConfig.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2002-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrations; - -@Configuration -public class SecurityConfig { - @Bean - RelyingPartyRegistrationRepository relyingPartyRegistrationRepository() { - RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistrations - .fromMetadataLocation("https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/metadata.php") - .registrationId("one") - .build(); - return new InMemoryRelyingPartyRegistrationRepository(relyingPartyRegistration); - } -} diff --git a/samples/boot/saml2login/src/main/resources/application.yml b/samples/boot/saml2login/src/main/resources/application.yml deleted file mode 100644 index 8b137891791..00000000000 --- a/samples/boot/saml2login/src/main/resources/application.yml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/samples/boot/saml2login/src/main/resources/templates/index.html b/samples/boot/saml2login/src/main/resources/templates/index.html deleted file mode 100644 index b72bb58ce00..00000000000 --- a/samples/boot/saml2login/src/main/resources/templates/index.html +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - Spring Security - SAML 2.0 Login - - - - - -

SAML 2.0 Login with Spring Security

-

You are successfully logged in as

-

You're email address is

-

All Your Attributes

-
-
-
-
- - diff --git a/samples/boot/webflux-form/spring-security-samples-boot-webflux-form.gradle b/samples/boot/webflux-form/spring-security-samples-boot-webflux-form.gradle deleted file mode 100644 index 35d9a28149a..00000000000 --- a/samples/boot/webflux-form/spring-security-samples-boot-webflux-form.gradle +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2002-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -apply plugin: 'io.spring.convention.spring-sample-boot' - -dependencies { - compile project(':spring-security-core') - compile project(':spring-security-config') - compile project(':spring-security-web') - compile 'org.springframework.boot:spring-boot-starter-thymeleaf' - compile 'org.springframework.boot:spring-boot-starter-webflux' - compile 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5' - - - testCompile project(':spring-security-test') - testCompile 'org.springframework.boot:spring-boot-starter-test' - testCompile 'io.projectreactor:reactor-test' - testCompile 'org.skyscreamer:jsonassert' - testCompile 'org.springframework:spring-test' - - integrationTestCompile seleniumDependencies -} - diff --git a/samples/boot/webflux-form/src/integration-test/java/sample/WebfluxFormApplicationTests.java b/samples/boot/webflux-form/src/integration-test/java/sample/WebfluxFormApplicationTests.java deleted file mode 100644 index c9062faa59d..00000000000 --- a/samples/boot/webflux-form/src/integration-test/java/sample/WebfluxFormApplicationTests.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2002-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.openqa.selenium.WebDriver; -import org.openqa.selenium.htmlunit.HtmlUnitDriver; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.web.server.LocalServerPort; -import org.springframework.test.context.junit4.SpringRunner; - -import com.gargoylesoftware.htmlunit.BrowserVersion; - -import sample.webdriver.IndexPage; -import sample.webdriver.LoginPage; - -/** - * @author Rob Winch - * @since 5.0 - */ -@RunWith(SpringRunner.class) -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -public class WebfluxFormApplicationTests { - WebDriver driver; - - @LocalServerPort - int port; - - @Before - public void setup() { - this.driver = new HtmlUnitDriver(BrowserVersion.CHROME); - } - - @Test - public void loginWhenInvalidUsernameThenError() { - LoginPage login = IndexPage.to(this.driver, this.port, LoginPage.class); - login.assertAt(); - - login - .loginForm() - .username("invalid") - .password("password") - .submit(LoginPage.class) - .assertError(); - } - - @Test - public void loginAndLogout() { - LoginPage login = IndexPage.to(this.driver, this.port, LoginPage.class); - login.assertAt(); - - IndexPage index = login - .loginForm() - .username("user") - .password("password") - .submit(IndexPage.class); - index.assertAt(); - - login = index.logout(); - login - .assertAt() - .assertLogout(); - } -} diff --git a/samples/boot/webflux-form/src/integration-test/java/sample/webdriver/IndexPage.java b/samples/boot/webflux-form/src/integration-test/java/sample/webdriver/IndexPage.java deleted file mode 100644 index 9ca07b68f70..00000000000 --- a/samples/boot/webflux-form/src/integration-test/java/sample/webdriver/IndexPage.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2002-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample.webdriver; - -import org.openqa.selenium.WebDriver; -import org.openqa.selenium.WebElement; -import org.openqa.selenium.support.PageFactory; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Rob Winch - * @since 5.0 - */ -public class IndexPage { - - private WebDriver driver; - - private WebElement logout; - - public IndexPage(WebDriver webDriver) { - this.driver = webDriver; - } - - public static T to(WebDriver driver, int port, Class page) { - driver.get("http://localhost:" + port +"/"); - return PageFactory.initElements(driver, page); - } - - public IndexPage assertAt() { - assertThat(this.driver.getTitle()).isEqualTo("Secured"); - return this; - } - - public LoginPage logout() { - this.logout.click(); - return LoginPage.create(this.driver); - } -} diff --git a/samples/boot/webflux-form/src/integration-test/java/sample/webdriver/LoginPage.java b/samples/boot/webflux-form/src/integration-test/java/sample/webdriver/LoginPage.java deleted file mode 100644 index 10585338660..00000000000 --- a/samples/boot/webflux-form/src/integration-test/java/sample/webdriver/LoginPage.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2002-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample.webdriver; - -import org.openqa.selenium.WebDriver; -import org.openqa.selenium.WebElement; -import org.openqa.selenium.support.FindBy; -import org.openqa.selenium.support.PageFactory; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Rob Winch - * @since 5.0 - */ -public class LoginPage { - - private WebDriver driver; - @FindBy(css = "div[role=alert]") - private WebElement alert; - - private LoginForm loginForm; - - public LoginPage(WebDriver webDriver) { - this.driver = webDriver; - this.loginForm = PageFactory.initElements(webDriver, LoginForm.class); - } - - static LoginPage create(WebDriver driver) { - return PageFactory.initElements(driver, LoginPage.class); - } - - public LoginPage assertAt() { - assertThat(this.driver.getTitle()).isEqualTo("Please Log In"); - return this; - } - - public LoginPage assertError() { - assertThat(this.alert.getText()).isEqualTo("Invalid username and password."); - return this; - } - - public LoginPage assertLogout() { - assertThat(this.alert.getText()).isEqualTo("You have been logged out."); - return this; - } - - public LoginForm loginForm() { - return this.loginForm; - } - - public static class LoginForm { - private WebDriver driver; - private WebElement username; - private WebElement password; - @FindBy(css = "button[type=submit]") - private WebElement submit; - - public LoginForm(WebDriver driver) { - this.driver = driver; - } - - public LoginForm username(String username) { - this.username.sendKeys(username); - return this; - } - - public LoginForm password(String password) { - this.password.sendKeys(password); - return this; - } - - public T submit(Class page) { - this.submit.click(); - return PageFactory.initElements(this.driver, page); - } - } -} diff --git a/samples/boot/webflux-form/src/main/java/sample/IndexController.java b/samples/boot/webflux-form/src/main/java/sample/IndexController.java deleted file mode 100644 index d84301d78b9..00000000000 --- a/samples/boot/webflux-form/src/main/java/sample/IndexController.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2002-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; - -/** - * @author Rob Winch - * @since 5.0 - */ -@Controller -public class IndexController { - - @GetMapping("/") - public String index() { - return "index"; - } - - @GetMapping("/login") - public String login() { - return "login"; - } -} diff --git a/samples/boot/webflux-form/src/main/java/sample/WebfluxFormApplication.java b/samples/boot/webflux-form/src/main/java/sample/WebfluxFormApplication.java deleted file mode 100644 index 48ddabcdb18..00000000000 --- a/samples/boot/webflux-form/src/main/java/sample/WebfluxFormApplication.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2002-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - * @author Rob Winch - * @since 5.0 - */ -@SpringBootApplication -public class WebfluxFormApplication { - - public static void main(String[] args) { - SpringApplication.run(WebfluxFormApplication.class, args); - } -} diff --git a/samples/boot/webflux-form/src/main/java/sample/WebfluxFormSecurityConfig.java b/samples/boot/webflux-form/src/main/java/sample/WebfluxFormSecurityConfig.java deleted file mode 100644 index 94642742dce..00000000000 --- a/samples/boot/webflux-form/src/main/java/sample/WebfluxFormSecurityConfig.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.springframework.context.annotation.Bean; -import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; -import org.springframework.security.config.web.server.ServerHttpSecurity; -import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.web.server.SecurityWebFilterChain; - -import static org.springframework.security.config.Customizer.withDefaults; - -/** - * @author Rob Winch - * @since 5.0 - */ -@EnableWebFluxSecurity -public class WebfluxFormSecurityConfig { - - @Bean - public MapReactiveUserDetailsService userDetailsService() { - UserDetails user = User.withDefaultPasswordEncoder() - .username("user") - .password("password") - .roles("USER") - .build(); - return new MapReactiveUserDetailsService(user); - } - - @Bean - SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { - http - .authorizeExchange((exchanges) -> - exchanges - .pathMatchers("/login").permitAll() - .anyExchange().authenticated() - ) - .httpBasic(withDefaults()) - .formLogin((formLogin) -> - formLogin - .loginPage("/login") - ); - return http.build(); - } -} diff --git a/samples/boot/webflux-form/src/main/resources/logback.xml b/samples/boot/webflux-form/src/main/resources/logback.xml deleted file mode 100644 index 3ebbcc0ddd6..00000000000 --- a/samples/boot/webflux-form/src/main/resources/logback.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - - - - - - diff --git a/samples/boot/webflux-form/src/main/resources/templates/index.html b/samples/boot/webflux-form/src/main/resources/templates/index.html deleted file mode 100644 index 992450d43f5..00000000000 --- a/samples/boot/webflux-form/src/main/resources/templates/index.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - Secured - - - -
-

Secured

- -
- -
-
- - diff --git a/samples/boot/webflux-form/src/main/resources/templates/login.html b/samples/boot/webflux-form/src/main/resources/templates/login.html deleted file mode 100644 index 4fa5c65688d..00000000000 --- a/samples/boot/webflux-form/src/main/resources/templates/login.html +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - Please Log In - - - - -
- -
- - diff --git a/samples/boot/webflux-x509/spring-security-samples-boot-webflux-x509.gradle b/samples/boot/webflux-x509/spring-security-samples-boot-webflux-x509.gradle deleted file mode 100644 index 57196d2b2bd..00000000000 --- a/samples/boot/webflux-x509/spring-security-samples-boot-webflux-x509.gradle +++ /dev/null @@ -1,11 +0,0 @@ -apply plugin: 'io.spring.convention.spring-sample-boot' - -dependencies { - compile project(':spring-security-core') - compile project(':spring-security-config') - compile project(':spring-security-web') - compile 'org.springframework.boot:spring-boot-starter-webflux' - - testCompile project(':spring-security-test') - testCompile 'org.springframework.boot:spring-boot-starter-test' -} diff --git a/samples/boot/webflux-x509/src/main/java/sample/MeController.java b/samples/boot/webflux-x509/src/main/java/sample/MeController.java deleted file mode 100644 index 7c10c6aedfd..00000000000 --- a/samples/boot/webflux-x509/src/main/java/sample/MeController.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2002-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.springframework.security.core.context.ReactiveSecurityContextHolder; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import reactor.core.publisher.Mono; - -/** - * @author Alexey Nesterov - * @since 5.2 - */ -@RestController -@RequestMapping("/me") -public class MeController { - - @GetMapping - public Mono me() { - return ReactiveSecurityContextHolder.getContext() - .map(SecurityContext::getAuthentication) - .map((authentication) -> "Hello, " + authentication.getName()); - } -} diff --git a/samples/boot/webflux-x509/src/main/java/sample/WebfluxX509Application.java b/samples/boot/webflux-x509/src/main/java/sample/WebfluxX509Application.java deleted file mode 100644 index 89813d073b9..00000000000 --- a/samples/boot/webflux-x509/src/main/java/sample/WebfluxX509Application.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Bean; -import org.springframework.security.config.web.server.ServerHttpSecurity; -import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; -import org.springframework.security.core.userdetails.ReactiveUserDetailsService; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.web.server.SecurityWebFilterChain; - -import static org.springframework.security.config.Customizer.withDefaults; - -/** - * @author Alexey Nesterov - * @since 5.2 - */ -@SpringBootApplication -public class WebfluxX509Application { - - @Bean - public ReactiveUserDetailsService reactiveUserDetailsService() { - return new MapReactiveUserDetailsService( - User.withUsername("client").password("").authorities("ROLE_USER").build() - ); - } - - @Bean - public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { - // @formatter:off - http - .x509(withDefaults()) - .authorizeExchange((exchanges) -> - exchanges - .anyExchange().authenticated() - ); - // @formatter:on - - return http.build(); - } - - public static void main(String[] args) { - SpringApplication.run(WebfluxX509Application.class); - } -} diff --git a/samples/boot/webflux-x509/src/main/resources/application.yml b/samples/boot/webflux-x509/src/main/resources/application.yml deleted file mode 100644 index cdeb85fa7be..00000000000 --- a/samples/boot/webflux-x509/src/main/resources/application.yml +++ /dev/null @@ -1,8 +0,0 @@ -server: - port: 8443 - ssl: - key-store: 'classpath:./certs/server.p12' - key-store-password: 'password' - client-auth: need - trust-store: 'classpath:./certs/server.p12' - trust-store-password: 'password' diff --git a/samples/boot/webflux-x509/src/main/resources/certs/curl_app.sh b/samples/boot/webflux-x509/src/main/resources/certs/curl_app.sh deleted file mode 100644 index dbf7af4b43d..00000000000 --- a/samples/boot/webflux-x509/src/main/resources/certs/curl_app.sh +++ /dev/null @@ -1,2 +0,0 @@ - curl -vvvv --cacert out/DevCA.crt --cert out/localhost.crt --key out/localhost.key https://localhost:8443/me - diff --git a/samples/boot/webflux-x509/src/main/resources/certs/server.p12 b/samples/boot/webflux-x509/src/main/resources/certs/server.p12 deleted file mode 100644 index ec536b2f600..00000000000 Binary files a/samples/boot/webflux-x509/src/main/resources/certs/server.p12 and /dev/null differ diff --git a/samples/boot/webflux-x509/src/test/java/sample/WebfluxX509ApplicationTest.java b/samples/boot/webflux-x509/src/test/java/sample/WebfluxX509ApplicationTest.java deleted file mode 100644 index e6ed9cf200c..00000000000 --- a/samples/boot/webflux-x509/src/test/java/sample/WebfluxX509ApplicationTest.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2002-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import io.netty.handler.ssl.ClientAuth; -import io.netty.handler.ssl.SslContextBuilder; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.web.server.LocalServerPort; -import org.springframework.core.io.ClassPathResource; -import org.springframework.http.client.reactive.ClientHttpConnector; -import org.springframework.http.client.reactive.ReactorClientHttpConnector; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.reactive.server.WebTestClient; -import reactor.netty.http.client.HttpClient; - -import java.io.IOException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.UnrecoverableEntryException; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; - -import static org.assertj.core.api.Assertions.assertThat; - -@RunWith(SpringRunner.class) -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -public class WebfluxX509ApplicationTest { - - @LocalServerPort - int port; - - @Test - public void shouldExtractAuthenticationFromCertificate() throws Exception { - WebTestClient webTestClient = createWebTestClientWithClientCertificate(); - webTestClient - .get().uri("/me") - .exchange() - .expectStatus().isOk() - .expectBody() - .consumeWith((result) -> { - String responseBody = new String(result.getResponseBody()); - assertThat(responseBody).contains("Hello, client"); - }); - } - - private WebTestClient createWebTestClientWithClientCertificate() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, UnrecoverableEntryException { - ClassPathResource serverKeystore = new ClassPathResource("/certs/server.p12"); - - KeyStore keyStore = KeyStore.getInstance("PKCS12"); - keyStore.load(serverKeystore.getInputStream(), "password".toCharArray()); - - X509Certificate devCA = (X509Certificate) keyStore.getCertificate("DevCA"); - - X509Certificate clientCrt = (X509Certificate) keyStore.getCertificate("client"); - KeyStore.Entry keyStoreEntry = keyStore.getEntry("client", - new KeyStore.PasswordProtection("password".toCharArray())); - PrivateKey clientKey = ((KeyStore.PrivateKeyEntry) keyStoreEntry).getPrivateKey(); - - SslContextBuilder sslContextBuilder = SslContextBuilder - .forClient().clientAuth(ClientAuth.REQUIRE) - .trustManager(devCA) - .keyManager(clientKey, clientCrt); - - HttpClient httpClient = HttpClient.create().secure((sslContextSpec) -> sslContextSpec.sslContext(sslContextBuilder)); - ClientHttpConnector httpConnector = new ReactorClientHttpConnector(httpClient); - - return WebTestClient - .bindToServer(httpConnector) - .baseUrl("https://localhost:" + port) - .build(); - } -} diff --git a/samples/certificates/Readme.txt b/samples/certificates/Readme.txt deleted file mode 100644 index 64b415cf83c..00000000000 --- a/samples/certificates/Readme.txt +++ /dev/null @@ -1,10 +0,0 @@ -This directory contains certificates and keys for use with SSL in the sample applications. Certificates are issued by -our "Spring Security Test CA" certificate authority. - -ca.pem - the certificate authority's certificate. -server.jks - Java keystore containing the server certificate and privatekey. It Also contains the certificate authority - file and this is used as both keystore and truststore for they jetty server when running the samples with - the maven jetty plugin ("mvn jetty:run"). - -rod.p12, dianne.p12, scott.p12 are all certificate/key combinations for client authentication and can be installed in -your browser if you want to try out support for X.509 authentication. \ No newline at end of file diff --git a/samples/certificates/ca.pem b/samples/certificates/ca.pem deleted file mode 100644 index a5b52ca9d7e..00000000000 --- a/samples/certificates/ca.pem +++ /dev/null @@ -1,22 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDojCCAoqgAwIBAgIEMKX1dzANBgkqhkiG9w0BAQUFADCBiTELMAkGA1UEBhMC -R0IxETAPBgNVBAgTCFNjb3RsYW5kMRAwDgYDVQQHEwdHbGFzZ293MRkwFwYDVQQK -ExBTcHJpbmcgRnJhbWV3b3JrMRgwFgYDVQQLEw9TcHJpbmcgU2VjdXJpdHkxIDAe -BgNVBAMTF1NwcmluZyBTZWN1cml0eSBUZXN0IENBMB4XDTA4MDEyNTExMTIyMVoX -DTE4MDIyNTAwMDAwMFowgYkxCzAJBgNVBAYTAkdCMREwDwYDVQQIEwhTY290bGFu -ZDEQMA4GA1UEBxMHR2xhc2dvdzEZMBcGA1UEChMQU3ByaW5nIEZyYW1ld29yazEY -MBYGA1UECxMPU3ByaW5nIFNlY3VyaXR5MSAwHgYDVQQDExdTcHJpbmcgU2VjdXJp -dHkgVGVzdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALzl/wEe -snYrwqaGZuB8hmwACtptazh1+eXCfd66FkioxlLF7yTnjCC7DT+vmMgSuThIEIsN -xlxLpEgyU3bU8GIuR8wyYIyvuSMcptdFJLV7NKYuRycxpDuqimTM7Br0nfNgKVEv -1QwguGWr6YN3aZ68/xe/D5xyPhakKu++7VFXIXw9f0+nqojdrFTqQ6l9GAVRgfX6 -h4JOaV1VFx83y2pnFj0iFneVxRcvXyWnyXlcOvJDIyVuyS/hYxb+E5rtBvp5XQ0o -5CP4OMwCZGx/jEqlL8oO7BwEgu9aEBxKvoIKJmHDTHgWIxgawTrKabmong4utnMI -yNrhsI77bmh2U7UCAwEAAaMQMA4wDAYDVR0PBAUDAwcGADANBgkqhkiG9w0BAQUF -AAOCAQEAuD8W9Ukkfyi0y65mwguFVAqBC3RSTMRXcjbLQV4rMDM/Q9kjA6acY4Ta -WgxGTwNCydqaqwDVsmn+6Je8Lp2xm9KLDLypVdNopGs+Mlfo55dhwqymXkQw1oJI -CPhR3nBmGEnSWW0UY9bPlpxRF2D5GDVwpuxDtXvWa4baPwRRI9MxwPWHA3ITl+fc -s9QVKy+pRAnuP9MSIp755cJ1CODOn2ElNCqnxxsZmcWcmI3LkHAwTmegl3PVvhrk -MKMEA/neshh/M/hWGNTFt77Hoa7pU9dv5RCWFvZPqsUgPrwGrmUvcmSDir3lSWQm -SuSED2LKVo+BFqwWS+jp49AR9b8B/Q== ------END CERTIFICATE----- diff --git a/samples/certificates/dianne.p12 b/samples/certificates/dianne.p12 deleted file mode 100755 index 6e5ba218db7..00000000000 Binary files a/samples/certificates/dianne.p12 and /dev/null differ diff --git a/samples/certificates/localhost-with-ca/ca.crt b/samples/certificates/localhost-with-ca/ca.crt deleted file mode 100644 index 3371be56578..00000000000 --- a/samples/certificates/localhost-with-ca/ca.crt +++ /dev/null @@ -1,13 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIB9TCCAV4CCQCmYJRrKq63RDANBgkqhkiG9w0BAQUFADA+MQswCQYDVQQGEwJV -UzELMAkGA1UECAwCQ0ExEDAOBgNVBAoMB1Bpdm90YWwxEDAOBgNVBAMMB1Bpdm90 -YWwwIBcNMTMwODAxMTQzNTMyWhgPMjExMzA3MDgxNDM1MzJaMD4xCzAJBgNVBAYT -AlVTMQswCQYDVQQIDAJDQTEQMA4GA1UECgwHUGl2b3RhbDEQMA4GA1UEAwwHUGl2 -b3RhbDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEArcTaLxERklIzLRqWwvZK -arXaePSnq+U0fLmhlw6i9cn67vUGbUzjOkeezPf/fWVHK23bdZxbxTQHJh4g5gw5 -o80RYs7tfGuYJNF2EomAGg83TaqjttF3HW1Ewf2rvAJdfyQyMUS8CxxJeRDMYb9+ -jYE0g5A4oRgzNgYSinjB2M8CAwEAATANBgkqhkiG9w0BAQUFAAOBgQAN60FcUgjK -/FXKCOxJ81Y2HG2TE9n237uGKtXs/D2VEv6rogEqRTWBs8VvErH5dgOwmUHWo7Ys -UloaPWrMfEQ/MuQDHknVItUK1fmHxAhje4WsmX2vSnGLLeoWiL92DnO/E10tbMoI -Is0A7KS2r3FAoIKrMYZNkGhMYpV2aEbSKg== ------END CERTIFICATE----- diff --git a/samples/certificates/localhost-with-ca/ca.csr b/samples/certificates/localhost-with-ca/ca.csr deleted file mode 100644 index 839d47c4c16..00000000000 --- a/samples/certificates/localhost-with-ca/ca.csr +++ /dev/null @@ -1,11 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIIBfTCB5wIBADA+MQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExEDAOBgNVBAoM -B1Bpdm90YWwxEDAOBgNVBAMMB1Bpdm90YWwwgZ8wDQYJKoZIhvcNAQEBBQADgY0A -MIGJAoGBAK3E2i8REZJSMy0alsL2Smq12nj0p6vlNHy5oZcOovXJ+u71Bm1M4zpH -nsz3/31lRytt23WcW8U0ByYeIOYMOaPNEWLO7XxrmCTRdhKJgBoPN02qo7bRdx1t -RMH9q7wCXX8kMjFEvAscSXkQzGG/fo2BNIOQOKEYMzYGEop4wdjPAgMBAAGgADAN -BgkqhkiG9w0BAQUFAAOBgQAONY6xNj0ODLnb4sWdARQqmHt1yic0GRbN3GXTBVuA -IJ+tUF9OykTSqf5IzWpJL/7ATQFnTMW3qJ8e0sSn61QU7yKHlBHHLCy92mTV5Lq/ -CIe1uoC5dHaNe7HMfFouHBjydAnn9vlkvvu781xhS8VXoRgYt3Vi5edQ6AIZFf58 -CA== ------END CERTIFICATE REQUEST----- diff --git a/samples/certificates/localhost-with-ca/ca.key b/samples/certificates/localhost-with-ca/ca.key deleted file mode 100644 index 547c189f332..00000000000 --- a/samples/certificates/localhost-with-ca/ca.key +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICXgIBAAKBgQCtxNovERGSUjMtGpbC9kpqtdp49Ker5TR8uaGXDqL1yfru9QZt -TOM6R57M9/99ZUcrbdt1nFvFNAcmHiDmDDmjzRFizu18a5gk0XYSiYAaDzdNqqO2 -0XcdbUTB/au8Al1/JDIxRLwLHEl5EMxhv36NgTSDkDihGDM2BhKKeMHYzwIDAQAB -AoGBAITgykNBlyE/8FhmQ7EUazWMmGL0Gx+MZwWVYebET4MKj/OYtPBx4jSXvexZ -HCsEflbMbAxUo6x6K5lPYrLn2E6RxN3TFdlzG+lApuUi24oDON++p0Xa52aKQ6Ye -JSZLZZyDAUDN/byDgnf5BqnFwjJuv9tRdnguTB3W17uRqRrZAkEA3KarWpAKp0z5 -BiPZubNx9D94uwJVK4AaOIrdHZGpHe4qKkkIk7eZ95kopmavm5EsQBiRnTymWHJL -M+LU77i85QJBAMmbdN5mDd66HDdewjb3o125Kfcedu74gHoxOdeMnSZCop0GBtKQ -yeCi/pQzLm/wLaFwZ5NBurfipsY0YD71F6MCQQDEzO8mnjSyVWgCvvURuWhY9kej -XIhEfURlzA09s05IgMUg4/T/c5GjEfr8t7fHJCt4m7E8sfyYBJDonVdY3Me1AkEA -hptZ64e+KQCgCEQnbiXnmJMhttJLXIDk3zDwyr8iycHh6u90LLDpaSfKzE5j6e81 -uD1hmktfjJky+tFLlZ10+wJAVbart8oKuoNL7/J3TlDnk/ibOxiUrpGQ8GyEtUyY -/tnF0aeVhxfKaaOJy9E2wAaJ3ySqAvuuO5FYAo9sTu+KfQ== ------END RSA PRIVATE KEY----- diff --git a/samples/certificates/localhost-with-ca/ca.key.org b/samples/certificates/localhost-with-ca/ca.key.org deleted file mode 100644 index e04bb194f8d..00000000000 --- a/samples/certificates/localhost-with-ca/ca.key.org +++ /dev/null @@ -1,18 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -Proc-Type: 4,ENCRYPTED -DEK-Info: DES-EDE3-CBC,102D7604E722947D - -lyUWkOAgjDtEJbc3/EvenlBpRN3T4N8lb2NNJu7cr6V03sgfayE/5yFk046GA4JV -J0JcbU913KN0iQy+oQMcqsMPRSUGmF4rNw2zCWGjVcfva/bHHr5HzKRZEGuwz4A1 -VD5tv0bZoL1XoZleFT3WQ0kquY/jHHSP/N5mzIBvv25E5ILo04hrVEqbOB4l2hby -MRPL4deWFZ2aW9RF4TuNVlI3FkWCseNAIj7Go5SfxyZ+s37DwHfHdil8U68AxSjf -1ccjAAzwLEv8I4UzXKn0P8OsnPb3WWuldEMAHFp6CshHRHHEPbe9bfseOE3ZHnq6 -YZ7qu8BWtHyKSYLtUh7axY6JRHc4fT5LXVly8aVLSFjqfjyM4de3qZ5SpTWnxPlR -8OZuu+pcqbNqTJtNB6R9j4GYWSxIi3blq3D1LVtxUh+wmxZxbK3UZOHAzWWFIojX -INHyfVXu70tIQKGoyCwLVWyArHIYzt7ZZF/Sa0lwbZRJnJGCGf1b2+bIX9AsUbWC -Zmi+Yu9hMtdzdhqsmRnnrEkH+yhsx+w6q4UuoPv0sPFTD3PXlk1cJtKGDbOjRzsQ -dAcShQFJK+z59LqPkhi76tBnQ8/LTJZHLdTZc/pL8myGT8rxYwkr05kM5fQu8+SJ -qIzcnm830fGr4eFJsJ24KKB38yUnNdtWOkeeSe80Tm1uLYd+ZbcWMVNdoESG4KQV -VURDP4zhZx2d0/4VCPr13USoQJ7En4qRvdb8vAsNpXZga9eDMcykeRDY6Szb3K5C -tQhX1pawMDNaHAMAAKGMvH9mm6D7gA4RlKc8LFgol6o22piuFhtHWA== ------END RSA PRIVATE KEY----- diff --git a/samples/certificates/localhost-with-ca/generate.sh b/samples/certificates/localhost-with-ca/generate.sh deleted file mode 100644 index 39506dab093..00000000000 --- a/samples/certificates/localhost-with-ca/generate.sh +++ /dev/null @@ -1,13 +0,0 @@ -openssl genrsa -des3 -passout pass:changeit -out ca.key 1024 -openssl req -new -passin pass:changeit -key ca.key -out ca.csr -cp ca.key ca.key.org -openssl rsa -in ca.key.org -passin pass:changeit -out ca.key -openssl x509 -req -days 36500 -passin pass:changeit -in ca.csr -signkey ca.key -out ca.crt - -keytool -genkey -storepass changeit -alias tomcat -keyalg RSA -keytool -storepass changeit -alias tomcat -certreq -file tomcat.csr -echo 02 > serial.txt -openssl x509 -CA ca.crt -passin pass:changeit -CAkey ca.key -CAserial serial.txt -req -in tomcat.csr -out tomcat.cer -days 36500 -rm serial.txt -keytool -storepass changeit -import -alias ca -file ca.crt -keytool -storepass changeit -import -alias tomcat -file tomcat.cer diff --git a/samples/certificates/localhost-with-ca/tomcat.cer b/samples/certificates/localhost-with-ca/tomcat.cer deleted file mode 100644 index f0569b66020..00000000000 --- a/samples/certificates/localhost-with-ca/tomcat.cer +++ /dev/null @@ -1,16 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICkjCCAfsCAQMwDQYJKoZIhvcNAQEFBQAwPjELMAkGA1UEBhMCVVMxCzAJBgNV -BAgMAkNBMRAwDgYDVQQKDAdQaXZvdGFsMRAwDgYDVQQDDAdQaXZvdGFsMCAXDTEz -MDgwMTE0MzU1MVoYDzIxMTMwNzA4MTQzNTUxWjBfMQswCQYDVQQGEwJVUzELMAkG -A1UECBMCTU8xCzAJBgNVBAcTAktDMREwDwYDVQQKEwhTZWN1cml0eTEPMA0GA1UE -CxMGU3ByaW5nMRIwEAYDVQQDEwlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUA -A4IBDwAwggEKAoIBAQCKCyVI5D4h0lWU9D40P0kHkADWONRLQ9o0fcQvJmQaLwJN -tL0KpDiAwXiot4KKZWMGmYKoFtvhnd/t7ybZsODqsImDCZjKKvHaMTpFOyrl9SHW -PT7bvt7jBL47QqL/UJ0bApxVMmLD0DhcOmTkMmzm932F/he9w5z1nIsVl4POX+hQ -yyuuS+AvZdnUr5W2+COsvI1hsibrpnUnIvcXPHmSVrl4kD16OJ3Z/8Baia4mC3sy -pxqXCiBUeWbDsR5s9tZtMOJH7PpDbLsxGR3Zely4xWx7fmn/lW57EFyhMjXWZfUH -pTRsPWlFEYdTdnyM00MWWXrt+Y3kW9mb3w2DCf2fAgMBAAEwDQYJKoZIhvcNAQEF -BQADgYEAR1Y5IIfRrFIKTOc7gx4X2IOyByNdMYfd1+CWnEycUNuFwCE5iBJAbyN6 -yy2kViSFuTHwyJVD49QJNiFXOKZYmE8EFke1Y3nxwwe9MqfeTsrHpYGtpSZwDzv9 -64UM0qOPWxt+P9txQShcokldSt8BZ4iOJ9G6yY5EQdswE6rGkts= ------END CERTIFICATE----- diff --git a/samples/certificates/localhost-with-ca/tomcat.csr b/samples/certificates/localhost-with-ca/tomcat.csr deleted file mode 100644 index e6bbf82116d..00000000000 --- a/samples/certificates/localhost-with-ca/tomcat.csr +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN NEW CERTIFICATE REQUEST----- -MIIC1DCCAbwCAQAwXzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAk1PMQswCQYDVQQHEwJLQzERMA8G -A1UEChMIU2VjdXJpdHkxDzANBgNVBAsTBlNwcmluZzESMBAGA1UEAxMJbG9jYWxob3N0MIIBIjAN -BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAigslSOQ+IdJVlPQ+ND9JB5AA1jjUS0PaNH3ELyZk -Gi8CTbS9CqQ4gMF4qLeCimVjBpmCqBbb4Z3f7e8m2bDg6rCJgwmYyirx2jE6RTsq5fUh1j0+277e -4wS+O0Ki/1CdGwKcVTJiw9A4XDpk5DJs5vd9hf4XvcOc9ZyLFZeDzl/oUMsrrkvgL2XZ1K+Vtvgj -rLyNYbIm66Z1JyL3Fzx5kla5eJA9ejid2f/AWomuJgt7MqcalwogVHlmw7EebPbWbTDiR+z6Q2y7 -MRkd2XpcuMVse35p/5VuexBcoTI11mX1B6U0bD1pRRGHU3Z8jNNDFll67fmN5FvZm98Ngwn9nwID -AQABoDAwLgYJKoZIhvcNAQkOMSEwHzAdBgNVHQ4EFgQUpQODsrGvfB6TWTCIEEdx0OKB1+QwDQYJ -KoZIhvcNAQELBQADggEBACL6M4Htn6tEebOH8vj3R8cVcgebxshQV/KD7+tWUq2RSno4JndsYxEF -H3Zh3vWhh5Q0nH55s1C/kiKYNP0jQXheeAiH6hatiCpSssgvDnw653ivBgqT3mo8sy1jpw9Pdx7F -6JuCksus+aI9PUKuI3DXXyAxKJfc/JmnnCXsyZz8sVu66bMrIel0kAODN6Da35QohDuStNuplu/R -ZHoiapQi3dxmWctC30fz0y7xqRVbRUKWHE7YWXqtWjFusUjXtZJobMeEb6DLeFfRsJ50OG8kgyZy -TvWQ9kP3ODeDqq74xiy7NxwGH8ytsWEwpSC10Z35vWb++rtl963A2itoK4E= ------END NEW CERTIFICATE REQUEST----- diff --git a/samples/certificates/rod.p12 b/samples/certificates/rod.p12 deleted file mode 100755 index 4cd05644305..00000000000 Binary files a/samples/certificates/rod.p12 and /dev/null differ diff --git a/samples/certificates/scott.p12 b/samples/certificates/scott.p12 deleted file mode 100644 index f0a6357e730..00000000000 Binary files a/samples/certificates/scott.p12 and /dev/null differ diff --git a/samples/certificates/server.jks b/samples/certificates/server.jks deleted file mode 100755 index aaa1119fff4..00000000000 Binary files a/samples/certificates/server.jks and /dev/null differ diff --git a/samples/javaconfig/aspectj/spring-security-samples-javaconfig-aspectj.gradle b/samples/javaconfig/aspectj/spring-security-samples-javaconfig-aspectj.gradle deleted file mode 100644 index 6487a14d436..00000000000 --- a/samples/javaconfig/aspectj/spring-security-samples-javaconfig-aspectj.gradle +++ /dev/null @@ -1,20 +0,0 @@ -apply plugin: 'io.spring.convention.spring-sample' -apply plugin: 'io.freefair.aspectj.post-compile-weaving' - -repositories { - mavenCentral() -} - -dependencies { - compile project(':spring-security-config') - compile project(':spring-security-core') - - aspect project(':spring-security-aspects') - - runtime project(':spring-security-aspects') -} - -aspectj { - version = aspectjVersion -} - diff --git a/samples/javaconfig/aspectj/src/main/java/sample/aspectj/AspectjSecurityConfig.java b/samples/javaconfig/aspectj/src/main/java/sample/aspectj/AspectjSecurityConfig.java deleted file mode 100644 index 038b8271f32..00000000000 --- a/samples/javaconfig/aspectj/src/main/java/sample/aspectj/AspectjSecurityConfig.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2002-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample.aspectj; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.AdviceMode; -import org.springframework.context.annotation.Bean; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; -import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; - -/** - * @author Rob Winch - */ -@EnableGlobalMethodSecurity(mode = AdviceMode.ASPECTJ, securedEnabled = true) -public class AspectjSecurityConfig { - @Bean - public Service service() { - return new Service(); - } - - @Bean - public SecuredService securedService() { - return new SecuredService(); - } - - @Autowired - public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { - auth.inMemoryAuthentication(); - } -} diff --git a/samples/javaconfig/aspectj/src/main/java/sample/aspectj/SecuredService.java b/samples/javaconfig/aspectj/src/main/java/sample/aspectj/SecuredService.java deleted file mode 100644 index 9f81cf17339..00000000000 --- a/samples/javaconfig/aspectj/src/main/java/sample/aspectj/SecuredService.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2002-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample.aspectj; - -import org.springframework.security.access.annotation.Secured; - -/** - * Service which is secured on the class level - * - * @author Mike Wiesner - * @since 3.0 - */ -@Secured("ROLE_USER") -public class SecuredService { - - public void secureMethod() { - // nothing - } - -} diff --git a/samples/javaconfig/aspectj/src/test/java/sample/aspectj/AspectJInterceptorTests.java b/samples/javaconfig/aspectj/src/test/java/sample/aspectj/AspectJInterceptorTests.java deleted file mode 100644 index 344de63e11b..00000000000 --- a/samples/javaconfig/aspectj/src/test/java/sample/aspectj/AspectJInterceptorTests.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright 2002-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample.aspectj; - -import org.junit.After; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; - - -import org.springframework.security.core.Authentication; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; - -import java.lang.reflect.Proxy; - -import static org.assertj.core.api.Assertions.assertThat; - -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes = AspectjSecurityConfig.class) -public class AspectJInterceptorTests { - private Authentication admin = new UsernamePasswordAuthenticationToken("test", "xxx", - AuthorityUtils.createAuthorityList("ROLE_ADMIN")); - private Authentication user = new UsernamePasswordAuthenticationToken("test", "xxx", - AuthorityUtils.createAuthorityList("ROLE_USER")); - - @Autowired - private Service service; - - @Autowired - private SecuredService securedService; - - @Test - public void publicMethod() { - service.publicMethod(); - } - - @Test(expected = AuthenticationCredentialsNotFoundException.class) - public void securedMethodNotAuthenticated() { - service.secureMethod(); - } - - @Test(expected = AccessDeniedException.class) - public void securedMethodWrongRole() { - SecurityContextHolder.getContext().setAuthentication(admin); - service.secureMethod(); - } - - @Test - public void securedMethodEverythingOk() { - SecurityContextHolder.getContext().setAuthentication(user); - service.secureMethod(); - } - - @Test(expected = AuthenticationCredentialsNotFoundException.class) - public void securedClassNotAuthenticated() { - securedService.secureMethod(); - } - - @Test(expected = AccessDeniedException.class) - public void securedClassWrongRole() { - SecurityContextHolder.getContext().setAuthentication(admin); - securedService.secureMethod(); - } - - @Test(expected = AccessDeniedException.class) - public void securedClassWrongRoleOnNewedInstance() { - SecurityContextHolder.getContext().setAuthentication(admin); - new SecuredService().secureMethod(); - } - - @Test - public void securedClassEverythingOk() { - SecurityContextHolder.getContext().setAuthentication(user); - securedService.secureMethod(); - new SecuredService().secureMethod(); - } - - // SEC-2595 - @Test - public void notProxy() { - assertThat(Proxy.isProxyClass(securedService.getClass())).isFalse(); - } - - @After - public void tearDown() { - SecurityContextHolder.clearContext(); - } -} diff --git a/samples/javaconfig/aspectj/src/test/resources/logback-test.xml b/samples/javaconfig/aspectj/src/test/resources/logback-test.xml deleted file mode 100644 index 2d51ba4180a..00000000000 --- a/samples/javaconfig/aspectj/src/test/resources/logback-test.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - - - - - - - - - diff --git a/samples/javaconfig/concurrency/spring-security-samples-javaconfig-concurrency.gradle b/samples/javaconfig/concurrency/spring-security-samples-javaconfig-concurrency.gradle deleted file mode 100644 index 7a8e417d8ee..00000000000 --- a/samples/javaconfig/concurrency/spring-security-samples-javaconfig-concurrency.gradle +++ /dev/null @@ -1,20 +0,0 @@ -apply plugin: 'io.spring.convention.spring-sample-war' - -dependencies { - compile project(':spring-security-config') - compile project(':spring-security-core') - compile project(':spring-security-samples-javaconfig-messages') - compile project(':spring-security-web') - compile 'javax.servlet.jsp.jstl:javax.servlet.jsp.jstl-api' - compile 'javax.validation:validation-api' - compile 'org.hibernate:hibernate-validator' - compile 'org.springframework:spring-jdbc' - compile 'org.springframework:spring-webmvc' - compile slf4jDependencies - - providedCompile 'javax.servlet.jsp:javax.servlet.jsp-api' - providedCompile 'javax.servlet:javax.servlet-api:3.0.1' - - runtime 'ch.qos.logback:logback-classic' - runtime 'opensymphony:sitemesh' -} diff --git a/samples/javaconfig/concurrency/src/main/java/org/springframework/security/samples/config/MessageSecurityWebApplicationInitializer.java b/samples/javaconfig/concurrency/src/main/java/org/springframework/security/samples/config/MessageSecurityWebApplicationInitializer.java deleted file mode 100644 index c33dc58cf28..00000000000 --- a/samples/javaconfig/concurrency/src/main/java/org/springframework/security/samples/config/MessageSecurityWebApplicationInitializer.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2002-2013 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.samples.config; - -import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer; -import org.springframework.security.web.session.HttpSessionEventPublisher; - -/** - * We customize {@link AbstractSecurityWebApplicationInitializer} to enable the - * {@link HttpSessionEventPublisher}. - * - * @author Rob Winch - */ -public class MessageSecurityWebApplicationInitializer extends - AbstractSecurityWebApplicationInitializer { - - @Override - protected boolean enableHttpSessionEventPublisher() { - return true; - } -} diff --git a/samples/javaconfig/concurrency/src/main/java/org/springframework/security/samples/config/SecurityConfig.java b/samples/javaconfig/concurrency/src/main/java/org/springframework/security/samples/config/SecurityConfig.java deleted file mode 100644 index 45773f3eb77..00000000000 --- a/samples/javaconfig/concurrency/src/main/java/org/springframework/security/samples/config/SecurityConfig.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.samples.config; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; -import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; - -import static org.springframework.security.config.Customizer.withDefaults; - -@EnableWebSecurity -@EnableGlobalMethodSecurity(prePostEnabled = true) -public class SecurityConfig extends WebSecurityConfigurerAdapter { - // @formatter:off - @Autowired - public void configureGlobal( - AuthenticationManagerBuilder auth) throws Exception { - auth - .inMemoryAuthentication() - .withUser("user").password("password").roles("USER"); - } - // @formatter:on - - // @formatter:off - @Override - protected void configure( - HttpSecurity http) throws Exception { - http - .authorizeRequests((authorizeRequests) -> - authorizeRequests - .anyRequest().authenticated() - ) - .formLogin(withDefaults()) - .sessionManagement((sessionManagement) -> - sessionManagement - .sessionConcurrency((sessionConcurrency) -> - sessionConcurrency - .maximumSessions(1) - .expiredUrl("/login?expired") - ) - ); - } - // @formatter:on -} diff --git a/samples/javaconfig/concurrency/src/test/java/org/springframework/security/samples/config/SecurityConfigTests.java b/samples/javaconfig/concurrency/src/test/java/org/springframework/security/samples/config/SecurityConfigTests.java deleted file mode 100644 index dd807f16d14..00000000000 --- a/samples/javaconfig/concurrency/src/test/java/org/springframework/security/samples/config/SecurityConfigTests.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2002-2013 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.samples.config; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; - -/** - * @author Rob Winch - * - */ -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes = SecurityConfig.class) -public class SecurityConfigTests { - - @Test - public void securityConfigurationLoads() { - } -} diff --git a/samples/javaconfig/data/spring-security-samples-javaconfig-data.gradle b/samples/javaconfig/data/spring-security-samples-javaconfig-data.gradle deleted file mode 100644 index 94680d9b970..00000000000 --- a/samples/javaconfig/data/spring-security-samples-javaconfig-data.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'io.spring.convention.spring-sample' - -dependencies { - compile project(':spring-security-config') - compile project(':spring-security-data') - compile 'javax.validation:validation-api' - compile 'org.eclipse.persistence:javax.persistence' - compile 'org.hibernate:hibernate-entitymanager' - compile 'org.hibernate:hibernate-validator' - compile 'org.hsqldb:hsqldb' - compile 'org.springframework.data:spring-data-jpa' -} diff --git a/samples/javaconfig/data/src/main/java/samples/DataConfig.java b/samples/javaconfig/data/src/main/java/samples/DataConfig.java deleted file mode 100644 index cd401a5df47..00000000000 --- a/samples/javaconfig/data/src/main/java/samples/DataConfig.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2002-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package samples; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.DependsOn; -import org.springframework.core.io.ClassPathResource; -import org.springframework.data.jpa.repository.config.EnableJpaRepositories; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; -import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; -import org.springframework.orm.jpa.JpaTransactionManager; -import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; -import org.springframework.orm.jpa.vendor.Database; -import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; -import org.springframework.security.data.repository.query.SecurityEvaluationContextExtension; -import org.springframework.transaction.PlatformTransactionManager; -import samples.data.Message; - -import javax.sql.DataSource; - -/** - * @author Rob Winch - */ -@Configuration -@ComponentScan -@EnableJpaRepositories -public class DataConfig { - - @Bean - public SecurityEvaluationContextExtension expressionEvaluationContextProvider() { - return new SecurityEvaluationContextExtension(); - } - - @Bean - public DataSource dataSource() { - EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder(); - return builder.setType(EmbeddedDatabaseType.HSQL).build(); - } - - @Bean - public LocalContainerEntityManagerFactoryBean entityManagerFactory() { - HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter(); - vendorAdapter.setDatabase(Database.HSQL); - vendorAdapter.setGenerateDdl(true); - - LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean(); - factory.setJpaVendorAdapter(vendorAdapter); - factory.setPackagesToScan(Message.class.getPackage().getName()); - factory.setDataSource(dataSource()); - - return factory; - } - - @Bean - @DependsOn("entityManagerFactory") - public ResourceDatabasePopulator initDatabase(DataSource dataSource) throws Exception { - ResourceDatabasePopulator populator = new ResourceDatabasePopulator(); - populator.addScript(new ClassPathResource("data.sql")); - populator.populate(dataSource.getConnection()); - return populator; - } - - @Bean - public PlatformTransactionManager transactionManager() { - JpaTransactionManager txManager = new JpaTransactionManager(); - txManager.setEntityManagerFactory(entityManagerFactory().getObject()); - return txManager; - } -} diff --git a/samples/javaconfig/data/src/main/java/samples/data/Message.java b/samples/javaconfig/data/src/main/java/samples/data/Message.java deleted file mode 100644 index 7c567e9c123..00000000000 --- a/samples/javaconfig/data/src/main/java/samples/data/Message.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2002-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package samples.data; - -import java.util.Calendar; - -import javax.persistence.*; - -import org.hibernate.validator.constraints.NotEmpty; - -@Entity -public class Message { - @Id - @GeneratedValue(strategy = GenerationType.AUTO) - private Long id; - - @NotEmpty(message = "Message is required.") - private String text; - - @NotEmpty(message = "Summary is required.") - private String summary; - - private Calendar created = Calendar.getInstance(); - - @OneToOne - private User to; - - public User getTo() { - return to; - } - - public void setTo(User to) { - this.to = to; - } - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public Calendar getCreated() { - return created; - } - - public void setCreated(Calendar created) { - this.created = created; - } - - public String getText() { - return text; - } - - public void setText(String text) { - this.text = text; - } - - public String getSummary() { - return summary; - } - - public void setSummary(String summary) { - this.summary = summary; - } -} diff --git a/samples/javaconfig/data/src/main/java/samples/data/MessageRepository.java b/samples/javaconfig/data/src/main/java/samples/data/MessageRepository.java deleted file mode 100644 index c98a1d3fde5..00000000000 --- a/samples/javaconfig/data/src/main/java/samples/data/MessageRepository.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2002-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package samples.data; - -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -/** - * @author Rob Winch - */ -@Repository -public interface MessageRepository extends JpaRepository { -} diff --git a/samples/javaconfig/data/src/main/java/samples/data/SecurityMessageRepository.java b/samples/javaconfig/data/src/main/java/samples/data/SecurityMessageRepository.java deleted file mode 100644 index f6b2ac956d4..00000000000 --- a/samples/javaconfig/data/src/main/java/samples/data/SecurityMessageRepository.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2002-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package samples.data; - -import org.springframework.data.jpa.repository.Query; -import org.springframework.stereotype.Repository; - -import java.util.List; - -/** - * @author Rob Winch - */ -@Repository -public interface SecurityMessageRepository extends MessageRepository { - @Query("select m from Message m where m.to.id = ?#{ principal?.id }") - List findAll(); -} \ No newline at end of file diff --git a/samples/javaconfig/data/src/main/java/samples/data/User.java b/samples/javaconfig/data/src/main/java/samples/data/User.java deleted file mode 100644 index 15d4f37d77c..00000000000 --- a/samples/javaconfig/data/src/main/java/samples/data/User.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2002-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package samples.data; - -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; - -/** - * @author Rob Winch - */ -@Entity -public class User { - @GeneratedValue(strategy = GenerationType.AUTO) - @Id - private Long id; - - private String firstName; - - private String lastName; - - private String email; - - private String password; - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getFirstName() { - return firstName; - } - - public void setFirstName(String firstName) { - this.firstName = firstName; - } - - public String getLastName() { - return lastName; - } - - public void setLastName(String lastName) { - this.lastName = lastName; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } -} \ No newline at end of file diff --git a/samples/javaconfig/data/src/test/java/samples/data/SecurityMessageRepositoryTests.java b/samples/javaconfig/data/src/test/java/samples/data/SecurityMessageRepositoryTests.java deleted file mode 100644 index 9972659fd2c..00000000000 --- a/samples/javaconfig/data/src/test/java/samples/data/SecurityMessageRepositoryTests.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2002-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package samples.data; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import samples.DataConfig; - -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Rob Winch - */ -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes = DataConfig.class) -public class SecurityMessageRepositoryTests { - @Autowired - SecurityMessageRepository repository; - - User user; - - @Before - public void setup() { - user = new User(); - user.setId(0L); - List authorities = AuthorityUtils - .createAuthorityList("ROLE_USER"); - UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( - user, "notused", authorities); - SecurityContextHolder.getContext().setAuthentication(authentication); - } - - @After - public void cleanup() { - SecurityContextHolder.clearContext(); - } - - @Test - public void findAllOnlyToCurrentUser() { - Long expectedId = user.getId(); - List messages = repository.findAll(); - assertThat(messages).hasSize(3); - for (Message m : messages) { - assertThat(m.getTo().getId()).isEqualTo(expectedId); - } - } -} diff --git a/samples/javaconfig/data/src/test/resources/data.sql b/samples/javaconfig/data/src/test/resources/data.sql deleted file mode 100644 index 97c60b155bd..00000000000 --- a/samples/javaconfig/data/src/test/resources/data.sql +++ /dev/null @@ -1,10 +0,0 @@ -insert into user(id,email,password,firstName,lastName) values (0,'rob@example.com','password','Rob','Winch'); -insert into user(id,email,password,firstName,lastName) values (1,'luke@example.com','password','Luke','Taylor'); - -insert into message(id,created,to_id,summary,text) values (100,'2014-07-10 10:00:00',0,'Hello Rob','This message is for Rob'); -insert into message(id,created,to_id,summary,text) values (101,'2014-07-10 14:00:00',0,'How are you Rob?','This message is for Rob'); -insert into message(id,created,to_id,summary,text) values (102,'2014-07-11 22:00:00',0,'Is this secure?','This message is for Rob'); - -insert into message(id,created,to_id,summary,text) values (110,'2014-07-12 10:00:00',1,'Hello Luke','This message is for Luke'); -insert into message(id,created,to_id,summary,text) values (111,'2014-07-12 10:00:00',1,'Greetings Luke','This message is for Luke'); -insert into message(id,created,to_id,summary,text) values (112,'2014-07-12 10:00:00',1,'Is this secure?','This message is for Luke'); \ No newline at end of file diff --git a/samples/javaconfig/form/spring-security-samples-javaconfig-form.gradle b/samples/javaconfig/form/spring-security-samples-javaconfig-form.gradle deleted file mode 100644 index 3dee8959666..00000000000 --- a/samples/javaconfig/form/spring-security-samples-javaconfig-form.gradle +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2002-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -apply plugin: 'io.spring.convention.spring-sample-war' - -dependencies { - compile project(':spring-security-config') - compile project(':spring-security-core') - compile project(':spring-security-samples-javaconfig-messages') - compile project(':spring-security-web') - compile 'javax.servlet.jsp.jstl:javax.servlet.jsp.jstl-api' - compile 'javax.validation:validation-api' - compile 'org.hibernate:hibernate-validator' - compile 'org.springframework:spring-webmvc' - compile slf4jDependencies - - compile 'javax.xml.bind:jaxb-api' - compile 'com.sun.xml.bind:jaxb-core' - compile 'com.sun.xml.bind:jaxb-impl' - - providedCompile 'javax.servlet.jsp:javax.servlet.jsp-api' - providedCompile 'javax.servlet:javax.servlet-api' - - runtime 'opensymphony:sitemesh' - - integrationTestCompile seleniumDependencies -} diff --git a/samples/javaconfig/form/src/integration-test/java/org/springframework/security/samples/FormJcTests.java b/samples/javaconfig/form/src/integration-test/java/org/springframework/security/samples/FormJcTests.java deleted file mode 100644 index 1ca1e669a1c..00000000000 --- a/samples/javaconfig/form/src/integration-test/java/org/springframework/security/samples/FormJcTests.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2002-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.samples; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.openqa.selenium.WebDriver; -import org.openqa.selenium.htmlunit.HtmlUnitDriver; -import org.springframework.security.samples.pages.HomePage; -import org.springframework.security.samples.pages.LoginPage; - -/** - * @author Michael Simons - */ -public class FormJcTests { - private WebDriver driver; - - private int port; - - @Before - public void setup() { - this.port = Integer.parseInt(System.getProperty("app.httpPort")); - this.driver = new HtmlUnitDriver(); - } - - @After - public void tearDown() { - this.driver.quit(); - } - - @Test - public void accessHomePageWithUnauthenticatedUserSendsToLoginPage() { - final LoginPage loginPage = HomePage.to(this.driver, this.port); - loginPage.assertAt(); - } - - @Test - public void authenticatedUserIsSentToOriginalPage() { - final String userName = "user"; - final HomePage homePage = HomePage.to(this.driver, this.port) - .loginForm() - .username(userName) - .password("password") - .submit(); - homePage - .assertAt() - .andTheUserNameDisplayedIs(userName); - } - - @Test - public void authenticatedUserLogsOut() { - LoginPage loginPage = HomePage.to(this.driver, this.port) - .loginForm() - .username("user") - .password("password") - .submit() - .logout(); - loginPage.assertAt(); - - loginPage = HomePage.to(this.driver, this.port); - loginPage.assertAt(); - } -} diff --git a/samples/javaconfig/form/src/integration-test/java/org/springframework/security/samples/pages/HomePage.java b/samples/javaconfig/form/src/integration-test/java/org/springframework/security/samples/pages/HomePage.java deleted file mode 100644 index 22ddd1c6391..00000000000 --- a/samples/javaconfig/form/src/integration-test/java/org/springframework/security/samples/pages/HomePage.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2002-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.samples.pages; - -import org.openqa.selenium.WebDriver; -import org.openqa.selenium.WebElement; -import org.openqa.selenium.support.FindBy; -import org.openqa.selenium.support.PageFactory; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Michael Simons - */ -public class HomePage { - private final WebDriver webDriver; - - @FindBy(css = "input[type=submit]") - private WebElement logoutButton; - - public static LoginPage to(WebDriver driver, int port) { - driver.get("http://localhost:" + port +"/"); - return PageFactory.initElements(driver, LoginPage.class); - } - - public HomePage(WebDriver webDriver) { - this.webDriver = webDriver; - } - - public Content assertAt() { - assertThat(this.webDriver.getTitle()).isEqualTo("SecureMail: View All"); - return PageFactory.initElements(this.webDriver, Content.class); - } - - public LoginPage logout() { - this.logoutButton.submit(); - return PageFactory.initElements(this.webDriver, LoginPage.class); - } - - public static class Content { - @FindBy(css = "p.navbar-text") - private WebElement message; - - public Content andTheUserNameDisplayedIs(final String userName) { - assertThat(message.getText()).isEqualTo(userName); - return this; - } - } -} diff --git a/samples/javaconfig/form/src/integration-test/java/org/springframework/security/samples/pages/LoginPage.java b/samples/javaconfig/form/src/integration-test/java/org/springframework/security/samples/pages/LoginPage.java deleted file mode 100644 index af6fbaa9881..00000000000 --- a/samples/javaconfig/form/src/integration-test/java/org/springframework/security/samples/pages/LoginPage.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2002-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.samples.pages; - -import org.openqa.selenium.WebDriver; -import org.openqa.selenium.WebElement; -import org.openqa.selenium.support.FindBy; -import org.openqa.selenium.support.PageFactory; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Michael Simons - */ -public class LoginPage { - - private final WebDriver webDriver; - - private final LoginForm loginForm; - - public LoginPage(WebDriver webDriver) { - this.webDriver = webDriver; - this.loginForm = PageFactory.initElements(this.webDriver, LoginForm.class); - } - - public LoginPage assertAt() { - assertThat(this.webDriver.getTitle()).isEqualTo("SecureMail: Please Login"); - return this; - } - - public LoginForm loginForm() { - return this.loginForm; - } - - public static class LoginForm { - private WebDriver webDriver; - private WebElement username; - private WebElement password; - @FindBy(css = "button[type=submit]") - private WebElement submit; - - public LoginForm(WebDriver webDriver) { - this.webDriver = webDriver; - } - - public LoginForm username(String username) { - this.username.sendKeys(username); - return this; - } - - public LoginForm password(String password) { - this.password.sendKeys(password); - return this; - } - - public HomePage submit() { - this.submit.click(); - return PageFactory.initElements(this.webDriver, HomePage.class); - } - } -} diff --git a/samples/javaconfig/form/src/main/java/org/springframework/security/samples/config/MessageSecurityWebApplicationInitializer.java b/samples/javaconfig/form/src/main/java/org/springframework/security/samples/config/MessageSecurityWebApplicationInitializer.java deleted file mode 100644 index f851f82de06..00000000000 --- a/samples/javaconfig/form/src/main/java/org/springframework/security/samples/config/MessageSecurityWebApplicationInitializer.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2002-2013 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.samples.config; - -import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer; - -/** - * No customizations of {@link AbstractSecurityWebApplicationInitializer} are necessary. - * - * @author Rob Winch - */ -public class MessageSecurityWebApplicationInitializer extends - AbstractSecurityWebApplicationInitializer { -} diff --git a/samples/javaconfig/form/src/main/java/org/springframework/security/samples/config/SecurityConfig.java b/samples/javaconfig/form/src/main/java/org/springframework/security/samples/config/SecurityConfig.java deleted file mode 100644 index 8f9cbba6f65..00000000000 --- a/samples/javaconfig/form/src/main/java/org/springframework/security/samples/config/SecurityConfig.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.samples.config; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; -import org.springframework.security.core.userdetails.User; - -@EnableWebSecurity -public class SecurityConfig extends WebSecurityConfigurerAdapter { - - // @formatter:off - @Override - protected void configure(HttpSecurity http) throws Exception { - http - .authorizeRequests((authorizeRequests) -> - authorizeRequests - .antMatchers("/resources/**").permitAll() - .anyRequest().authenticated() - ) - .formLogin((formLogin) -> - formLogin - .loginPage("/login") - .permitAll() - ) - .logout((logout) -> - logout - .permitAll() - ); - } - // @formatter:on - - // @formatter:off - @Autowired - public void configureGlobal( - AuthenticationManagerBuilder auth) throws Exception { - auth - .inMemoryAuthentication() - .withUser(User.withDefaultPasswordEncoder().username("user").password("password").roles("USER")); - } - // @formatter:on -} diff --git a/samples/javaconfig/form/src/main/resources/logback.xml b/samples/javaconfig/form/src/main/resources/logback.xml deleted file mode 100644 index 3ebbcc0ddd6..00000000000 --- a/samples/javaconfig/form/src/main/resources/logback.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - - - - - - diff --git a/samples/javaconfig/form/src/main/resources/views/login.html b/samples/javaconfig/form/src/main/resources/views/login.html deleted file mode 100644 index c28c5b2add9..00000000000 --- a/samples/javaconfig/form/src/main/resources/views/login.html +++ /dev/null @@ -1,24 +0,0 @@ - - - Please Login - - -
-
-
- Please Login -
Invalid - username and password.
-
You - have been logged out.
- - -
- -
-
-
-
- - \ No newline at end of file diff --git a/samples/javaconfig/form/src/main/webapp/WEB-INF/views/login.jspx b/samples/javaconfig/form/src/main/webapp/WEB-INF/views/login.jspx deleted file mode 100644 index 1f472ba566d..00000000000 --- a/samples/javaconfig/form/src/main/webapp/WEB-INF/views/login.jspx +++ /dev/null @@ -1,36 +0,0 @@ - - - - -Please Login - - - - -
- Please Login - -
- Invalid username and password. -
-
- -
- You have been logged out. -
-
- - - - -
- -
-
-
- - -
diff --git a/samples/javaconfig/form/src/main/webapp/WEB-INF/views/messages/compose.jspx b/samples/javaconfig/form/src/main/webapp/WEB-INF/views/messages/compose.jspx deleted file mode 100644 index a92f7e2d6cf..00000000000 --- a/samples/javaconfig/form/src/main/webapp/WEB-INF/views/messages/compose.jspx +++ /dev/null @@ -1,26 +0,0 @@ - - - - - Compose - - -
-

Messages : Create

- - - - - - -
- -
-
-
- - -
\ No newline at end of file diff --git a/samples/javaconfig/form/src/main/webapp/WEB-INF/views/messages/inbox.jspx b/samples/javaconfig/form/src/main/webapp/WEB-INF/views/messages/inbox.jspx deleted file mode 100644 index ed02d313c6f..00000000000 --- a/samples/javaconfig/form/src/main/webapp/WEB-INF/views/messages/inbox.jspx +++ /dev/null @@ -1,40 +0,0 @@ - - - - - Inbox - - -

Inbox

- - - - - - - - - - - - - - - - - - - - - - - -
CreatedSummary
You have not received any mail yet.
- - -
\ No newline at end of file diff --git a/samples/javaconfig/form/src/main/webapp/WEB-INF/views/messages/show.jspx b/samples/javaconfig/form/src/main/webapp/WEB-INF/views/messages/show.jspx deleted file mode 100644 index 82007c267b0..00000000000 --- a/samples/javaconfig/form/src/main/webapp/WEB-INF/views/messages/show.jspx +++ /dev/null @@ -1,24 +0,0 @@ - - - - - <c:out value="${message.summary}"/> - - -
-

Message :

-
-
Created
-
-
Message
-
-
-
- - -
\ No newline at end of file diff --git a/samples/javaconfig/form/src/test/java/org/springframework/security/samples/config/SecurityConfigTests.java b/samples/javaconfig/form/src/test/java/org/springframework/security/samples/config/SecurityConfigTests.java deleted file mode 100644 index dd807f16d14..00000000000 --- a/samples/javaconfig/form/src/test/java/org/springframework/security/samples/config/SecurityConfigTests.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2002-2013 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.samples.config; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; - -/** - * @author Rob Winch - * - */ -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes = SecurityConfig.class) -public class SecurityConfigTests { - - @Test - public void securityConfigurationLoads() { - } -} diff --git a/samples/javaconfig/hellojs/spring-security-samples-javaconfig-hellojs.gradle b/samples/javaconfig/hellojs/spring-security-samples-javaconfig-hellojs.gradle deleted file mode 100644 index 86700d410d5..00000000000 --- a/samples/javaconfig/hellojs/spring-security-samples-javaconfig-hellojs.gradle +++ /dev/null @@ -1,20 +0,0 @@ -apply plugin: 'io.spring.convention.spring-sample-war' - -dependencies { - compile project(':spring-security-config') - compile project(':spring-security-core') - compile project(':spring-security-samples-javaconfig-messages') - compile project(':spring-security-web') - compile 'com.fasterxml.jackson.core:jackson-databind' - compile 'javax.servlet.jsp.jstl:javax.servlet.jsp.jstl-api' - compile 'javax.validation:validation-api' - compile 'org.hibernate:hibernate-validator' - compile 'org.springframework:spring-jdbc' - compile 'org.springframework:spring-webmvc' - compile slf4jDependencies - - providedCompile 'javax.servlet:javax.servlet-api' - providedCompile 'javax.servlet.jsp:javax.servlet.jsp-api' - - runtime 'opensymphony:sitemesh' -} diff --git a/samples/javaconfig/hellojs/src/main/java/org/springframework/security/samples/config/MessageSecurityWebApplicationInitializer.java b/samples/javaconfig/hellojs/src/main/java/org/springframework/security/samples/config/MessageSecurityWebApplicationInitializer.java deleted file mode 100644 index f851f82de06..00000000000 --- a/samples/javaconfig/hellojs/src/main/java/org/springframework/security/samples/config/MessageSecurityWebApplicationInitializer.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2002-2013 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.samples.config; - -import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer; - -/** - * No customizations of {@link AbstractSecurityWebApplicationInitializer} are necessary. - * - * @author Rob Winch - */ -public class MessageSecurityWebApplicationInitializer extends - AbstractSecurityWebApplicationInitializer { -} diff --git a/samples/javaconfig/hellojs/src/main/java/org/springframework/security/samples/config/SecurityConfig.java b/samples/javaconfig/hellojs/src/main/java/org/springframework/security/samples/config/SecurityConfig.java deleted file mode 100644 index 595d2413e0d..00000000000 --- a/samples/javaconfig/hellojs/src/main/java/org/springframework/security/samples/config/SecurityConfig.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2002-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.samples.config; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; - -@EnableWebSecurity -public class SecurityConfig { - - // @formatter:off - @Autowired - public void configureGlobal( - AuthenticationManagerBuilder auth) throws Exception { - auth - .inMemoryAuthentication() - .withUser("user").password("{noop}password").roles("USER"); - } - // @formatter:on -} diff --git a/samples/javaconfig/hellojs/src/main/java/org/springframework/security/samples/mvc/MessageJsonController.java b/samples/javaconfig/hellojs/src/main/java/org/springframework/security/samples/mvc/MessageJsonController.java deleted file mode 100644 index 696d3c75e71..00000000000 --- a/samples/javaconfig/hellojs/src/main/java/org/springframework/security/samples/mvc/MessageJsonController.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2002-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.samples.mvc; - -import java.util.ArrayList; -import java.util.Calendar; -import java.util.List; -import java.util.Optional; - -import javax.validation.Valid; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.security.samples.data.Message; -import org.springframework.security.samples.data.MessageRepository; -import org.springframework.stereotype.Controller; -import org.springframework.validation.BindingResult; -import org.springframework.validation.ObjectError; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.servlet.mvc.support.RedirectAttributes; - -@Controller -@RequestMapping(value = "/", produces = "application/json") -public class MessageJsonController { - private MessageRepository messageRepository; - - @Autowired - public MessageJsonController(MessageRepository messageRepository) { - this.messageRepository = messageRepository; - } - - @RequestMapping - public ResponseEntity> list() { - Iterable messages = messageRepository.findAll(); - return new ResponseEntity<>(messages, HttpStatus.OK); - } - - @RequestMapping("{id}") - public ResponseEntity> view(@PathVariable Long id) { - Optional message = messageRepository.findById(id); - return new ResponseEntity<>(message, HttpStatus.OK); - } - - @RequestMapping(method = RequestMethod.POST, consumes = "application/json") - public ResponseEntity create(@Valid @RequestBody Message message, - BindingResult result, RedirectAttributes redirect) { - if (result.hasErrors()) { - List errors = new ArrayList<>(result.getErrorCount()); - for (ObjectError r : result.getAllErrors()) { - errors.add(r.getDefaultMessage()); - } - return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST); - } - message.setCreated(Calendar.getInstance()); - message = messageRepository.save(message); - return new ResponseEntity<>(message, HttpStatus.OK); - } -} diff --git a/samples/javaconfig/hellojs/src/main/resources/resources/js/bootstrap.js b/samples/javaconfig/hellojs/src/main/resources/resources/js/bootstrap.js deleted file mode 100644 index ee5a14587b9..00000000000 --- a/samples/javaconfig/hellojs/src/main/resources/resources/js/bootstrap.js +++ /dev/null @@ -1,2280 +0,0 @@ -/* =================================================== - * bootstrap-transition.js v2.3.2 - * https://twitter.github.com/bootstrap/javascript.html#transitions - * =================================================== - * Copyright 2012 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ========================================================== */ - - -!function ($) { - - "use strict"; // jshint ;_; - - - /* CSS TRANSITION SUPPORT (https://www.modernizr.com/) - * ======================================================= */ - - $(function () { - - $.support.transition = (function () { - - var transitionEnd = (function () { - - var el = document.createElement('bootstrap') - , transEndEventNames = { - 'WebkitTransition' : 'webkitTransitionEnd' - , 'MozTransition' : 'transitionend' - , 'OTransition' : 'oTransitionEnd otransitionend' - , 'transition' : 'transitionend' - } - , name - - for (name in transEndEventNames){ - if (el.style[name] !== undefined) { - return transEndEventNames[name] - } - } - - }()) - - return transitionEnd && { - end: transitionEnd - } - - })() - - }) - -}(window.jQuery);/* ========================================================== - * bootstrap-alert.js v2.3.2 - * https://twitter.github.com/bootstrap/javascript.html#alerts - * ========================================================== - * Copyright 2012 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ========================================================== */ - - -!function ($) { - - "use strict"; // jshint ;_; - - - /* ALERT CLASS DEFINITION - * ====================== */ - - var dismiss = '[data-dismiss="alert"]' - , Alert = function (el) { - $(el).on('click', dismiss, this.close) - } - - Alert.prototype.close = function (e) { - var $this = $(this) - , selector = $this.attr('data-target') - , $parent - - if (!selector) { - selector = $this.attr('href') - selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 - } - - $parent = $(selector) - - e && e.preventDefault() - - $parent.length || ($parent = $this.hasClass('alert') ? $this : $this.parent()) - - $parent.trigger(e = $.Event('close')) - - if (e.isDefaultPrevented()) return - - $parent.removeClass('in') - - function removeElement() { - $parent - .trigger('closed') - .remove() - } - - $.support.transition && $parent.hasClass('fade') ? - $parent.on($.support.transition.end, removeElement) : - removeElement() - } - - - /* ALERT PLUGIN DEFINITION - * ======================= */ - - var old = $.fn.alert - - $.fn.alert = function (option) { - return this.each(function () { - var $this = $(this) - , data = $this.data('alert') - if (!data) $this.data('alert', (data = new Alert(this))) - if (typeof option == 'string') data[option].call($this) - }) - } - - $.fn.alert.Constructor = Alert - - - /* ALERT NO CONFLICT - * ================= */ - - $.fn.alert.noConflict = function () { - $.fn.alert = old - return this - } - - - /* ALERT DATA-API - * ============== */ - - $(document).on('click.alert.data-api', dismiss, Alert.prototype.close) - -}(window.jQuery);/* ============================================================ - * bootstrap-button.js v2.3.2 - * https://twitter.github.com/bootstrap/javascript.html#buttons - * ============================================================ - * Copyright 2012 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ============================================================ */ - - -!function ($) { - - "use strict"; // jshint ;_; - - - /* BUTTON PUBLIC CLASS DEFINITION - * ============================== */ - - var Button = function (element, options) { - this.$element = $(element) - this.options = $.extend({}, $.fn.button.defaults, options) - } - - Button.prototype.setState = function (state) { - var d = 'disabled' - , $el = this.$element - , data = $el.data() - , val = $el.is('input') ? 'val' : 'html' - - state = state + 'Text' - data.resetText || $el.data('resetText', $el[val]()) - - $el[val](data[state] || this.options[state]) - - // push to event loop to allow forms to submit - setTimeout(function () { - state == 'loadingText' ? - $el.addClass(d).attr(d, d) : - $el.removeClass(d).removeAttr(d) - }, 0) - } - - Button.prototype.toggle = function () { - var $parent = this.$element.closest('[data-toggle="buttons-radio"]') - - $parent && $parent - .find('.active') - .removeClass('active') - - this.$element.toggleClass('active') - } - - - /* BUTTON PLUGIN DEFINITION - * ======================== */ - - var old = $.fn.button - - $.fn.button = function (option) { - return this.each(function () { - var $this = $(this) - , data = $this.data('button') - , options = typeof option == 'object' && option - if (!data) $this.data('button', (data = new Button(this, options))) - if (option == 'toggle') data.toggle() - else if (option) data.setState(option) - }) - } - - $.fn.button.defaults = { - loadingText: 'loading...' - } - - $.fn.button.Constructor = Button - - - /* BUTTON NO CONFLICT - * ================== */ - - $.fn.button.noConflict = function () { - $.fn.button = old - return this - } - - - /* BUTTON DATA-API - * =============== */ - - $(document).on('click.button.data-api', '[data-toggle^=button]', function (e) { - var $btn = $(e.target) - if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn') - $btn.button('toggle') - }) - -}(window.jQuery);/* ========================================================== - * bootstrap-carousel.js v2.3.2 - * https://twitter.github.com/bootstrap/javascript.html#carousel - * ========================================================== - * Copyright 2012 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ========================================================== */ - - -!function ($) { - - "use strict"; // jshint ;_; - - - /* CAROUSEL CLASS DEFINITION - * ========================= */ - - var Carousel = function (element, options) { - this.$element = $(element) - this.$indicators = this.$element.find('.carousel-indicators') - this.options = options - this.options.pause == 'hover' && this.$element - .on('mouseenter', $.proxy(this.pause, this)) - .on('mouseleave', $.proxy(this.cycle, this)) - } - - Carousel.prototype = { - - cycle: function (e) { - if (!e) this.paused = false - if (this.interval) clearInterval(this.interval); - this.options.interval - && !this.paused - && (this.interval = setInterval($.proxy(this.next, this), this.options.interval)) - return this - } - - , getActiveIndex: function () { - this.$active = this.$element.find('.item.active') - this.$items = this.$active.parent().children() - return this.$items.index(this.$active) - } - - , to: function (pos) { - var activeIndex = this.getActiveIndex() - , that = this - - if (pos > (this.$items.length - 1) || pos < 0) return - - if (this.sliding) { - return this.$element.one('slid', function () { - that.to(pos) - }) - } - - if (activeIndex == pos) { - return this.pause().cycle() - } - - return this.slide(pos > activeIndex ? 'next' : 'prev', $(this.$items[pos])) - } - - , pause: function (e) { - if (!e) this.paused = true - if (this.$element.find('.next, .prev').length && $.support.transition.end) { - this.$element.trigger($.support.transition.end) - this.cycle(true) - } - clearInterval(this.interval) - this.interval = null - return this - } - - , next: function () { - if (this.sliding) return - return this.slide('next') - } - - , prev: function () { - if (this.sliding) return - return this.slide('prev') - } - - , slide: function (type, next) { - var $active = this.$element.find('.item.active') - , $next = next || $active[type]() - , isCycling = this.interval - , direction = type == 'next' ? 'left' : 'right' - , fallback = type == 'next' ? 'first' : 'last' - , that = this - , e - - this.sliding = true - - isCycling && this.pause() - - $next = $next.length ? $next : this.$element.find('.item')[fallback]() - - e = $.Event('slide', { - relatedTarget: $next[0] - , direction: direction - }) - - if ($next.hasClass('active')) return - - if (this.$indicators.length) { - this.$indicators.find('.active').removeClass('active') - this.$element.one('slid', function () { - var $nextIndicator = $(that.$indicators.children()[that.getActiveIndex()]) - $nextIndicator && $nextIndicator.addClass('active') - }) - } - - if ($.support.transition && this.$element.hasClass('slide')) { - this.$element.trigger(e) - if (e.isDefaultPrevented()) return - $next.addClass(type) - $next[0].offsetWidth // force reflow - $active.addClass(direction) - $next.addClass(direction) - this.$element.one($.support.transition.end, function () { - $next.removeClass([type, direction].join(' ')).addClass('active') - $active.removeClass(['active', direction].join(' ')) - that.sliding = false - setTimeout(function () { that.$element.trigger('slid') }, 0) - }) - } else { - this.$element.trigger(e) - if (e.isDefaultPrevented()) return - $active.removeClass('active') - $next.addClass('active') - this.sliding = false - this.$element.trigger('slid') - } - - isCycling && this.cycle() - - return this - } - - } - - - /* CAROUSEL PLUGIN DEFINITION - * ========================== */ - - var old = $.fn.carousel - - $.fn.carousel = function (option) { - return this.each(function () { - var $this = $(this) - , data = $this.data('carousel') - , options = $.extend({}, $.fn.carousel.defaults, typeof option == 'object' && option) - , action = typeof option == 'string' ? option : options.slide - if (!data) $this.data('carousel', (data = new Carousel(this, options))) - if (typeof option == 'number') data.to(option) - else if (action) data[action]() - else if (options.interval) data.pause().cycle() - }) - } - - $.fn.carousel.defaults = { - interval: 5000 - , pause: 'hover' - } - - $.fn.carousel.Constructor = Carousel - - - /* CAROUSEL NO CONFLICT - * ==================== */ - - $.fn.carousel.noConflict = function () { - $.fn.carousel = old - return this - } - - /* CAROUSEL DATA-API - * ================= */ - - $(document).on('click.carousel.data-api', '[data-slide], [data-slide-to]', function (e) { - var $this = $(this), href - , $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7 - , options = $.extend({}, $target.data(), $this.data()) - , slideIndex - - $target.carousel(options) - - if (slideIndex = $this.attr('data-slide-to')) { - $target.data('carousel').pause().to(slideIndex).cycle() - } - - e.preventDefault() - }) - -}(window.jQuery);/* ============================================================= - * bootstrap-collapse.js v2.3.2 - * https://twitter.github.com/bootstrap/javascript.html#collapse - * ============================================================= - * Copyright 2012 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ============================================================ */ - - -!function ($) { - - "use strict"; // jshint ;_; - - - /* COLLAPSE PUBLIC CLASS DEFINITION - * ================================ */ - - var Collapse = function (element, options) { - this.$element = $(element) - this.options = $.extend({}, $.fn.collapse.defaults, options) - - if (this.options.parent) { - this.$parent = $(this.options.parent) - } - - this.options.toggle && this.toggle() - } - - Collapse.prototype = { - - constructor: Collapse - - , dimension: function () { - var hasWidth = this.$element.hasClass('width') - return hasWidth ? 'width' : 'height' - } - - , show: function () { - var dimension - , scroll - , actives - , hasData - - if (this.transitioning || this.$element.hasClass('in')) return - - dimension = this.dimension() - scroll = $.camelCase(['scroll', dimension].join('-')) - actives = this.$parent && this.$parent.find('> .accordion-group > .in') - - if (actives && actives.length) { - hasData = actives.data('collapse') - if (hasData && hasData.transitioning) return - actives.collapse('hide') - hasData || actives.data('collapse', null) - } - - this.$element[dimension](0) - this.transition('addClass', $.Event('show'), 'shown') - $.support.transition && this.$element[dimension](this.$element[0][scroll]) - } - - , hide: function () { - var dimension - if (this.transitioning || !this.$element.hasClass('in')) return - dimension = this.dimension() - this.reset(this.$element[dimension]()) - this.transition('removeClass', $.Event('hide'), 'hidden') - this.$element[dimension](0) - } - - , reset: function (size) { - var dimension = this.dimension() - - this.$element - .removeClass('collapse') - [dimension](size || 'auto') - [0].offsetWidth - - this.$element[size !== null ? 'addClass' : 'removeClass']('collapse') - - return this - } - - , transition: function (method, startEvent, completeEvent) { - var that = this - , complete = function () { - if (startEvent.type == 'show') that.reset() - that.transitioning = 0 - that.$element.trigger(completeEvent) - } - - this.$element.trigger(startEvent) - - if (startEvent.isDefaultPrevented()) return - - this.transitioning = 1 - - this.$element[method]('in') - - $.support.transition && this.$element.hasClass('collapse') ? - this.$element.one($.support.transition.end, complete) : - complete() - } - - , toggle: function () { - this[this.$element.hasClass('in') ? 'hide' : 'show']() - } - - } - - - /* COLLAPSE PLUGIN DEFINITION - * ========================== */ - - var old = $.fn.collapse - - $.fn.collapse = function (option) { - return this.each(function () { - var $this = $(this) - , data = $this.data('collapse') - , options = $.extend({}, $.fn.collapse.defaults, $this.data(), typeof option == 'object' && option) - if (!data) $this.data('collapse', (data = new Collapse(this, options))) - if (typeof option == 'string') data[option]() - }) - } - - $.fn.collapse.defaults = { - toggle: true - } - - $.fn.collapse.Constructor = Collapse - - - /* COLLAPSE NO CONFLICT - * ==================== */ - - $.fn.collapse.noConflict = function () { - $.fn.collapse = old - return this - } - - - /* COLLAPSE DATA-API - * ================= */ - - $(document).on('click.collapse.data-api', '[data-toggle=collapse]', function (e) { - var $this = $(this), href - , target = $this.attr('data-target') - || e.preventDefault() - || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7 - , option = $(target).data('collapse') ? 'toggle' : $this.data() - $this[$(target).hasClass('in') ? 'addClass' : 'removeClass']('collapsed') - $(target).collapse(option) - }) - -}(window.jQuery);/* ============================================================ - * bootstrap-dropdown.js v2.3.2 - * https://twitter.github.com/bootstrap/javascript.html#dropdowns - * ============================================================ - * Copyright 2012 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ============================================================ */ - - -!function ($) { - - "use strict"; // jshint ;_; - - - /* DROPDOWN CLASS DEFINITION - * ========================= */ - - var toggle = '[data-toggle=dropdown]' - , Dropdown = function (element) { - var $el = $(element).on('click.dropdown.data-api', this.toggle) - $('html').on('click.dropdown.data-api', function () { - $el.parent().removeClass('open') - }) - } - - Dropdown.prototype = { - - constructor: Dropdown - - , toggle: function (e) { - var $this = $(this) - , $parent - , isActive - - if ($this.is('.disabled, :disabled')) return - - $parent = getParent($this) - - isActive = $parent.hasClass('open') - - clearMenus() - - if (!isActive) { - if ('ontouchstart' in document.documentElement) { - // if mobile we we use a backdrop because click events don't delegate - $('