1
0
mirror of synced 2026-09-06 09:20:07 +00:00

Compare commits

..

146 Commits

Author SHA1 Message Date
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
Josh Cummings 974156d5fb Release 5.2.9.RELEASE 2021-02-11 10:37:22 -07:00
Rob Winch e2121532a2 Optimize HttpSessionSecurityContextRepository
Closes gh-9387
2021-02-11 09:38:04 -07:00
Rob Winch 7cab7b06c5 Optimize HttpSessionSecurityContextRepository
Closes gh-9387
2021-02-11 09:38:04 -07:00
Josh Cummings f60daa5152 Update to GAE 1.9.86
Closes gh-9442
2021-02-11 09:31:37 -07:00
Josh Cummings f63b770ec5 Update to Tomcat 9.0.43
Closes gh-9441
2021-02-11 09:31:30 -07:00
Josh Cummings 44bb975f82 Update to Jetty 9.4.36.v20210114
Closes gh-9440
2021-02-11 09:31:25 -07:00
Josh Cummings 3cb6b3e5d6 Update to hibernate-validator 6.1.7.Final
Closes gh-9439
2021-02-11 09:31:20 -07:00
Josh Cummings db07cea579 Update to hibernate-entitymanager 5.4.28.Final
Closes gh-9438
2021-02-11 09:31:15 -07:00
Josh Cummings 0fb60c3aa7 Update to thymeleaf-spring5 3.0.12
Closes gh-9437
2021-02-11 09:31:11 -07:00
Josh Cummings 31cb29cb2d Update to Spring Data Moore-SR12
Closes gh-9436
2021-02-11 09:31:03 -07:00
Josh Cummings 46bfc00db2 Update to Reactor Dysprosium-SR16
Closes gh-9435
2021-02-11 09:30:57 -07:00
Josh Cummings 987b14d1d4 Update to Spring Framework 5.2.12.RELEASE
Closes gh-9434
2021-02-11 09:30:52 -07:00
Josh Cummings 1f19ee04e1 Update to Spring Boot 2.2.13.RELEASE
Closes gh-9433
2021-02-11 09:30:39 -07:00
Josh Cummings 005eca7bd9 Fix Test Configuration
- Typo in PlaceholderConfig was causing Windows builds to
resolve the CLASSPATH environment variable

Closes gh-9421
2021-02-10 11:37:32 -07:00
Josh Cummings 68ac3ef36b Polish Tests
Issue gh-9331
2021-02-03 09:34:20 -07:00
happier233 7a5c34ca57 Configure CurrentSecurityContextArgumentResolver BeanResolver
Closes gh-9331
2021-02-03 09:34:13 -07:00
Joe Grandja 542c625d7d Allow null or empty authorities for DefaultOAuth2User
Make DefaultOAuth2User more inline with other part of
spring-security.
For example,
- DefaultOAuth2AuthenticatedPrincipal
- AbstractAuthenticationToken

Closes gh-9366
2021-02-02 04:43:29 -05:00
Rob Winch 4de2dbb4cd Update to spring-build-conventions:0.0.23.1.RELEASE
Fixes use of repo.spring.io
2021-01-26 11:11:05 -06:00
Benjamin Faal 98399c920a Make user info response status check error only
Closes gh-9336
2021-01-25 11:10:03 -05:00
Josh Cummings 52ad49074d Migrate SAML 2.0 Tests and Docs to PCFOne
Issue gh-9362
2021-01-25 08:32:17 -07:00
Josh Cummings 6df5dc4ecf Migrate SAML 2.0 Samples to PCFOne
Closes gh-9362
2021-01-22 13:51:46 -07:00
Josh Cummings 32acb04efe Fix SAML 2.0 Javaconfig Sample
Issue gh-9362
2021-01-22 13:51:37 -07:00
Eleftheria Stein 57dfbeecbb Provide artifactoryUsername/Password in docs and schema jobs 2021-01-22 16:07:17 +01:00
Rob Winch 1181740f79 Constant Time Comparison for CSRF tokens
Closes gh-9291
2021-01-20 16:18:25 -06:00
Rob Winch 628ea00ad4 Fix CsrfWebFilter error message when expected CSRF not found
Closes gh-9337
2021-01-12 11:31:26 -06:00
Josh Cummings 6dc22835fd Renew Sample Certificate
Closes gh-9320
2021-01-04 12:12:17 -07:00
Ovidiu Popa 174b71c017 OidcIdToken cannot be serialized to JSON if token contains claim of type JSONArray or JSONObject
ObjectToListStringConverter and ObjectToMapStringObjectConverter were checking if the source object is of type List or Map and if the first element or key is a String. If we have a JSONArray containing Strings the above check will pass, meaning that a JSONArray will be returned which is not serializable (same applies to JSONObject)

With this change, even if the check is passing a new List or Map will be returned.

Closes gh-9210
2020-12-03 11:42:00 -05:00
Josh Cummings c7b4e4a493 Next Development Version 2020-12-02 19:09:04 -07:00
Josh Cummings d03d31c2e7 Release 5.2.8.RELEASE 2020-12-02 16:06:32 -07:00
Josh Cummings 9976eb9e3a Update to Spring LDAP Core 2.3.3
Closes gh-9245
2020-12-02 16:04:41 -07:00
Josh Cummings 7cf1ca7ab1 Update to Powermock 2.0.9
Closes gh-9244
2020-12-02 16:04:36 -07:00
Josh Cummings 664d9f1ba6 Update to HSQLDB 2.5.1
Closes gh-9243
2020-12-02 16:04:31 -07:00
Josh Cummings f7b160d7a8 Update to Hibernate EntityManager 5.4.25
Closes gh-9242
2020-12-02 16:04:26 -07:00
Josh Cummings 5b60098548 Update to Jetty 9.4.35
Closes gh-9241
2020-12-02 16:04:13 -07:00
Josh Cummings e6b383a9c4 Update to HttpComponents HttpClient 4.5.13
Closes gh-9240
2020-12-02 16:04:07 -07:00
Josh Cummings 5342133494 Update to RSocket 1.0.3
Closes gh-9239
2020-12-02 16:04:01 -07:00
Josh Cummings 7498ad95a1 Update to Reactor Dysprosium-SR14
Closes gh-9238
2020-12-02 16:03:56 -07:00
Josh Cummings 04d382d8d8 Update to Google App Engine 1.9.83
Closes gh-9237
2020-12-02 16:03:51 -07:00
Josh Cummings 83a177d327 Update to Jackson 2.10.5.1
Closes gh-9236
2020-12-02 16:03:46 -07:00
Josh Cummings ebb2afa5e8 Update to Spring Data Moore-SR11
Closes gh-9235
2020-12-02 16:03:41 -07:00
Josh Cummings 276a934bcd Update to Spring 5.2.11
Closes gh-9234
2020-12-02 16:03:35 -07:00
Josh Cummings e4b538146a Update to Spring Boot 2.2.11
Closes gh-9233
2020-12-02 16:03:27 -07:00
Rob Winch 5948f492f5 Add missing withCredentials in Jenkinsfile 2020-11-17 09:28:13 -06:00
Rob Winch 1bac5498dd Fix artifactoryUsername/Password in build.gradle
Missing )
2020-11-17 09:05:37 -06:00
Rob Winch 07479dce6d Use artifactoryUsername/Password in Jenkinsfile 2020-11-17 09:02:49 -06:00
Rob Winch 05c0176b98 allprojects uses artifactoryUsername/Password 2020-11-17 09:02:21 -06:00
Rob Winch 03c2cc846d Use artifactoryUsername/Password for plugin repositories 2020-11-17 08:54:39 -06:00
Hideaki Matsunami ec7deca76f add white space before strong notation. 2020-10-30 15:51:27 -06:00
Ayush Kohli 21c7f7518f Closes gh-8196
Add leveloffset
2020-10-28 15:15:19 -06:00
Josh Cummings 0462c42290 Update Test Controllers
Closes gh-9121
2020-10-12 18:12:02 -06:00
Josh Cummings 21f7187f40 Next Development Version 2020-10-07 11:37:33 -06:00
88 changed files with 2425 additions and 786 deletions
Vendored
+39 -54
View File
@@ -8,6 +8,10 @@ properties(projectProperties)
def SUCCESS = hudson.model.Result.SUCCESS.toString()
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: {
stage('Check') {
@@ -15,14 +19,15 @@ try {
checkout scm
sh "git clean -dfx"
try {
withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) {
sh "./gradlew clean check --refresh-dependencies --no-daemon --stacktrace"
withCredentials([ARTIFACTORY_CREDENTIALS]) {
withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) {
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'
}
}
}
@@ -34,11 +39,13 @@ try {
sh "git clean -dfx"
withCredentials([string(credentialsId: 'spring-sonar.login', variable: 'SONAR_LOGIN')]) {
try {
withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) {
if ("master" == env.BRANCH_NAME) {
sh "./gradlew sonarqube -PexcludeProjects='**/samples/**' -Dsonar.host.url=$SPRING_SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN --refresh-dependencies --no-daemon --stacktrace"
} else {
sh "./gradlew sonarqube -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"
withCredentials([ARTIFACTORY_CREDENTIALS]) {
withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) {
if ("master" == env.BRANCH_NAME) {
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 $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"
}
}
}
} catch(Exception e) {
@@ -55,8 +62,10 @@ try {
checkout scm
sh "git clean -dfx"
try {
withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) {
sh "./gradlew clean test -PforceMavenRepositories=snapshot -PspringVersion='5.2.+' -PreactorVersion=Dysprosium-BUILD-SNAPSHOT -PspringDataVersion=Lovelace-BUILD-SNAPSHOT --refresh-dependencies --no-daemon --stacktrace"
withCredentials([ARTIFACTORY_CREDENTIALS]) {
withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) {
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) {
currentBuild.result = 'FAILED: snapshots'
@@ -65,46 +74,16 @@ try {
}
}
},
jdk9: {
stage('JDK 9') {
node {
checkout scm
sh "git clean -dfx"
try {
withEnv(["JAVA_HOME=${ tool 'jdk9' }"]) {
sh "./gradlew clean test --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 {
withEnv(["JAVA_HOME=${ tool 'jdk10' }"]) {
sh "./gradlew clean test --refresh-dependencies --no-daemon --stacktrace"
}
} catch(Exception e) {
currentBuild.result = 'FAILED: jdk10'
throw e
}
}
}
},
jdk11: {
stage('JDK 11') {
node {
checkout scm
sh "git clean -dfx"
try {
withEnv(["JAVA_HOME=${ tool 'jdk11' }"]) {
sh "./gradlew clean test --refresh-dependencies --no-daemon --stacktrace"
withCredentials([ARTIFACTORY_CREDENTIALS]) {
withEnv(["JAVA_HOME=${ tool 'jdk11' }"]) {
sh "./gradlew $JENKINS_USER clean test -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace"
}
}
} catch(Exception e) {
currentBuild.result = 'FAILED: jdk11'
@@ -119,8 +98,10 @@ try {
checkout scm
sh "git clean -dfx"
try {
withEnv(["JAVA_HOME=${ tool 'openjdk12' }"]) {
sh "./gradlew clean test --refresh-dependencies --no-daemon --stacktrace"
withCredentials([ARTIFACTORY_CREDENTIALS]) {
withEnv(["JAVA_HOME=${ tool 'openjdk12' }"]) {
sh "./gradlew $JENKINS_USER clean test -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace"
}
}
} catch(Exception e) {
currentBuild.result = 'FAILED: jdk12'
@@ -138,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: '02bd1690-b54f-4c9f-819d-a77cb7a9822c', usernameVariable: 'ARTIFACTORY_USERNAME', passwordVariable: 'ARTIFACTORY_PASSWORD')]) {
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"
}
}
}
@@ -156,8 +137,10 @@ try {
checkout scm
sh "git clean -dfx"
withCredentials([file(credentialsId: 'docs.spring.io-jenkins_private_ssh_key', variable: 'DEPLOY_SSH_KEY')]) {
withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) {
sh "./gradlew deployDocs -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME --refresh-dependencies --no-daemon --stacktrace"
withCredentials([ARTIFACTORY_CREDENTIALS]) {
withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) {
sh "./gradlew $JENKINS_USER deployDocs -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace"
}
}
}
}
@@ -169,8 +152,10 @@ try {
checkout scm
sh "git clean -dfx"
withCredentials([file(credentialsId: 'docs.spring.io-jenkins_private_ssh_key', variable: 'DEPLOY_SSH_KEY')]) {
withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) {
sh "./gradlew deploySchema -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME --refresh-dependencies --no-daemon --stacktrace"
withCredentials([ARTIFACTORY_CREDENTIALS]) {
withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) {
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.
+27 -3
View File
@@ -1,12 +1,20 @@
buildscript {
dependencies {
classpath 'io.spring.gradle:spring-build-conventions:0.0.23.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 {
maven { url 'https://repo.spring.io/plugins-snapshot' }
maven {
url = 'https://repo.spring.io/plugins-snapshot'
if (project.hasProperty('artifactoryUsername')) {
credentials {
username "$artifactoryUsername"
password "$artifactoryPassword"
}
}
}
maven { url 'https://plugins.gradle.org/m2/' }
}
}
@@ -35,3 +43,19 @@ subprojects {
options.encoding = "UTF-8"
}
}
if (project.hasProperty('artifactoryUsername')) {
allprojects { project ->
project.repositories { repos ->
all { repo ->
if (!repo.url.toString().startsWith("https://repo.spring.io/")) {
return;
}
repo.credentials {
username = artifactoryUsername
password = artifactoryPassword
}
}
}
}
}
@@ -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
@@ -1933,9 +1941,9 @@ public final class HttpSecurity extends
*
* private RelyingPartyRegistration getSaml2RelyingPartyRegistration() {
* //remote IDP entity ID
* String idpEntityId = "https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php";
* String idpEntityId = "https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/metadata.php";
* //remote WebSSO Endpoint - Where to Send AuthNRequests to
* String webSsoEndpoint = "https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php";
* String webSsoEndpoint = "https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/SSOService.php";
* //local registration ID
* String registrationId = "simplesamlphp";
* //local entity ID - autogenerated based on URL
@@ -2024,9 +2032,9 @@ public final class HttpSecurity extends
*
* private RelyingPartyRegistration getSaml2RelyingPartyRegistration() {
* //remote IDP entity ID
* String idpEntityId = "https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php";
* String idpEntityId = "https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/metadata.php";
* //remote WebSSO Endpoint - Where to Send AuthNRequests to
* String webSsoEndpoint = "https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php";
* String webSsoEndpoint = "https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/SSOService.php";
* //local registration ID
* String registrationId = "simplesamlphp";
* //local entity ID - autogenerated based on URL
@@ -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)
@@ -822,19 +822,19 @@ public class MiscHttpConfigTests {
@RestController
static class AuthenticationController {
@GetMapping("/password")
public String password(@AuthenticationPrincipal Authentication authentication) {
public String password(Authentication authentication) {
return (String) authentication.getCredentials();
}
@GetMapping("/roles")
public String roles(@AuthenticationPrincipal Authentication authentication) {
public String roles(Authentication authentication) {
return authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(","));
}
@GetMapping("/details")
public String details(@AuthenticationPrincipal Authentication authentication) {
public String details(Authentication authentication) {
return authentication.getDetails().getClass().getName();
}
}
@@ -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,
@@ -519,7 +519,7 @@ public class OAuth2ResourceServerSpecTests {
@EnableWebFlux
@EnableWebFluxSecurity
static class PlaceholderConfig {
@Value("${classpath:org/springframework/security/config/web/server/OAuth2ResourceServerSpecTests-simple.pub}")
@Value("classpath:org/springframework/security/config/web/server/OAuth2ResourceServerSpecTests-simple.pub")
RSAPublicKey key;
@Bean
@@ -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
@@ -233,7 +233,7 @@ You cannot have two separate sessions at once.
So if you log in again in another window or tab you are just reauthenticating in the same session.
The server doesn't know anything about tabs, windows or browser instances.
All it sees are HTTP requests and it ties those to a particular session according to the value of the JSESSIONID cookie that they contain.
When a user authenticates during a session, Spring Security's concurrent session control checks the number of__other authenticated sessions__ that they have.
When a user authenticates during a session, Spring Security's concurrent session control checks the number of __other authenticated sessions__ that they have.
If they are already authenticated with the same session, then re-authenticating will have no effect.
@@ -1,7 +1,7 @@
= Appendix
include::database-schema.adoc[]
include::database-schema.adoc[leveloffset=+1]
include::namespace.adoc[]
@@ -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;
}
@@ -270,9 +270,9 @@ spring:
saml2:
login:
relying-parties:
- entity-id: &idp-entity-id https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php
- entity-id: &idp-entity-id https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/metadata.php
registration-id: simplesamlphp
web-sso-url: &idp-sso-url https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php
web-sso-url: &idp-sso-url https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/SSOService.php
signing-credentials: &service-provider-credentials
- private-key: |
-----BEGIN PRIVATE KEY-----
+4 -4
View File
@@ -1,5 +1,5 @@
aspectjVersion=1.9.6
gaeVersion=1.9.82
springBootVersion=2.2.10.RELEASE
version=5.2.7.RELEASE
aspectjVersion=1.9.7
gaeVersion=1.9.93
springBootVersion=2.2.13.RELEASE
version=5.2.14.RELEASE
org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError
+55 -55
View File
@@ -1,16 +1,16 @@
if (!project.hasProperty('reactorVersion')) {
ext.reactorVersion = 'Dysprosium-SR12'
ext.reactorVersion = 'Dysprosium-SR25'
}
if (!project.hasProperty('springVersion')) {
ext.springVersion = '5.2.9.RELEASE'
ext.springVersion = '5.2.19.RELEASE'
}
if (!project.hasProperty('springDataVersion')) {
ext.springDataVersion = 'Moore-SR10'
ext.springDataVersion = 'Moore-SR13'
}
ext.rsocketVersion = '1.0.2'
ext.rsocketVersion = '1.0.5'
dependencyManagement {
imports {
@@ -24,12 +24,12 @@ dependencyManagement {
dependency 'opensymphony:sitemesh:2.4.2'
dependency 'org.gebish:geb-spock:0.10.0'
dependency 'org.jasig.cas:cas-server-webapp:4.2.7'
dependency 'org.powermock:powermock-api-mockito2:2.0.7'
dependency 'org.powermock:powermock-api-support:2.0.7'
dependency 'org.powermock:powermock-core:2.0.7'
dependency 'org.powermock:powermock-module-junit4-common:2.0.7'
dependency 'org.powermock:powermock-module-junit4:2.0.7'
dependency 'org.powermock:powermock-reflect:2.0.7'
dependency 'org.powermock:powermock-api-mockito2:2.0.9'
dependency 'org.powermock:powermock-api-support:2.0.9'
dependency 'org.powermock:powermock-core:2.0.9'
dependency 'org.powermock:powermock-module-junit4-common:2.0.9'
dependency 'org.powermock:powermock-module-junit4:2.0.9'
dependency 'org.powermock:powermock-reflect:2.0.9'
dependency 'org.python:jython:2.5.3'
dependency 'org.spockframework:spock-core:1.0-groovy-2.4'
dependency 'org.spockframework:spock-spring:1.0-groovy-2.4'
@@ -44,11 +44,11 @@ dependencyManagement {
dependency 'ch.qos.logback:logback-core:1.2.3'
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'
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'
@@ -138,41 +138,41 @@ dependencyManagement {
dependency 'org.apache.directory.shared:shared-cursor:0.9.15'
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.12'
dependency 'org.apache.httpcomponents:httpcore:4.4.8'
dependency 'org.apache.httpcomponents:httpmime:4.5.3'
dependency 'org.apache.httpcomponents:httpclient:4.5.13'
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.38'
dependency 'org.apache.tomcat.embed:tomcat-embed-el:9.0.38'
dependency 'org.apache.tomcat.embed:tomcat-embed-jasper:9.0.38'
dependency 'org.apache.tomcat.embed:tomcat-embed-logging-log4j:9.0.38'
dependency 'org.apache.tomcat.embed:tomcat-embed-websocket:8.5.57'
dependency 'org.apache.tomcat:tomcat-annotations-api:9.0.38'
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.27.v20200227'
dependency 'org.eclipse.jetty.websocket:websocket-client:9.4.27.v20200227'
dependency 'org.eclipse.jetty.websocket:websocket-common:9.4.27.v20200227'
dependency 'org.eclipse.jetty:jetty-client:9.4.27.v20200227'
dependency 'org.eclipse.jetty:jetty-http:9.4.27.v20200227'
dependency 'org.eclipse.jetty:jetty-io:9.4.27.v20200227'
dependency 'org.eclipse.jetty:jetty-security:9.4.27.v20200227'
dependency 'org.eclipse.jetty:jetty-server:9.4.27.v20200227'
dependency 'org.eclipse.jetty:jetty-servlet:9.4.27.v20200227'
dependency 'org.eclipse.jetty:jetty-util:9.4.27.v20200227'
dependency 'org.eclipse.jetty:jetty-xml:9.4.27.v20200227'
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.22.Final'
dependency 'org.hibernate:hibernate-validator:6.1.6.Final'
dependency 'org.hsqldb:hsqldb:2.5.0'
dependency 'org.hibernate:hibernate-entitymanager:5.4.33'
dependency 'org.hibernate:hibernate-validator:6.1.7.Final'
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.2.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.11.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.
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.client.userinfo;
@@ -22,6 +21,11 @@ import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import com.nimbusds.oauth2.sdk.ErrorObject;
import com.nimbusds.openid.connect.sdk.UserInfoErrorResponse;
import net.minidev.json.JSONObject;
import reactor.core.publisher.Mono;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
@@ -41,12 +45,6 @@ import org.springframework.util.StringUtils;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;
import com.nimbusds.oauth2.sdk.ErrorObject;
import com.nimbusds.openid.connect.sdk.UserInfoErrorResponse;
import net.minidev.json.JSONObject;
import reactor.core.publisher.Mono;
/**
* An implementation of an {@link ReactiveOAuth2UserService} that supports standard OAuth 2.0 Provider's.
* <p>
@@ -119,7 +117,7 @@ public class DefaultReactiveOAuth2UserService implements ReactiveOAuth2UserServi
}
Mono<Map<String, Object>> userAttributes = requestHeadersSpec
.retrieve()
.onStatus(s -> s != HttpStatus.OK, response -> parse(response).map(userInfoErrorResponse -> {
.onStatus(HttpStatus::isError, response -> parse(response).map(userInfoErrorResponse -> {
String description = userInfoErrorResponse.getErrorObject().getDescription();
OAuth2Error oauth2Error = new OAuth2Error(
INVALID_USER_INFO_RESPONSE_ERROR_CODE, description,
@@ -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
}
}
}
}
@@ -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.
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.client.userinfo;
import java.time.Duration;
@@ -50,6 +49,7 @@ import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
import org.springframework.web.reactive.function.client.WebClient;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
@@ -152,6 +152,24 @@ public class DefaultReactiveOAuth2UserServiceTests {
assertThat(userAuthority.getAttributes()).isEqualTo(user.getAttributes());
}
// gh-9336
@Test
public void loadUserWhenUserInfo201CreatedResponseThenReturnUser() {
// @formatter:off
String userInfoResponse = "{\n"
+ " \"id\": \"user1\",\n"
+ " \"first-name\": \"first\",\n"
+ " \"last-name\": \"last\",\n"
+ " \"middle-name\": \"middle\",\n"
+ " \"address\": \"address\",\n"
+ " \"email\": \"user1@example.com\"\n"
+ "}\n";
// @formatter:on
this.server.enqueue(new MockResponse().setResponseCode(201)
.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).setBody(userInfoResponse));
assertThatCode(() -> this.userService.loadUser(oauth2UserRequest()).block()).doesNotThrowAnyException();
}
// gh-5500
@Test
public void loadUserWhenAuthenticationMethodHeaderSuccessResponseThenHttpMethodGet() throws Exception {
@@ -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.
@@ -56,12 +56,6 @@ final class ObjectToListStringConverter implements ConditionalGenericConverter {
if (source == null) {
return null;
}
if (source instanceof List) {
List<?> sourceList = (List<?>) source;
if (!sourceList.isEmpty() && sourceList.get(0) instanceof String) {
return source;
}
}
if (source instanceof Collection) {
Collection<String> results = new ArrayList<>();
for (Object object : ((Collection<?>) source)) {
@@ -52,9 +52,6 @@ final class ObjectToMapStringObjectConverter implements ConditionalGenericConver
return null;
}
Map<?, ?> sourceMap = (Map<?, ?>) source;
if (!sourceMap.isEmpty() && sourceMap.keySet().iterator().next() instanceof String) {
return source;
}
Map<String, Object> result = new HashMap<>();
sourceMap.forEach((k, v) -> result.put(k.toString(), v));
return result;
@@ -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.
@@ -30,6 +30,8 @@ import java.util.SortedSet;
import java.util.Comparator;
import java.util.LinkedHashSet;
import org.springframework.security.core.authority.AuthorityUtils;
/**
* The default implementation of an {@link OAuth2User}.
*
@@ -59,14 +61,16 @@ public class DefaultOAuth2User implements OAuth2User, Serializable {
* @param attributes the attributes about the user
* @param nameAttributeKey the key used to access the user's &quot;name&quot; from {@link #getAttributes()}
*/
public DefaultOAuth2User(Collection<? extends GrantedAuthority> authorities, Map<String, Object> attributes, String nameAttributeKey) {
Assert.notEmpty(authorities, "authorities cannot be empty");
public DefaultOAuth2User(Collection<? extends GrantedAuthority> authorities, Map<String, Object> attributes,
String nameAttributeKey) {
Assert.notEmpty(attributes, "attributes cannot be empty");
Assert.hasText(nameAttributeKey, "nameAttributeKey cannot be empty");
if (!attributes.containsKey(nameAttributeKey)) {
throw new IllegalArgumentException("Missing attribute '" + nameAttributeKey + "' in attributes");
}
this.authorities = Collections.unmodifiableSet(new LinkedHashSet<>(this.sortAuthorities(authorities)));
this.authorities = (authorities != null)
? Collections.unmodifiableSet(new LinkedHashSet<>(this.sortAuthorities(authorities)))
: Collections.unmodifiableSet(new LinkedHashSet<>(AuthorityUtils.NO_AUTHORITIES));
this.attributes = Collections.unmodifiableMap(new LinkedHashMap<>(attributes));
this.nameAttributeKey = nameAttributeKey;
}
@@ -15,9 +15,10 @@
*/
package org.springframework.security.oauth2.core.converter;
import net.minidev.json.JSONArray;
import net.minidev.json.JSONObject;
import org.assertj.core.util.Lists;
import org.junit.Test;
import org.springframework.core.convert.ConversionService;
import java.net.URL;
import java.time.Instant;
@@ -29,6 +30,8 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.core.convert.ConversionService;
import static org.assertj.core.api.Assertions.assertThat;
/**
@@ -141,9 +144,9 @@ public class ClaimConversionServiceTests {
}
@Test
public void convertCollectionStringWhenListStringThenReturnSame() {
public void convertCollectionStringWhenListStringThenReturnNotSameButEqual() {
List<String> list = Lists.list("1", "2", "3", "4");
assertThat(this.conversionService.convert(list, Collection.class)).isSameAs(list);
assertThat(this.conversionService.convert(list, Collection.class)).isNotSameAs(list).isEqualTo(list);
}
@Test
@@ -152,6 +155,17 @@ public class ClaimConversionServiceTests {
.isEqualTo(Lists.list("1", "2", "3", "4"));
}
@Test
public void convertListStringWhenJsonArrayThenConverts() {
JSONArray jsonArray = new JSONArray();
jsonArray.add("1");
jsonArray.add("2");
jsonArray.add("3");
jsonArray.add(null);
assertThat(this.conversionService.convert(jsonArray, List.class)).isNotInstanceOf(JSONArray.class)
.isEqualTo(Lists.list("1", "2", "3"));
}
@Test
public void convertCollectionStringWhenNotConvertibleThenReturnSingletonList() {
String string = "not-convertible-collection";
@@ -165,9 +179,9 @@ public class ClaimConversionServiceTests {
}
@Test
public void convertListStringWhenListStringThenReturnSame() {
public void convertListStringWhenListStringThenReturnNotSameButEqual() {
List<String> list = Lists.list("1", "2", "3", "4");
assertThat(this.conversionService.convert(list, List.class)).isSameAs(list);
assertThat(this.conversionService.convert(list, List.class)).isNotSameAs(list).isEqualTo(list);
}
@Test
@@ -189,7 +203,7 @@ public class ClaimConversionServiceTests {
}
@Test
public void convertMapStringObjectWhenMapStringObjectThenReturnSame() {
public void convertMapStringObjectWhenMapStringObjectThenReturnNotSameButEqual() {
Map<String, Object> mapStringObject = new HashMap<String, Object>() {
{
put("key1", "value1");
@@ -197,7 +211,8 @@ public class ClaimConversionServiceTests {
put("key3", "value3");
}
};
assertThat(this.conversionService.convert(mapStringObject, Map.class)).isSameAs(mapStringObject);
assertThat(this.conversionService.convert(mapStringObject, Map.class)).isNotSameAs(mapStringObject)
.isEqualTo(mapStringObject);
}
@Test
@@ -219,6 +234,22 @@ public class ClaimConversionServiceTests {
assertThat(this.conversionService.convert(mapIntegerObject, Map.class)).isEqualTo(mapStringObject);
}
@Test
public void convertMapStringObjectWhenJsonObjectThenConverts() {
JSONObject jsonObject = new JSONObject();
jsonObject.put("1", "value1");
jsonObject.put("2", "value2");
Map<String, Object> mapStringObject = new HashMap<String, Object>() {
{
put("1", "value1");
put("2", "value2");
}
};
assertThat(this.conversionService.convert(jsonObject, Map.class)).isNotInstanceOf(JSONObject.class)
.isEqualTo(mapStringObject);
}
@Test
public void convertMapStringObjectWhenNotConvertibleThenReturnNull() {
List<String> notConvertibleList = Lists.list("1", "2", "3", "4");
@@ -15,7 +15,10 @@
*/
package org.springframework.security.oauth2.core.converter;
import net.minidev.json.JSONArray;
import net.minidev.json.JSONObject;
import org.assertj.core.util.Lists;
import org.assertj.core.util.Maps;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.convert.TypeDescriptor;
@@ -45,6 +48,8 @@ public class ClaimTypeConverterTests {
private static final String COLLECTION_STRING_CLAIM = "collection-string-claim";
private static final String LIST_STRING_CLAIM = "list-string-claim";
private static final String MAP_STRING_OBJECT_CLAIM = "map-string-object-claim";
private static final String JSON_ARRAY_CLAIM = "json-array-claim";
private static final String JSON_OBJECT_CLAIM = "json-object-claim";
private ClaimTypeConverter claimTypeConverter;
@Before
@@ -107,7 +112,12 @@ public class ClaimTypeConverterTests {
mapIntegerObject.put(1, "value1");
Map<String, Object> mapStringObject = new HashMap<>();
mapStringObject.put("1", "value1");
JSONArray jsonArray = new JSONArray();
jsonArray.add("1");
List<String> jsonArrayListString = Lists.list("1");
JSONObject jsonObject = new JSONObject();
jsonObject.put("1", "value1");
Map<String, Object> jsonObjectMap = Maps.newHashMap("1", "value1");
Map<String, Object> claims = new HashMap<>();
claims.put(STRING_CLAIM, Boolean.TRUE);
claims.put(BOOLEAN_CLAIM, "true");
@@ -116,7 +126,8 @@ public class ClaimTypeConverterTests {
claims.put(COLLECTION_STRING_CLAIM, listNumber);
claims.put(LIST_STRING_CLAIM, listNumber);
claims.put(MAP_STRING_OBJECT_CLAIM, mapIntegerObject);
claims.put(JSON_ARRAY_CLAIM, jsonArray);
claims.put(JSON_OBJECT_CLAIM, jsonObject);
claims = this.claimTypeConverter.convert(claims);
assertThat(claims.get(STRING_CLAIM)).isEqualTo("true");
@@ -126,6 +137,8 @@ public class ClaimTypeConverterTests {
assertThat(claims.get(COLLECTION_STRING_CLAIM)).isEqualTo(listString);
assertThat(claims.get(LIST_STRING_CLAIM)).isEqualTo(listString);
assertThat(claims.get(MAP_STRING_OBJECT_CLAIM)).isEqualTo(mapStringObject);
assertThat(claims.get(JSON_ARRAY_CLAIM)).isEqualTo(jsonArrayListString);
assertThat(claims.get(JSON_OBJECT_CLAIM)).isEqualTo(jsonObjectMap);
}
@Test
@@ -153,9 +166,9 @@ public class ClaimTypeConverterTests {
assertThat(claims.get(BOOLEAN_CLAIM)).isSameAs(bool);
assertThat(claims.get(INSTANT_CLAIM)).isSameAs(instant);
assertThat(claims.get(URL_CLAIM)).isSameAs(url);
assertThat(claims.get(COLLECTION_STRING_CLAIM)).isSameAs(listString);
assertThat(claims.get(LIST_STRING_CLAIM)).isSameAs(listString);
assertThat(claims.get(MAP_STRING_OBJECT_CLAIM)).isSameAs(mapStringObject);
assertThat(claims.get(COLLECTION_STRING_CLAIM)).isNotSameAs(listString).isEqualTo(listString);
assertThat(claims.get(LIST_STRING_CLAIM)).isNotSameAs(listString).isEqualTo(listString);
assertThat(claims.get(MAP_STRING_OBJECT_CLAIM)).isNotSameAs(mapStringObject).isEqualTo(mapStringObject);
}
@Test
@@ -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.
@@ -18,6 +18,7 @@ package org.springframework.security.oauth2.core.oidc.user;
import org.junit.Test;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
@@ -57,11 +58,6 @@ public class DefaultOidcUserTests {
private static final OidcIdToken ID_TOKEN = new OidcIdToken("id-token-value", Instant.EPOCH, Instant.MAX, ID_TOKEN_CLAIMS);
private static final OidcUserInfo USER_INFO = new OidcUserInfo(USER_INFO_CLAIMS);
@Test(expected = IllegalArgumentException.class)
public void constructorWhenAuthoritiesIsNullThenThrowIllegalArgumentException() {
new DefaultOidcUser(null, ID_TOKEN);
}
@Test(expected = IllegalArgumentException.class)
public void constructorWhenIdTokenIsNullThenThrowIllegalArgumentException() {
new DefaultOidcUser(AUTHORITIES, null);
@@ -72,6 +68,26 @@ public class DefaultOidcUserTests {
new DefaultOidcUser(AUTHORITIES, ID_TOKEN, "invalid");
}
@Test
public void constructorWhenAuthoritiesIsNullThenCreatedWithEmptyAuthorities() {
DefaultOidcUser user = new DefaultOidcUser(null, ID_TOKEN);
assertThat(user.getClaims()).containsOnlyKeys(IdTokenClaimNames.ISS, IdTokenClaimNames.SUB);
assertThat(user.getIdToken()).isEqualTo(ID_TOKEN);
assertThat(user.getName()).isEqualTo(SUBJECT);
assertThat(user.getAuthorities()).isEmpty();
assertThat(user.getAttributes()).containsOnlyKeys(IdTokenClaimNames.ISS, IdTokenClaimNames.SUB);
}
@Test
public void constructorWhenAuthoritiesIsEmptyThenCreated() {
DefaultOidcUser user = new DefaultOidcUser(AuthorityUtils.NO_AUTHORITIES, ID_TOKEN);
assertThat(user.getClaims()).containsOnlyKeys(IdTokenClaimNames.ISS, IdTokenClaimNames.SUB);
assertThat(user.getIdToken()).isEqualTo(ID_TOKEN);
assertThat(user.getName()).isEqualTo(SUBJECT);
assertThat(user.getAuthorities()).isEmpty();
assertThat(user.getAttributes()).containsOnlyKeys(IdTokenClaimNames.ISS, IdTokenClaimNames.SUB);
}
@Test
public void constructorWhenAuthoritiesIdTokenProvidedThenCreated() {
DefaultOidcUser user = new DefaultOidcUser(AUTHORITIES, ID_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.
@@ -41,16 +41,6 @@ public class DefaultOAuth2UserTests {
private static final Map<String, Object> ATTRIBUTES = Collections.singletonMap(
ATTRIBUTE_NAME_KEY, USERNAME);
@Test(expected = IllegalArgumentException.class)
public void constructorWhenAuthoritiesIsNullThenThrowIllegalArgumentException() {
new DefaultOAuth2User(null, ATTRIBUTES, ATTRIBUTE_NAME_KEY);
}
@Test(expected = IllegalArgumentException.class)
public void constructorWhenAuthoritiesIsEmptyThenThrowIllegalArgumentException() {
new DefaultOAuth2User(Collections.emptySet(), ATTRIBUTES, ATTRIBUTE_NAME_KEY);
}
@Test(expected = IllegalArgumentException.class)
public void constructorWhenAttributesIsNullThenThrowIllegalArgumentException() {
new DefaultOAuth2User(AUTHORITIES, null, ATTRIBUTE_NAME_KEY);
@@ -71,6 +61,22 @@ public class DefaultOAuth2UserTests {
new DefaultOAuth2User(AUTHORITIES, ATTRIBUTES, "invalid");
}
@Test
public void constructorWhenAuthoritiesIsNullThenCreatedWithEmptyAuthorities() {
DefaultOAuth2User user = new DefaultOAuth2User(null, ATTRIBUTES, ATTRIBUTE_NAME_KEY);
assertThat(user.getName()).isEqualTo(USERNAME);
assertThat(user.getAuthorities()).isEmpty();
assertThat(user.getAttributes()).containsOnlyKeys(ATTRIBUTE_NAME_KEY);
}
@Test
public void constructorWhenAuthoritiesIsEmptyThenCreated() {
DefaultOAuth2User user = new DefaultOAuth2User(Collections.emptySet(), ATTRIBUTES, ATTRIBUTE_NAME_KEY);
assertThat(user.getName()).isEqualTo(USERNAME);
assertThat(user.getAuthorities()).isEmpty();
assertThat(user.getAttributes()).containsOnlyKeys(ATTRIBUTE_NAME_KEY);
}
@Test
public void constructorWhenAllParametersProvidedAndValidThenCreated() {
DefaultOAuth2User user = new DefaultOAuth2User(AUTHORITIES, ATTRIBUTES, ATTRIBUTE_NAME_KEY);
@@ -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");
@@ -37,9 +37,9 @@ import static org.springframework.util.Assert.notNull;
* A fully configured registration may look like
* <pre>
* //remote IDP entity ID
* String idpEntityId = "https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php";
* String idpEntityId = "https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/metadata.php";
* //remote WebSSO Endpoint - Where to Send AuthNRequests to
* String webSsoEndpoint = "https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php";
* String webSsoEndpoint = "https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/SSOService.php";
* //local registration ID
* String registrationId = "simplesamlphp";
* //local entity ID - autogenerated based on URL
@@ -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')
@@ -124,7 +124,7 @@ public class Saml2LoginIntegrationTests {
public void authenticateRequestWhenUnauthenticatedThenRespondsWithRedirectAuthNRequestXML() throws Exception {
mockMvc.perform(get("http://localhost:8080/saml2/authenticate/simplesamlphp"))
.andExpect(status().is3xxRedirection())
.andExpect(header().string("Location", startsWith("https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php?SAMLRequest=")));
.andExpect(header().string("Location", startsWith("https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/SSOService.php?SAMLRequest=")));
}
@Test
@@ -134,7 +134,7 @@ public class Saml2LoginIntegrationTests {
.param("RelayState", "relay state value with spaces")
)
.andExpect(status().is3xxRedirection())
.andExpect(header().string("Location", startsWith("https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php?SAMLRequest=")))
.andExpect(header().string("Location", startsWith("https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/SSOService.php?SAMLRequest=")))
.andExpect(header().string("Location", containsString("RelayState=relay%20state%20value%20with%20spaces")));
}
@@ -155,7 +155,7 @@ public class Saml2LoginIntegrationTests {
String destination = authnRequest.getDestination();
assertEquals(
"Destination must match",
"https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php",
"https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/SSOService.php",
destination
);
String acsURL = authnRequest.getAssertionConsumerServiceURL();
@@ -317,14 +317,14 @@ public class Saml2LoginIntegrationTests {
Response response = OpenSamlActionTestingSupport.buildResponse();
response.setID("_" + UUID.randomUUID().toString());
response.setDestination("http://localhost:8080/login/saml2/sso/simplesamlphp");
response.setIssuer(buildIssuer("https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php"));
response.setIssuer(buildIssuer("https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/metadata.php"));
return response;
}
private Assertion buildAssertion(String username) {
Assertion assertion = OpenSamlActionTestingSupport.buildAssertion();
assertion.setIssueInstant(DateTime.now());
assertion.setIssuer(buildIssuer("https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php"));
assertion.setIssuer(buildIssuer("https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/metadata.php"));
assertion.setSubject(buildSubject(username));
assertion.setConditions(buildConditions());
@@ -346,7 +346,7 @@ public class Saml2LoginIntegrationTests {
final PrivateKey privateKey = KeySupport.decodePrivateKey(key.getBytes(UTF_8), new char[0]);
BasicCredential cred = CredentialSupport.getSimpleCredential(publicKey, privateKey);
cred.setUsageType(usageType);
cred.setEntityId("https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php");
cred.setEntityId("https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/metadata.php");
return cred;
}
@@ -12,5 +12,5 @@ spring:
verification:
credentials:
- certificate-location: "classpath:credentials/idp-certificate.crt"
entity-id: https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php
sso-url: https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php
entity-id: https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/metadata.php
sso-url: https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/SSOService.php
@@ -28,7 +28,7 @@
<a th:href="@{/logout}">Log Out</a>
</li>
<li>
<a href="https://simplesaml-for-spring-saml.cfapps.io/module.php/core/authenticate.php?as=example-userpass&logout">
<a href="https://simplesaml-for-spring-saml.apps.pcfone.io/module.php/core/authenticate.php?as=example-userpass&logout">
Log out of SimpleSAMLPhp
</a>
</li>
@@ -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')
@@ -5,6 +5,7 @@ dependencies {
compile project(':spring-security-config')
compile "org.bouncycastle:bcprov-jdk15on"
compile "org.bouncycastle:bcpkix-jdk15on"
compile slf4jDependencies
testCompile project(':spring-security-test')
}
@@ -27,6 +27,10 @@ import org.springframework.security.web.session.HttpSessionEventPublisher;
public class MessageSecurityWebApplicationInitializer extends
AbstractSecurityWebApplicationInitializer {
public MessageSecurityWebApplicationInitializer() {
super(SecurityConfig.class);
}
@Override
protected boolean enableHttpSessionEventPublisher() {
return true;
@@ -15,6 +15,8 @@
*/
package org.springframework.security.samples.config;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@@ -23,6 +25,7 @@ import org.springframework.security.converter.RsaKeyConverters;
import org.springframework.security.saml2.credentials.Saml2X509Credential;
import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationFilter;
import java.io.ByteArrayInputStream;
@@ -39,11 +42,12 @@ import static org.springframework.security.saml2.credentials.Saml2X509Credential
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
RelyingPartyRegistration getSaml2AuthenticationConfiguration() throws Exception {
@Bean
RelyingPartyRegistrationRepository getSaml2AuthenticationConfiguration() throws Exception {
//remote IDP entity ID
String idpEntityId = "https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php";
String idpEntityId = "https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/metadata.php";
//remote WebSSO Endpoint - Where to Send AuthNRequests to
String webSsoEndpoint = "https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php";
String webSsoEndpoint = "https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/SSOService.php";
//local registration ID
String registrationId = "simplesamlphp";
//local entity ID - autogenerated based on URL
@@ -53,14 +57,14 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
//IDP certificate for verification of incoming messages
Saml2X509Credential idpVerificationCertificate = getVerificationCertificate();
String acsUrlTemplate = "{baseUrl}" + Saml2WebSsoAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI;
return RelyingPartyRegistration.withRegistrationId(registrationId)
return new InMemoryRelyingPartyRegistrationRepository(RelyingPartyRegistration.withRegistrationId(registrationId)
.remoteIdpEntityId(idpEntityId)
.idpWebSsoUrl(webSsoEndpoint)
.credentials(c -> c.add(signingCredential))
.credentials(c -> c.add(idpVerificationCertificate))
.localEntityIdTemplate(localEntityIdTemplate)
.assertionConsumerServiceUrlTemplate(acsUrlTemplate)
.build();
.build());
}
@Override
@@ -70,14 +74,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
.authorizeRequests()
.anyRequest().authenticated()
.and()
.saml2Login()
.relyingPartyRegistrationRepository(
new InMemoryRelyingPartyRegistrationRepository(
getSaml2AuthenticationConfiguration()
)
)
.loginProcessingUrl("/sample/jc/saml2/sso/{registrationId}")
;
.saml2Login();
// @formatter:on
}
@@ -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>
@@ -44,7 +44,7 @@ public class SecurityConfigTests {
public void filterWhenLoginProcessingUrlIsSetInJavaConfigThenTheFilterHasIt() {
FilterChainProxy filterChain = context.getBean(FilterChainProxy.class);
Assert.assertNotNull(filterChain);
final List<Filter> filters = filterChain.getFilters("/sample/jc/saml2/sso/test-id");
final List<Filter> filters = filterChain.getFilters("/login/saml2/sso/one");
Assert.assertNotNull(filters);
Saml2WebSsoAuthenticationFilter filter = (Saml2WebSsoAuthenticationFilter) filters
.stream()
@@ -55,6 +55,6 @@ public class SecurityConfigTests {
.get();
final Object matcher = ReflectionTestUtils.getField(filter, "requiresAuthenticationRequestMatcher");
final Object pattern = ReflectionTestUtils.getField(matcher, "pattern");
Assert.assertEquals("loginProcessingUrl mismatch", "/sample/jc/saml2/sso/{registrationId}", pattern);
Assert.assertEquals("loginProcessingUrl mismatch", "/login/saml2/sso/{registrationId}", pattern);
}
}
@@ -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) }
@@ -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));
}
}
@@ -142,13 +142,7 @@ public class HttpSessionSecurityContextRepository implements SecurityContextRepo
+ response
+ ". You must use the HttpRequestResponseHolder.response after invoking loadContext");
}
// saveContext() might already be called by the response wrapper
// if something in the chain called sendError() or sendRedirect(). This ensures we
// only call it
// once per request.
if (!responseWrapper.isContextSaved()) {
responseWrapper.saveContext(context);
}
responseWrapper.saveContext(context);
}
public boolean containsContext(HttpServletRequest request) {
@@ -305,6 +299,7 @@ public class HttpSessionSecurityContextRepository implements SecurityContextRepo
private final boolean httpSessionExistedAtStartOfRequest;
private final SecurityContext contextBeforeExecution;
private final Authentication authBeforeExecution;
private boolean isSaveContextInvoked;
/**
* Takes the parameters required to call <code>saveContext()</code> successfully
@@ -355,6 +350,7 @@ public class HttpSessionSecurityContextRepository implements SecurityContextRepo
// SEC-1587 A non-anonymous context may still be in the session
// SEC-1735 remove if the contextBeforeExecution was not anonymous
httpSession.removeAttribute(springSecurityContextKey);
this.isSaveContextInvoked = true;
}
return;
}
@@ -371,7 +367,7 @@ public class HttpSessionSecurityContextRepository implements SecurityContextRepo
if (contextChanged(context)
|| httpSession.getAttribute(springSecurityContextKey) == null) {
httpSession.setAttribute(springSecurityContextKey, context);
this.isSaveContextInvoked = true;
if (logger.isDebugEnabled()) {
logger.debug("SecurityContext '" + context
+ "' stored to HttpSession: '" + httpSession);
@@ -381,7 +377,7 @@ public class HttpSessionSecurityContextRepository implements SecurityContextRepo
}
private boolean contextChanged(SecurityContext context) {
return context != contextBeforeExecution
return this.isSaveContextInvoked || context != contextBeforeExecution
|| context.getAuthentication() != authBeforeExecution;
}
@@ -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.
@@ -13,9 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.csrf;
import java.io.IOException;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.HashSet;
@@ -28,6 +30,9 @@ import javax.servlet.http.HttpSession;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.log.LogMessage;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.crypto.codec.Utf8;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.access.AccessDeniedHandlerImpl;
import org.springframework.security.web.util.UrlUtils;
@@ -35,8 +40,6 @@ import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.web.filter.OncePerRequestFilter;
import static java.lang.Boolean.TRUE;
/**
* <p>
* Applies
@@ -58,6 +61,7 @@ import static java.lang.Boolean.TRUE;
* @since 3.2
*/
public final class CsrfFilter extends OncePerRequestFilter {
/**
* The default {@link RequestMatcher} that indicates if CSRF protection is required or
* not. The default is to ignore GET, HEAD, TRACE, OPTIONS and process all other
@@ -66,18 +70,21 @@ public final class CsrfFilter extends OncePerRequestFilter {
public static final RequestMatcher DEFAULT_CSRF_MATCHER = new DefaultRequiresCsrfMatcher();
/**
* The attribute name to use when marking a given request as one that should not be filtered.
* The attribute name to use when marking a given request as one that should not be
* filtered.
*
* To use, set the attribute on your {@link HttpServletRequest}:
* <pre>
* To use, set the attribute on your {@link HttpServletRequest}: <pre>
* CsrfFilter.skipRequest(request);
* </pre>
*/
private static final String SHOULD_NOT_FILTER = "SHOULD_NOT_FILTER" + CsrfFilter.class.getName();
private final Log logger = LogFactory.getLog(getClass());
private final CsrfTokenRepository tokenRepository;
private RequestMatcher requireCsrfProtectionMatcher = DEFAULT_CSRF_MATCHER;
private AccessDeniedHandler accessDeniedHandler = new AccessDeniedHandlerImpl();
public CsrfFilter(CsrfTokenRepository csrfTokenRepository) {
@@ -87,62 +94,46 @@ public final class CsrfFilter extends OncePerRequestFilter {
@Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
return TRUE.equals(request.getAttribute(SHOULD_NOT_FILTER));
return Boolean.TRUE.equals(request.getAttribute(SHOULD_NOT_FILTER));
}
/*
* (non-Javadoc)
*
* @see
* org.springframework.web.filter.OncePerRequestFilter#doFilterInternal(javax.servlet
* .http.HttpServletRequest, javax.servlet.http.HttpServletResponse,
* javax.servlet.FilterChain)
*/
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
request.setAttribute(HttpServletResponse.class.getName(), response);
CsrfToken csrfToken = this.tokenRepository.loadToken(request);
final boolean missingToken = csrfToken == null;
boolean missingToken = (csrfToken == null);
if (missingToken) {
csrfToken = this.tokenRepository.generateToken(request);
this.tokenRepository.saveToken(csrfToken, request, response);
}
request.setAttribute(CsrfToken.class.getName(), csrfToken);
request.setAttribute(csrfToken.getParameterName(), csrfToken);
if (!this.requireCsrfProtectionMatcher.matches(request)) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Did not protect against CSRF since request did not match "
+ this.requireCsrfProtectionMatcher);
}
filterChain.doFilter(request, response);
return;
}
String actualToken = request.getHeader(csrfToken.getHeaderName());
if (actualToken == null) {
actualToken = request.getParameter(csrfToken.getParameterName());
}
if (!csrfToken.getToken().equals(actualToken)) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Invalid CSRF token found for "
+ UrlUtils.buildFullRequestUrl(request));
}
if (missingToken) {
this.accessDeniedHandler.handle(request, response,
new MissingCsrfTokenException(actualToken));
}
else {
this.accessDeniedHandler.handle(request, response,
new InvalidCsrfTokenException(csrfToken, actualToken));
}
if (!equalsConstantTime(csrfToken.getToken(), actualToken)) {
this.logger.debug(
LogMessage.of(() -> "Invalid CSRF token found for " + UrlUtils.buildFullRequestUrl(request)));
AccessDeniedException exception = (!missingToken) ? new InvalidCsrfTokenException(csrfToken, actualToken)
: new MissingCsrfTokenException(actualToken);
this.accessDeniedHandler.handle(request, response, exception);
return;
}
filterChain.doFilter(request, response);
}
public static void skipRequest(HttpServletRequest request) {
request.setAttribute(SHOULD_NOT_FILTER, TRUE);
request.setAttribute(SHOULD_NOT_FILTER, Boolean.TRUE);
}
/**
@@ -154,14 +145,11 @@ public final class CsrfFilter extends OncePerRequestFilter {
* The default is to apply CSRF protection for any HTTP method other than GET, HEAD,
* TRACE, OPTIONS.
* </p>
*
* @param requireCsrfProtectionMatcher the {@link RequestMatcher} used to determine if
* CSRF protection should be applied.
*/
public void setRequireCsrfProtectionMatcher(
RequestMatcher requireCsrfProtectionMatcher) {
Assert.notNull(requireCsrfProtectionMatcher,
"requireCsrfProtectionMatcher cannot be null");
public void setRequireCsrfProtectionMatcher(RequestMatcher requireCsrfProtectionMatcher) {
Assert.notNull(requireCsrfProtectionMatcher, "requireCsrfProtectionMatcher cannot be null");
this.requireCsrfProtectionMatcher = requireCsrfProtectionMatcher;
}
@@ -172,7 +160,6 @@ public final class CsrfFilter extends OncePerRequestFilter {
* <p>
* The default is to use AccessDeniedHandlerImpl with no arguments.
* </p>
*
* @param accessDeniedHandler the {@link AccessDeniedHandler} to use
*/
public void setAccessDeniedHandler(AccessDeniedHandler accessDeniedHandler) {
@@ -180,20 +167,39 @@ public final class CsrfFilter extends OncePerRequestFilter {
this.accessDeniedHandler = accessDeniedHandler;
}
private static final class DefaultRequiresCsrfMatcher implements RequestMatcher {
private final HashSet<String> allowedMethods = new HashSet<>(
Arrays.asList("GET", "HEAD", "TRACE", "OPTIONS"));
/**
* Constant time comparison to prevent against timing attacks.
* @param expected
* @param actual
* @return
*/
private static boolean equalsConstantTime(String expected, String 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 final class DefaultRequiresCsrfMatcher implements RequestMatcher {
private final HashSet<String> allowedMethods = new HashSet<>(Arrays.asList("GET", "HEAD", "TRACE", "OPTIONS"));
/*
* (non-Javadoc)
*
* @see
* org.springframework.security.web.util.matcher.RequestMatcher#matches(javax.
* servlet.http.HttpServletRequest)
*/
@Override
public boolean matches(HttpServletRequest request) {
return !this.allowedMethods.contains(request.getMethod());
}
@Override
public String toString() {
return "CsrfNotRequired " + this.allowedMethods;
}
}
}
@@ -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.");
}
}
@@ -108,7 +108,7 @@ public final class CurrentSecurityContextArgumentResolver
StandardEvaluationContext context = new StandardEvaluationContext();
context.setRootObject(securityContext);
context.setVariable("this", securityContext);
context.setBeanResolver(this.beanResolver);
Expression expression = this.parser.parseExpression(expressionToParse);
securityContextResult = expression.getValue(context);
}
@@ -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,26 +16,28 @@
package org.springframework.security.web.server.csrf;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import reactor.core.publisher.Mono;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.multipart.FormFieldPart;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.crypto.codec.Utf8;
import org.springframework.security.web.server.authorization.HttpStatusServerAccessDeniedHandler;
import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher.MatchResult;
import org.springframework.util.Assert;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import static java.lang.Boolean.TRUE;
/**
* <p>
@@ -64,13 +66,14 @@ import static java.lang.Boolean.TRUE;
* @since 5.0
*/
public class CsrfWebFilter implements WebFilter {
public static final ServerWebExchangeMatcher DEFAULT_CSRF_MATCHER = new DefaultRequireCsrfProtectionMatcher();
/**
* The attribute name to use when marking a given request as one that should not be filtered.
* The attribute name to use when marking a given request as one that should not be
* filtered.
*
* To use, set the attribute on your {@link ServerWebExchange}:
* <pre>
* To use, set the attribute on your {@link ServerWebExchange}: <pre>
* CsrfWebFilter.skipExchange(exchange);
* </pre>
*/
@@ -80,32 +83,31 @@ public class CsrfWebFilter implements WebFilter {
private ServerCsrfTokenRepository csrfTokenRepository = new WebSessionServerCsrfTokenRepository();
private ServerAccessDeniedHandler accessDeniedHandler = new HttpStatusServerAccessDeniedHandler(HttpStatus.FORBIDDEN);
private ServerAccessDeniedHandler accessDeniedHandler = new HttpStatusServerAccessDeniedHandler(
HttpStatus.FORBIDDEN);
private boolean isTokenFromMultipartDataEnabled;
public void setAccessDeniedHandler(
ServerAccessDeniedHandler accessDeniedHandler) {
public void setAccessDeniedHandler(ServerAccessDeniedHandler accessDeniedHandler) {
Assert.notNull(accessDeniedHandler, "accessDeniedHandler");
this.accessDeniedHandler = accessDeniedHandler;
}
public void setCsrfTokenRepository(
ServerCsrfTokenRepository csrfTokenRepository) {
public void setCsrfTokenRepository(ServerCsrfTokenRepository csrfTokenRepository) {
Assert.notNull(csrfTokenRepository, "csrfTokenRepository cannot be null");
this.csrfTokenRepository = csrfTokenRepository;
}
public void setRequireCsrfProtectionMatcher(
ServerWebExchangeMatcher requireCsrfProtectionMatcher) {
public void setRequireCsrfProtectionMatcher(ServerWebExchangeMatcher requireCsrfProtectionMatcher) {
Assert.notNull(requireCsrfProtectionMatcher, "requireCsrfProtectionMatcher cannot be null");
this.requireCsrfProtectionMatcher = requireCsrfProtectionMatcher;
}
/**
* Specifies if the {@code CsrfWebFilter} should try to resolve the actual CSRF token from the body of multipart
* data requests.
* @param tokenFromMultipartDataEnabled true if should read from multipart form body, else false. Default is false
* Specifies if the {@code CsrfWebFilter} should try to resolve the actual CSRF token
* from the body of multipart data requests.
* @param tokenFromMultipartDataEnabled true if should read from multipart form body,
* else false. Default is false
*/
public void setTokenFromMultipartDataEnabled(boolean tokenFromMultipartDataEnabled) {
this.isTokenFromMultipartDataEnabled = tokenFromMultipartDataEnabled;
@@ -113,38 +115,33 @@ public class CsrfWebFilter implements WebFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
if (TRUE.equals(exchange.getAttribute(SHOULD_NOT_FILTER))) {
if (Boolean.TRUE.equals(exchange.getAttribute(SHOULD_NOT_FILTER))) {
return chain.filter(exchange).then(Mono.empty());
}
return this.requireCsrfProtectionMatcher.matches(exchange)
.filter( matchResult -> matchResult.isMatch())
.filter( matchResult -> !exchange.getAttributes().containsKey(CsrfToken.class.getName()))
.flatMap(m -> validateToken(exchange))
.flatMap(m -> continueFilterChain(exchange, chain))
.switchIfEmpty(continueFilterChain(exchange, chain).then(Mono.empty()))
.onErrorResume(CsrfException.class, e -> this.accessDeniedHandler
.handle(exchange, e));
return this.requireCsrfProtectionMatcher.matches(exchange).filter(MatchResult::isMatch)
.filter((matchResult) -> !exchange.getAttributes().containsKey(CsrfToken.class.getName()))
.flatMap((m) -> validateToken(exchange)).flatMap((m) -> continueFilterChain(exchange, chain))
.switchIfEmpty(continueFilterChain(exchange, chain).then(Mono.empty()))
.onErrorResume(CsrfException.class, (ex) -> this.accessDeniedHandler.handle(exchange, ex));
}
public static void skipExchange(ServerWebExchange exchange) {
exchange.getAttributes().put(SHOULD_NOT_FILTER, TRUE);
exchange.getAttributes().put(SHOULD_NOT_FILTER, Boolean.TRUE);
}
private Mono<Void> validateToken(ServerWebExchange exchange) {
return this.csrfTokenRepository.loadToken(exchange)
.switchIfEmpty(Mono.defer(() -> Mono.error(new CsrfException("CSRF Token has been associated to this client"))))
.filterWhen(expected -> containsValidCsrfToken(exchange, expected))
.switchIfEmpty(Mono.defer(() -> Mono.error(new CsrfException("Invalid CSRF Token"))))
.then();
.switchIfEmpty(
Mono.defer(() -> Mono.error(new CsrfException("An expected CSRF token cannot be found"))))
.filterWhen((expected) -> containsValidCsrfToken(exchange, expected))
.switchIfEmpty(Mono.defer(() -> Mono.error(new CsrfException("Invalid CSRF Token")))).then();
}
private Mono<Boolean> containsValidCsrfToken(ServerWebExchange exchange, CsrfToken expected) {
return exchange.getFormData()
.flatMap(data -> Mono.justOrEmpty(data.getFirst(expected.getParameterName())))
.switchIfEmpty(Mono.justOrEmpty(exchange.getRequest().getHeaders().getFirst(expected.getHeaderName())))
.switchIfEmpty(tokenFromMultipartData(exchange, expected))
.map(actual -> actual.equals(expected.getToken()));
return exchange.getFormData().flatMap((data) -> Mono.justOrEmpty(data.getFirst(expected.getParameterName())))
.switchIfEmpty(Mono.justOrEmpty(exchange.getRequest().getHeaders().getFirst(expected.getHeaderName())))
.switchIfEmpty(tokenFromMultipartData(exchange, expected))
.map((actual) -> equalsConstantTime(actual, expected.getToken()));
}
private Mono<String> tokenFromMultipartData(ServerWebExchange exchange, CsrfToken expected) {
@@ -157,14 +154,12 @@ public class CsrfWebFilter implements WebFilter {
if (!contentType.includes(MediaType.MULTIPART_FORM_DATA)) {
return Mono.empty();
}
return exchange.getMultipartData()
.map(d -> d.getFirst(expected.getParameterName()))
.cast(FormFieldPart.class)
.map(FormFieldPart::value);
return exchange.getMultipartData().map((d) -> d.getFirst(expected.getParameterName())).cast(FormFieldPart.class)
.map(FormFieldPart::value);
}
private Mono<Void> continueFilterChain(ServerWebExchange exchange, WebFilterChain chain) {
return Mono.defer(() ->{
return Mono.defer(() -> {
Mono<CsrfToken> csrfToken = csrfToken(exchange);
exchange.getAttributes().put(CsrfToken.class.getName(), csrfToken);
return chain.filter(exchange);
@@ -172,26 +167,45 @@ public class CsrfWebFilter implements WebFilter {
}
private Mono<CsrfToken> csrfToken(ServerWebExchange exchange) {
return this.csrfTokenRepository.loadToken(exchange)
.switchIfEmpty(generateToken(exchange));
return this.csrfTokenRepository.loadToken(exchange).switchIfEmpty(generateToken(exchange));
}
/**
* Constant time comparison to prevent against timing attacks.
* @param expected
* @param actual
* @return
*/
private static boolean equalsConstantTime(String expected, String 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 Mono<CsrfToken> generateToken(ServerWebExchange exchange) {
return this.csrfTokenRepository.generateToken(exchange)
.delayUntil(token -> this.csrfTokenRepository.saveToken(exchange, token));
.delayUntil((token) -> this.csrfTokenRepository.saveToken(exchange, token));
}
private static class DefaultRequireCsrfProtectionMatcher implements ServerWebExchangeMatcher {
private static final Set<HttpMethod> ALLOWED_METHODS = new HashSet<>(
Arrays.asList(HttpMethod.GET, HttpMethod.HEAD, HttpMethod.TRACE, HttpMethod.OPTIONS));
Arrays.asList(HttpMethod.GET, HttpMethod.HEAD, HttpMethod.TRACE, HttpMethod.OPTIONS));
@Override
public Mono<MatchResult> matches(ServerWebExchange exchange) {
return Mono.just(exchange.getRequest())
.flatMap(r -> Mono.justOrEmpty(r.getMethod()))
.filter(m -> ALLOWED_METHODS.contains(m))
.flatMap(m -> MatchResult.notMatch())
.switchIfEmpty(MatchResult.match());
return Mono.just(exchange.getRequest()).flatMap((r) -> Mono.justOrEmpty(r.getMethod()))
.filter(ALLOWED_METHODS::contains).flatMap((m) -> MatchResult.notMatch())
.switchIfEmpty(MatchResult.match());
}
}
}
@@ -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();
}
@@ -16,11 +16,15 @@
package org.springframework.security.web.context;
import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.servlet.Filter;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
@@ -30,6 +34,7 @@ import javax.servlet.http.HttpSession;
import org.junit.After;
import org.junit.Test;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockHttpSession;
@@ -37,10 +42,14 @@ import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Transient;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -174,6 +183,48 @@ public class HttpSessionSecurityContextRepositoryTests {
verify(session).setAttribute(SPRING_SECURITY_CONTEXT_KEY, ctx);
}
@Test
public void saveContextWhenSaveNewContextThenOriginalContextThenOriginalContextSaved() throws Exception {
HttpSessionSecurityContextRepository repository = new HttpSessionSecurityContextRepository();
SecurityContextPersistenceFilter securityContextPersistenceFilter = new SecurityContextPersistenceFilter(
repository);
UserDetails original = User.withUsername("user").password("password").roles("USER").build();
SecurityContext originalContext = createSecurityContext(original);
UserDetails impersonate = User.withUserDetails(original).username("impersonate").build();
SecurityContext impersonateContext = createSecurityContext(impersonate);
MockHttpServletRequest mockRequest = new MockHttpServletRequest();
MockHttpServletResponse mockResponse = new MockHttpServletResponse();
Filter saveImpersonateContext = (request, response, chain) -> {
SecurityContextHolder.setContext(impersonateContext);
// ensure the response is committed to trigger save
response.flushBuffer();
chain.doFilter(request, response);
};
Filter saveOriginalContext = (request, response, chain) -> {
SecurityContextHolder.setContext(originalContext);
chain.doFilter(request, response);
};
HttpServlet servlet = new HttpServlet() {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.getWriter().write("Hi");
}
};
SecurityContextHolder.setContext(originalContext);
MockFilterChain chain = new MockFilterChain(servlet, saveImpersonateContext, saveOriginalContext);
securityContextPersistenceFilter.doFilter(mockRequest, mockResponse, chain);
assertThat(mockRequest.getSession().getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY))
.isEqualTo(originalContext);
}
@Test
public void nonSecurityContextInSessionIsIgnored() {
HttpSessionSecurityContextRepository repo = new HttpSessionSecurityContextRepository();
@@ -668,6 +719,13 @@ public class HttpSessionSecurityContextRepositoryTests {
assertThat(session).isNull();
}
private SecurityContext createSecurityContext(UserDetails userDetails) {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(userDetails,
userDetails.getPassword(), userDetails.getAuthorities());
SecurityContext securityContext = new SecurityContextImpl(token);
return securityContext;
}
@Transient
private static class SomeTransientAuthentication extends AbstractAuthenticationToken {
SomeTransientAuthentication() {
@@ -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);
@@ -27,6 +27,7 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.MethodParameter;
import org.springframework.expression.BeanResolver;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.authority.AuthorityUtils;
@@ -35,17 +36,28 @@ import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.ReflectionUtils;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.mock;
import static org.mockito.BDDMockito.verify;
import static org.mockito.BDDMockito.when;
/**
* @author Rob Winch
*
*/
public class AuthenticationPrincipalArgumentResolverTests {
private BeanResolver beanResolver;
private Object expectedPrincipal;
private AuthenticationPrincipalArgumentResolver resolver;
@Before
public void setup() {
beanResolver = mock(BeanResolver.class);
resolver = new AuthenticationPrincipalArgumentResolver();
resolver.setBeanResolver(this.beanResolver);
}
@After
@@ -128,6 +140,17 @@ public class AuthenticationPrincipalArgumentResolverTests {
.isEqualTo(this.expectedPrincipal);
}
@Test
public void resolveArgumentSpelBean() throws Exception {
CustomUserPrincipal principal = new CustomUserPrincipal();
setAuthenticationPrincipal(principal);
when(this.beanResolver.resolve(any(), eq("test"))).thenReturn(principal.property);
this.expectedPrincipal = principal.property;
assertThat(this.resolver.resolveArgument(showUserSpelBean(), null, null, null))
.isEqualTo(this.expectedPrincipal);
verify(this.beanResolver).resolve(any(), eq("test"));
}
@Test
public void resolveArgumentSpelCopy() throws Exception {
CopyUserPrincipal principal = new CopyUserPrincipal("property");
@@ -198,6 +221,10 @@ public class AuthenticationPrincipalArgumentResolverTests {
return getMethodParameter("showUserSpel", String.class);
}
private MethodParameter showUserSpelBean() {
return getMethodParameter("showUserSpelBean", String.class);
}
private MethodParameter showUserSpelCopy() {
return getMethodParameter("showUserSpelCopy", CopyUserPrincipal.class);
}
@@ -255,6 +282,9 @@ public class AuthenticationPrincipalArgumentResolverTests {
@AuthenticationPrincipal(expression = "property") String user) {
}
public void showUserSpelBean(@AuthenticationPrincipal(expression = "@test") String user) {
}
public void showUserSpelCopy(
@AuthenticationPrincipal(expression = "new org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolverTests$CopyUserPrincipal(#this)") CopyUserPrincipal user) {
}
@@ -26,6 +26,7 @@ import org.junit.Before;
import org.junit.Test;
import org.springframework.core.MethodParameter;
import org.springframework.expression.BeanResolver;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication;
@@ -38,6 +39,11 @@ import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.mock;
import static org.mockito.BDDMockito.verify;
import static org.mockito.BDDMockito.when;
/**
* @author Dan Zheng
@@ -45,11 +51,16 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
*
*/
public class CurrentSecurityContextArgumentResolverTests {
private BeanResolver beanResolver;
private CurrentSecurityContextArgumentResolver resolver;
@Before
public void setup() {
this.beanResolver = mock(BeanResolver.class);
this.resolver = new CurrentSecurityContextArgumentResolver();
this.resolver.setBeanResolver(this.beanResolver);
}
@After
@@ -104,6 +115,15 @@ public class CurrentSecurityContextArgumentResolverTests {
assertThat(auth1.getPrincipal()).isEqualTo(principal);
}
@Test
public void resolveArgumentWithAuthenticationWithBean() throws Exception {
String principal = "john";
when(this.beanResolver.resolve(any(), eq("test"))).thenReturn(principal);
assertThat(this.resolver.resolveArgument(showSecurityContextAuthenticationWithBean(), null, null, null))
.isEqualTo(principal);
verify(this.beanResolver).resolve(any(), eq("test"));
}
@Test
public void resolveArgumentWithNullAuthentication() {
SecurityContext context = SecurityContextHolder.getContext();
@@ -217,6 +237,10 @@ public class CurrentSecurityContextArgumentResolverTests {
return getMethodParameter("showSecurityContextAuthenticationAnnotation", Authentication.class);
}
public MethodParameter showSecurityContextAuthenticationWithBean() {
return getMethodParameter("showSecurityContextAuthenticationWithBean", String.class);
}
private MethodParameter showSecurityContextAuthenticationWithOptionalPrincipal() {
return getMethodParameter("showSecurityContextAuthenticationWithOptionalPrincipal", Object.class);
}
@@ -279,6 +303,10 @@ public class CurrentSecurityContextArgumentResolverTests {
public void showSecurityContextAuthenticationAnnotation(@CurrentSecurityContext(expression = "authentication") Authentication authentication) {
}
public void showSecurityContextAuthenticationWithBean(
@CurrentSecurityContext(expression = "@test") String name) {
}
public void showSecurityContextAuthenticationWithOptionalPrincipal(@CurrentSecurityContext(expression = "authentication?.principal") Object principal) {
}
@@ -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;
@@ -65,8 +67,19 @@ public class CsrfWebFilterTests {
private MockServerWebExchange get = from(
MockServerHttpRequest.get("/"));
private ServerWebExchange post = from(
MockServerHttpRequest.post("/"));
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() {
@@ -110,6 +123,8 @@ public class CsrfWebFilterTests {
.verifyComplete();
assertThat(this.post.getResponse().getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
StepVerifier.create(this.post.getResponse().getBodyAsString())
.assertNext(b -> assertThat(b).contains("An expected CSRF token cannot be found"));
}
@Test
@@ -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()