1
0
mirror of synced 2026-09-05 00:41:22 +00:00

Compare commits

..

101 Commits

Author SHA1 Message Date
Eleftheria Stein 60e1db16ab Release 5.2.15.RELEASE 2021-12-20 20:40:02 +02:00
Eleftheria Stein 3a3399d388 Make gretty samples compatible with logback 1.2.9
Explicitly reference the logback.xml file to prevent gretty from configuring the defaults using groovy.

Issue gh-10642
2021-12-20 19:06:45 +02:00
Eleftheria Stein 0e06882b3f Update logback to 1.2.9
Closes gh-10642
2021-12-20 19:05:57 +02:00
Eleftheria Stein 7803089462 Next development version 2021-12-20 16:32:20 +02:00
Eleftheria Stein 58cba234c0 Revert "Next development version"
This reverts commit b856e90f8b.
2021-12-20 16:31:52 +02:00
Eleftheria Stein b856e90f8b Next development version 2021-12-20 16:30:07 +02:00
Eleftheria Stein 3f7041a8d9 Release 5.2.14.RELEASE 2021-12-20 13:28:54 +02:00
Eleftheria Stein ed03fe8154 Update to GAE 1.9.93
Closes gh-10628
2021-12-20 13:25:41 +02:00
Eleftheria Stein 21c01084ab Upgrade jboss jandex to 2.0.5.Final
Closes gh-10622
2021-12-20 12:57:59 +02:00
Eleftheria Stein 8922103d12 Upgrade jboss logging to 3.3.3.Final
Closes gh-10623
2021-12-20 12:57:33 +02:00
Eleftheria Stein c7727891e3 Update to hibernate-entitymanager 5.4.33
Closes gh-10624
2021-12-20 12:56:58 +02:00
Eleftheria Stein ee4bb21f6a Upgrade httpmime to 4.5.13
Closes gh-10627
2021-12-20 12:56:13 +02:00
Eleftheria Stein 1739a4f339 Upgrade httpcore to 4.4.15
Closes gh-10626
2021-12-20 12:55:18 +02:00
Eleftheria Stein 9d9c8956e3 Upgrade attoparser to 2.0.5.RELEASE
Closes gh-10625
2021-12-20 12:54:53 +02:00
Eleftheria Stein e11f90bb33 Upgrade Unbescape to 1.1.6.RELEASE
Closes gh-10621
2021-12-20 12:54:13 +02:00
Eleftheria Stein 0eb7ad597c Update to thymeleaf-spring5 3.0.14
Closes gh-10620
2021-12-20 12:40:53 +02:00
Eleftheria Stein 9c892c1c35 Update to embedded Tomcat websocket 8.5.73
Closes gh-10619
2021-12-20 12:37:39 +02:00
Eleftheria Stein c92dd46378 Upgrade to embedded Apache Tomcat 9.0.56
Closes gh-10618
2021-12-20 12:35:51 +02:00
Eleftheria Stein f3c28cca77 Upgrade Reactor to Dysprosium-SR25
Closes gh-10617
2021-12-20 12:33:19 +02:00
Eleftheria Stein 0f177a9964 Upgrade Spring Framework to 5.2.19.RELEASE
Closes gh-10616
2021-12-20 12:32:33 +02:00
Steve Riesenberg 65b3584ac6 Update copyright year
Issue gh-10557
2021-12-01 17:38:23 -06:00
Steve Riesenberg 1d814f95d5 Fix case sensitive headers comparison
Closes gh-10557
2021-12-01 17:03:18 -06:00
Marcus Da Coregio 01be7eca6e Improve log message when no CSRF token found
Closes gh-10436
2021-11-19 09:00:55 -03:00
Josh Cummings ff7f8f913d Fix setJWTClaimSetJWSKeySelector Typo
Closes gh-10504
2021-11-16 15:35:34 -07:00
Josh Cummings 76aff9df62 Fix jwtDecoder Documentation Usage
Closes gh-10505
2021-11-16 15:22:55 -07:00
Joe Grandja d963a13832 Next development version 2021-10-18 13:40:40 -04:00
Joe Grandja 01269e239d Release 5.2.13.RELEASE 2021-10-18 12:23:34 -04:00
Josh Cummings 21f0ccd088 Restructure SwitchUserFilter Logs
Issue gh-6311
2021-10-18 12:01:43 -04:00
Eleftheria Stein b1588c3d73 Upgrade Spring Framework to 5.2.18.RELEASE
Close gh-10375
2021-10-14 15:07:50 +02:00
Eleftheria Stein e74ae71382 Update to Jetty 9.4.44.v20210927
Closes gh-10378
2021-10-14 10:55:36 +02:00
Eleftheria Stein 302da19708 Update to embedded Tomcat websocket 8.5.72
Closes gh-10379
2021-10-14 10:55:13 +02:00
Eleftheria Stein 30fbc269b9 Upgrade Reactor to Dysprosium-SR24
Closes gh-10374
2021-10-14 10:54:49 +02:00
Eleftheria Stein 27866fb767 Update to nohttp 0.0.10
Closes gh-10377
2021-10-14 10:54:23 +02:00
Eleftheria Stein 006fa5ed70 Upgrade to embedded Apache Tomcat 9.0.54
Closes gh-10376
2021-10-14 10:33:56 +02:00
Rob Winch f6f351f419 Remove Unsupported JDKs
The JDKs 9 and 10 are no longer supported by Oracle and the
CAs are not up to date, so removing from the build.

18:13:02  * What went wrong:
18:13:02  Execution failed for task ':buildSrc:compileJava'.
18:13:02  > Could not resolve all files for configuration ':buildSrc:compileClasspath'.
18:13:02     > Could not resolve com.thaiopensource:trang:20091111.
18:13:02       Required by:
18:13:02           project :buildSrc
18:13:02        > Could not resolve com.thaiopensource:trang:20091111.
18:13:02           > Could not get resource 'https://repo.maven.apache.org/maven2/com/thaiopensource/trang/20091111/trang-20091111.pom'.
18:13:02              > Could not GET 'https://repo.maven.apache.org/maven2/com/thaiopensource/trang/20091111/trang-20091111.pom'.
18:13:02                 > sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
18:13:02     > Could not resolve net.sourceforge.saxon:saxon:9.1.0.8.
18:13:02       Required by:
18:13:02           project :buildSrc
18:13:02        > Could not resolve net.sourceforge.saxon:saxon:9.1.0.8.
18:13:02           > Could not get resource 'https://repo.maven.apache.org/maven2/net/sourceforge/saxon/saxon/9.1.0.8/saxon-9.1.0.8.pom'.
18:13:02              > Could not GET 'https://repo.maven.apache.org/maven2/net/sourceforge/saxon/saxon/9.1.0.8/saxon-9.1.0.8.pom'.
18:13:02                 > sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to request
2021-09-27 14:23:18 -05:00
heowc dbe2ef8758 Fix typo
Closes gh-10276
2021-09-22 16:56:36 -06:00
Rob Winch a15eb7eecf Remove finally block for junit
Allow this to be gathered by Gradle enterprise since if build is up to
date there will be no tests ran which causes failure. Additionally,
Gradle Enterprise displays the tests better than Jenkins.
2021-09-22 16:23:49 -05:00
Rob Winch b53cab9204 Add jenkins user to Jenkinsfile 2021-09-22 16:21:55 -05:00
Fabio Guenci 9925c6a4c0 Preserve Null Claim Values
Prior to this commit ClaimTypeConverter returned the claims with the
original value for all the claims with a null converted value.
The changes allows ClaimTypeConverter to overwrite and return claims
with converted value of null.

Closes gh-10135
2021-08-16 08:44:27 -06:00
Eleftheria Stein 0de2a51184 Next development version 2021-08-16 11:10:04 +02:00
Eleftheria Stein eaeb419d65 Release 5.2.12.RELEASE 2021-08-16 10:47:00 +02:00
Eleftheria Stein 060523292d Update to embedded Tomcat websocket 8.5.69
Closes gh-10170
2021-08-12 16:36:01 +02:00
Eleftheria Stein 523cdba8ae Update to org.slf4j 1.7.32
Closes gh-10168
2021-08-12 16:19:27 +02:00
Eleftheria Stein e4165c4c80 Update to Jetty 9.4.43.v20210629
Closes gh-10167
2021-08-12 16:19:03 +02:00
Eleftheria Stein 7c622dba33 Update to embedded Apache Tomcat 9.0.52
Closes gh-10166
2021-08-12 16:18:36 +02:00
Eleftheria Stein fb8672a31b Update to jaxb-impl 2.3.5
Closes gh-10165
2021-08-12 16:18:00 +02:00
Eleftheria Stein 7d92b7bda7 Update to Spring Framework 5.2.16.RELEASE
Closes gh-10164
2021-08-12 16:17:11 +02:00
Eleftheria Stein 410c891f75 Update to Reactor Dysprosium-SR22
Closes gh-10163
2021-08-12 16:16:43 +02:00
Eleftheria Stein 6bcf499884 Update to org.aspectj 1.9.7
Closes gh-10169
2021-08-12 16:16:13 +02:00
Steve Riesenberg e1b6a7ba29 Revert "URL encode client credentials"
This reverts commit c0200512a7.

Issue gh-9610 gh-9863
Closes gh-10018
2021-07-20 14:06:46 -05:00
Rob Winch 8bb69c4514 Update to use s01.oss.sonatype.org Maven Publishing
Closes gh-10024
2021-06-30 10:40:42 -05:00
Rob Winch a752919cc9 Update to spring-build-conventions:0.0.23.2.RELEASE
Closes gh-10029
2021-06-30 10:27:53 -05:00
/usr/local/ΕΨΗΕΛΩΝ 2c1126c5aa Improve AuthenticationManagerBeanDefinitionParser XML parsing
Closes gh-7282
2021-06-28 13:42:02 +02:00
Marcus Da Coregio a840aa7531 Next development version 2021-06-21 14:45:35 -03:00
Marcus Da Coregio 560fb35dc5 Relase 5.2.11.RELEASE 2021-06-21 08:45:10 -03:00
Eleftheria Stein 6753e1da68 Update to Spring LDAP Core 2.3.4.RELEASE
Closes gh-9968
2021-06-21 11:27:47 +02:00
Eleftheria Stein 703f1f1e04 Update to org.slf4j 1.7.31
Closes gh-9967
2021-06-21 11:26:11 +02:00
Eleftheria Stein f0d208667b Update to HSQLDB 2.5.2
Closes gh-9966
2021-06-21 11:25:05 +02:00
Eleftheria Stein dc7387075c Update to hibernate-entitymanager 5.4.32.Final
Closes gh-9965
2021-06-21 11:21:40 +02:00
Eleftheria Stein c193a06ac5 Update to Jetty 9.4.42.v20210604
Closes gh-9964
2021-06-21 11:19:18 +02:00
Eleftheria Stein 3e27f6aece Update to embedded Apache Tomcat 9.0.48
Closes gh-9963
2021-06-21 11:17:18 +02:00
Eleftheria Stein 4314c335c8 Update to embedded Tomcat websocket 8.5.68
Closes gh-9962
2021-06-21 11:15:38 +02:00
Eleftheria Stein c87c5eb888 Update ehcache to 2.10.9.2
Closes gh-9961
2021-06-21 11:13:54 +02:00
Eleftheria Stein 613ec13e95 Update to jaxb-impl 2.3.4
Closes gh-9960
2021-06-21 11:12:47 +02:00
Eleftheria Stein dabe2bacb0 Update to RSocket 1.0.5
Closes gh-9959
2021-06-21 11:12:22 +02:00
Eleftheria Stein 6afe47f164 Update to Spring Framework 5.2.15.RELEASE
Closes gh-9958
2021-06-21 11:08:49 +02:00
Eleftheria Stein 766a48c16e Update to Reactor Dysprosium-SR20
Closes gh-9957
2021-06-21 11:07:18 +02:00
Eleftheria Stein c2473ed979 Update to nohttp 0.0.8
Closes gh-9956
2021-06-21 11:05:39 +02:00
Eleftheria Stein f91608dcba Disable default logout page when logout disabled
Closes gh-9475
2021-06-18 10:52:00 +02:00
Steve Riesenberg 67a18f564a Store one request by default in WebSessionOAuth2ServerAuthorizationRequestRepository
Related to gh-9649
Closes gh-9857
2021-06-15 12:14:37 -05:00
Marcus Hert da Coregio 5a4cfe1226 Fix Adding Filter Relative to Custom Filter
Closes gh-9787
2021-06-14 17:36:52 -03:00
Josh Cummings 08f7a97ae7 Anonymous Authentication Argument Resolution Docs
Closes gh-3338
2021-06-08 16:31:08 -06:00
Josh Cummings c41aeed6cb Fix Getting Started Link
Closes gh-6502
2021-06-08 13:54:02 -06:00
Steve Riesenberg c0200512a7 URL encode client credentials
Closes gh-9610
2021-06-08 08:27:20 -05:00
Josh Cummings d5062bb828 PayloadInterceptorRSocket retains all payloads
Flux#skip discards its corresponding elements, meaning that they
aren't intended for reuse. When using RSocket's ByteBufPayloads,
this means that the bytes are releaseed back into RSocket's pool.

Since the downstream request may still need the skipped payload,
we should construct the publisher in a different way so as to
avoid the preemptive release.

Deferring Spring JavaFormat to clarify what changed.

Closes gh-9345
2021-06-04 13:47:48 -06:00
Eleftheria Stein 898bdeb0fd Fix Resource Server clock skew default value in docs
Closes gh-6611
2021-06-02 13:19:30 +03:00
Steve Riesenberg c79cb8eff6 Handle encoded spaces in the root dn
Fixes an issue where provider URLs passed to the constructor of the
DefaultSpringSecurityContextSource can be URL encoded, resulting in
an invalid base dn. Additionally adds support for list constructor
to support spaces in base dn.

Closes gh-9742

# Conflicts:
#	ldap/src/integration-test/java/org/springframework/security/ldap/DefaultSpringSecurityContextSourceTests.java
#	ldap/src/main/java/org/springframework/security/ldap/DefaultSpringSecurityContextSource.java
2021-05-26 12:57:48 -05:00
Steve Riesenberg d3a3c36ad3 Handle custom status codes in error handler
Fixes an issue where custom status codes in the error response cause an
IllegalArgumentException to be thrown when resolving an HttpStatus.

Closes gh-9741
2021-05-25 16:14:35 -05:00
Rob Winch c9a8419e22 Additional HttpSessionOAuth2AuthorizationRequestRepository tests
Issue gh-5145
2021-05-13 20:12:15 -04:00
Craig Andrews ecb4a5749a HttpSessionOAuth2AuthorizationRequestRepository: store one request by default
Add setAllowMultipleAuthorizationRequests allowing applications to
revert to the previous functionality should they need to do so.

Closes gh-5145
Intentionally regresses gh-5110
2021-05-13 20:12:00 -04:00
Rob Winch 362855b8b8 docs.af.pivotal.io->docs-ip.spring.io
The build conventions plugin does not support a property, so we must
override the configuration for docs.host to docs-ip.spring.io

Closes gh-9686
2021-04-27 10:26:51 -05:00
Rob Winch 0a56dc4ef5 docs.af.pivotal.io->docs-ip.spring.io
Closes gh-9686
2021-04-27 09:54:05 -05:00
Joe Grandja 99db0ca2c5 WebFlux httpBasic() matches on XHR requests
Closes gh-9660
2021-04-20 10:05:06 -04:00
Craig Andrews ab34c0308c Add guard around logger.debug statement
The log message involves string concatenation, the cost of which
should only be incurred if debug logging is enabled

Issue gh-9648
2021-04-16 10:57:53 -06:00
Rob Winch adf3e94c9f Fix HttpSecurity.addFilter* Ordering
Closes gh-9633
2021-04-14 21:18:51 -05:00
Denis Washington 521706d496 Limit oauth2Login() links to redirect-based flows
This prevents the generated login page from showing links for
authorization grant types like "client_credentials" which are
not redirect-based, and thus not meant for interactive use in
the browser.

Closes gh-9457
2021-04-14 06:41:52 -04:00
Eleftheria Stein ea19b31133 Next development version 2021-04-12 19:00:02 +02:00
Eleftheria Stein 46fdb250dc Release 5.2.10.RELEASE 2021-04-12 18:17:48 +02:00
Eleftheria Stein b500b3ea69 Update to OpenSAML 3.4.6
Closes gh-9607
2021-04-12 10:55:05 +02:00
Eleftheria Stein 59171434d5 Update to hibernate-entitymanager 5.4.30.Final
Closes gh-9606
2021-04-12 10:54:38 +02:00
Eleftheria Stein 5d18dd6d7d Update to Groovy 2.4.21
Closes gh-9605
2021-04-12 10:54:17 +02:00
Eleftheria Stein 41b0e51dbb Update to embedded Apache Tomcat 9.0.45
Closes gh-9604
2021-04-12 10:53:38 +02:00
Eleftheria Stein 310c1148ce Update blockhound to 1.0.6.RELEASE
Closes gh-9603
2021-04-12 10:53:11 +02:00
Eleftheria Stein 93defb2ff2 Update to RSocket 1.0.4
Closes gh-9602
2021-04-12 10:52:33 +02:00
Eleftheria Stein fb7efffad3 Update to Spring Data Moore-SR13
Closes gh-9601
2021-04-12 10:52:09 +02:00
Eleftheria Stein 6db79b70e6 Update to Spring Framework 5.2.13.RELEASE
Close gh-9600
2021-04-12 10:51:41 +02:00
Eleftheria Stein 78a618c260 Update to Reactor Dysprosium-SR18
Closes gh-9599
2021-04-12 10:51:11 +02:00
Eleftheria Stein cfc5256fad Update to GAE 1.9.88
Closes gh-9608
2021-04-12 10:50:46 +02:00
Eleftheria Stein 289b11b873 Update to nohttp 0.0.6.RELEASE
Closes gh-9609
2021-04-12 10:50:22 +02:00
佚名 8dc702c80f Add null check in CsrfFilter and CsrfWebFilter
Solve the problem that CsrfFilter and CsrfWebFilter
throws NPE exception when comparing two byte array
is equal in low JDK version.

When JDK version is lower than 1.8.0_45, method
java.security.MessageDigest#isEqual does not verify
whether the two arrays are null. And the above two
class call this method without null judgment.

ZiQiang Zhao<1694392889@qq.com>

Closes gh-9561
2021-04-09 21:57:14 -06:00
Josh Cummings bd0247adef Next Development Version 2021-02-11 12:22:42 -07:00
89 changed files with 2095 additions and 578 deletions
Vendored
+11 -48
View File
@@ -10,6 +10,7 @@ currentBuild.result = SUCCESS
def ARTIFACTORY_CREDENTIALS = usernamePassword(credentialsId: '02bd1690-b54f-4c9f-819d-a77cb7a9822c', usernameVariable: 'ARTIFACTORY_USERNAME', passwordVariable: 'ARTIFACTORY_PASSWORD')
def JENKINS_USER='-Duser.name="spring-builds+jenkins"'
try {
parallel check: {
@@ -20,15 +21,13 @@ try {
try {
withCredentials([ARTIFACTORY_CREDENTIALS]) {
withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) {
sh "./gradlew clean check -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace"
sh "./gradlew $JENKINS_USER clean check -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace"
}
}
} catch(Exception e) {
currentBuild.result = 'FAILED: check'
throw e
} finally {
junit '**/build/test-results/*/*.xml'
}
}
}
@@ -43,9 +42,9 @@ try {
withCredentials([ARTIFACTORY_CREDENTIALS]) {
withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) {
if ("master" == env.BRANCH_NAME) {
sh "./gradlew sonarqube -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD -PexcludeProjects='**/samples/**' -Dsonar.host.url=$SPRING_SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN --refresh-dependencies --no-daemon --stacktrace"
sh "./gradlew $JENKINS_USER sonarqube -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD -PexcludeProjects='**/samples/**' -Dsonar.host.url=$SPRING_SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN --refresh-dependencies --no-daemon --stacktrace"
} else {
sh "./gradlew sonarqube -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD -PexcludeProjects='**/samples/**' -Dsonar.projectKey='spring-security-${env.BRANCH_NAME}' -Dsonar.projectName='spring-security-${env.BRANCH_NAME}' -Dsonar.host.url=$SPRING_SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN --refresh-dependencies --no-daemon --stacktrace"
sh "./gradlew $JENKINS_USER sonarqube -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD -PexcludeProjects='**/samples/**' -Dsonar.projectKey='spring-security-${env.BRANCH_NAME}' -Dsonar.projectName='spring-security-${env.BRANCH_NAME}' -Dsonar.host.url=$SPRING_SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN --refresh-dependencies --no-daemon --stacktrace"
}
}
}
@@ -65,7 +64,7 @@ try {
try {
withCredentials([ARTIFACTORY_CREDENTIALS]) {
withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) {
sh "./gradlew clean test -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD -PforceMavenRepositories=snapshot -PspringVersion='5.2.+' -PreactorVersion=Dysprosium-BUILD-SNAPSHOT -PspringDataVersion=Lovelace-BUILD-SNAPSHOT --refresh-dependencies --no-daemon --stacktrace"
sh "./gradlew $JENKINS_USER clean test -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD -PforceMavenRepositories=snapshot -PspringVersion='5.2.+' -PreactorVersion=Dysprosium-BUILD-SNAPSHOT -PspringDataVersion=Lovelace-BUILD-SNAPSHOT --refresh-dependencies --no-daemon --stacktrace"
}
}
} catch(Exception e) {
@@ -75,42 +74,6 @@ try {
}
}
},
jdk9: {
stage('JDK 9') {
node {
checkout scm
sh "git clean -dfx"
try {
withCredentials([ARTIFACTORY_CREDENTIALS]) {
withEnv(["JAVA_HOME=${ tool 'jdk9' }"]) {
sh "./gradlew clean test -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace"
}
}
} catch(Exception e) {
currentBuild.result = 'FAILED: jdk9'
throw e
}
}
}
},
jdk10: {
stage('JDK 10') {
node {
checkout scm
sh "git clean -dfx"
try {
withCredentials([ARTIFACTORY_CREDENTIALS]) {
withEnv(["JAVA_HOME=${ tool 'jdk10' }"]) {
sh "./gradlew clean test -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace"
}
}
} catch(Exception e) {
currentBuild.result = 'FAILED: jdk10'
throw e
}
}
}
},
jdk11: {
stage('JDK 11') {
node {
@@ -119,7 +82,7 @@ try {
try {
withCredentials([ARTIFACTORY_CREDENTIALS]) {
withEnv(["JAVA_HOME=${ tool 'jdk11' }"]) {
sh "./gradlew clean test -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace"
sh "./gradlew $JENKINS_USER clean test -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace"
}
}
} catch(Exception e) {
@@ -137,7 +100,7 @@ try {
try {
withCredentials([ARTIFACTORY_CREDENTIALS]) {
withEnv(["JAVA_HOME=${ tool 'openjdk12' }"]) {
sh "./gradlew clean test -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace"
sh "./gradlew $JENKINS_USER clean test -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace"
}
}
} catch(Exception e) {
@@ -156,10 +119,10 @@ try {
sh "git clean -dfx"
withCredentials([file(credentialsId: 'spring-signing-secring.gpg', variable: 'SIGNING_KEYRING_FILE')]) {
withCredentials([string(credentialsId: 'spring-gpg-passphrase', variable: 'SIGNING_PASSWORD')]) {
withCredentials([usernamePassword(credentialsId: 'oss-token', passwordVariable: 'OSSRH_PASSWORD', usernameVariable: 'OSSRH_USERNAME')]) {
withCredentials([usernamePassword(credentialsId: 'oss-s01-token', passwordVariable: 'OSSRH_PASSWORD', usernameVariable: 'OSSRH_USERNAME')]) {
withCredentials([ARTIFACTORY_CREDENTIALS]) {
withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) {
sh "./gradlew deployArtifacts finalizeDeployArtifacts -Psigning.secretKeyRingFile=$SIGNING_KEYRING_FILE -Psigning.keyId=$SPRING_SIGNING_KEYID -Psigning.password='$SIGNING_PASSWORD' -PossrhUsername=$OSSRH_USERNAME -PossrhPassword=$OSSRH_PASSWORD -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace"
sh "./gradlew $JENKINS_USER deployArtifacts finalizeDeployArtifacts -Psigning.secretKeyRingFile=$SIGNING_KEYRING_FILE -Psigning.keyId=$SPRING_SIGNING_KEYID -Psigning.password='$SIGNING_PASSWORD' -PossrhTokenUsername=$OSSRH_USERNAME -PossrhTokenPassword=$OSSRH_PASSWORD -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace"
}
}
}
@@ -176,7 +139,7 @@ try {
withCredentials([file(credentialsId: 'docs.spring.io-jenkins_private_ssh_key', variable: 'DEPLOY_SSH_KEY')]) {
withCredentials([ARTIFACTORY_CREDENTIALS]) {
withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) {
sh "./gradlew deployDocs -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace"
sh "./gradlew $JENKINS_USER deployDocs -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace"
}
}
}
@@ -191,7 +154,7 @@ try {
withCredentials([file(credentialsId: 'docs.spring.io-jenkins_private_ssh_key', variable: 'DEPLOY_SSH_KEY')]) {
withCredentials([ARTIFACTORY_CREDENTIALS]) {
withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) {
sh "./gradlew deploySchema -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME --refresh-dependencies --no-daemon --stacktrace"
sh "./gradlew $JENKINS_USER deploySchema -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME --refresh-dependencies --no-daemon --stacktrace"
}
}
}
+1 -1
View File
@@ -19,7 +19,7 @@ Be sure to read the https://docs.spring.io/spring-security/site/docs/current/ref
Extensive JavaDoc for the Spring Security code is also available in the https://docs.spring.io/spring-security/site/docs/current/api/[Spring Security API Documentation].
== Quick Start
We recommend you visit https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/[Spring Security Reference] and read the "Getting Started" page.
See https://docs.spring.io/spring-security/site/docs/5.2.x/reference/html5/#servlet-hello[Hello Spring Security] to get started with a "Hello, World" application.
== Building from Source
Spring Security uses a https://gradle.org[Gradle]-based build system.
+2 -2
View File
@@ -1,8 +1,8 @@
buildscript {
dependencies {
classpath 'io.spring.gradle:spring-build-conventions:0.0.23.1.RELEASE'
classpath 'io.spring.gradle:spring-build-conventions:0.0.23.2.RELEASE'
classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion"
classpath 'io.spring.nohttp:nohttp-gradle:0.0.5.RELEASE'
classpath 'io.spring.nohttp:nohttp-gradle:0.0.10'
classpath "io.freefair.gradle:aspectj-plugin:4.0.2"
}
repositories {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 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.
@@ -15,7 +15,6 @@
*/
package org.springframework.security.config.annotation.web.builders;
import java.io.Serializable;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
@@ -53,14 +52,12 @@ import org.springframework.web.filter.CorsFilter;
* @author Rob Winch
* @since 3.2
*/
@SuppressWarnings("serial")
final class FilterComparator implements Comparator<Filter>, Serializable {
final class FilterOrderRegistration {
private static final int INITIAL_ORDER = 100;
private static final int ORDER_STEP = 100;
private final Map<String, Integer> filterToOrder = new HashMap<>();
FilterComparator() {
FilterOrderRegistration() {
Step order = new Step(INITIAL_ORDER, ORDER_STEP);
put(ChannelProcessingFilter.class, order.next());
put(ConcurrentSessionFilter.class, order.next());
@@ -111,78 +108,19 @@ final class FilterComparator implements Comparator<Filter>, Serializable {
put(SwitchUserFilter.class, order.next());
}
public int compare(Filter lhs, Filter rhs) {
Integer left = getOrder(lhs.getClass());
Integer right = getOrder(rhs.getClass());
return left - right;
}
/**
* Determines if a particular {@link Filter} is registered to be sorted
*
* @param filter
* @return
*/
public boolean isRegistered(Class<? extends Filter> filter) {
return getOrder(filter) != null;
}
/**
* Registers a {@link Filter} to exist after a particular {@link Filter} that is
* already registered.
* Register a {@link Filter} with its specific position. If the {@link Filter} was
* already registered before, the position previously defined is not going to be
* overriden
* @param filter the {@link Filter} to register
* @param afterFilter the {@link Filter} that is already registered and that
* {@code filter} should be placed after.
* @param position the position to associate with the {@link Filter}
*/
public void registerAfter(Class<? extends Filter> filter,
Class<? extends Filter> afterFilter) {
Integer position = getOrder(afterFilter);
if (position == null) {
throw new IllegalArgumentException(
"Cannot register after unregistered Filter " + afterFilter);
}
put(filter, position + 1);
}
/**
* Registers a {@link Filter} to exist at a particular {@link Filter} position
* @param filter the {@link Filter} to register
* @param atFilter the {@link Filter} that is already registered and that
* {@code filter} should be placed at.
*/
public void registerAt(Class<? extends Filter> filter,
Class<? extends Filter> atFilter) {
Integer position = getOrder(atFilter);
if (position == null) {
throw new IllegalArgumentException(
"Cannot register after unregistered Filter " + atFilter);
}
put(filter, position);
}
/**
* Registers a {@link Filter} to exist before a particular {@link Filter} that is
* already registered.
* @param filter the {@link Filter} to register
* @param beforeFilter the {@link Filter} that is already registered and that
* {@code filter} should be placed before.
*/
public void registerBefore(Class<? extends Filter> filter,
Class<? extends Filter> beforeFilter) {
Integer position = getOrder(beforeFilter);
if (position == null) {
throw new IllegalArgumentException(
"Cannot register after unregistered Filter " + beforeFilter);
}
put(filter, position - 1);
}
private void put(Class<? extends Filter> filter, int position) {
void put(Class<? extends Filter> filter, int position) {
String className = filter.getName();
filterToOrder.put(className, position);
if (this.filterToOrder.containsKey(className)) {
return;
}
this.filterToOrder.put(className, position);
}
/**
@@ -192,7 +130,7 @@ final class FilterComparator implements Comparator<Filter>, Serializable {
* @param clazz the {@link Filter} class to determine the sort order
* @return the sort order or null if not defined
*/
private Integer getOrder(Class<?> clazz) {
Integer getOrder(Class<?> clazz) {
while (clazz != null) {
Integer result = filterToOrder.get(clazz.getName());
if (result != null) {
@@ -16,6 +16,8 @@
package org.springframework.security.config.annotation.web.builders;
import org.springframework.context.ApplicationContext;
import org.springframework.core.OrderComparator;
import org.springframework.core.Ordered;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
@@ -78,10 +80,16 @@ import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
/**
@@ -125,9 +133,9 @@ public final class HttpSecurity extends
implements SecurityBuilder<DefaultSecurityFilterChain>,
HttpSecurityBuilder<HttpSecurity> {
private final RequestMatcherConfigurer requestMatcherConfigurer;
private List<Filter> filters = new ArrayList<>();
private List<OrderedFilter> filters = new ArrayList<>();
private RequestMatcher requestMatcher = AnyRequestMatcher.INSTANCE;
private FilterComparator comparator = new FilterComparator();
private FilterOrderRegistration filterOrders = new FilterOrderRegistration();
/**
* Creates a new instance
@@ -2528,8 +2536,12 @@ public final class HttpSecurity extends
@Override
protected DefaultSecurityFilterChain performBuild() {
filters.sort(comparator);
return new DefaultSecurityFilterChain(requestMatcher, filters);
this.filters.sort(OrderComparator.INSTANCE);
List<Filter> sortedFilters = new ArrayList<>(this.filters.size());
for (Filter filter : this.filters) {
sortedFilters.add(((OrderedFilter) filter).filter);
}
return new DefaultSecurityFilterChain(this.requestMatcher, sortedFilters);
}
/*
@@ -2570,8 +2582,7 @@ public final class HttpSecurity extends
* .servlet.Filter, java.lang.Class)
*/
public HttpSecurity addFilterAfter(Filter filter, Class<? extends Filter> afterFilter) {
comparator.registerAfter(filter.getClass(), afterFilter);
return addFilter(filter);
return addFilterAtOffsetOf(filter, 1, afterFilter);
}
/*
@@ -2583,8 +2594,14 @@ public final class HttpSecurity extends
*/
public HttpSecurity addFilterBefore(Filter filter,
Class<? extends Filter> beforeFilter) {
comparator.registerBefore(filter.getClass(), beforeFilter);
return addFilter(filter);
return addFilterAtOffsetOf(filter, -1, beforeFilter);
}
private HttpSecurity addFilterAtOffsetOf(Filter filter, int offset, Class<? extends Filter> registeredFilter) {
int order = this.filterOrders.getOrder(registeredFilter) + offset;
this.filters.add(new OrderedFilter(filter, order));
this.filterOrders.put(filter.getClass(), order);
return this;
}
/*
@@ -2595,14 +2612,12 @@ public final class HttpSecurity extends
* servlet.Filter)
*/
public HttpSecurity addFilter(Filter filter) {
Class<? extends Filter> filterClass = filter.getClass();
if (!comparator.isRegistered(filterClass)) {
throw new IllegalArgumentException(
"The Filter class "
+ filterClass.getName()
+ " does not have a registered order and cannot be added without a specified order. Consider using addFilterBefore or addFilterAfter instead.");
Integer order = this.filterOrders.getOrder(filter.getClass());
if (order == null) {
throw new IllegalArgumentException("The Filter class " + filter.getClass().getName()
+ " does not have a registered order and cannot be added without a specified order. Consider using addFilterBefore or addFilterAfter instead.");
}
this.filters.add(filter);
this.filters.add(new OrderedFilter(filter, order));
return this;
}
@@ -2626,8 +2641,7 @@ public final class HttpSecurity extends
* @return the {@link HttpSecurity} for further customizations
*/
public HttpSecurity addFilterAt(Filter filter, Class<? extends Filter> atFilter) {
this.comparator.registerAt(filter.getClass(), atFilter);
return addFilter(filter);
return addFilterAtOffsetOf(filter, 0, atFilter);
}
/**
@@ -3023,4 +3037,38 @@ public final class HttpSecurity extends
}
return apply(configurer);
}
/*
* A Filter that implements Ordered to be sorted. After sorting occurs, the original
* filter is what is used by FilterChainProxy
*/
private static final class OrderedFilter implements Ordered, Filter {
private final Filter filter;
private final int order;
private OrderedFilter(Filter filter, int order) {
this.filter = filter;
this.order = order;
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
this.filter.doFilter(servletRequest, servletResponse, filterChain);
}
@Override
public int getOrder() {
return this.order;
}
@Override
public String toString() {
return "OrderedFilter{" + "filter=" + this.filter + ", order=" + this.order + '}';
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 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.
@@ -100,7 +100,10 @@ public final class DefaultLoginPageConfigurer<H extends HttpSecurityBuilder<H>>
if (loginPageGeneratingFilter.isEnabled() && authenticationEntryPoint == null) {
loginPageGeneratingFilter = postProcess(loginPageGeneratingFilter);
http.addFilter(loginPageGeneratingFilter);
http.addFilter(this.logoutPageGeneratingFilter);
LogoutConfigurer<H> logoutConfigurer = http.getConfigurer(LogoutConfigurer.class);
if (logoutConfigurer != null) {
http.addFilter(this.logoutPageGeneratingFilter);
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 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.
@@ -57,6 +57,7 @@ import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequest
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
@@ -686,10 +687,12 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>> exten
this.authorizationEndpointConfig.authorizationRequestBaseUri :
OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI;
Map<String, String> loginUrlToClientName = new HashMap<>();
clientRegistrations.forEach(registration -> loginUrlToClientName.put(
authorizationRequestBaseUri + "/" + registration.getRegistrationId(),
registration.getClientName()));
clientRegistrations.forEach((registration) -> {
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(registration.getAuthorizationGrantType())) {
String authorizationRequestUri = authorizationRequestBaseUri + "/" + registration.getRegistrationId();
loginUrlToClientName.put(authorizationRequestUri, registration.getClientName());
}
});
return loginUrlToClientName;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 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.
@@ -52,6 +52,8 @@ public class AuthenticationManagerBeanDefinitionParser implements BeanDefinition
private static final String ATT_REF = "ref";
private static final String ATT_ERASE_CREDENTIALS = "erase-credentials";
private static final String AUTHENTICATION_EVENT_PUBLISHER_BEAN_NAME = "defaultAuthenticationEventPublisher";
public BeanDefinition parse(Element element, ParserContext pc) {
String id = element.getAttribute("id");
@@ -124,12 +126,14 @@ public class AuthenticationManagerBeanDefinitionParser implements BeanDefinition
false);
}
// Add the default event publisher
BeanDefinition publisher = new RootBeanDefinition(
DefaultAuthenticationEventPublisher.class);
String pubId = pc.getReaderContext().generateBeanName(publisher);
pc.registerBeanComponent(new BeanComponentDefinition(publisher, pubId));
providerManagerBldr.addPropertyReference("authenticationEventPublisher", pubId);
if (!pc.getRegistry().containsBeanDefinition(AUTHENTICATION_EVENT_PUBLISHER_BEAN_NAME)) {
// Add the default event publisher to the context
BeanDefinition publisher = new RootBeanDefinition(DefaultAuthenticationEventPublisher.class);
pc.registerBeanComponent(new BeanComponentDefinition(publisher, AUTHENTICATION_EVENT_PUBLISHER_BEAN_NAME));
}
providerManagerBldr.addPropertyReference("authenticationEventPublisher",
AUTHENTICATION_EVENT_PUBLISHER_BEAN_NAME);
pc.registerBeanComponent(new BeanComponentDefinition(providerManagerBldr
.getBeanDefinition(), id));
@@ -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.
@@ -33,6 +33,7 @@ import java.util.function.Supplier;
import org.springframework.security.oauth2.client.web.server.ServerAuthorizationRequestRepository;
import org.springframework.security.oauth2.client.web.server.WebSessionOAuth2ServerAuthorizationRequestRepository;
import org.springframework.http.HttpStatus;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
import reactor.core.publisher.Mono;
@@ -85,6 +86,7 @@ import org.springframework.security.oauth2.client.web.server.ServerOAuth2Authori
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.client.web.server.WebSessionOAuth2ServerAuthorizationRequestRepository;
import org.springframework.security.oauth2.client.web.server.authentication.OAuth2LoginAuthenticationWebFilter;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.oauth2.core.user.OAuth2User;
@@ -112,6 +114,7 @@ import org.springframework.security.web.server.WebFilterExchange;
import org.springframework.security.web.server.authentication.AnonymousAuthenticationWebFilter;
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
import org.springframework.security.web.server.authentication.HttpBasicServerAuthenticationEntryPoint;
import org.springframework.security.web.server.authentication.HttpStatusServerEntryPoint;
import org.springframework.security.web.server.authentication.ReactivePreAuthenticatedAuthenticationManager;
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationEntryPoint;
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationFailureHandler;
@@ -1285,7 +1288,11 @@ public class ServerHttpSecurity {
return Collections.emptyMap();
}
Map<String, String> result = new HashMap<>();
registrations.iterator().forEachRemaining(r -> result.put("/oauth2/authorization/" + r.getRegistrationId(), r.getClientName()));
registrations.iterator().forEachRemaining((r) -> {
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(r.getAuthorizationGrantType())) {
result.put("/oauth2/authorization/" + r.getRegistrationId(), r.getClientName());
}
});
return result;
}
@@ -2960,11 +2967,17 @@ public class ServerHttpSecurity {
* @see #httpBasic()
*/
public class HttpBasicSpec {
private final ServerWebExchangeMatcher xhrMatcher = (exchange) -> Mono.just(exchange.getRequest().getHeaders())
.filter((h) -> h.getOrEmpty("X-Requested-With").contains("XMLHttpRequest"))
.flatMap((h) -> ServerWebExchangeMatcher.MatchResult.match())
.switchIfEmpty(ServerWebExchangeMatcher.MatchResult.notMatch());
private ReactiveAuthenticationManager authenticationManager;
private ServerSecurityContextRepository securityContextRepository;
private ServerAuthenticationEntryPoint entryPoint = new HttpBasicServerAuthenticationEntryPoint();
private ServerAuthenticationEntryPoint entryPoint;
/**
* The {@link ReactiveAuthenticationManager} used to authenticate. Defaults to
@@ -3029,7 +3042,13 @@ public class ServerHttpSecurity {
MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_XML,
MediaType.MULTIPART_FORM_DATA, MediaType.TEXT_XML);
restMatcher.setIgnoredMediaTypes(Collections.singleton(MediaType.ALL));
ServerHttpSecurity.this.defaultEntryPoints.add(new DelegateEntry(restMatcher, this.entryPoint));
ServerWebExchangeMatcher notHtmlMatcher = new NegatedServerWebExchangeMatcher(
new MediaTypeServerWebExchangeMatcher(MediaType.TEXT_HTML));
ServerWebExchangeMatcher restNotHtmlMatcher = new AndServerWebExchangeMatcher(
Arrays.asList(notHtmlMatcher, restMatcher));
ServerWebExchangeMatcher preferredMatcher = new OrServerWebExchangeMatcher(
Arrays.asList(this.xhrMatcher, restNotHtmlMatcher));
ServerHttpSecurity.this.defaultEntryPoints.add(new DelegateEntry(preferredMatcher, this.entryPoint));
AuthenticationWebFilter authenticationFilter = new AuthenticationWebFilter(
this.authenticationManager);
authenticationFilter.setAuthenticationFailureHandler(new ServerAuthenticationEntryPointFailureHandler(this.entryPoint));
@@ -3038,7 +3057,15 @@ public class ServerHttpSecurity {
http.addFilterAt(authenticationFilter, SecurityWebFiltersOrder.HTTP_BASIC);
}
private HttpBasicSpec() {}
private HttpBasicSpec() {
List<DelegateEntry> entryPoints = new ArrayList<>();
entryPoints
.add(new DelegateEntry(this.xhrMatcher, new HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED)));
DelegatingServerAuthenticationEntryPoint defaultEntryPoint = new DelegatingServerAuthenticationEntryPoint(
entryPoints);
defaultEntryPoint.setDefaultEntryPoint(new HttpBasicServerAuthenticationEntryPoint());
this.entryPoint = defaultEntryPoint;
}
}
/**
@@ -3239,7 +3266,10 @@ public class ServerHttpSecurity {
}
if (loginPage != null) {
http.addFilterAt(loginPage, SecurityWebFiltersOrder.LOGIN_PAGE_GENERATING);
http.addFilterAt(new LogoutPageGeneratingWebFilter(), SecurityWebFiltersOrder.LOGOUT_PAGE_GENERATING);
if (http.logout != null) {
http.addFilterAt(new LogoutPageGeneratingWebFilter(),
SecurityWebFiltersOrder.LOGOUT_PAGE_GENERATING);
}
}
}
@@ -0,0 +1,75 @@
/*
* 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.config.annotation.web.builders;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.junit.Test;
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
import static org.assertj.core.api.Assertions.assertThat;
public class FilterOrderRegistrationTests {
private final FilterOrderRegistration filterOrderRegistration = new FilterOrderRegistration();
@Test
public void putWhenNewFilterThenInsertCorrect() {
int position = 153;
this.filterOrderRegistration.put(MyFilter.class, position);
Integer order = this.filterOrderRegistration.getOrder(MyFilter.class);
assertThat(order).isEqualTo(position);
}
@Test
public void putWhenCustomFilterAlreadyExistsThenDoesNotOverride() {
int position = 160;
this.filterOrderRegistration.put(MyFilter.class, position);
this.filterOrderRegistration.put(MyFilter.class, 173);
Integer order = this.filterOrderRegistration.getOrder(MyFilter.class);
assertThat(order).isEqualTo(position);
}
@Test
public void putWhenPredefinedFilterThenDoesNotOverride() {
int position = 100;
Integer predefinedFilterOrderBefore = this.filterOrderRegistration.getOrder(ChannelProcessingFilter.class);
this.filterOrderRegistration.put(MyFilter.class, position);
Integer myFilterOrder = this.filterOrderRegistration.getOrder(MyFilter.class);
Integer predefinedFilterOrderAfter = this.filterOrderRegistration.getOrder(ChannelProcessingFilter.class);
assertThat(myFilterOrder).isEqualTo(position);
assertThat(predefinedFilterOrderAfter).isEqualTo(predefinedFilterOrderBefore).isEqualTo(position);
}
static class MyFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
filterChain.doFilter(servletRequest, servletResponse);
}
}
}
@@ -0,0 +1,268 @@
/*
* 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.config.annotation.web.builders;
import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.assertj.core.api.ListAssert;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.test.SpringTestRule;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.access.ExceptionTranslationFilter;
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;
import org.springframework.security.web.header.HeaderWriterFilter;
import static org.assertj.core.api.Assertions.assertThat;
public class HttpSecurityAddFilterTest {
@Rule
public final SpringTestRule spring = new SpringTestRule();
@Test
public void addFilterAfterWhenSameFilterDifferentPlacesThenOrderCorrect() {
this.spring.register(MyFilterMultipleAfterConfig.class).autowire();
assertThatFilters().containsSubsequence(WebAsyncManagerIntegrationFilter.class, MyFilter.class,
ExceptionTranslationFilter.class, MyFilter.class);
}
@Test
public void addFilterBeforeWhenSameFilterDifferentPlacesThenOrderCorrect() {
this.spring.register(MyFilterMultipleBeforeConfig.class).autowire();
assertThatFilters().containsSubsequence(MyFilter.class, WebAsyncManagerIntegrationFilter.class, MyFilter.class,
ExceptionTranslationFilter.class);
}
@Test
public void addFilterAtWhenSameFilterDifferentPlacesThenOrderCorrect() {
this.spring.register(MyFilterMultipleAtConfig.class).autowire();
assertThatFilters().containsSubsequence(MyFilter.class, WebAsyncManagerIntegrationFilter.class, MyFilter.class,
ExceptionTranslationFilter.class);
}
@Test
public void addFilterAfterWhenAfterCustomFilterThenOrderCorrect() {
this.spring.register(MyOtherFilterRelativeToMyFilterAfterConfig.class).autowire();
assertThatFilters().containsSubsequence(WebAsyncManagerIntegrationFilter.class, MyFilter.class,
MyOtherFilter.class);
}
@Test
public void addFilterBeforeWhenBeforeCustomFilterThenOrderCorrect() {
this.spring.register(MyOtherFilterRelativeToMyFilterBeforeConfig.class).autowire();
assertThatFilters().containsSubsequence(MyOtherFilter.class, MyFilter.class,
WebAsyncManagerIntegrationFilter.class);
}
@Test
public void addFilterAtWhenAtCustomFilterThenOrderCorrect() {
this.spring.register(MyOtherFilterRelativeToMyFilterAtConfig.class).autowire();
assertThatFilters().containsSubsequence(WebAsyncManagerIntegrationFilter.class, MyFilter.class,
MyOtherFilter.class, SecurityContextPersistenceFilter.class);
}
@Test
public void addFilterBeforeWhenCustomFilterDifferentPlacesThenOrderCorrect() {
this.spring.register(MyOtherFilterBeforeToMyFilterMultipleAfterConfig.class).autowire();
assertThatFilters().containsSubsequence(WebAsyncManagerIntegrationFilter.class, MyOtherFilter.class,
MyFilter.class, ExceptionTranslationFilter.class);
}
@Test
public void addFilterBeforeAndAfterWhenCustomFiltersDifferentPlacesThenOrderCorrect() {
this.spring.register(MyAnotherFilterRelativeToMyCustomFiltersMultipleConfig.class).autowire();
assertThatFilters().containsSubsequence(HeaderWriterFilter.class, MyFilter.class, MyOtherFilter.class,
MyOtherFilter.class, MyAnotherFilter.class, MyFilter.class, ExceptionTranslationFilter.class);
}
private ListAssert<Class<?>> assertThatFilters() {
FilterChainProxy filterChain = this.spring.getContext().getBean(FilterChainProxy.class);
List<Class<?>> filters = filterChain.getFilters("/").stream().map(Object::getClass)
.collect(Collectors.toList());
return assertThat(filters);
}
public static class MyFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
filterChain.doFilter(servletRequest, servletResponse);
}
}
static class MyOtherFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
filterChain.doFilter(servletRequest, servletResponse);
}
}
static class MyAnotherFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
filterChain.doFilter(servletRequest, servletResponse);
}
}
@EnableWebSecurity
static class MyFilterMultipleAfterConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.addFilterAfter(new MyFilter(), WebAsyncManagerIntegrationFilter.class)
.addFilterAfter(new MyFilter(), ExceptionTranslationFilter.class);
// @formatter:on
}
}
@EnableWebSecurity
static class MyFilterMultipleBeforeConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.addFilterBefore(new MyFilter(), WebAsyncManagerIntegrationFilter.class)
.addFilterBefore(new MyFilter(), ExceptionTranslationFilter.class);
// @formatter:on
}
}
@EnableWebSecurity
static class MyFilterMultipleAtConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.addFilterAt(new MyFilter(), ChannelProcessingFilter.class)
.addFilterAt(new MyFilter(), UsernamePasswordAuthenticationFilter.class);
// @formatter:on
}
}
@EnableWebSecurity
static class MyOtherFilterRelativeToMyFilterAfterConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.addFilterAfter(new MyFilter(), WebAsyncManagerIntegrationFilter.class)
.addFilterAfter(new MyOtherFilter(), MyFilter.class);
// @formatter:on
}
}
@EnableWebSecurity
static class MyOtherFilterRelativeToMyFilterBeforeConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.addFilterBefore(new MyFilter(), WebAsyncManagerIntegrationFilter.class)
.addFilterBefore(new MyOtherFilter(), MyFilter.class);
// @formatter:on
}
}
@EnableWebSecurity
static class MyOtherFilterRelativeToMyFilterAtConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.addFilterAt(new MyFilter(), WebAsyncManagerIntegrationFilter.class)
.addFilterAt(new MyOtherFilter(), MyFilter.class);
// @formatter:on
}
}
@EnableWebSecurity
static class MyOtherFilterBeforeToMyFilterMultipleAfterConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.addFilterAfter(new MyFilter(), WebAsyncManagerIntegrationFilter.class)
.addFilterAfter(new MyFilter(), ExceptionTranslationFilter.class)
.addFilterBefore(new MyOtherFilter(), MyFilter.class);
// @formatter:on
}
}
@EnableWebSecurity
static class MyAnotherFilterRelativeToMyCustomFiltersMultipleConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.addFilterAfter(new MyFilter(), HeaderWriterFilter.class)
.addFilterBefore(new MyOtherFilter(), ExceptionTranslationFilter.class)
.addFilterAfter(new MyOtherFilter(), MyFilter.class)
.addFilterAt(new MyAnotherFilter(), MyOtherFilter.class)
.addFilterAfter(new MyFilter(), MyAnotherFilter.class);
// @formatter:on
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 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.
@@ -44,11 +44,14 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.springframework.security.config.Customizer.withDefaults;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
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.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests for {@link DefaultLoginPageConfigurer}
@@ -217,6 +220,18 @@ public class DefaultLoginPageConfigurerTests {
));
}
@Test
public void formLoginWhenLogoutEnabledThenCreatesDefaultLogoutPage() throws Exception {
this.spring.register(DefaultLogoutPageConfig.class).autowire();
this.mvc.perform(get("/logout").with(user("user"))).andExpect(status().isOk());
}
@Test
public void formLoginWhenLogoutDisabledThenDefaultLogoutPageDoesNotExist() throws Exception {
this.spring.register(LogoutDisabledConfig.class).autowire();
this.mvc.perform(get("/logout").with(user("user"))).andExpect(status().isNotFound());
}
@EnableWebSecurity
static class DefaultLoginPageConfig extends WebSecurityConfigurerAdapter {
@Override
@@ -552,6 +567,41 @@ public class DefaultLoginPageConfigurerTests {
}
}
@EnableWebSecurity
static class DefaultLogoutPageConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.formLogin(withDefaults());
// @formatter:on
}
}
@EnableWebSecurity
static class LogoutDisabledConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.formLogin(withDefaults())
.logout((logout) -> logout
.disable()
);
// @formatter:on
}
}
static class ReflectingObjectPostProcessor implements ObjectPostProcessor<Object> {
@Override
public <O> O postProcess(O object) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 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.
@@ -116,6 +116,11 @@ public class OAuth2LoginConfigurerTests {
.getBuilder("github").clientId("clientId").clientSecret("clientSecret")
.build();
// @formatter:off
private static final ClientRegistration CLIENT_CREDENTIALS_REGISTRATION = TestClientRegistrations.clientCredentials()
.build();
// @formatter:on
private ConfigurableApplicationContext context;
@Autowired
@@ -431,6 +436,18 @@ public class OAuth2LoginConfigurerTests {
assertThat(this.response.getRedirectedUrl()).doesNotMatch("http://localhost/oauth2/authorization/google");
}
// gh-9457
@Test
public void oauth2LoginWithOneAuthorizationCodeClientAndOtherClientsConfiguredThenRedirectForAuthorization()
throws Exception {
loadConfig(OAuth2LoginConfigAuthorizationCodeClientAndOtherClients.class);
String requestUri = "/";
this.request = new MockHttpServletRequest("GET", requestUri);
this.request.setServletPath(requestUri);
this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain);
assertThat(this.response.getRedirectedUrl()).matches("http://localhost/oauth2/authorization/google");
}
@Test
public void oauth2LoginWithCustomLoginPageThenRedirectCustomLoginPage() throws Exception {
loadConfig(OAuth2LoginConfigCustomLoginPage.class);
@@ -801,6 +818,23 @@ public class OAuth2LoginConfigurerTests {
}
}
@EnableWebSecurity
static class OAuth2LoginConfigAuthorizationCodeClientAndOtherClients extends CommonWebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.oauth2Login()
.clientRegistrationRepository(
new InMemoryClientRegistrationRepository(
GOOGLE_CLIENT_REGISTRATION, CLIENT_CREDENTIALS_REGISTRATION));
// @formatter:on
super.configure(http);
}
}
@EnableWebSecurity
static class OAuth2LoginConfigCustomLoginPage extends CommonWebSecurityConfigurerAdapter {
@Override
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 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.
@@ -20,6 +20,7 @@ import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
import org.springframework.security.authentication.ProviderManager;
@@ -49,6 +50,18 @@ public class AuthenticationManagerBeanDefinitionParserTests {
+ " </user-service>"
+ " </authentication-provider>"
+ "</authentication-manager>";
// Issue #7282
// @formatter:off
private static final String CONTEXT_MULTI = "<authentication-manager id='amSecondary'>"
+ " <authentication-provider>"
+ " <user-service>"
+ " <user name='john' password='{noop}doe' authorities='ROLE_C,ROLE_D' />"
+ " </user-service>"
+ " </authentication-provider>"
+ "</authentication-manager>";
// @formatter:on
@Rule
public final SpringTestRule spring = new SpringTestRule();
@@ -60,6 +73,18 @@ public class AuthenticationManagerBeanDefinitionParserTests {
assertThat(context.getBeansOfType(AuthenticationProvider.class)).hasSize(1);
}
@Test
public void eventPublishersAreRegisteredAsTopLevelBeans() {
ConfigurableApplicationContext context = this.spring.context(CONTEXT).getContext();
assertThat(context.getBeansOfType(AuthenticationEventPublisher.class)).hasSize(1);
}
@Test
public void onlyOneEventPublisherIsRegisteredForMultipleAuthenticationManagers() {
ConfigurableApplicationContext context = this.spring.context(CONTEXT + '\n' + CONTEXT_MULTI).getContext();
assertThat(context.getBeansOfType(AuthenticationEventPublisher.class)).hasSize(1);
}
@Test
public void eventsArePublishedByDefault() throws Exception {
ConfigurableApplicationContext appContext = this.spring.context(CONTEXT)
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 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.
@@ -25,7 +25,10 @@ import org.springframework.security.web.server.context.WebSessionServerSecurityC
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.security.test.web.reactive.server.WebTestClientBuilder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.security.config.Customizer.withDefaults;
/**
@@ -167,7 +170,8 @@ public class LogoutSpecTests {
}
@Test
public void logoutWhenDisabledThenPostToLogoutDoesNothing() {
public void logoutWhenDisabledThenDefaultLogoutPageDoesNotExist() {
// @formatter:off
SecurityWebFilterChain securityWebFilter = this.http
.authorizeExchange()
.anyExchange().authenticated()
@@ -177,7 +181,7 @@ public class LogoutSpecTests {
.build();
WebTestClient webTestClient = WebTestClientBuilder
.bindToWebFilters(securityWebFilter)
.bindToControllerAndWebFilters(HomeController.class, securityWebFilter)
.build();
WebDriver driver = WebTestClientHtmlUnitDriverBuilder
@@ -191,15 +195,10 @@ public class LogoutSpecTests {
.username("user")
.password("password")
.submit(FormLoginTests.HomePage.class);
// @formatter:on
homePage.assertAt();
FormLoginTests.DefaultLogoutPage.to(driver)
.assertAt()
.logout();
homePage
.assertAt();
FormLoginTests.DefaultLogoutPage.to(driver);
assertThat(driver.getPageSource()).isEmpty();
}
@@ -243,4 +242,15 @@ public class LogoutSpecTests {
FormLoginTests.HomePage.to(driver, FormLoginTests.DefaultLoginPage.class)
.assertAt();
}
@RestController
public static class HomeController {
@GetMapping("/")
public String ok() {
return "ok";
}
}
}
@@ -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.
@@ -136,6 +136,11 @@ public class OAuth2LoginTests {
.clientSecret("secret")
.build();
// @formatter:off
private static ClientRegistration clientCredentials = TestClientRegistrations.clientCredentials()
.build();
// @formatter:on
@Autowired
public void setApplicationContext(ApplicationContext context) {
if (context.getBeanNamesForType(WebHandler.class).length > 0) {
@@ -214,6 +219,32 @@ public class OAuth2LoginTests {
}
}
// gh-9457
@Test
public void defaultLoginPageWithAuthorizationCodeAndClientCredentialsClientRegistrationThenRedirect() {
this.spring.register(OAuth2LoginWithAuthorizationCodeAndClientCredentialsClientRegistration.class).autowire();
// @formatter:off
WebTestClient webTestClient = WebTestClientBuilder
.bindToWebFilters(new GitHubWebFilter(), this.springSecurity)
.build();
WebDriver driver = WebTestClientHtmlUnitDriverBuilder
.webTestClientSetup(webTestClient)
.build();
// @formatter:on
driver.get("http://localhost/");
assertThat(driver.getCurrentUrl()).startsWith("https://github.com/login/oauth/authorize");
}
@EnableWebFluxSecurity
static class OAuth2LoginWithAuthorizationCodeAndClientCredentialsClientRegistration {
@Bean
InMemoryReactiveClientRegistrationRepository clientRegistrationRepository() {
return new InMemoryReactiveClientRegistrationRepository(github, clientCredentials);
}
}
@Test
public void oauth2AuthorizeWhenCustomObjectsThenUsed() {
this.spring.register(OAuth2LoginWithSingleClientRegistrations.class,
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 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.
@@ -41,6 +41,7 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.server.ServerAuthorizationRequestRepository;
@@ -48,6 +49,8 @@ import org.springframework.security.oauth2.client.web.server.authentication.OAut
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationRequests;
import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor;
import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
import org.springframework.security.web.server.authentication.HttpStatusServerEntryPoint;
import org.springframework.security.web.server.authentication.ServerX509AuthenticationConverter;
import org.springframework.security.web.server.savedrequest.ServerRequestCache;
import org.springframework.security.web.server.savedrequest.WebSessionServerRequestCache;
@@ -184,6 +187,25 @@ public class ServerHttpSecurityTests {
.expectBody().isEmpty();
}
@Test
public void basicWhenXHRRequestThenUnauthorized() {
ServerAuthenticationEntryPoint authenticationEntryPoint = spy(
new HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED));
this.http.httpBasic().authenticationEntryPoint(authenticationEntryPoint);
this.http.authorizeExchange().anyExchange().authenticated();
WebTestClient client = buildClient();
// @formatter:off
client.get().uri("/")
.header("X-Requested-With", "XMLHttpRequest")
.exchange()
.expectStatus().isUnauthorized()
.expectHeader().doesNotExist("WWW-Authenticate")
.expectHeader().valueMatches(HttpHeaders.CACHE_CONTROL, ".+")
.expectBody().isEmpty();
// @formatter:on
verify(authenticationEntryPoint).commence(any(), any());
}
@Test
public void buildWhenServerWebExchangeFromContextThenFound() {
SecurityWebFilterChain filter = this.http.build();
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2016 the original author or authors.
* Copyright 2010-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.
@@ -287,7 +287,7 @@ public abstract class AbstractJaasAuthenticationProvider
* subclasses for different functionality
*
* @param token The authentication token being processed
* @param ase The excetion that caused the authentication failure
* @param ase The exception that caused the authentication failure
*/
protected void publishFailureEvent(UsernamePasswordAuthenticationToken token,
AuthenticationException ase) {
@@ -247,7 +247,7 @@ public class JaasAuthenticationProvider extends AbstractJaasAuthenticationProvid
* subclasses for different functionality
*
* @param token The authentication token being processed
* @param ase The excetion that caused the authentication failure
* @param ase The exception that caused the authentication failure
*/
@Override
protected void publishFailureEvent(UsernamePasswordAuthenticationToken token,
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 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.
@@ -135,7 +135,7 @@ public class DelegatingPasswordEncoderTests {
}
@Test
public void matchesWhenNoClosingPrefixStringThenIllegalArgumentExcetion() {
public void matchesWhenNoClosingPrefixStringThenIllegalArgumentException() {
assertThatThrownBy(() -> this.passwordEncoder.matches(this.rawPassword, "{bcrypt" + this.rawPassword))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("There is no PasswordEncoder mapped for the id \"null\"");
@@ -15,6 +15,7 @@ remotes {
docs {
retryCount = 5 // retry 5 times (default is 0)
retryWaitSec = 10 // wait 10 seconds between retries (default is 0)
host = "docs-ip.spring.io"
}
}
@@ -510,7 +510,7 @@ ReactiveJwtDecoder jwtDecoder() {
----
[NOTE]
By default, Resource Server configures a clock skew of 30 seconds.
By default, Resource Server configures a clock skew of 60 seconds.
[[webflux-oauth2resourceserver-validation-custom]]
==== Configuring a Custom Validator
@@ -100,3 +100,35 @@ This is an example of the use of the `AuthenticatedVoter` which we will see in t
It uses an `AuthenticationTrustResolver` to process this particular configuration attribute and grant access to anonymous users.
The `AuthenticatedVoter` approach is more powerful, since it allows you to differentiate between anonymous, remember-me and fully-authenticated users.
If you don't need this functionality though, then you can stick with `ROLE_ANONYMOUS`, which will be processed by Spring Security's standard `RoleVoter`.
[[anonymous-auth-mvc-controller]]
=== Getting Anonymous Authentications with Spring MVC
https://docs.spring.io/spring-framework/docs/5.2.x/spring-framework-reference/web.html#mvc-ann-arguments[Spring MVC resolves parameters of type `Principal`] using its own argument resolver.
This means that a construct like this one:
[source,java]
----
@GetMapping("/")
public String method(Authentication authentication) {
if (authentication instanceof AnonymousAuthenticationToken) {
return "anonymous";
} else {
return "not anonymous";
}
}
----
will always return "not anonymous", even for anonymous requests.
The reason is that Spring MVC resolves the parameter using `HttpServletRequest#getPrincipal`, which is `null` when the request is anonymous.
If you'd like to obtain the `Authentication` in anonymous requests, use `@CurrentSecurityContext` instead:
[source,java]
----
@GetMapping("/")
public String method(@CurrentSecurityContext SecurityContext context) {
return context.getAuthentication().getName();
}
----
@@ -531,7 +531,7 @@ JwtDecoder jwtDecoder() {
----
[NOTE]
By default, Resource Server configures a clock skew of 30 seconds.
By default, Resource Server configures a clock skew of 60 seconds.
[[oauth2resourceserver-jwt-validation-custom]]
==== Configuring a Custom Validator
@@ -1375,7 +1375,7 @@ Next, we can construct a `JWTProcessor`:
JWTProcessor jwtProcessor(JWTClaimSetJWSKeySelector keySelector) {
ConfigurableJWTProcessor<SecurityContext> jwtProcessor =
new DefaultJWTProcessor();
jwtProcessor.setJWTClaimSetJWSKeySelector(keySelector);
jwtProcessor.setJWTClaimsSetAwareJWSKeySelector(keySelector);
return jwtProcessor;
}
----
@@ -1422,9 +1422,9 @@ Now that we have a tenant-aware processor and a tenant-aware validator, we can p
----
@Bean
JwtDecoder jwtDecoder(JWTProcessor jwtProcessor, OAuth2TokenValidator<Jwt> jwtValidator) {
NimbusJwtDecoder decoder = new NimbusJwtDecoder(processor);
NimbusJwtDecoder decoder = new NimbusJwtDecoder(jwtProcessor);
OAuth2TokenValidator<Jwt> validator = new DelegatingOAuth2TokenValidator<>
(JwtValidators.createDefault(), this.jwtValidator);
(JwtValidators.createDefault(), jwtValidator);
decoder.setJwtValidator(validator);
return decoder;
}
+3 -3
View File
@@ -1,5 +1,5 @@
aspectjVersion=1.9.6
gaeVersion=1.9.86
aspectjVersion=1.9.7
gaeVersion=1.9.93
springBootVersion=2.2.13.RELEASE
version=5.2.9.RELEASE
version=5.2.15.RELEASE
org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError
+48 -48
View File
@@ -1,16 +1,16 @@
if (!project.hasProperty('reactorVersion')) {
ext.reactorVersion = 'Dysprosium-SR16'
ext.reactorVersion = 'Dysprosium-SR25'
}
if (!project.hasProperty('springVersion')) {
ext.springVersion = '5.2.12.RELEASE'
ext.springVersion = '5.2.19.RELEASE'
}
if (!project.hasProperty('springDataVersion')) {
ext.springDataVersion = 'Moore-SR12'
ext.springDataVersion = 'Moore-SR13'
}
ext.rsocketVersion = '1.0.3'
ext.rsocketVersion = '1.0.5'
dependencyManagement {
imports {
@@ -40,15 +40,15 @@ dependencyManagement {
dependencies {
dependency 'antlr:antlr:2.7.7'
dependency 'asm:asm:3.1'
dependency 'ch.qos.logback:logback-classic:1.2.3'
dependency 'ch.qos.logback:logback-core:1.2.3'
dependency 'ch.qos.logback:logback-classic:1.2.9'
dependency 'ch.qos.logback:logback-core:1.2.9'
dependency 'com.fasterxml.jackson.core:jackson-annotations:2.10.5'
dependency 'com.fasterxml.jackson.core:jackson-core:2.10.5'
dependency 'com.fasterxml.jackson.core:jackson-databind:2.10.5.1'
dependency 'com.fasterxml:classmate:1.3.4'
dependency 'com.github.stephenc.jcip:jcip-annotations:1.0-1'
dependency 'com.google.appengine:appengine-api-1.0-sdk:$gaeVersion'
dependency 'com.google.appengine:appengine-api-labs:$gaeVersion'
dependency 'com.google.appengine:appengine-api-labs:1.9.88'
dependency 'com.google.appengine:appengine-api-stubs:$gaeVersion'
dependency 'com.google.appengine:appengine-testing:$gaeVersion'
dependency 'com.google.appengine:appengine:$gaeVersion'
@@ -61,7 +61,7 @@ dependencyManagement {
dependency 'com.squareup.okhttp3:okhttp:3.14.9'
dependency 'com.squareup.okio:okio:1.13.0'
dependency 'com.sun.xml.bind:jaxb-core:2.3.0.1'
dependency 'com.sun.xml.bind:jaxb-impl:2.3.3'
dependency 'com.sun.xml.bind:jaxb-impl:2.3.5'
dependency 'com.unboundid:unboundid-ldapsdk:4.0.14'
dependency 'com.vaadin.external.google:android-json:0.0.20131108.vaadin1'
dependency 'commons-cli:commons-cli:1.4'
@@ -72,7 +72,7 @@ dependencyManagement {
dependency 'commons-lang:commons-lang:2.6'
dependency 'commons-logging:commons-logging:1.2'
dependency 'dom4j:dom4j:1.6.1'
dependency 'io.projectreactor.tools:blockhound:1.0.4.RELEASE'
dependency 'io.projectreactor.tools:blockhound:1.0.6.RELEASE'
dependency "io.rsocket:rsocket-core:${rsocketVersion}"
dependency "io.rsocket:rsocket-transport-netty:${rsocketVersion}"
dependency 'javax.activation:activation:1.1.1'
@@ -91,7 +91,7 @@ dependencyManagement {
dependency 'net.jcip:jcip-annotations:1.0'
dependency 'net.minidev:accessors-smart:1.2'
dependency 'net.minidev:json-smart:2.3'
dependency 'net.sf.ehcache:ehcache:2.10.6'
dependency 'net.sf.ehcache:ehcache:2.10.9.2'
dependency 'net.sourceforge.htmlunit:htmlunit:2.36.0'
dependency 'net.sourceforge.htmlunit:neko-htmlunit:2.34.0'
dependency 'net.sourceforge.nekohtml:nekohtml:1.9.22'
@@ -139,40 +139,40 @@ dependencyManagement {
dependency 'org.apache.directory.shared:shared-ldap-constants:0.9.15'
dependency 'org.apache.directory.shared:shared-ldap:0.9.15'
dependency 'org.apache.httpcomponents:httpclient:4.5.13'
dependency 'org.apache.httpcomponents:httpcore:4.4.8'
dependency 'org.apache.httpcomponents:httpmime:4.5.3'
dependency 'org.apache.httpcomponents:httpmime:4.5.13'
dependency 'org.apache.httpcomponents:httpcore:4.4.15'
dependency 'org.apache.mina:mina-core:2.0.0-M6'
dependency 'org.apache.taglibs:taglibs-standard-impl:1.2.5'
dependency 'org.apache.taglibs:taglibs-standard-jstlel:1.2.5'
dependency 'org.apache.taglibs:taglibs-standard-spec:1.2.5'
dependency 'org.apache.tomcat.embed:tomcat-embed-core:9.0.43'
dependency 'org.apache.tomcat.embed:tomcat-embed-el:9.0.43'
dependency 'org.apache.tomcat.embed:tomcat-embed-jasper:9.0.43'
dependency 'org.apache.tomcat.embed:tomcat-embed-logging-log4j:9.0.43'
dependency 'org.apache.tomcat.embed:tomcat-embed-websocket:8.5.57'
dependency 'org.apache.tomcat:tomcat-annotations-api:9.0.43'
dependency 'org.apache.tomcat.embed:tomcat-embed-core:9.0.56'
dependency 'org.apache.tomcat.embed:tomcat-embed-el:9.0.56'
dependency 'org.apache.tomcat.embed:tomcat-embed-jasper:9.0.56'
dependency 'org.apache.tomcat.embed:tomcat-embed-logging-log4j:9.0.56'
dependency 'org.apache.tomcat.embed:tomcat-embed-websocket:8.5.73'
dependency 'org.apache.tomcat:tomcat-annotations-api:9.0.56'
dependency "org.aspectj:aspectjrt:$aspectjVersion"
dependency "org.aspectj:aspectjtools:$aspectjVersion"
dependency "org.aspectj:aspectjweaver:$aspectjVersion"
dependency 'org.assertj:assertj-core:3.12.2'
dependency 'org.attoparser:attoparser:2.0.4.RELEASE'
dependency 'org.attoparser:attoparser:2.0.5.RELEASE'
dependency 'org.bouncycastle:bcpkix-jdk15on:1.64'
dependency 'org.bouncycastle:bcprov-jdk15on:1.64'
dependency 'org.codehaus.groovy:groovy-all:2.4.20'
dependency 'org.codehaus.groovy:groovy-json:2.4.20'
dependency 'org.codehaus.groovy:groovy:2.4.20'
dependency 'org.codehaus.groovy:groovy-all:2.4.21'
dependency 'org.codehaus.groovy:groovy-json:2.4.21'
dependency 'org.codehaus.groovy:groovy:2.4.21'
dependency 'org.eclipse.jdt:ecj:3.12.3'
dependency 'org.eclipse.jetty.websocket:websocket-api:9.4.36.v20210114'
dependency 'org.eclipse.jetty.websocket:websocket-client:9.4.36.v20210114'
dependency 'org.eclipse.jetty.websocket:websocket-common:9.4.36.v20210114'
dependency 'org.eclipse.jetty:jetty-client:9.4.36.v20210114'
dependency 'org.eclipse.jetty:jetty-http:9.4.36.v20210114'
dependency 'org.eclipse.jetty:jetty-io:9.4.36.v20210114'
dependency 'org.eclipse.jetty:jetty-security:9.4.36.v20210114'
dependency 'org.eclipse.jetty:jetty-server:9.4.36.v20210114'
dependency 'org.eclipse.jetty:jetty-servlet:9.4.36.v20210114'
dependency 'org.eclipse.jetty:jetty-util:9.4.36.v20210114'
dependency 'org.eclipse.jetty:jetty-xml:9.4.36.v20210114'
dependency 'org.eclipse.jetty.websocket:websocket-api:9.4.44.v20210927'
dependency 'org.eclipse.jetty.websocket:websocket-client:9.4.44.v20210927'
dependency 'org.eclipse.jetty.websocket:websocket-common:9.4.44.v20210927'
dependency 'org.eclipse.jetty:jetty-client:9.4.44.v20210927'
dependency 'org.eclipse.jetty:jetty-http:9.4.44.v20210927'
dependency 'org.eclipse.jetty:jetty-io:9.4.44.v20210927'
dependency 'org.eclipse.jetty:jetty-security:9.4.44.v20210927'
dependency 'org.eclipse.jetty:jetty-server:9.4.44.v20210927'
dependency 'org.eclipse.jetty:jetty-servlet:9.4.44.v20210927'
dependency 'org.eclipse.jetty:jetty-util:9.4.44.v20210927'
dependency 'org.eclipse.jetty:jetty-xml:9.4.44.v20210927'
dependency 'org.eclipse.persistence:javax.persistence:2.2.1'
dependency 'org.gebish:geb-ast:0.10.0'
dependency 'org.gebish:geb-core:0.10.0'
@@ -182,20 +182,20 @@ dependencyManagement {
dependency 'org.hibernate.common:hibernate-commons-annotations:5.0.1.Final'
dependency 'org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final'
dependency 'org.hibernate:hibernate-core:5.2.18.Final'
dependency 'org.hibernate:hibernate-entitymanager:5.4.28.Final'
dependency 'org.hibernate:hibernate-entitymanager:5.4.33'
dependency 'org.hibernate:hibernate-validator:6.1.7.Final'
dependency 'org.hsqldb:hsqldb:2.5.1'
dependency 'org.hsqldb:hsqldb:2.5.2'
dependency 'org.jasig.cas.client:cas-client-core:3.5.1'
dependency 'org.javassist:javassist:3.22.0-CR2'
dependency 'org.jboss.logging:jboss-logging:3.3.1.Final'
dependency 'org.jboss.logging:jboss-logging:3.3.3.Final'
dependency 'org.jboss.spec.javax.transaction:jboss-transaction-api_1.2_spec:1.0.1.Final'
dependency 'org.jboss:jandex:2.0.3.Final'
dependency 'org.jboss:jandex:2.0.5.Final'
dependency 'org.mockito:mockito-core:3.0.0'
dependency 'org.objenesis:objenesis:2.6'
dependency 'org.openid4java:openid4java-nodeps:0.9.6'
dependency 'org.opensaml:opensaml-core:3.4.5'
dependency 'org.opensaml:opensaml-saml-api:3.4.5'
dependency 'org.opensaml:opensaml-saml-impl:3.4.5'
dependency 'org.opensaml:opensaml-core:3.4.6'
dependency 'org.opensaml:opensaml-saml-api:3.4.6'
dependency 'org.opensaml:opensaml-saml-impl:3.4.6'
dependency 'org.ow2.asm:asm:6.2.1'
dependency 'org.reactivestreams:reactive-streams:1.0.3'
dependency 'org.seleniumhq.selenium:htmlunit-driver:2.36.0'
@@ -203,16 +203,16 @@ dependencyManagement {
dependency 'org.seleniumhq.selenium:selenium-support:3.141.59'
dependency 'org.seleniumhq.selenium:selenium-api:3.141.59'
dependency 'org.skyscreamer:jsonassert:1.5.0'
dependency 'org.slf4j:jcl-over-slf4j:1.7.30'
dependency 'org.slf4j:jul-to-slf4j:1.7.30'
dependency 'org.slf4j:log4j-over-slf4j:1.7.30'
dependency 'org.slf4j:slf4j-api:1.7.30'
dependency 'org.slf4j:slf4j-nop:1.7.30'
dependency 'org.slf4j:jcl-over-slf4j:1.7.32'
dependency 'org.slf4j:jul-to-slf4j:1.7.32'
dependency 'org.slf4j:log4j-over-slf4j:1.7.32'
dependency 'org.slf4j:slf4j-api:1.7.32'
dependency 'org.slf4j:slf4j-nop:1.7.32'
dependency 'org.sonatype.sisu.inject:cglib:2.2.1-v20090111'
dependency 'org.springframework.ldap:spring-ldap-core:2.3.3.RELEASE'
dependency 'org.springframework.ldap:spring-ldap-core:2.3.4.RELEASE'
dependency 'org.synchronoss.cloud:nio-multipart-parser:1.1.0'
dependency 'org.thymeleaf:thymeleaf-spring5:3.0.12.RELEASE'
dependency 'org.unbescape:unbescape:1.1.5.RELEASE'
dependency 'org.unbescape:unbescape:1.1.6.RELEASE'
dependency 'org.thymeleaf:thymeleaf-spring5:3.0.14.RELEASE'
dependency 'org.w3c.css:sac:1.3'
dependency 'xalan:serializer:2.7.2'
dependency 'xalan:xalan:2.7.2'
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 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.
@@ -53,8 +53,17 @@ public class DefaultSpringSecurityContextSourceTests {
@Test
public void supportsSpacesInUrl() {
new DefaultSpringSecurityContextSource(
DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource(
"ldap://myhost:10389/dc=spring%20framework,dc=org");
assertThat(contextSource.getBaseLdapPathAsString()).isEqualTo("dc=spring framework,dc=org");
}
// gh-9742
@Test
public void constructorWhenUrlEncodedSpacesWithPlusCharacterThenBaseDnIsProperlyDecoded() {
DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource(
"ldap://blah:123/dc=spring+framework,dc=org ldap://blah:456/dc=spring+framework,dc=org");
assertThat(contextSource.getBaseLdapPathAsString()).isEqualTo("dc=spring framework,dc=org");
}
@Test
@@ -105,6 +114,7 @@ public class DefaultSpringSecurityContextSourceTests {
DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource(
this.contextSource.getUrls()[0]
+ "ou=space%20cadets,dc=springframework,dc=org");
assertThat(contextSource.getBaseLdapPathAsString()).isEqualTo("ou=space cadets,dc=springframework,dc=org");
contextSource.afterPropertiesSet();
contextSource.getContext(
"uid=space cadet,ou=space cadets,dc=springframework,dc=org",
@@ -147,6 +157,18 @@ public class DefaultSpringSecurityContextSourceTests {
assertThat(ctxSrc.isPooled()).isTrue();
}
// gh-9742
@Test
public void constructorWhenServerListWithSpacesInBaseDnThenSuccess() {
List<String> serverUrls = new ArrayList<>();
serverUrls.add("ldap://ad1.example.org:789");
serverUrls.add("ldap://ad2.example.org:389");
serverUrls.add("ldaps://ad3.example.org:636");
DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource(serverUrls,
"dc=spring framework,dc=org");
assertThat(contextSource.getBaseLdapPathAsString()).isEqualTo("dc=spring framework,dc=org");
}
@Test(expected = IllegalArgumentException.class)
public void instantiationFailsWithIncorrectServerUrl() {
List<String> serverUrls = new ArrayList<>();
@@ -0,0 +1,89 @@
/*
* 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.ldap.search;
import javax.naming.ldap.LdapName;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
import org.springframework.security.ldap.server.ApacheDSContainer;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Additional tests for {@link FilterBasedLdapUserSearch} with spaces in the base dn.
*
* @author Steve Riesenberg
*/
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = FilterBasedLdapUserSearchWithSpacesTests.ApacheDsContainerWithSpacesConfig.class)
public class FilterBasedLdapUserSearchWithSpacesTests {
@Autowired
private DefaultSpringSecurityContextSource contextSource;
// gh-9742
@Test
public void searchForUserWhenSpacesInBaseDnThenSuccess() throws Exception {
FilterBasedLdapUserSearch locator = new FilterBasedLdapUserSearch("ou=space cadets", "(uid={0})",
this.contextSource);
locator.setSearchSubtree(false);
locator.setSearchTimeLimit(0);
locator.setDerefLinkFlag(false);
DirContextOperations bob = locator.searchForUser("space cadet");
assertThat(bob.getStringAttribute("uid")).isEqualTo("space cadet");
assertThat(bob.getDn()).isEqualTo(new LdapName("uid=space cadet,ou=space cadets"));
}
@Configuration
static class ApacheDsContainerWithSpacesConfig implements DisposableBean {
private ApacheDSContainer container;
@Bean
ApacheDSContainer ldapContainer() throws Exception {
this.container = new ApacheDSContainer("dc=spring framework,dc=org",
"classpath:test-server-with-spaces.ldif");
this.container.setPort(53390);
return this.container;
}
@Bean
ContextSource contextSource(ApacheDSContainer ldapContainer) {
return new DefaultSpringSecurityContextSource(
"ldap://127.0.0.1:" + ldapContainer.getPort() + "/dc=spring%20framework,dc=org");
}
@Override
public void destroy() {
this.container.stop();
}
}
}
@@ -0,0 +1,14 @@
dn: ou=space cadets,dc=spring framework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: space cadets
dn: uid=space cadet,ou=space cadets,dc=spring framework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Space Cadet
sn: Cadet
uid: space cadet
userPassword: spacecadetspassword
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 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.
@@ -15,6 +15,10 @@
*/
package org.springframework.security.ldap;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
@@ -85,7 +89,7 @@ public class DefaultSpringSecurityContextSource extends LdapContextSource {
}
setUrls(urls.toArray(new String[0]));
setBase(this.rootDn);
setBase((this.rootDn != null) ? decodeUrl(this.rootDn) : null);
setPooled(true);
setAuthenticationStrategy(new SimpleDirContextAuthenticationStrategy() {
@Override
@@ -150,7 +154,7 @@ public class DefaultSpringSecurityContextSource extends LdapContextSource {
Assert.notNull(baseDn, "The Base DN for the LDAP server must not be null.");
Assert.notEmpty(urls, "At least one LDAP server URL must be provided.");
String trimmedBaseDn = baseDn.trim();
String encodedBaseDn = encodeUrl(baseDn.trim());
StringBuilder providerUrl = new StringBuilder();
for (String serverUrl : urls) {
@@ -163,7 +167,7 @@ public class DefaultSpringSecurityContextSource extends LdapContextSource {
if (!trimmedUrl.endsWith("/")) {
providerUrl.append("/");
}
providerUrl.append(trimmedBaseDn);
providerUrl.append(encodedBaseDn);
providerUrl.append(" ");
}
@@ -171,4 +175,22 @@ public class DefaultSpringSecurityContextSource extends LdapContextSource {
}
private static String encodeUrl(String url) {
try {
return URLEncoder.encode(url, StandardCharsets.UTF_8.toString());
}
catch (UnsupportedEncodingException ex) {
throw new IllegalStateException(ex);
}
}
private String decodeUrl(String url) {
try {
return URLDecoder.decode(url, StandardCharsets.UTF_8.toString());
}
catch (UnsupportedEncodingException ex) {
throw new IllegalStateException(ex);
}
}
}
@@ -48,7 +48,7 @@ public class OAuth2ErrorResponseErrorHandler implements ResponseErrorHandler {
@Override
public void handleError(ClientHttpResponse response) throws IOException {
if (!HttpStatus.BAD_REQUEST.equals(response.getStatusCode())) {
if (HttpStatus.BAD_REQUEST.value() != response.getRawStatusCode()) {
this.defaultErrorHandler.handleError(response);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 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.
@@ -31,6 +31,7 @@ import java.util.Map;
*
* @author Joe Grandja
* @author Rob Winch
* @author Craig Andrews
* @since 5.0
* @see AuthorizationRequestRepository
* @see OAuth2AuthorizationRequest
@@ -41,6 +42,8 @@ public final class HttpSessionOAuth2AuthorizationRequestRepository implements Au
private final String sessionAttributeName = DEFAULT_AUTHORIZATION_REQUEST_ATTR_NAME;
private boolean allowMultipleAuthorizationRequests;
@Override
public OAuth2AuthorizationRequest loadAuthorizationRequest(HttpServletRequest request) {
Assert.notNull(request, "request cannot be null");
@@ -63,9 +66,14 @@ public final class HttpSessionOAuth2AuthorizationRequestRepository implements Au
}
String state = authorizationRequest.getState();
Assert.hasText(state, "authorizationRequest.state cannot be empty");
Map<String, OAuth2AuthorizationRequest> authorizationRequests = this.getAuthorizationRequests(request);
authorizationRequests.put(state, authorizationRequest);
request.getSession().setAttribute(this.sessionAttributeName, authorizationRequests);
if (this.allowMultipleAuthorizationRequests) {
Map<String, OAuth2AuthorizationRequest> authorizationRequests = this.getAuthorizationRequests(request);
authorizationRequests.put(state, authorizationRequest);
request.getSession().setAttribute(this.sessionAttributeName, authorizationRequests);
}
else {
request.getSession().setAttribute(this.sessionAttributeName, authorizationRequest);
}
}
@Override
@@ -77,11 +85,16 @@ public final class HttpSessionOAuth2AuthorizationRequestRepository implements Au
}
Map<String, OAuth2AuthorizationRequest> authorizationRequests = this.getAuthorizationRequests(request);
OAuth2AuthorizationRequest originalRequest = authorizationRequests.remove(stateParameter);
if (!authorizationRequests.isEmpty()) {
request.getSession().setAttribute(this.sessionAttributeName, authorizationRequests);
} else {
if (authorizationRequests.size() == 0) {
request.getSession().removeAttribute(this.sessionAttributeName);
}
else if (authorizationRequests.size() == 1) {
request.getSession().setAttribute(this.sessionAttributeName,
authorizationRequests.values().iterator().next());
}
else {
request.getSession().setAttribute(this.sessionAttributeName, authorizationRequests);
}
return originalRequest;
}
@@ -107,11 +120,38 @@ public final class HttpSessionOAuth2AuthorizationRequestRepository implements Au
*/
private Map<String, OAuth2AuthorizationRequest> getAuthorizationRequests(HttpServletRequest request) {
HttpSession session = request.getSession(false);
Map<String, OAuth2AuthorizationRequest> authorizationRequests = session == null ? null :
(Map<String, OAuth2AuthorizationRequest>) session.getAttribute(this.sessionAttributeName);
if (authorizationRequests == null) {
Object sessionAttributeValue = (session != null) ? session.getAttribute(this.sessionAttributeName) : null;
if (sessionAttributeValue == null) {
return new HashMap<>();
}
return authorizationRequests;
else if (sessionAttributeValue instanceof OAuth2AuthorizationRequest) {
OAuth2AuthorizationRequest auth2AuthorizationRequest = (OAuth2AuthorizationRequest) sessionAttributeValue;
Map<String, OAuth2AuthorizationRequest> authorizationRequests = new HashMap<>(1);
authorizationRequests.put(auth2AuthorizationRequest.getState(), auth2AuthorizationRequest);
return authorizationRequests;
}
else if (sessionAttributeValue instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, OAuth2AuthorizationRequest> authorizationRequests = (Map<String, OAuth2AuthorizationRequest>) sessionAttributeValue;
return authorizationRequests;
}
else {
throw new IllegalStateException(
"authorizationRequests is supposed to be a Map or OAuth2AuthorizationRequest but actually is a "
+ sessionAttributeValue.getClass());
}
}
/**
* Configure if multiple {@link OAuth2AuthorizationRequest}s should be stored per
* session. Default is false (not allow multiple {@link OAuth2AuthorizationRequest}
* per session).
* @param allowMultipleAuthorizationRequests true allows more than one
* {@link OAuth2AuthorizationRequest} to be stored per session.
* @since 5.5
*/
@Deprecated
public void setAllowMultipleAuthorizationRequests(boolean allowMultipleAuthorizationRequests) {
this.allowMultipleAuthorizationRequests = allowMultipleAuthorizationRequests;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 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.
@@ -34,6 +34,7 @@ import reactor.core.publisher.Mono;
* {@link OAuth2AuthorizationRequest} in the {@code WebSession}.
*
* @author Rob Winch
* @author Steve Riesenberg
* @since 5.1
* @see AuthorizationRequestRepository
* @see OAuth2AuthorizationRequest
@@ -46,6 +47,8 @@ public final class WebSessionOAuth2ServerAuthorizationRequestRepository
private final String sessionAttributeName = DEFAULT_AUTHORIZATION_REQUEST_ATTR_NAME;
private boolean allowMultipleAuthorizationRequests;
@Override
public Mono<OAuth2AuthorizationRequest> loadAuthorizationRequest(
ServerWebExchange exchange) {
@@ -53,17 +56,33 @@ public final class WebSessionOAuth2ServerAuthorizationRequestRepository
if (state == null) {
return Mono.empty();
}
return getStateToAuthorizationRequest(exchange)
.filter(stateToAuthorizationRequest -> stateToAuthorizationRequest.containsKey(state))
.map(stateToAuthorizationRequest -> stateToAuthorizationRequest.get(state));
// @formatter:off
return this.getSessionAttributes(exchange)
.filter((sessionAttrs) -> sessionAttrs.containsKey(this.sessionAttributeName))
.map(this::getAuthorizationRequests)
.filter((stateToAuthorizationRequest) -> stateToAuthorizationRequest.containsKey(state))
.map((stateToAuthorizationRequest) -> stateToAuthorizationRequest.get(state));
// @formatter:on
}
@Override
public Mono<Void> saveAuthorizationRequest(
OAuth2AuthorizationRequest authorizationRequest, ServerWebExchange exchange) {
Assert.notNull(authorizationRequest, "authorizationRequest cannot be null");
return saveStateToAuthorizationRequest(exchange)
.doOnNext(stateToAuthorizationRequest -> stateToAuthorizationRequest.put(authorizationRequest.getState(), authorizationRequest))
Assert.notNull(exchange, "exchange cannot be null");
// @formatter:off
return getSessionAttributes(exchange)
.doOnNext((sessionAttrs) -> {
if (this.allowMultipleAuthorizationRequests) {
Map<String, OAuth2AuthorizationRequest> authorizationRequests = this.getAuthorizationRequests(
sessionAttrs);
authorizationRequests.put(authorizationRequest.getState(), authorizationRequest);
sessionAttrs.put(this.sessionAttributeName, authorizationRequests);
}
else {
sessionAttrs.put(this.sessionAttributeName, authorizationRequest);
}
})
.then();
}
@@ -74,27 +93,24 @@ public final class WebSessionOAuth2ServerAuthorizationRequestRepository
if (state == null) {
return Mono.empty();
}
return exchange.getSession()
.map(WebSession::getAttributes)
.handle((sessionAttrs, sink) -> {
Map<String, OAuth2AuthorizationRequest> stateToAuthzRequest = sessionAttrsMapStateToAuthorizationRequest(sessionAttrs);
if (stateToAuthzRequest == null) {
sink.complete();
return;
}
OAuth2AuthorizationRequest removedValue = stateToAuthzRequest.remove(state);
if (stateToAuthzRequest.isEmpty()) {
sessionAttrs.remove(this.sessionAttributeName);
} else if (removedValue != null) {
// gh-7327 Overwrite the existing Map to ensure the state is saved for distributed sessions
sessionAttrs.put(this.sessionAttributeName, stateToAuthzRequest);
}
if (removedValue == null) {
sink.complete();
} else {
sink.next(removedValue);
}
});
// @formatter:off
return getSessionAttributes(exchange)
.flatMap((sessionAttrs) -> {
Map<String, OAuth2AuthorizationRequest> authorizationRequests = this.getAuthorizationRequests(
sessionAttrs);
OAuth2AuthorizationRequest originalRequest = authorizationRequests.remove(state);
if (authorizationRequests.isEmpty()) {
sessionAttrs.remove(this.sessionAttributeName);
}
else if (authorizationRequests.size() == 1) {
sessionAttrs.put(this.sessionAttributeName, authorizationRequests.values().iterator().next());
}
else {
sessionAttrs.put(this.sessionAttributeName, authorizationRequests);
}
return Mono.justOrEmpty(originalRequest);
});
// @formatter:on
}
/**
@@ -111,31 +127,40 @@ public final class WebSessionOAuth2ServerAuthorizationRequestRepository
return exchange.getSession().map(WebSession::getAttributes);
}
private Mono<Map<String, OAuth2AuthorizationRequest>> getStateToAuthorizationRequest(ServerWebExchange exchange) {
Assert.notNull(exchange, "exchange cannot be null");
return getSessionAttributes(exchange)
.flatMap(sessionAttrs -> Mono.justOrEmpty(this.sessionAttrsMapStateToAuthorizationRequest(sessionAttrs)));
private Map<String, OAuth2AuthorizationRequest> getAuthorizationRequests(Map<String, Object> sessionAttrs) {
Object sessionAttributeValue = sessionAttrs.get(this.sessionAttributeName);
if (sessionAttributeValue == null) {
return new HashMap<>();
}
else if (sessionAttributeValue instanceof OAuth2AuthorizationRequest) {
OAuth2AuthorizationRequest oauth2AuthorizationRequest = (OAuth2AuthorizationRequest) sessionAttributeValue;
Map<String, OAuth2AuthorizationRequest> authorizationRequests = new HashMap<>(1);
authorizationRequests.put(oauth2AuthorizationRequest.getState(), oauth2AuthorizationRequest);
return authorizationRequests;
}
else if (sessionAttributeValue instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, OAuth2AuthorizationRequest> authorizationRequests = (Map<String, OAuth2AuthorizationRequest>) sessionAttrs
.get(this.sessionAttributeName);
return authorizationRequests;
}
else {
throw new IllegalStateException(
"authorizationRequests is supposed to be a Map or OAuth2AuthorizationRequest but actually is a "
+ sessionAttributeValue.getClass());
}
}
private Mono<Map<String, OAuth2AuthorizationRequest>> saveStateToAuthorizationRequest(ServerWebExchange exchange) {
Assert.notNull(exchange, "exchange cannot be null");
return getSessionAttributes(exchange)
.doOnNext(sessionAttrs -> {
Object stateToAuthzRequest = sessionAttrs.get(this.sessionAttributeName);
if (stateToAuthzRequest == null) {
stateToAuthzRequest = new HashMap<String, OAuth2AuthorizationRequest>();
}
// No matter stateToAuthzRequest was in session or not, we should always put it into session again
// in case of redis or hazelcast session. #6215
sessionAttrs.put(this.sessionAttributeName, stateToAuthzRequest);
}).flatMap(sessionAttrs -> Mono.justOrEmpty(this.sessionAttrsMapStateToAuthorizationRequest(sessionAttrs)));
}
private Map<String, OAuth2AuthorizationRequest> sessionAttrsMapStateToAuthorizationRequest(Map<String, Object> sessionAttrs) {
return (Map<String, OAuth2AuthorizationRequest>) sessionAttrs.get(this.sessionAttributeName);
/**
* Configure if multiple {@link OAuth2AuthorizationRequest}s should be stored per
* session. Default is false (not allow multiple {@link OAuth2AuthorizationRequest}
* per session).
* @param allowMultipleAuthorizationRequests true allows more than one
* {@link OAuth2AuthorizationRequest} to be stored per session.
* @since 5.5
*/
@Deprecated
public void setAllowMultipleAuthorizationRequests(boolean allowMultipleAuthorizationRequests) {
this.allowMultipleAuthorizationRequests = allowMultipleAuthorizationRequests;
}
}
@@ -15,12 +15,19 @@
*/
package org.springframework.security.oauth2.client.http;
import java.io.IOException;
import org.junit.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.mock.http.MockHttpInputMessage;
import org.springframework.mock.http.client.MockClientHttpResponse;
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
import org.springframework.web.client.UnknownHttpStatusCodeException;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
@@ -58,4 +65,49 @@ public class OAuth2ErrorResponseErrorHandlerTests {
.isInstanceOf(OAuth2AuthorizationException.class)
.hasMessage("[insufficient_scope] The access token expired");
}
@Test
public void handleErrorWhenErrorResponseWithInvalidStatusCodeThenHandled() {
CustomMockClientHttpResponse response = new CustomMockClientHttpResponse(new byte[0], 596);
assertThatExceptionOfType(UnknownHttpStatusCodeException.class)
.isThrownBy(() -> this.errorHandler.handleError(response)).withMessage("596 : [no body]");
}
private static final class CustomMockClientHttpResponse extends MockHttpInputMessage implements ClientHttpResponse {
private final int statusCode;
private CustomMockClientHttpResponse(byte[] content, int statusCode) {
super(content);
this.statusCode = statusCode;
}
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.valueOf(getRawStatusCode());
}
@Override
public int getRawStatusCode() {
return this.statusCode;
}
@Override
public String getStatusText() throws IOException {
HttpStatus httpStatus = HttpStatus.resolve(this.statusCode);
return (httpStatus != null) ? httpStatus.getReasonPhrase() : "";
}
@Override
public void close() {
try {
getBody().close();
}
catch (IOException ex) {
// ignore
}
}
}
}
@@ -0,0 +1,121 @@
/*
* 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.oauth2.client.web;
import org.junit.Before;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link HttpSessionOAuth2AuthorizationRequestRepository} when
* {@link HttpSessionOAuth2AuthorizationRequestRepository#setAllowMultipleAuthorizationRequests(boolean)}
* is enabled.
*
* @author Joe Grandja
* @author Craig Andrews
*/
public class HttpSessionOAuth2AuthorizationRequestRepositoryAllowMultipleAuthorizationRequestsTests
extends HttpSessionOAuth2AuthorizationRequestRepositoryTests {
@Before
public void setup() {
this.authorizationRequestRepository = new HttpSessionOAuth2AuthorizationRequestRepository();
this.authorizationRequestRepository.setAllowMultipleAuthorizationRequests(true);
}
// gh-5110
@Test
public void loadAuthorizationRequestWhenMultipleSavedThenReturnMatchingAuthorizationRequest() {
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
String state1 = "state-1122";
OAuth2AuthorizationRequest authorizationRequest1 = createAuthorizationRequest().state(state1).build();
this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest1, request, response);
String state2 = "state-3344";
OAuth2AuthorizationRequest authorizationRequest2 = createAuthorizationRequest().state(state2).build();
this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest2, request, response);
String state3 = "state-5566";
OAuth2AuthorizationRequest authorizationRequest3 = createAuthorizationRequest().state(state3).build();
this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest3, request, response);
request.addParameter(OAuth2ParameterNames.STATE, state1);
OAuth2AuthorizationRequest loadedAuthorizationRequest1 = this.authorizationRequestRepository
.loadAuthorizationRequest(request);
assertThat(loadedAuthorizationRequest1).isEqualTo(authorizationRequest1);
request.removeParameter(OAuth2ParameterNames.STATE);
request.addParameter(OAuth2ParameterNames.STATE, state2);
OAuth2AuthorizationRequest loadedAuthorizationRequest2 = this.authorizationRequestRepository
.loadAuthorizationRequest(request);
assertThat(loadedAuthorizationRequest2).isEqualTo(authorizationRequest2);
request.removeParameter(OAuth2ParameterNames.STATE);
request.addParameter(OAuth2ParameterNames.STATE, state3);
OAuth2AuthorizationRequest loadedAuthorizationRequest3 = this.authorizationRequestRepository
.loadAuthorizationRequest(request);
assertThat(loadedAuthorizationRequest3).isEqualTo(authorizationRequest3);
}
@Test
public void loadAuthorizationRequestWhenSavedWithAllowMultipleAuthorizationRequests() {
// save 2 requests with legacy (allowMultipleAuthorizationRequests=true) and load
// with new
HttpSessionOAuth2AuthorizationRequestRepository legacy = new HttpSessionOAuth2AuthorizationRequestRepository();
legacy.setAllowMultipleAuthorizationRequests(true);
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
String state1 = "state-1122";
OAuth2AuthorizationRequest authorizationRequest1 = createAuthorizationRequest().state(state1).build();
legacy.saveAuthorizationRequest(authorizationRequest1, request, response);
String state2 = "state-3344";
OAuth2AuthorizationRequest authorizationRequest2 = createAuthorizationRequest().state(state2).build();
legacy.saveAuthorizationRequest(authorizationRequest2, request, response);
request.setParameter(OAuth2ParameterNames.STATE, state1);
OAuth2AuthorizationRequest loaded = this.authorizationRequestRepository.loadAuthorizationRequest(request);
assertThat(loaded).isEqualTo(authorizationRequest1);
}
@Test
public void saveAuthorizationRequestWhenSavedWithAllowMultipleAuthorizationRequests() {
// save 2 requests with legacy (allowMultipleAuthorizationRequests=true), save
// with new, and load with new
HttpSessionOAuth2AuthorizationRequestRepository legacy = new HttpSessionOAuth2AuthorizationRequestRepository();
legacy.setAllowMultipleAuthorizationRequests(true);
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
String state1 = "state-1122";
OAuth2AuthorizationRequest authorizationRequest1 = createAuthorizationRequest().state(state1).build();
legacy.saveAuthorizationRequest(authorizationRequest1, request, response);
String state2 = "state-3344";
OAuth2AuthorizationRequest authorizationRequest2 = createAuthorizationRequest().state(state2).build();
legacy.saveAuthorizationRequest(authorizationRequest2, request, response);
String state3 = "state-5566";
OAuth2AuthorizationRequest authorizationRequest3 = createAuthorizationRequest().state(state3).build();
this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest3, request, response);
request.setParameter(OAuth2ParameterNames.STATE, state3);
OAuth2AuthorizationRequest loaded = this.authorizationRequestRepository.loadAuthorizationRequest(request);
assertThat(loaded).isEqualTo(authorizationRequest3);
}
}
@@ -0,0 +1,76 @@
/*
* 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.oauth2.client.web;
import org.junit.Before;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link HttpSessionOAuth2AuthorizationRequestRepository} when
* {@link HttpSessionOAuth2AuthorizationRequestRepository#setAllowMultipleAuthorizationRequests(boolean)}
* is disabled.
*
* @author Joe Grandja
* @author Craig Andrews
*/
public class HttpSessionOAuth2AuthorizationRequestRepositoryDoNotAllowMultipleAuthorizationRequestsTests
extends HttpSessionOAuth2AuthorizationRequestRepositoryTests {
@Before
public void setup() {
this.authorizationRequestRepository = new HttpSessionOAuth2AuthorizationRequestRepository();
this.authorizationRequestRepository.setAllowMultipleAuthorizationRequests(false);
}
// gh-5145
@Test
public void loadAuthorizationRequestWhenMultipleSavedThenReturnLastAuthorizationRequest() {
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
String state1 = "state-1122";
OAuth2AuthorizationRequest authorizationRequest1 = createAuthorizationRequest().state(state1).build();
this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest1, request, response);
String state2 = "state-3344";
OAuth2AuthorizationRequest authorizationRequest2 = createAuthorizationRequest().state(state2).build();
this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest2, request, response);
String state3 = "state-5566";
OAuth2AuthorizationRequest authorizationRequest3 = createAuthorizationRequest().state(state3).build();
this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest3, request, response);
request.addParameter(OAuth2ParameterNames.STATE, state1);
OAuth2AuthorizationRequest loadedAuthorizationRequest1 = this.authorizationRequestRepository
.loadAuthorizationRequest(request);
assertThat(loadedAuthorizationRequest1).isNull();
request.removeParameter(OAuth2ParameterNames.STATE);
request.addParameter(OAuth2ParameterNames.STATE, state2);
OAuth2AuthorizationRequest loadedAuthorizationRequest2 = this.authorizationRequestRepository
.loadAuthorizationRequest(request);
assertThat(loadedAuthorizationRequest2).isNull();
request.removeParameter(OAuth2ParameterNames.STATE);
request.addParameter(OAuth2ParameterNames.STATE, state3);
OAuth2AuthorizationRequest loadedAuthorizationRequest3 = this.authorizationRequestRepository
.loadAuthorizationRequest(request);
assertThat(loadedAuthorizationRequest3).isEqualTo(authorizationRequest3);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 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.
@@ -34,11 +34,12 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
* Tests for {@link HttpSessionOAuth2AuthorizationRequestRepository}.
*
* @author Joe Grandja
* @author Craig Andrews
*/
@RunWith(MockitoJUnitRunner.class)
public class HttpSessionOAuth2AuthorizationRequestRepositoryTests {
private HttpSessionOAuth2AuthorizationRequestRepository authorizationRequestRepository =
new HttpSessionOAuth2AuthorizationRequestRepository();
public abstract class HttpSessionOAuth2AuthorizationRequestRepositoryTests {
protected HttpSessionOAuth2AuthorizationRequestRepository authorizationRequestRepository;
@Test(expected = IllegalArgumentException.class)
public void loadAuthorizationRequestWhenHttpServletRequestIsNullThenThrowIllegalArgumentException() {
@@ -70,42 +71,6 @@ public class HttpSessionOAuth2AuthorizationRequestRepositoryTests {
assertThat(loadedAuthorizationRequest).isEqualTo(authorizationRequest);
}
// gh-5110
@Test
public void loadAuthorizationRequestWhenMultipleSavedThenReturnMatchingAuthorizationRequest() {
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
String state1 = "state-1122";
OAuth2AuthorizationRequest authorizationRequest1 = createAuthorizationRequest().state(state1).build();
this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest1, request, response);
String state2 = "state-3344";
OAuth2AuthorizationRequest authorizationRequest2 = createAuthorizationRequest().state(state2).build();
this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest2, request, response);
String state3 = "state-5566";
OAuth2AuthorizationRequest authorizationRequest3 = createAuthorizationRequest().state(state3).build();
this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest3, request, response);
request.addParameter(OAuth2ParameterNames.STATE, state1);
OAuth2AuthorizationRequest loadedAuthorizationRequest1 =
this.authorizationRequestRepository.loadAuthorizationRequest(request);
assertThat(loadedAuthorizationRequest1).isEqualTo(authorizationRequest1);
request.removeParameter(OAuth2ParameterNames.STATE);
request.addParameter(OAuth2ParameterNames.STATE, state2);
OAuth2AuthorizationRequest loadedAuthorizationRequest2 =
this.authorizationRequestRepository.loadAuthorizationRequest(request);
assertThat(loadedAuthorizationRequest2).isEqualTo(authorizationRequest2);
request.removeParameter(OAuth2ParameterNames.STATE);
request.addParameter(OAuth2ParameterNames.STATE, state3);
OAuth2AuthorizationRequest loadedAuthorizationRequest3 =
this.authorizationRequestRepository.loadAuthorizationRequest(request);
assertThat(loadedAuthorizationRequest3).isEqualTo(authorizationRequest3);
}
@Test
public void loadAuthorizationRequestWhenSavedAndStateParameterNullThenReturnNull() {
MockHttpServletRequest request = new MockHttpServletRequest();
@@ -284,11 +249,9 @@ public class HttpSessionOAuth2AuthorizationRequestRepositoryTests {
assertThat(removedAuthorizationRequest).isNull();
}
private OAuth2AuthorizationRequest.Builder createAuthorizationRequest() {
return OAuth2AuthorizationRequest.authorizationCode()
.authorizationUri("https://example.com/oauth2/authorize")
.clientId("client-id-1234")
.state("state-1234");
protected OAuth2AuthorizationRequest.Builder createAuthorizationRequest() {
return OAuth2AuthorizationRequest.authorizationCode().authorizationUri("https://example.com/oauth2/authorize")
.clientId("client-id-1234").state("state-1234");
}
static class MockDistributedHttpSession extends MockHttpSession {
@@ -0,0 +1,252 @@
/*
* 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.oauth2.client.web.server;
import java.util.HashMap;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.MockServerHttpResponse;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebSession;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import org.springframework.web.server.i18n.AcceptHeaderLocaleContextResolver;
import org.springframework.web.server.session.WebSessionManager;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link WebSessionOAuth2ServerAuthorizationRequestRepository} when
* {@link WebSessionOAuth2ServerAuthorizationRequestRepository#setAllowMultipleAuthorizationRequests(boolean)}
* is enabled.
*
* @author Steve Riesenberg
*/
public class WebSessionOAuth2ServerAuthorizationRequestRepositoryAllowMultipleAuthorizationRequestsTests
extends WebSessionOAuth2ServerAuthorizationRequestRepositoryTests {
@Before
public void setup() {
this.repository = new WebSessionOAuth2ServerAuthorizationRequestRepository();
this.repository.setAllowMultipleAuthorizationRequests(true);
}
@Test
public void loadAuthorizationRequestWhenMultipleSavedThenAuthorizationRequest() {
String oldState = "state0";
// @formatter:off
MockServerHttpRequest oldRequest = MockServerHttpRequest.get("/")
.queryParam(OAuth2ParameterNames.STATE, oldState)
.build();
OAuth2AuthorizationRequest oldAuthorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
.authorizationUri("https://example.com/oauth2/authorize")
.clientId("client-id")
.redirectUri("http://localhost/client-1")
.state(oldState)
.build();
// @formatter:on
WebSessionManager sessionManager = (e) -> this.exchange.getSession();
this.exchange = new DefaultServerWebExchange(this.exchange.getRequest(), new MockServerHttpResponse(),
sessionManager, ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver());
ServerWebExchange oldExchange = new DefaultServerWebExchange(oldRequest, new MockServerHttpResponse(),
sessionManager, ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver());
// @formatter:off
Mono<OAuth2AuthorizationRequest> saveAndSaveAndLoad = this.repository
.saveAuthorizationRequest(oldAuthorizationRequest, oldExchange)
.then(this.repository.saveAuthorizationRequest(this.authorizationRequest, this.exchange))
.then(this.repository.loadAuthorizationRequest(oldExchange));
StepVerifier.create(saveAndSaveAndLoad)
.expectNext(oldAuthorizationRequest)
.verifyComplete();
StepVerifier.create(this.repository.loadAuthorizationRequest(this.exchange))
.expectNext(this.authorizationRequest)
.verifyComplete();
// @formatter:on
}
// gh-5145
@Test
public void loadAuthorizationRequestWhenSavedWithAllowMultipleAuthorizationRequestsThenReturnOldAuthorizationRequest() {
// save 2 requests with legacy (allowMultipleAuthorizationRequests=true) and load
// with new
WebSessionOAuth2ServerAuthorizationRequestRepository legacy = new WebSessionOAuth2ServerAuthorizationRequestRepository();
legacy.setAllowMultipleAuthorizationRequests(true);
// @formatter:off
String state1 = "state-1122";
OAuth2AuthorizationRequest authorizationRequest1 = OAuth2AuthorizationRequest.authorizationCode()
.authorizationUri("https://example.com/oauth2/authorize")
.clientId("client-id")
.redirectUri("http://localhost/client-1")
.state(state1)
.build();
StepVerifier.create(legacy.saveAuthorizationRequest(authorizationRequest1, this.exchange))
.verifyComplete();
String state2 = "state-3344";
OAuth2AuthorizationRequest authorizationRequest2 = OAuth2AuthorizationRequest.authorizationCode()
.authorizationUri("https://example.com/oauth2/authorize")
.clientId("client-id")
.redirectUri("http://localhost/client-1")
.state(state2)
.build();
StepVerifier.create(legacy.saveAuthorizationRequest(authorizationRequest2, this.exchange))
.verifyComplete();
ServerHttpRequest newRequest = MockServerHttpRequest.get("/")
.queryParam(OAuth2ParameterNames.STATE, state1)
.build();
ServerWebExchange newExchange = this.exchange.mutate()
.request(newRequest)
.build();
StepVerifier.create(this.repository.loadAuthorizationRequest(newExchange))
.expectNext(authorizationRequest1)
.verifyComplete();
// @formatter:on
}
// gh-5145
@Test
public void saveAuthorizationRequestWhenSavedWithAllowMultipleAuthorizationRequestsThenLoadNewAuthorizationRequest() {
// save 2 requests with legacy (allowMultipleAuthorizationRequests=true), save
// with new, and load with new
WebSessionOAuth2ServerAuthorizationRequestRepository legacy = new WebSessionOAuth2ServerAuthorizationRequestRepository();
legacy.setAllowMultipleAuthorizationRequests(true);
// @formatter:off
String state1 = "state-1122";
OAuth2AuthorizationRequest authorizationRequest1 = OAuth2AuthorizationRequest.authorizationCode()
.authorizationUri("https://example.com/oauth2/authorize")
.clientId("client-id")
.redirectUri("http://localhost/client-1")
.state(state1)
.build();
StepVerifier.create(legacy.saveAuthorizationRequest(authorizationRequest1, this.exchange))
.verifyComplete();
String state2 = "state-3344";
OAuth2AuthorizationRequest authorizationRequest2 = OAuth2AuthorizationRequest.authorizationCode()
.authorizationUri("https://example.com/oauth2/authorize")
.clientId("client-id")
.redirectUri("http://localhost/client-1")
.state(state2)
.build();
StepVerifier.create(legacy.saveAuthorizationRequest(authorizationRequest2, this.exchange))
.verifyComplete();
String state3 = "state-5566";
OAuth2AuthorizationRequest authorizationRequest3 = OAuth2AuthorizationRequest.authorizationCode()
.authorizationUri("https://example.com/oauth2/authorize")
.clientId("client-id")
.redirectUri("http://localhost/client-1")
.state(state3)
.build();
ServerHttpRequest newRequest = MockServerHttpRequest.get("/")
.queryParam(OAuth2ParameterNames.STATE, state3)
.build();
ServerWebExchange newExchange = this.exchange.mutate()
.request(newRequest)
.build();
Mono<OAuth2AuthorizationRequest> saveAndLoad = this.repository
.saveAuthorizationRequest(authorizationRequest3, this.exchange)
.then(this.repository.loadAuthorizationRequest(newExchange));
StepVerifier.create(saveAndLoad)
.expectNext(authorizationRequest3)
.verifyComplete();
// @formatter:on
}
@Test
public void removeAuthorizationRequestWhenMultipleThenOnlyOneRemoved() {
String oldState = "state0";
// @formatter:off
MockServerHttpRequest oldRequest = MockServerHttpRequest.get("/")
.queryParam(OAuth2ParameterNames.STATE, oldState)
.build();
OAuth2AuthorizationRequest oldAuthorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
.authorizationUri("https://example.com/oauth2/authorize")
.clientId("client-id")
.redirectUri("http://localhost/client-1")
.state(oldState)
.build();
// @formatter:on
WebSessionManager sessionManager = (e) -> this.exchange.getSession();
this.exchange = new DefaultServerWebExchange(this.exchange.getRequest(), new MockServerHttpResponse(),
sessionManager, ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver());
ServerWebExchange oldExchange = new DefaultServerWebExchange(oldRequest, new MockServerHttpResponse(),
sessionManager, ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver());
// @formatter:off
Mono<OAuth2AuthorizationRequest> saveAndSaveAndRemove = this.repository
.saveAuthorizationRequest(oldAuthorizationRequest, oldExchange)
.then(this.repository.saveAuthorizationRequest(this.authorizationRequest, this.exchange))
.then(this.repository.removeAuthorizationRequest(this.exchange));
StepVerifier.create(saveAndSaveAndRemove).expectNext(this.authorizationRequest)
.verifyComplete();
StepVerifier.create(this.repository.loadAuthorizationRequest(this.exchange))
.verifyComplete();
StepVerifier.create(this.repository.loadAuthorizationRequest(oldExchange))
.expectNext(oldAuthorizationRequest)
.verifyComplete();
// @formatter:on
}
// gh-7327
@Test
public void removeAuthorizationRequestWhenMultipleThenRemovedAndSessionAttributeUpdated() {
String oldState = "state0";
// @formatter:off
MockServerHttpRequest oldRequest = MockServerHttpRequest.get("/")
.queryParam(OAuth2ParameterNames.STATE, oldState)
.build();
OAuth2AuthorizationRequest oldAuthorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
.authorizationUri("https://example.com/oauth2/authorize")
.clientId("client-id")
.redirectUri("http://localhost/client-1")
.state(oldState)
.build();
// @formatter:on
Map<String, Object> sessionAttrs = spy(new HashMap<>());
WebSession session = mock(WebSession.class);
given(session.getAttributes()).willReturn(sessionAttrs);
WebSessionManager sessionManager = (e) -> Mono.just(session);
this.exchange = new DefaultServerWebExchange(this.exchange.getRequest(), new MockServerHttpResponse(),
sessionManager, ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver());
ServerWebExchange oldExchange = new DefaultServerWebExchange(oldRequest, new MockServerHttpResponse(),
sessionManager, ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver());
// @formatter:off
Mono<OAuth2AuthorizationRequest> saveAndSaveAndRemove = this.repository
.saveAuthorizationRequest(oldAuthorizationRequest, oldExchange)
.then(this.repository.saveAuthorizationRequest(this.authorizationRequest, this.exchange))
.then(this.repository.removeAuthorizationRequest(this.exchange));
StepVerifier.create(saveAndSaveAndRemove).expectNext(this.authorizationRequest)
.verifyComplete();
StepVerifier.create(this.repository.loadAuthorizationRequest(this.exchange))
.verifyComplete();
// @formatter:on
verify(sessionAttrs, times(3)).put(any(), any());
}
}
@@ -0,0 +1,159 @@
/*
* 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.oauth2.client.web.server;
import java.util.HashMap;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.MockServerHttpResponse;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebSession;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import org.springframework.web.server.i18n.AcceptHeaderLocaleContextResolver;
import org.springframework.web.server.session.WebSessionManager;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link WebSessionOAuth2ServerAuthorizationRequestRepository} when
* {@link WebSessionOAuth2ServerAuthorizationRequestRepository#setAllowMultipleAuthorizationRequests(boolean)}
* is disabled.
*
* @author Steve Riesenberg
*/
public class WebSessionOAuth2ServerAuthorizationRequestRepositoryDoNotAllowMultipleAuthorizationRequestsTests
extends WebSessionOAuth2ServerAuthorizationRequestRepositoryTests {
@Before
public void setup() {
this.repository = new WebSessionOAuth2ServerAuthorizationRequestRepository();
this.repository.setAllowMultipleAuthorizationRequests(false);
}
// gh-5145
@Test
public void loadAuthorizationRequestWhenMultipleSavedThenReturnLastAuthorizationRequest() {
// @formatter:off
String state1 = "state-1122";
OAuth2AuthorizationRequest authorizationRequest1 = OAuth2AuthorizationRequest.authorizationCode()
.authorizationUri("https://example.com/oauth2/authorize")
.clientId("client-id")
.redirectUri("http://localhost/client-1")
.state(state1)
.build();
StepVerifier.create(this.repository.saveAuthorizationRequest(authorizationRequest1, this.exchange))
.verifyComplete();
String state2 = "state-3344";
OAuth2AuthorizationRequest authorizationRequest2 = OAuth2AuthorizationRequest.authorizationCode()
.authorizationUri("https://example.com/oauth2/authorize")
.clientId("client-id")
.redirectUri("http://localhost/client-1")
.state(state2)
.build();
StepVerifier.create(this.repository.saveAuthorizationRequest(authorizationRequest2, this.exchange))
.verifyComplete();
String state3 = "state-5566";
OAuth2AuthorizationRequest authorizationRequest3 = OAuth2AuthorizationRequest.authorizationCode()
.authorizationUri("https://example.com/oauth2/authorize")
.clientId("client-id")
.redirectUri("http://localhost/client-1")
.state(state3)
.build();
StepVerifier.create(this.repository.saveAuthorizationRequest(authorizationRequest3, this.exchange))
.verifyComplete();
ServerHttpRequest newRequest1 = MockServerHttpRequest.get("/")
.queryParam(OAuth2ParameterNames.STATE, state1)
.build();
ServerWebExchange newExchange1 = this.exchange.mutate()
.request(newRequest1)
.build();
StepVerifier.create(this.repository.loadAuthorizationRequest(newExchange1))
.verifyComplete();
ServerHttpRequest newRequest2 = MockServerHttpRequest.get("/")
.queryParam(OAuth2ParameterNames.STATE, state2)
.build();
ServerWebExchange newExchange2 = this.exchange.mutate()
.request(newRequest2)
.build();
StepVerifier.create(this.repository.loadAuthorizationRequest(newExchange2))
.verifyComplete();
ServerHttpRequest newRequest3 = MockServerHttpRequest.get("/")
.queryParam(OAuth2ParameterNames.STATE, state3)
.build();
ServerWebExchange newExchange3 = this.exchange.mutate()
.request(newRequest3)
.build();
StepVerifier.create(this.repository.loadAuthorizationRequest(newExchange3))
.expectNext(authorizationRequest3)
.verifyComplete();
// @formatter:on
}
// gh-5145
@Test
public void removeAuthorizationRequestWhenMultipleThenSessionAttributeRemoved() {
String oldState = "state0";
// @formatter:off
MockServerHttpRequest oldRequest = MockServerHttpRequest.get("/")
.queryParam(OAuth2ParameterNames.STATE, oldState)
.build();
OAuth2AuthorizationRequest oldAuthorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
.authorizationUri("https://example.com/oauth2/authorize")
.clientId("client-id")
.redirectUri("http://localhost/client-1")
.state(oldState)
.build();
// @formatter:on
Map<String, Object> sessionAttrs = spy(new HashMap<>());
WebSession session = mock(WebSession.class);
given(session.getAttributes()).willReturn(sessionAttrs);
WebSessionManager sessionManager = (e) -> Mono.just(session);
this.exchange = new DefaultServerWebExchange(this.exchange.getRequest(), new MockServerHttpResponse(),
sessionManager, ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver());
ServerWebExchange oldExchange = new DefaultServerWebExchange(oldRequest, new MockServerHttpResponse(),
sessionManager, ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver());
// @formatter:off
Mono<OAuth2AuthorizationRequest> saveAndSaveAndRemove = this.repository
.saveAuthorizationRequest(oldAuthorizationRequest, oldExchange)
.then(this.repository.saveAuthorizationRequest(this.authorizationRequest, this.exchange))
.then(this.repository.removeAuthorizationRequest(this.exchange));
StepVerifier.create(saveAndSaveAndRemove).expectNext(this.authorizationRequest)
.verifyComplete();
StepVerifier.create(this.repository.loadAuthorizationRequest(this.exchange))
.verifyComplete();
// @formatter:on
verify(sessionAttrs, times(2)).put(anyString(), any());
verify(sessionAttrs).remove(anyString());
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 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,51 +16,39 @@
package org.springframework.security.oauth2.client.web.server;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import org.springframework.http.codec.ServerCodecConfigurer;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.MockServerHttpResponse;
import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebSession;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import org.springframework.web.server.i18n.AcceptHeaderLocaleContextResolver;
import org.springframework.web.server.session.WebSessionManager;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* @author Rob Winch
* @since 5.1
*/
public class WebSessionOAuth2ServerAuthorizationRequestRepositoryTests {
public abstract class WebSessionOAuth2ServerAuthorizationRequestRepositoryTests {
private WebSessionOAuth2ServerAuthorizationRequestRepository repository =
new WebSessionOAuth2ServerAuthorizationRequestRepository();
protected WebSessionOAuth2ServerAuthorizationRequestRepository repository;
private OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
// @formatter:off
protected OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
.authorizationUri("https://example.com/oauth2/authorize")
.clientId("client-id")
.redirectUri("http://localhost/client-1")
.state("state")
.build();
private ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/")
.queryParam(OAuth2ParameterNames.STATE, "state"));
protected ServerWebExchange exchange = MockServerWebExchange
.from(MockServerHttpRequest.get("/").queryParam(OAuth2ParameterNames.STATE, "state"));
@Test
public void loadAuthorizationRequestWhenNullExchangeThenIllegalArgumentException() {
@@ -106,39 +94,6 @@ public class WebSessionOAuth2ServerAuthorizationRequestRepositoryTests {
.verifyComplete();
}
@Test
public void loadAuthorizationRequestWhenMultipleSavedThenAuthorizationRequest() {
String oldState = "state0";
MockServerHttpRequest oldRequest = MockServerHttpRequest.get("/")
.queryParam(OAuth2ParameterNames.STATE, oldState).build();
OAuth2AuthorizationRequest oldAuthorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
.authorizationUri("https://example.com/oauth2/authorize")
.clientId("client-id")
.redirectUri("http://localhost/client-1")
.state(oldState)
.build();
WebSessionManager sessionManager = e -> this.exchange.getSession();
this.exchange = new DefaultServerWebExchange(this.exchange.getRequest(), new MockServerHttpResponse(), sessionManager,
ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver());
ServerWebExchange oldExchange = new DefaultServerWebExchange(oldRequest, new MockServerHttpResponse(), sessionManager,
ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver());
Mono<OAuth2AuthorizationRequest> saveAndSaveAndLoad = this.repository.saveAuthorizationRequest(oldAuthorizationRequest, oldExchange)
.then(this.repository.saveAuthorizationRequest(this.authorizationRequest, this.exchange))
.then(this.repository.loadAuthorizationRequest(oldExchange));
StepVerifier.create(saveAndSaveAndLoad)
.expectNext(oldAuthorizationRequest)
.verifyComplete();
StepVerifier.create(this.repository.loadAuthorizationRequest(this.exchange))
.expectNext(this.authorizationRequest)
.verifyComplete();
}
@Test
public void saveAuthorizationRequestWhenAuthorizationRequestNullThenThrowsIllegalArgumentException() {
this.authorizationRequest = null;
@@ -203,80 +158,6 @@ public class WebSessionOAuth2ServerAuthorizationRequestRepositoryTests {
.verifyComplete();
}
@Test
public void removeAuthorizationRequestWhenMultipleThenOnlyOneRemoved() {
String oldState = "state0";
MockServerHttpRequest oldRequest = MockServerHttpRequest.get("/")
.queryParam(OAuth2ParameterNames.STATE, oldState).build();
OAuth2AuthorizationRequest oldAuthorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
.authorizationUri("https://example.com/oauth2/authorize")
.clientId("client-id")
.redirectUri("http://localhost/client-1")
.state(oldState)
.build();
WebSessionManager sessionManager = e -> this.exchange.getSession();
this.exchange = new DefaultServerWebExchange(this.exchange.getRequest(), new MockServerHttpResponse(), sessionManager,
ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver());
ServerWebExchange oldExchange = new DefaultServerWebExchange(oldRequest, new MockServerHttpResponse(), sessionManager,
ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver());
Mono<OAuth2AuthorizationRequest> saveAndSaveAndRemove = this.repository.saveAuthorizationRequest(oldAuthorizationRequest, oldExchange)
.then(this.repository.saveAuthorizationRequest(this.authorizationRequest, this.exchange))
.then(this.repository.removeAuthorizationRequest(this.exchange));
StepVerifier.create(saveAndSaveAndRemove)
.expectNext(this.authorizationRequest)
.verifyComplete();
StepVerifier.create(this.repository.loadAuthorizationRequest(this.exchange))
.verifyComplete();
StepVerifier.create(this.repository.loadAuthorizationRequest(oldExchange))
.expectNext(oldAuthorizationRequest)
.verifyComplete();
}
// gh-7327
@Test
public void removeAuthorizationRequestWhenMultipleThenRemovedAndSessionAttributeUpdated() {
String oldState = "state0";
MockServerHttpRequest oldRequest = MockServerHttpRequest.get("/")
.queryParam(OAuth2ParameterNames.STATE, oldState).build();
OAuth2AuthorizationRequest oldAuthorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
.authorizationUri("https://example.com/oauth2/authorize")
.clientId("client-id")
.redirectUri("http://localhost/client-1")
.state(oldState)
.build();
Map<String, Object> sessionAttrs = spy(new HashMap<>());
WebSession session = mock(WebSession.class);
when(session.getAttributes()).thenReturn(sessionAttrs);
WebSessionManager sessionManager = e -> Mono.just(session);
this.exchange = new DefaultServerWebExchange(this.exchange.getRequest(), new MockServerHttpResponse(), sessionManager,
ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver());
ServerWebExchange oldExchange = new DefaultServerWebExchange(oldRequest, new MockServerHttpResponse(), sessionManager,
ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver());
Mono<OAuth2AuthorizationRequest> saveAndSaveAndRemove = this.repository.saveAuthorizationRequest(oldAuthorizationRequest, oldExchange)
.then(this.repository.saveAuthorizationRequest(this.authorizationRequest, this.exchange))
.then(this.repository.removeAuthorizationRequest(this.exchange));
StepVerifier.create(saveAndSaveAndRemove)
.expectNext(this.authorizationRequest)
.verifyComplete();
StepVerifier.create(this.repository.loadAuthorizationRequest(this.exchange))
.verifyComplete();
verify(sessionAttrs, times(3)).put(any(), any());
}
private void assertSessionStartedIs(boolean expected) {
Mono<Boolean> isStarted = this.exchange.getSession().map(WebSession::isStarted);
StepVerifier.create(isStarted)
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 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.
@@ -45,19 +45,20 @@ public final class MappedJwtClaimSetConverter implements Converter<Map<String, O
private final static TypeDescriptor INSTANT_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Instant.class);
private final static TypeDescriptor URL_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(URL.class);
private final Map<String, Converter<Object, ?>> claimTypeConverters;
private final Converter<Map<String, Object>, Map<String, Object>> delegate;
/**
* Constructs a {@link MappedJwtClaimSetConverter} with the provided arguments
*
* This will completely replace any set of default converters.
*
* A converter that returns {@code null} removes the claim from the claim set. A
* converter that returns a non-{@code null} value adds or replaces that claim in the
* claim set.
* @param claimTypeConverters The {@link Map} of converters to use
*/
public MappedJwtClaimSetConverter(Map<String, Converter<Object, ?>> claimTypeConverters) {
Assert.notNull(claimTypeConverters, "claimTypeConverters cannot be null");
this.claimTypeConverters = claimTypeConverters;
this.delegate = new ClaimTypeConverter(claimTypeConverters);
}
/**
@@ -81,6 +82,9 @@ public final class MappedJwtClaimSetConverter implements Converter<Map<String, O
*
* To completely replace the underlying {@link Map} of converters, see {@link MappedJwtClaimSetConverter#MappedJwtClaimSetConverter(Map)}.
*
* A converter that returns {@code null} removes the claim from the claim set. A
* converter that returns a non-{@code null} value adds or replaces that claim in the
* claim set.
* @param claimTypeConverters
* @return An instance of {@link MappedJwtClaimSetConverter} that contains the converters provided,
* plus any defaults that were not overridden.
@@ -144,12 +148,16 @@ public final class MappedJwtClaimSetConverter implements Converter<Map<String, O
@Override
public Map<String, Object> convert(Map<String, Object> claims) {
Assert.notNull(claims, "claims cannot be null");
Map<String, Object> mappedClaims = this.delegate.convert(claims);
mappedClaims = removeClaims(mappedClaims);
mappedClaims = addClaims(mappedClaims);
Map<String, Object> mappedClaims = new HashMap<>(claims);
for (Map.Entry<String, Converter<Object, ?>> entry : this.claimTypeConverters.entrySet()) {
String claimName = entry.getKey();
Converter<Object, ?> converter = entry.getValue();
if (converter != null) {
Object claim = claims.get(claimName);
Object mappedClaim = converter.convert(claim);
mappedClaims.compute(claimName, (key, value) -> mappedClaim);
}
}
Instant issuedAt = (Instant) mappedClaims.get(JwtClaimNames.IAT);
Instant expiresAt = (Instant) mappedClaims.get(JwtClaimNames.EXP);
if (issuedAt == null && expiresAt != null) {
@@ -159,23 +167,4 @@ public final class MappedJwtClaimSetConverter implements Converter<Map<String, O
return mappedClaims;
}
private Map<String, Object> removeClaims(Map<String, Object> claims) {
Map<String, Object> result = new HashMap<>();
for (Map.Entry<String, Object> entry : claims.entrySet()) {
if (entry.getValue() != null) {
result.put(entry.getKey(), entry.getValue());
}
}
return result;
}
private Map<String, Object> addClaims(Map<String, Object> claims) {
Map<String, Object> result = new HashMap<>(claims);
for (Map.Entry<String, Converter<Object, ?>> entry : claimTypeConverters.entrySet()) {
if (!claims.containsKey(entry.getKey()) && entry.getValue().convert(null) != null) {
result.put(entry.getKey(), entry.getValue().convert(null));
}
}
return result;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 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.
@@ -140,11 +140,19 @@ public class MappedJwtClaimSetConverterTests {
assertThat(target.get(JwtClaimNames.SUB)).isEqualTo("1234");
}
// gh-10135
@Test
public void convertWhenConverterReturnsNullThenClaimIsRemoved() {
MappedJwtClaimSetConverter converter = MappedJwtClaimSetConverter
.withDefaults(Collections.emptyMap());
.withDefaults(Collections.singletonMap(JwtClaimNames.NBF, (nbfClaimValue) -> null));
Map<String, Object> source = Collections.singletonMap(JwtClaimNames.NBF, Instant.now());
Map<String, Object> target = converter.convert(source);
assertThat(target).doesNotContainKey(JwtClaimNames.NBF);
}
@Test
public void convertWhenClaimValueIsNullThenClaimIsRemoved() {
MappedJwtClaimSetConverter converter = MappedJwtClaimSetConverter.withDefaults(Collections.emptyMap());
Map<String, Object> source = Collections.singletonMap(JwtClaimNames.ISS, null);
Map<String, Object> target = converter.convert(source);
@@ -1,5 +1,5 @@
/*
* Copyright 2019 the original author or authors.
* Copyright 2019-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.
@@ -104,15 +104,18 @@ class PayloadInterceptorRSocket extends RSocketProxy implements ResponderRSocket
return intercept(PayloadExchangeType.REQUEST_CHANNEL, firstPayload)
.flatMapMany(context ->
innerFlux
.skip(1)
.flatMap(p -> intercept(PayloadExchangeType.PAYLOAD, p).thenReturn(p))
.transform(securedPayloads -> Flux.concat(Flux.just(firstPayload), securedPayloads))
.index()
.concatMap(tuple -> justOrIntercept(tuple.getT1(), tuple.getT2()))
.transform(securedPayloads -> this.source.requestChannel(securedPayloads))
.subscriberContext(context)
);
});
}
private Mono<Payload> justOrIntercept(Long index, Payload payload) {
return (index == 0) ? Mono.just(payload) : intercept(PayloadExchangeType.PAYLOAD, payload).thenReturn(payload);
}
@Override
public Mono<Void> metadataPush(Payload payload) {
return intercept(PayloadExchangeType.METADATA_PUSH, payload)
@@ -1,5 +1,5 @@
/*
* Copyright 2019 the original author or authors.
* Copyright 2019-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.
@@ -19,6 +19,8 @@ package org.springframework.security.rsocket.core;
import io.rsocket.Payload;
import io.rsocket.RSocket;
import io.rsocket.metadata.WellKnownMimeType;
import io.rsocket.util.ByteBufPayload;
import io.rsocket.util.DefaultPayload;
import io.rsocket.util.RSocketProxy;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -28,7 +30,9 @@ import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscription;
import org.springframework.http.MediaType;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
@@ -41,6 +45,8 @@ import org.springframework.security.rsocket.core.DefaultPayloadExchange;
import org.springframework.security.rsocket.core.PayloadInterceptorRSocket;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;
import reactor.util.context.Context;
import reactor.core.CoreSubscriber;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
@@ -50,10 +56,13 @@ import reactor.test.publisher.TestPublisher;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
@@ -315,6 +324,57 @@ public class PayloadInterceptorRSocketTests {
verify(this.delegate).requestChannel(any());
}
// gh-9345
@Test
public void requestChannelWhenInterceptorCompletesThenAllPayloadsRetained() {
ExecutorService executors = Executors.newSingleThreadExecutor();
Payload payload = ByteBufPayload.create("data");
Payload payloadTwo = ByteBufPayload.create("moredata");
Payload payloadThree = ByteBufPayload.create("stillmoredata");
Context ctx = Context.empty();
Flux<Payload> payloads = this.payloadResult.flux();
when(this.interceptor.intercept(any(), any())).thenReturn(Mono.empty())
.thenReturn(Mono.error(() -> new AccessDeniedException("Access Denied")));
when(this.delegate.requestChannel(any())).thenAnswer((invocation) -> {
Flux<Payload> input = invocation.getArgument(0);
return Flux.from(input).switchOnFirst((signal, innerFlux) -> innerFlux.map(Payload::getDataUtf8)
.transform((data) -> Flux.<String>create((emitter) -> {
Runnable run = () -> data.subscribe(new CoreSubscriber<String>() {
@Override
public void onSubscribe(Subscription s) {
s.request(3);
}
@Override
public void onNext(String s) {
emitter.next(s);
}
@Override
public void onError(Throwable t) {
emitter.error(t);
}
@Override
public void onComplete() {
emitter.complete();
}
});
executors.execute(run);
})).map(DefaultPayload::create));
});
PayloadInterceptorRSocket interceptor = new PayloadInterceptorRSocket(this.delegate,
Arrays.asList(this.interceptor), this.metadataMimeType, this.dataMimeType, ctx);
StepVerifier.create(interceptor.requestChannel(payloads).doOnDiscard(Payload.class, Payload::release))
.then(() -> this.payloadResult.assertSubscribers())
.then(() -> this.payloadResult.emit(payload, payloadTwo, payloadThree))
.assertNext((next) -> assertThat(next.getDataUtf8()).isEqualTo(payload.getDataUtf8()))
.verifyError(AccessDeniedException.class);
verify(this.interceptor, times(2)).intercept(this.exchange.capture(), any());
assertThat(this.exchange.getValue().getPayload()).isEqualTo(payloadTwo);
verify(this.delegate).requestChannel(any());
}
@Test
public void requestChannelWhenInterceptorErrorsThenDelegateNotSubscribed() {
RuntimeException expected = new RuntimeException("Oops");
@@ -1,5 +1,7 @@
apply plugin: 'io.spring.convention.spring-sample-boot'
ext['thymeleaf.version']='3.0.14.RELEASE'
dependencies {
compile project(':spring-security-config')
compile project(':spring-security-web')
@@ -1,5 +1,7 @@
apply plugin: 'io.spring.convention.spring-sample-boot'
ext['thymeleaf.version']='3.0.14.RELEASE'
dependencies {
compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
compile 'org.springframework.boot:spring-boot-starter-web'
@@ -1,5 +1,7 @@
apply plugin: 'io.spring.convention.spring-sample-boot'
ext['thymeleaf.version']='3.0.14.RELEASE'
dependencies {
compile project(':spring-security-config')
compile project(':spring-security-oauth2-client')
@@ -16,6 +16,8 @@
apply plugin: 'io.spring.convention.spring-sample-boot'
ext['thymeleaf.version']='3.0.14.RELEASE'
dependencies {
compile project(':spring-security-core')
compile project(':spring-security-config')
@@ -18,3 +18,7 @@ dependencies {
runtime 'ch.qos.logback:logback-classic'
runtime 'opensymphony:sitemesh'
}
gretty {
logbackConfigFile = "src/main/resources/logback.xml"
}
@@ -38,3 +38,7 @@ dependencies {
integrationTestCompile seleniumDependencies
}
gretty {
logbackConfigFile = "src/main/resources/logback.xml"
}
@@ -18,3 +18,7 @@ dependencies {
runtime 'opensymphony:sitemesh'
}
gretty {
logbackConfigFile = "src/main/resources/logback.xml"
}
@@ -20,3 +20,7 @@ dependencies {
testCompile project(':spring-security-test')
}
gretty {
logbackConfigFile = "src/main/resources/logback.xml"
}
@@ -27,3 +27,7 @@ dependencies {
integrationTestCompile seleniumDependencies
}
gretty {
logbackConfigFile = "src/main/resources/logback.xml"
}
@@ -20,3 +20,7 @@ dependencies {
testCompile project(':spring-security-test')
}
gretty {
logbackConfigFile = "src/main/resources/logback.xml"
}
@@ -39,3 +39,7 @@ dependencies {
integrationTestCompile seleniumDependencies
}
gretty {
logbackConfigFile = "src/main/resources/logback.xml"
}
@@ -37,3 +37,7 @@ dependencies {
integrationTestCompile seleniumDependencies
}
gretty {
logbackConfigFile = "src/main/resources/logback.xml"
}
@@ -29,3 +29,7 @@ dependencies {
providedCompile 'javax.servlet:javax.servlet-api'
}
gretty {
logbackConfigFile = "src/main/resources/logback.xml"
}
@@ -20,3 +20,7 @@ dependencies {
runtime 'net.sourceforge.nekohtml:nekohtml'
runtime 'opensymphony:sitemesh'
}
gretty {
logbackConfigFile = "src/main/resources/logback.xml"
}
@@ -17,3 +17,7 @@ dependencies {
runtime 'opensymphony:sitemesh'
}
gretty {
logbackConfigFile = "src/main/resources/logback.xml"
}
@@ -17,3 +17,7 @@ dependencies {
runtime 'opensymphony:sitemesh'
}
gretty {
logbackConfigFile = "src/main/resources/logback.xml"
}
@@ -9,3 +9,7 @@ dependencies {
testCompile project(':spring-security-test')
}
gretty {
logbackConfigFile = "src/main/resources/logback.xml"
}
@@ -17,3 +17,7 @@ dependencies {
runtime 'opensymphony:sitemesh'
}
gretty {
logbackConfigFile = "src/main/resources/logback.xml"
}
@@ -65,6 +65,7 @@ gretty {
sslKeyStorePath = keystore
sslKeyStorePassword = password
jvmArgs = ["-Djavax.net.ssl.trustStore=${keystore}", "-Djavax.net.ssl.trustStorePassword=${password}"]
logbackConfigFile = "src/main/resources/logback.xml"
}
def casServer() {
@@ -0,0 +1,12 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="WARN">
<appender-ref ref="STDOUT" />
</root>
</configuration>
@@ -53,4 +53,5 @@ gretty {
sslKeyStorePath = keystore
sslKeyStorePassword = password
jvmArgs = ["-Djavax.net.ssl.trustStore=${keystore}", "-Djavax.net.ssl.trustStorePassword=${password}"]
logbackConfigFile = "src/main/resources/logback.xml"
}
@@ -0,0 +1,12 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="WARN">
<appender-ref ref="STDOUT" />
</root>
</configuration>
@@ -41,3 +41,7 @@ dependencies {
integrationTestCompile seleniumDependencies
}
gretty {
logbackConfigFile = "src/main/resources/logback.xml"
}
@@ -38,7 +38,7 @@ dependencies {
testCompile "com.google.appengine:appengine-testing:$gaeVersion"
testRuntime "com.google.appengine:appengine-api-labs:$gaeVersion"
testRuntime "com.google.appengine:appengine-api-labs:1.9.88"
}
appengineRun.onlyIf { !gradle.taskGraph.hasTask(appengineFunctionalTest) }
@@ -31,3 +31,7 @@ dependencies {
integrationTestCompile seleniumDependencies
}
gretty {
logbackConfigFile = "src/main/resources/logback.xml"
}
@@ -26,3 +26,7 @@ dependencies {
integrationTestCompile seleniumDependencies
}
gretty {
logbackConfigFile = "src/main/resources/logback.xml"
}
@@ -0,0 +1,12 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="WARN">
<appender-ref ref="STDOUT" />
</root>
</configuration>
@@ -15,3 +15,7 @@ dependencies {
runtime 'opensymphony:sitemesh'
}
gretty {
logbackConfigFile = "src/main/resources/logback.xml"
}
@@ -0,0 +1,12 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="WARN">
<appender-ref ref="STDOUT" />
</root>
</configuration>
@@ -31,3 +31,7 @@ dependencies {
integrationTestCompile seleniumDependencies
}
gretty {
logbackConfigFile = "src/main/resources/logback.xml"
}
@@ -29,3 +29,7 @@ dependencies {
integrationTestCompile seleniumDependencies
}
gretty {
logbackConfigFile = "src/main/resources/logback.xml"
}
@@ -12,3 +12,7 @@ dependencies {
runtime jstlDependencies
runtime slf4jDependencies
}
gretty {
logbackConfigFile = "src/main/resources/logback.xml"
}
@@ -14,3 +14,7 @@ dependencies {
//jettyRun {
// userRealms = [jettyRun.class.classLoader.loadClass('org.mortbay.jetty.security.HashUserRealm').newInstance('Preauth Realm' '$projectDir/realm.properties')]
//}
gretty {
logbackConfigFile = "src/main/resources/logback.xml"
}
@@ -19,3 +19,7 @@ dependencies {
}
eclipse.wtp.component.contextPath = 'servletapi'
gretty {
logbackConfigFile = "src/main/resources/logback.xml"
}
@@ -14,3 +14,7 @@ dependencies {
runtime project(':spring-security-web')
runtime jstlDependencies
}
gretty {
logbackConfigFile = "src/main/resources/logback.xml"
}
@@ -34,6 +34,7 @@ import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.core.log.LogMessage;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.AccountStatusUserDetailsChecker;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
@@ -46,6 +47,7 @@ import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsChecker;
@@ -171,8 +173,10 @@ public class SwitchUserFilter extends GenericFilterBean
Authentication targetUser = attemptSwitchUser(request);
// update the current context to the new target user
SecurityContextHolder.getContext().setAuthentication(targetUser);
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(targetUser);
SecurityContextHolder.setContext(context);
this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", targetUser));
// redirect to target url
this.successHandler.onAuthenticationSuccess(request, response,
targetUser);
@@ -189,14 +193,17 @@ public class SwitchUserFilter extends GenericFilterBean
Authentication originalUser = attemptExitUser(request);
// update the current context back to the original user
SecurityContextHolder.getContext().setAuthentication(originalUser);
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(originalUser);
SecurityContextHolder.setContext(context);
this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", originalUser));
// redirect to target url
this.successHandler.onAuthenticationSuccess(request, response, originalUser);
return;
}
this.logger.trace(LogMessage.format("Did not attempt to switch user since request did not match [%s] or [%s]",
this.switchUserMatcher, this.exitUserMatcher));
chain.doFilter(request, response);
}
@@ -218,25 +225,13 @@ public class SwitchUserFilter extends GenericFilterBean
UsernamePasswordAuthenticationToken targetUserRequest;
String username = request.getParameter(this.usernameParameter);
if (username == null) {
username = "";
}
if (this.logger.isDebugEnabled()) {
this.logger.debug("Attempt to switch to user [" + username + "]");
}
username = (username != null) ? username : "";
this.logger.debug(LogMessage.format("Attempting to switch to user [%s]", username));
UserDetails targetUser = this.userDetailsService.loadUserByUsername(username);
this.userDetailsChecker.check(targetUser);
// OK, create the switch user token
targetUserRequest = createSwitchUserToken(request, targetUser);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Switch User Token [" + targetUserRequest + "]");
}
// publish event
if (this.eventPublisher != null) {
this.eventPublisher.publishEvent(new AuthenticationSwitchUserEvent(
@@ -273,10 +268,9 @@ public class SwitchUserFilter extends GenericFilterBean
Authentication original = getSourceAuthentication(current);
if (original == null) {
this.logger.debug("Could not find original user Authentication object!");
throw new AuthenticationCredentialsNotFoundException(
this.messages.getMessage("SwitchUserFilter.noOriginalAuthentication",
"Could not find original Authentication object"));
this.logger.debug("Failed to find original user");
throw new AuthenticationCredentialsNotFoundException(this.messages
.getMessage("SwitchUserFilter.noOriginalAuthentication", "Failed to find original user"));
}
// get the source user details
@@ -373,8 +367,7 @@ public class SwitchUserFilter extends GenericFilterBean
// check for switch user type of authority
if (auth instanceof SwitchUserGrantedAuthority) {
original = ((SwitchUserGrantedAuthority) auth).getSource();
this.logger.debug("Found original switch user granted authority ["
+ original + "]");
this.logger.debug(LogMessage.format("Found original switch user granted authority [%s]", original));
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 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.
@@ -174,17 +174,18 @@ public final class CsrfFilter extends OncePerRequestFilter {
* @return
*/
private static boolean equalsConstantTime(String expected, String actual) {
byte[] expectedBytes = bytesUtf8(expected);
byte[] actualBytes = bytesUtf8(actual);
if (expected == actual) {
return true;
}
if (expected == null || actual == null) {
return false;
}
// Encode after ensure that the string is not null
byte[] expectedBytes = Utf8.encode(expected);
byte[] actualBytes = Utf8.encode(actual);
return MessageDigest.isEqual(expectedBytes, actualBytes);
}
private static byte[] bytesUtf8(String s) {
// need to check if Utf8.encode() runs in constant time (probably not).
// This may leak length of string.
return (s != null) ? Utf8.encode(s) : null;
}
private static final class DefaultRequiresCsrfMatcher implements RequestMatcher {
private final HashSet<String> allowedMethods = new HashSet<>(Arrays.asList("GET", "HEAD", "TRACE", "OPTIONS"));
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 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.
@@ -25,6 +25,6 @@ package org.springframework.security.web.csrf;
public class MissingCsrfTokenException extends CsrfException {
public MissingCsrfTokenException(String actualToken) {
super("Could not verify the provided CSRF token because your session was not found.");
super("Could not verify the provided CSRF token because no token was found to compare.");
}
}
@@ -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.
@@ -177,17 +177,18 @@ public class CsrfWebFilter implements WebFilter {
* @return
*/
private static boolean equalsConstantTime(String expected, String actual) {
byte[] expectedBytes = bytesUtf8(expected);
byte[] actualBytes = bytesUtf8(actual);
if (expected == actual) {
return true;
}
if (expected == null || actual == null) {
return false;
}
// Encode after ensure that the string is not null
byte[] expectedBytes = Utf8.encode(expected);
byte[] actualBytes = Utf8.encode(actual);
return MessageDigest.isEqual(expectedBytes, actualBytes);
}
private static byte[] bytesUtf8(String s) {
// need to check if Utf8.encode() runs in constant time (probably not).
// This may leak length of string.
return (s != null) ? Utf8.encode(s) : null;
}
private Mono<CsrfToken> generateToken(ServerWebExchange exchange) {
return this.csrfTokenRepository.generateToken(exchange)
.delayUntil((token) -> this.csrfTokenRepository.saveToken(exchange, token));
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 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,7 +16,6 @@
package org.springframework.security.web.server.header;
import java.util.Arrays;
import java.util.Collections;
import org.springframework.http.HttpHeaders;
import org.springframework.web.server.ServerWebExchange;
@@ -42,11 +41,17 @@ public class StaticServerHttpHeadersWriter implements ServerHttpHeadersWriter {
@Override
public Mono<Void> writeHttpHeaders(ServerWebExchange exchange) {
HttpHeaders headers = exchange.getResponse().getHeaders();
boolean containsOneHeaderToAdd = Collections.disjoint(headers.keySet(), this.headersToAdd.keySet());
if (containsOneHeaderToAdd) {
this.headersToAdd.forEach((name, values) -> {
headers.put(name, values);
});
// Note: We need to ensure that the following algorithm compares headers
// case insensitively, which should be true of headers.containsKey().
boolean containsNoHeadersToAdd = true;
for (String headerName : this.headersToAdd.keySet()) {
if (headers.containsKey(headerName)) {
containsNoHeadersToAdd = false;
break;
}
}
if (containsNoHeadersToAdd) {
this.headersToAdd.forEach(headers::put);
}
return Mono.empty();
}
@@ -46,8 +46,10 @@ public final class SimpleRedirectInvalidSessionStrategy implements InvalidSessio
public void onInvalidSessionDetected(HttpServletRequest request,
HttpServletResponse response) throws IOException {
logger.debug("Starting new session (if required) and redirecting to '"
+ destinationUrl + "'");
if (logger.isDebugEnabled()) {
logger.debug("Starting new session (if required) and redirecting to '"
+ destinationUrl + "'");
}
if (createNewSession) {
request.getSession();
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 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,6 +16,7 @@
package org.springframework.security.web.csrf;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Arrays;
import javax.servlet.FilterChain;
@@ -89,6 +90,18 @@ public class CsrfFilterTests {
this.response = new MockHttpServletResponse();
}
@Test
public void nullConstantTimeEquals() throws Exception {
Method method = CsrfFilter.class.getDeclaredMethod("equalsConstantTime", String.class, String.class);
method.setAccessible(true);
assertThat(method.invoke(CsrfFilter.class, null, null)).isEqualTo(true);
String expectedToken = "Hello—World";
String actualToken = new String("Hello—World");
assertThat(method.invoke(CsrfFilter.class, expectedToken, null)).isEqualTo(false);
assertThat(method.invoke(CsrfFilter.class, expectedToken, "hello-world")).isEqualTo(false);
assertThat(method.invoke(CsrfFilter.class, expectedToken, actualToken)).isEqualTo(true);
}
@Test(expected = IllegalArgumentException.class)
public void constructorNullRepository() {
new CsrfFilter(null);
@@ -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,6 +16,8 @@
package org.springframework.security.web.server.csrf;
import java.lang.reflect.Method;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -67,6 +69,18 @@ public class CsrfWebFilterTests {
private MockServerWebExchange post = MockServerWebExchange.from(MockServerHttpRequest.post("/"));
@Test
public void nullConstantTimeEquals() throws Exception {
Method method = CsrfWebFilter.class.getDeclaredMethod("equalsConstantTime", String.class, String.class);
method.setAccessible(true);
assertThat(method.invoke(CsrfWebFilter.class, null, null)).isEqualTo(true);
String expectedToken = "Hello—World";
String actualToken = new String("Hello—World");
assertThat(method.invoke(CsrfWebFilter.class, expectedToken, null)).isEqualTo(false);
assertThat(method.invoke(CsrfWebFilter.class, expectedToken, "hello-world")).isEqualTo(false);
assertThat(method.invoke(CsrfWebFilter.class, expectedToken, actualToken)).isEqualTo(true);
}
@Test
public void filterWhenGetThenSessionNotCreatedAndChainContinues() {
PublisherProbe<Void> chainResult = PublisherProbe.empty();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 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.
@@ -17,10 +17,13 @@ package org.springframework.security.web.server.header;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Locale;
import org.junit.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.web.server.ServerWebExchange;
/**
@@ -56,6 +59,24 @@ public class StaticServerHttpHeadersWriterTests {
assertThat(headers.get(ContentTypeOptionsServerHttpHeadersWriter.X_CONTENT_OPTIONS)).containsOnly(headerValue);
}
// gh-10557
@Test
public void writeHeadersWhenHeaderWrittenWithDifferentCaseThenDoesNotWriteHeaders() {
String headerName = HttpHeaders.CACHE_CONTROL.toLowerCase(Locale.ROOT);
String headerValue = "max-age=120";
this.headers.set(headerName, headerValue);
// Note: This test inverts which collection uses case sensitive headers,
// due to the fact that gh-10557 reports NettyHeadersAdapter as the
// response headers implementation, which is not accessible here.
HttpHeaders caseSensitiveHeaders = new HttpHeaders(new LinkedMultiValueMap<>());
caseSensitiveHeaders.set(HttpHeaders.CACHE_CONTROL, CacheControlServerHttpHeadersWriter.CACHE_CONTRTOL_VALUE);
caseSensitiveHeaders.set(HttpHeaders.PRAGMA, CacheControlServerHttpHeadersWriter.PRAGMA_VALUE);
caseSensitiveHeaders.set(HttpHeaders.EXPIRES, CacheControlServerHttpHeadersWriter.EXPIRES_VALUE);
this.writer = new StaticServerHttpHeadersWriter(caseSensitiveHeaders);
this.writer.writeHttpHeaders(this.exchange);
assertThat(this.headers.get(headerName)).containsOnly(headerValue);
}
@Test
public void writeHeadersWhenMultiHeaderThenWritesAllHeaders() {
writer = StaticServerHttpHeadersWriter.builder()