1
0
mirror of synced 2026-07-17 08:35:13 +00:00

Compare commits

..

131 Commits

Author SHA1 Message Date
github-actions[bot] 7ef659a643 Release 6.0.2 2023-02-20 15:22:27 +00:00
Josh Cummings f9f6bd3e3e Merge branch '5.8.x' into 6.0.x 2023-02-17 16:09:21 -07:00
Josh Cummings a93cc3f0a0 Merge branch '5.7.x' into 5.8.x
Closes gh-12739
2023-02-17 15:54:47 -07:00
Josh Cummings 2fcf48a11b Update org.springframework.data to 2021.2.8
Closes gh-12738
2023-02-17 15:53:49 -07:00
Josh Cummings 0398d86f39 Update org.springframework to 5.3.25
Closes gh-12737
2023-02-17 15:53:40 -07:00
Josh Cummings c9112dfaab Update hibernate-entitymanager to 5.6.15.Final
Closes gh-12736
2023-02-17 15:53:32 -07:00
Josh Cummings 4156766fe0 Update org.aspectj to 1.9.19
Closes gh-12735
2023-02-17 15:53:24 -07:00
Josh Cummings f125785328 Update io.spring.nohttp to 0.0.11
Closes gh-12734
2023-02-17 15:53:16 -07:00
Josh Cummings 4643c0d999 Update blockhound to 1.0.7.RELEASE
Closes gh-12733
2023-02-17 15:53:08 -07:00
Josh Cummings d2d82b5e94 Update io.projectreactor to 2020.0.28
Closes gh-12732
2023-02-17 15:52:54 -07:00
Josh Cummings d4d03ea146 Update jackson-bom to 2.13.5
Closes gh-12731
2023-02-17 15:52:32 -07:00
Byeonggon Lee f2c4656abd Fix typo in form.adoc
Closes gh-12678
2023-02-17 15:52:26 -06:00
Steve Riesenberg edff50cb08 Merge branch '5.8.x' into 6.0.x 2023-02-17 12:01:59 -06:00
Steve Riesenberg 6ba88e6545 Update org.springframework.data to 2021.2.8
Closes gh-12726
2023-02-17 11:58:43 -06:00
Steve Riesenberg a3855ea092 Update org.springframework to 5.3.25
Closes gh-12725
2023-02-17 11:58:39 -06:00
Steve Riesenberg f57b433fb1 Update junit-bom to 5.9.2
Closes gh-12723
2023-02-17 11:58:32 -06:00
Steve Riesenberg 30566eba9e Update hibernate-entitymanager to 5.6.15.Final
Closes gh-12722
2023-02-17 11:58:28 -06:00
Steve Riesenberg c373f6afd9 Update org.aspectj to 1.9.19
Closes gh-12721
2023-02-17 11:58:24 -06:00
Steve Riesenberg d245a54951 Update io.spring.nohttp to 0.0.11
Closes gh-12720
2023-02-17 11:58:21 -06:00
Steve Riesenberg 5f599d3d77 Update blockhound to 1.0.7.RELEASE
Closes gh-12719
2023-02-17 11:58:17 -06:00
Steve Riesenberg f49a933490 Update io.projectreactor to 2020.0.28
Closes gh-12717
2023-02-17 11:58:11 -06:00
Steve Riesenberg 8961264ec5 Update jackson-bom to 2.13.5
Closes gh-12714
2023-02-17 11:58:00 -06:00
Steve Riesenberg aaa6983fda Exclude mockk 1.13.4 update
Issue gh-12695
2023-02-17 11:55:57 -06:00
Marcus Da Coregio 6f1c9184db Update spring-ldap-core to 3.0.1
Closes gh-12713
2023-02-17 14:53:54 -03:00
Marcus Da Coregio 8fb9c91acf Update org.springframework.data to 2022.0.2
Closes gh-12712
2023-02-17 14:53:51 -03:00
Marcus Da Coregio 01c3a25d7f Update org.springframework to 6.0.5
Closes gh-12711
2023-02-17 14:53:47 -03:00
Marcus Da Coregio e8a251aa7f Update junit-bom to 5.9.2
Closes gh-12708
2023-02-17 14:53:37 -03:00
Marcus Da Coregio e008b93715 Update hibernate-core to 6.1.7.Final
Closes gh-12707
2023-02-17 14:53:34 -03:00
Marcus Da Coregio 8174ed2291 Update org.aspectj to 1.9.19
Closes gh-12706
2023-02-17 14:53:31 -03:00
Marcus Da Coregio e7c1e2d78d Update maven-resolver-provider to 3.8.7
Closes gh-12705
2023-02-17 14:53:28 -03:00
Marcus Da Coregio 3f2ff0572e Update jakarta.servlet.jsp-api to 3.1.1
Closes gh-12704
2023-02-17 14:53:24 -03:00
Marcus Da Coregio d127ad310f Update io.spring.nohttp to 0.0.11
Closes gh-12703
2023-02-17 14:53:21 -03:00
Marcus Da Coregio b0c823970b Update io.projectreactor to 2022.0.3
Closes gh-12701
2023-02-17 14:53:14 -03:00
Marcus Da Coregio 67a0a1a010 Update mockk to 1.13.4
Closes gh-12700
2023-02-17 14:53:10 -03:00
Marcus Da Coregio bb644fe371 Update micrometer-observation to 1.10.4
Closes gh-12699
2023-02-17 14:53:07 -03:00
Marcus Da Coregio eef519adc5 Update jackson-bom to 2.14.2
Closes gh-12696
2023-02-17 14:52:58 -03:00
Josh Cummings a1b282ff03 Merge branch '5.7.x' into 5.8.x
Closes gh-12693
2023-02-17 10:09:32 -07:00
Josh Cummings 2db4430dcd Preserve OpenSamlAssertingPartyDetails Instance
Closes gh-12667
2023-02-17 10:02:17 -07:00
Josh Cummings cedb9fd199 Merge branch '5.8.x' into 6.0.x
Closes gh-12687
2023-02-16 14:56:32 -07:00
Josh Cummings 0baf650f38 Merge branch '5.7.x' into 5.8.x
Closes gh-12686
2023-02-16 14:55:22 -07:00
Leonid Rozenblyum 000b4bc495 Fix NPE in HttpSecurity#addFilterBefore, HttpSecurity#addFilterAfter
Before the fix, these methods would throw a NPE in case when the filter class passed as the second parameter, is not registered yet.

In particular, this exception can occur when mixing standard and custom DSL to register filters.

The fix doesn't change the situation that standard DSL for registration of filters cannot refer to filters that are registered via custom DSL even though those calls were done earlier.

It just provides more user-friendly error handling for this and most likely other scenarios of calls of HttpSecurity#addFilterBefore, HttpSecurity#addFilterAfter.

The error handling is implemented similarly to HttpSecurity#addFilter.

Closes gh-12637
2023-02-16 14:54:44 -07:00
Steve Riesenberg c4f68d83bf Document default CsrfTokenRequestHandler in 6.0
Closes gh-12651
2023-02-16 13:26:23 -06:00
Marcus Da Coregio 5ccf414f02 Merge branch '5.8.x' into 6.0.x 2023-02-16 10:57:33 -03:00
Marcus Da Coregio 82c86b822f Polish session-management.adoc
Remove unresolved anchor

Issue gh-12519
2023-02-16 10:57:02 -03:00
Marcus Da Coregio e59f71f036 Polish session-management.adoc
Remove default values from configuration

Issue gh-12519
2023-02-16 10:52:55 -03:00
Marcus Da Coregio ce222de7e6 Merge branch '5.8.x' into 6.0.x
Closes gh-12680
2023-02-16 10:42:56 -03:00
Marcus Da Coregio 4f3faa78f7 Revisit Session Management docs
Closes gh-12519
2023-02-16 10:39:59 -03:00
Steve Riesenberg 8ab0bf76a3 Merge branch '5.8.x' into 6.0.x 2023-02-15 17:18:40 -06:00
Steve Riesenberg 2876605324 Polish migration doc
Issue gh-12675
2023-02-15 17:18:09 -06:00
Steve Riesenberg bf2951b5af Add sections for migrating exploit protection in 6.0
Issue gh-12462
2023-02-15 17:18:09 -06:00
Steve Riesenberg ca1961d35e Link to the latest 6.0.x release
Issue gh-12675
2023-02-15 17:01:28 -06:00
Steve Riesenberg 821db0a1ea Polish migration doc
Issue gh-12675
2023-02-15 17:00:49 -06:00
Tao Sun 6f5c633241 Fix typo in Authentication Migrations page 2023-02-15 15:14:09 -07:00
Steve Riesenberg 45b81b194b Expand migration docs regarding CSRF
Closes gh-12462
2023-02-15 14:53:28 -06:00
Josh Cummings 8ca726f4fa Specify query string
Issue gh-12665
2023-02-14 08:24:07 -07:00
Josh Cummings e7d65966fd Merge branch '5.8.x' into 6.0.x
Closes gh-12671
2023-02-14 08:01:31 -07:00
Josh Cummings 0d4c619648 Include continue in query string
Closes gh-12665
2023-02-14 08:00:19 -07:00
Dmitriy Grushin 2b36499700 Update expression-based.adoc
Removed a duplicate paragraph that was phrased a bit differently.
2023-02-07 13:00:59 -07:00
Tobias Meurer 7dd5cc6082 Pick Up Custom SecurityContextRespository
Closes gh-12579
2023-02-07 12:46:12 -07:00
Marcus Da Coregio 52ed165476 Move classpath checks to class member variable
Closes gh-11437
2023-02-07 09:25:06 -03:00
Marcus Da Coregio c15f45d9ee Only register hints for servlet applications
Closes gh-12622
2023-02-03 16:37:33 -03:00
Marcus Da Coregio 3572111cf5 Add JwtDecoder hint for oauth2Login
Closes gh-12615
2023-02-03 14:34:32 -03:00
luamas 7409d14504 fix javax.json.bind.Jsonb to jakarta.json.bind.Jsonb
Closes gh-12616
2023-02-03 12:30:17 -03:00
Josh Cummings 7dc3759f5d Merge branch '5.8.x' into 6.0.x 2023-02-01 15:25:47 -07:00
Josh Cummings 4a8961bc80 Merge branch '5.7.x' into 5.8.x 2023-02-01 15:24:48 -07:00
jongwooo aad3e61f4d chore: Use cache in continuous-integration-workflow.yml
Signed-off-by: jongwooo <jongwooo.han@gmail.com>
2023-02-01 14:26:25 -07:00
Rob Winch 4264258c31 Merge branch '5.8.x' into 6.0.x 2023-01-30 16:06:23 -06:00
Rob Winch eaa9692c0c Merge branch '5.7.x' into 5.8.x 2023-01-30 16:05:56 -06:00
Dan Allen 51d5fb9c03 upgrade the Gradle Antora Plugin to 1.0.0 2023-01-30 16:04:40 -06:00
Steve Riesenberg 179428f7da Add section for migrating WebSocket support
Issue gh-12378
2023-01-26 15:45:09 -06:00
Steve Riesenberg 13487be268 Default to XorCsrfChannelInterceptor in 6.0.x
Closes gh-12378
2023-01-26 15:45:04 -06:00
Steve Riesenberg 1363a4eece Merge branch '5.8.x' into 6.0.x 2023-01-26 15:44:47 -06:00
Josh Cummings c3563df25a Include HttpStatusRequestRequestedHandler
Closes gh-12548
2023-01-26 14:07:22 -07:00
Josh Cummings 66711f2365 Add RequestRejectedHandler Test
Issue gh-12548
2023-01-26 13:07:16 -07:00
Steve Riesenberg 33e72b35f9 Add section for migrating WebSocket support
Issue gh-12378
2023-01-23 16:00:36 -06:00
Steve Riesenberg c306df9b46 Add XorCsrfChannelInterceptor
Issue gh-12378
2023-01-23 16:00:35 -06:00
Rob Winch 9603a68e8f Merge branch '5.8.x' into 6.0.x 2023-01-17 17:07:20 -06:00
Rob Winch d42405de42 Merge branch '5.7.x' into 5.8.x 2023-01-17 17:06:03 -06:00
Rob Winch 7db357d36a Use io.spring.antora.generate-antora-yml 2023-01-17 17:05:55 -06:00
Rob Winch 1084cdb75a Merge branch '5.8.x' into 6.0.x 2023-01-17 15:10:05 -06:00
Rob Winch 5beabbe357 Merge branch '5.7.x' into 5.8.x
Closes gh-12553
2023-01-17 15:03:14 -06:00
Dan Allen f5bc6ce665 fix unclosed block in docs 2023-01-17 15:02:30 -06:00
Marcus Da Coregio 6f72c07747 Merge branch '5.8.x' into 6.0.x 2023-01-17 14:43:35 -03:00
Marcus Da Coregio 108b03da3a Merge branch '5.7.x' into 5.8.x 2023-01-17 14:43:17 -03:00
Dan Allen 4cbb057b97 enable fetch option in Antora unless Gradle is running in offline mode 2023-01-17 14:42:54 -03:00
Marcus Da Coregio 20b745e0b7 Merge branch '5.8.x' into 6.0.x 2023-01-17 09:47:08 -03:00
Marcus Da Coregio 8758a00e90 Merge branch '5.7.x' into 5.8.x 2023-01-17 09:46:56 -03:00
Marcus Da Coregio c0f7cecc6d Merge branch '5.6.x' into 5.7.x 2023-01-17 09:46:41 -03:00
Dan Allen 22ffa833ca upgrade Antora plugin and configure playbook provider to support local build 2023-01-17 09:46:24 -03:00
Josh Cummings e0697de7b2 Merge branch '5.8.x' into 6.0.x
Closes gh-12527
2023-01-11 12:48:27 -07:00
Josh Cummings 090c5f96ce Merge branch '5.7.x' into 5.8.x
Closes gh-12526
2023-01-11 12:47:55 -07:00
Josh Cummings f41b77a4db Fix Diagram to Say SecurityContextHolderFilter
Closes gh-11800
2023-01-11 12:47:07 -07:00
Josh Cummings 4d2dab9b6b Lookup Parent Observation
Closes gh-12524
2023-01-11 10:13:33 -07:00
Josh Cummings 21ceb333a8 Merge branch '5.8.x' into 6.0.x
Closes gh-12517
2023-01-10 10:43:25 -07:00
Josh Cummings 6f43104eb3 Merge branch '5.7.x' into 5.8.x
Closes gh-12516
2023-01-10 10:42:45 -07:00
Josh Cummings 2028507bf8 Fix Typo in Sample
Closes gh-11095
2023-01-10 10:38:28 -07:00
Steve Riesenberg 4e80338a9b Polish gh-12466 2023-01-10 11:31:51 -06:00
Wellington Domiciano 2c8854bb7f Adjusts setRequestHandler javadoc in CsrfFilter
Adjusts setRequestHandler method javadoc in CsrfFilter class to reflect
changes in 6.0.

In 6.0, the default CsrfTokenRequestHandler changed to
XorCsrfTokenRequestAttributeHandler, however, the javadoc for the
setRequestHandler method still said it was
CsrfTokenRequestAttributeHandler.

This change adjusts the information to make it more accurate, because,
although XorCsrfTokenRequestAttributeHandler is a subclass of
CsrfTokenRequestAttributeHandler, the behavior is quite different.

Closes gh-12464
2023-01-10 11:31:51 -06:00
Josh Cummings 5b6b3d585f Change EnableReactiveMethodSecurity Defaults
Closes gh-12506
2023-01-10 08:30:52 -07:00
Marcus Da Coregio d1fc789ae2 Merge branch '5.8.x' into 6.0.x
Closes gh-12511
2023-01-10 09:42:48 -03:00
Marcus Da Coregio ae46032ced Merge branch '5.7.x' into 5.8.x
Closes gh-12510
2023-01-10 09:39:40 -03:00
Marcus Da Coregio ffdb397830 Save the SecurityContext when switching user
Closes gh-12504
2023-01-10 09:27:56 -03:00
Josh Cummings d2d2ad6e96 Merge branch '5.8.x' into 6.0.x
Closes gh-12498
2023-01-06 13:57:40 -07:00
Josh Cummings 88a8ef647b Add Details about @Configuration
Closes gh-12486
2023-01-06 13:56:56 -07:00
Josh Cummings 748e912685 Merge branch '5.8.x' into 6.0.x
Closes gh-12495
2023-01-06 12:56:21 -07:00
Josh Cummings 5e1db6a771 Merge branch '5.7.x' into 5.8.x
Closes gh-12494
2023-01-06 12:55:43 -07:00
Jon Kjennbakken 225dc593a8 Polish NimbusJwtDecoderTests
- Add missing mock

Closes gh-12238
2023-01-06 12:53:36 -07:00
Josh Cummings c308e4665a Polish Event Name
Provide a name with no spaces separate from the human-friendly
one with spaces.

Closes gh-12490
2023-01-06 11:13:11 -07:00
Junichi Sakaeda 930cc68768 Duplicate words. 2023-01-05 10:36:17 -07:00
Olivier Délèze 9535566f84 Update multitenancy.adoc
The Java example at line 421 should use the injected `jwtValidator` and not from the current class referenced by `this. jwtValidator`.
2023-01-05 10:32:57 -07:00
Wellington Domiciano 27b3f4d403 Adjusts setRequestHandler javadoc in CsrfWebFilter
Adjusts setRequestHandler method javadoc in CsrfWebFilter class to reflect changes in 6.0.

In 6.0, the default ServerCsrfTokenRequestHandler changed to XorServerCsrfTokenRequestAttributeHandler, however, the javadoc for the setRequestHandler method still said it was ServerCsrfTokenRequestAttributeHandler.

This change adjusts the information to make it more accurate, because, although XorServerCsrfTokenRequestAttributeHandler is a subclass of ServerCsrfTokenRequestAttributeHandler, the behavior is quite different.

Closes gh-12465
2023-01-04 10:53:47 -07:00
Josh Cummings 9c0a35a6f6 Merge branch '5.8.x' into 6.0.x
Closes gh-12459
2022-12-23 15:55:43 -07:00
Josh Cummings fda0e9a2b6 Merge branch '5.7.x' into 5.8.x
Closes gh-12458
2022-12-23 15:54:37 -07:00
Josh Cummings 3cfaf0d11d Avoid LinkedMultiValueMap in Serializable Object
Closes gh-11785
2022-12-23 15:54:00 -07:00
Marcus Da Coregio 4551025992 Merge branch '5.8.x' into 6.0.x
Closes gh-12444
2022-12-21 10:25:04 -03:00
Marcus Da Coregio 892bbcfe0f Add EnableWebFluxSecurity migration step
Closes gh-12434
2022-12-21 10:24:25 -03:00
Do Nhu Vy 7497ff9023 Polish README.adoc 2022-12-20 11:48:41 -03:00
Josh Cummings 7bd6deccc3 Revert "Disable Some R2dbc Tests"
This reverts commit 813179931a.

Closes gh-12339
2022-12-19 15:42:22 -07:00
Marcus Da Coregio b9f9139f5e Merge branch '5.8.x' into 6.0.x 2022-12-19 16:53:22 -03:00
Marcus Da Coregio 5406fed5dc Merge branch '5.7.x' into 5.8.x 2022-12-19 16:53:05 -03:00
Eleftheria Stein-Kousathana fbfa13bd47 Fix OAuth 2.0 testing docs 2022-12-19 16:52:25 -03:00
Marcus Da Coregio b3aaa4d5ff Merge branch '5.8.x' into 6.0.x 2022-12-19 15:04:36 -03:00
Marcus Da Coregio 992bda32f9 Merge branch '5.7.x' into 5.8.x 2022-12-19 15:04:17 -03:00
Marcus Da Coregio 0c6b427132 Merge branch '5.6.x' into 5.7.x 2022-12-19 15:03:50 -03:00
github-actions[bot] 0f7d17bae6 Next development version 2022-12-19 15:59:53 +00:00
github-actions[bot] 44d5cb0a63 Next development version 2022-12-19 15:59:53 +00:00
github-actions[bot] c11248b589 Next development version 2022-12-19 15:57:16 +00:00
github-actions[bot] afb28348cb Next development version 2022-12-19 15:56:54 +00:00
github-actions[bot] 972075677f Release 5.6.10 2022-12-19 15:23:12 +00:00
github-actions[bot] 314ae69b70 Release 5.8.1 2022-12-19 15:22:42 +00:00
github-actions[bot] 11614edcfe Release 5.7.6 2022-12-19 15:22:32 +00:00
80 changed files with 2035 additions and 569 deletions
@@ -55,6 +55,7 @@ jobs:
with:
java-version: '17'
distribution: 'temurin'
cache: 'gradle'
- name: Set up Gradle
uses: gradle/gradle-build-action@v2
- name: Set up gradle user name
+7 -6
View File
@@ -30,7 +30,7 @@ In the instructions below, https://vimeo.com/34436402[`./gradlew`] is invoked fr
a cross-platform, self-contained bootstrap mechanism for the build.
=== Prerequisites
https://help.github.com/set-up-git-redirect[Git] and the https://www.oracle.com/technetwork/java/javase/downloads[JDK17 build].
https://docs.github.com/en/get-started/quickstart/set-up-git[Git] and the https://www.oracle.com/java/technologies/downloads/#java17[JDK17 build].
Be sure that your `JAVA_HOME` environment variable points to the `jdk-17` folder extracted from the JDK download.
@@ -40,13 +40,15 @@ Be sure that your `JAVA_HOME` environment variable points to the `jdk-17` folder
git clone git@github.com:spring-projects/spring-security.git
----
=== Install all spring-\* jars into your local Maven cache
=== Install all `spring-*.jar` into your local Maven repository.
[indent=0]
----
./gradlew publishToMavenLocal
----
=== Compile and test; build all jars, distribution zips, and docs
=== Compile and test; build all JARs, distribution zips, and docs
[indent=0]
----
./gradlew build
@@ -59,18 +61,17 @@ You can build the reference docs for this branch by running the following comman
./gradlew :spring-security-docs:antora
----
That command publishes the docs site to the _docs/build/site_ directory.
That command publishes the docs site to the `_docs/build/site_` directory.
The https://github.com/spring-projects/spring-security/tree/docs-build[playbook branch] describes how to build the reference docs in detail.
Discover more commands with `./gradlew tasks`.
See also the https://github.com/spring-projects/spring-framework/wiki/Gradle-build-and-release-FAQ[Gradle build and release FAQ].
== Getting Support
Check out the https://stackoverflow.com/questions/tagged/spring-security[Spring Security tags on Stack Overflow].
https://spring.io/services[Commercial support] is available too.
== Contributing
https://help.github.com/articles/creating-a-pull-request[Pull requests] are welcome; see the https://github.com/spring-projects/spring-security/blob/main/CONTRIBUTING.adoc[contributor guidelines] for details.
https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request[Pull requests] are welcome; see the https://github.com/spring-projects/spring-security/blob/main/CONTRIBUTING.adoc[contributor guidelines] for details.
== License
Spring Security is Open Source software released under the
+1 -1
View File
@@ -3,7 +3,7 @@ import io.spring.gradle.IncludeRepoTask
buildscript {
dependencies {
classpath "io.spring.javaformat:spring-javaformat-gradle-plugin:$springJavaformatVersion"
classpath 'io.spring.nohttp:nohttp-gradle:0.0.10'
classpath 'io.spring.nohttp:nohttp-gradle:0.0.11'
classpath "io.freefair.gradle:aspectj-plugin:6.6-rc1"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
classpath "com.netflix.nebula:nebula-project-plugin:8.2.0"
+3 -3
View File
@@ -87,19 +87,19 @@ dependencies {
implementation localGroovy()
implementation 'io.github.gradle-nexus:publish-plugin:1.1.0'
implementation 'io.projectreactor:reactor-core:3.5.1'
implementation 'io.projectreactor:reactor-core:3.5.3'
implementation 'org.gretty:gretty:3.0.9'
implementation 'com.apollographql.apollo:apollo-runtime:2.4.5'
implementation 'com.github.ben-manes:gradle-versions-plugin:0.38.0'
implementation 'com.github.spullara.mustache.java:compiler:0.9.4'
implementation 'io.spring.javaformat:spring-javaformat-gradle-plugin:0.0.15'
implementation 'io.spring.nohttp:nohttp-gradle:0.0.10'
implementation 'io.spring.nohttp:nohttp-gradle:0.0.11'
implementation 'net.sourceforge.htmlunit:htmlunit:2.37.0'
implementation 'org.hidetake:gradle-ssh-plugin:2.10.1'
implementation 'org.jfrog.buildinfo:build-info-extractor-gradle:4.29.0'
implementation 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.7.1'
testImplementation platform('org.junit:junit-bom:5.9.1')
testImplementation platform('org.junit:junit-bom:5.9.2')
testImplementation "org.junit.jupiter:junit-jupiter-api"
testImplementation "org.junit.jupiter:junit-jupiter-params"
testImplementation "org.junit.jupiter:junit-jupiter-engine"
@@ -36,7 +36,7 @@ class CheckstylePlugin implements Plugin<Project> {
if (checkstyleDir.exists() && checkstyleDir.directory) {
project.getPluginManager().apply('checkstyle')
project.dependencies.add('checkstyle', 'io.spring.javaformat:spring-javaformat-checkstyle:0.0.15')
project.dependencies.add('checkstyle', 'io.spring.nohttp:nohttp-checkstyle:0.0.10')
project.dependencies.add('checkstyle', 'io.spring.nohttp:nohttp-checkstyle:0.0.11')
project.checkstyle {
configDirectory = checkstyleDir
@@ -61,6 +61,14 @@ import org.springframework.util.ClassUtils;
public class LdapAuthenticationProviderConfigurer<B extends ProviderManagerBuilder<B>>
extends SecurityConfigurerAdapter<AuthenticationManager, B> {
private static final String APACHEDS_CLASSNAME = "org.apache.directory.server.core.DefaultDirectoryService";
private static final String UNBOUNDID_CLASSNAME = "com.unboundid.ldap.listener.InMemoryDirectoryServer";
private static final boolean apacheDsPresent;
private static final boolean unboundIdPresent;
private String groupRoleAttribute = "cn";
private String groupSearchBase = "";
@@ -91,6 +99,12 @@ public class LdapAuthenticationProviderConfigurer<B extends ProviderManagerBuild
private GrantedAuthoritiesMapper authoritiesMapper;
static {
ClassLoader classLoader = LdapAuthenticationProviderConfigurer.class.getClassLoader();
apacheDsPresent = ClassUtils.isPresent(APACHEDS_CLASSNAME, classLoader);
unboundIdPresent = ClassUtils.isPresent(UNBOUNDID_CLASSNAME, classLoader);
}
private LdapAuthenticationProvider build() throws Exception {
BaseLdapPathContextSource contextSource = getContextSource();
LdapAuthenticator ldapAuthenticator = createLdapAuthenticator(contextSource);
@@ -562,13 +576,13 @@ public class LdapAuthenticationProviderConfigurer<B extends ProviderManagerBuild
}
private void startEmbeddedLdapServer() throws Exception {
if (ClassUtils.isPresent(APACHEDS_CLASSNAME, getClass().getClassLoader())) {
if (apacheDsPresent) {
ApacheDSContainer apacheDsContainer = new ApacheDSContainer(this.root, this.ldif);
apacheDsContainer.setPort(getPort());
postProcess(apacheDsContainer);
this.port = apacheDsContainer.getLocalPort();
}
else if (ClassUtils.isPresent(UNBOUNDID_CLASSNAME, getClass().getClassLoader())) {
else if (unboundIdPresent) {
UnboundIdContainer unboundIdContainer = new UnboundIdContainer(this.root, this.ldif);
unboundIdContainer.setPort(getPort());
postProcess(unboundIdContainer);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2023 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.
@@ -73,6 +73,6 @@ public @interface EnableReactiveMethodSecurity {
* used.
* @since 5.8
*/
boolean useAuthorizationManager() default false;
boolean useAuthorizationManager() default true;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
@@ -3051,7 +3051,12 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
}
private HttpSecurity addFilterAtOffsetOf(Filter filter, int offset, Class<? extends Filter> registeredFilter) {
int order = this.filterOrders.getOrder(registeredFilter) + offset;
Integer registeredFilterOrder = this.filterOrders.getOrder(registeredFilter);
if (registeredFilterOrder == null) {
throw new IllegalArgumentException(
"The Filter class " + registeredFilter.getName() + " does not have a registered order");
}
int order = registeredFilterOrder + offset;
this.filters.add(new OrderedFilter(filter, order));
this.filterOrders.put(filter.getClass(), order);
return this;
@@ -56,7 +56,9 @@ import org.springframework.security.web.access.expression.DefaultWebSecurityExpr
import org.springframework.security.web.access.intercept.AuthorizationFilter;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.debug.DebugFilter;
import org.springframework.security.web.firewall.CompositeRequestRejectedHandler;
import org.springframework.security.web.firewall.HttpFirewall;
import org.springframework.security.web.firewall.HttpStatusRequestRejectedHandler;
import org.springframework.security.web.firewall.ObservationMarkingRequestRejectedHandler;
import org.springframework.security.web.firewall.RequestRejectedHandler;
import org.springframework.security.web.firewall.StrictHttpFirewall;
@@ -309,8 +311,10 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
filterChainProxy.setRequestRejectedHandler(this.requestRejectedHandler);
}
else if (!this.observationRegistry.isNoop()) {
filterChainProxy
.setRequestRejectedHandler(new ObservationMarkingRequestRejectedHandler(this.observationRegistry));
CompositeRequestRejectedHandler requestRejectedHandler = new CompositeRequestRejectedHandler(
new ObservationMarkingRequestRejectedHandler(this.observationRegistry),
new HttpStatusRequestRejectedHandler());
filterChainProxy.setRequestRejectedHandler(requestRejectedHandler);
}
filterChainProxy.setFilterChainDecorator(getFilterChainDecorator());
filterChainProxy.afterPropertiesSet();
@@ -51,12 +51,18 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Import(OAuth2ClientConfiguration.OAuth2ClientWebMvcImportSelector.class)
final class OAuth2ClientConfiguration {
private static final boolean webMvcPresent;
static {
ClassLoader classLoader = OAuth2ClientConfiguration.class.getClassLoader();
webMvcPresent = ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", classLoader);
}
static class OAuth2ClientWebMvcImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
if (!ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet",
getClass().getClassLoader())) {
if (!webMvcPresent) {
return new String[0];
}
return new String[] { "org.springframework.security.config.annotation.web.configuration."
@@ -30,9 +30,16 @@ import org.springframework.util.ClassUtils;
*/
class SpringWebMvcImportSelector implements ImportSelector {
private static final boolean webMvcPresent;
static {
ClassLoader classLoader = SpringWebMvcImportSelector.class.getClassLoader();
webMvcPresent = ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", classLoader);
}
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
if (!ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", getClass().getClassLoader())) {
if (!webMvcPresent) {
return new String[0];
}
return new String[] {
@@ -40,14 +40,20 @@ import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
*/
public class CorsConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<CorsConfigurer<H>, H> {
private static final String HANDLER_MAPPING_INTROSPECTOR = "org.springframework.web.servlet.handler.HandlerMappingIntrospector";
private static final String CORS_CONFIGURATION_SOURCE_BEAN_NAME = "corsConfigurationSource";
private static final String CORS_FILTER_BEAN_NAME = "corsFilter";
private static final String HANDLER_MAPPING_INTROSPECTOR = "org.springframework.web.servlet.handler.HandlerMappingIntrospector";
private static final boolean mvcPresent;
private CorsConfigurationSource configurationSource;
static {
mvcPresent = ClassUtils.isPresent(HANDLER_MAPPING_INTROSPECTOR, CorsConfigurer.class.getClassLoader());
}
/**
* Creates a new instance
*
@@ -84,7 +90,6 @@ public class CorsConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHt
CorsConfigurationSource.class);
return new CorsFilter(configurationSource);
}
boolean mvcPresent = ClassUtils.isPresent(HANDLER_MAPPING_INTROSPECTOR, context.getClassLoader());
if (mvcPresent) {
return MvcCorsFilter.getMvcCorsFilter(context);
}
@@ -380,6 +380,9 @@ public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
http.setSharedObject(SecurityContextRepository.class, defaultRepository);
}
}
else {
this.sessionManagementSecurityContextRepository = securityContextRepository;
}
RequestCache requestCache = http.getSharedObject(RequestCache.class);
if (requestCache == null) {
if (stateless) {
@@ -47,10 +47,17 @@ import org.springframework.web.reactive.result.method.annotation.ArgumentResolve
*/
final class ReactiveOAuth2ClientImportSelector implements ImportSelector {
private static final boolean oauth2ClientPresent;
static {
oauth2ClientPresent = ClassUtils.isPresent(
"org.springframework.security.oauth2.client.registration.ClientRegistration",
ReactiveOAuth2ClientImportSelector.class.getClassLoader());
}
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
if (!ClassUtils.isPresent("org.springframework.security.oauth2.client.registration.ClientRegistration",
getClass().getClassLoader())) {
if (!oauth2ClientPresent) {
return new String[0];
}
return new String[] { "org.springframework.security.config.annotation.web.reactive."
@@ -53,13 +53,17 @@ class WebFluxSecurityConfiguration {
public static final String REACTIVE_CLIENT_REGISTRATION_REPOSITORY_CLASSNAME = "org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository";
private static final boolean isOAuth2Present = ClassUtils.isPresent(
REACTIVE_CLIENT_REGISTRATION_REPOSITORY_CLASSNAME, WebFluxSecurityConfiguration.class.getClassLoader());
private static final boolean isOAuth2Present;
private List<SecurityWebFilterChain> securityWebFilterChains;
private ObservationRegistry observationRegistry = ObservationRegistry.NOOP;
static {
isOAuth2Present = ClassUtils.isPresent(REACTIVE_CLIENT_REGISTRATION_REPOSITORY_CLASSNAME,
WebFluxSecurityConfiguration.class.getClassLoader());
}
@Autowired
ApplicationContext context;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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,7 +41,7 @@ import org.springframework.security.messaging.access.intercept.AuthorizationChan
import org.springframework.security.messaging.access.intercept.MessageMatcherDelegatingAuthorizationManager;
import org.springframework.security.messaging.context.AuthenticationPrincipalArgumentResolver;
import org.springframework.security.messaging.context.SecurityContextChannelInterceptor;
import org.springframework.security.messaging.web.csrf.CsrfChannelInterceptor;
import org.springframework.security.messaging.web.csrf.XorCsrfChannelInterceptor;
import org.springframework.security.messaging.web.socket.server.CsrfTokenHandshakeInterceptor;
import org.springframework.util.Assert;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
@@ -59,6 +59,8 @@ final class WebSocketMessageBrokerSecurityConfiguration
private static final String SIMPLE_URL_HANDLER_MAPPING_BEAN_NAME = "stompWebSocketHandlerMapping";
private static final String CSRF_CHANNEL_INTERCEPTOR_BEAN_NAME = "csrfChannelInterceptor";
private MessageMatcherDelegatingAuthorizationManager b;
private static final AuthorizationManager<Message<?>> ANY_MESSAGE_AUTHENTICATED = MessageMatcherDelegatingAuthorizationManager
@@ -69,7 +71,7 @@ final class WebSocketMessageBrokerSecurityConfiguration
private final SecurityContextChannelInterceptor securityContextChannelInterceptor = new SecurityContextChannelInterceptor();
private final ChannelInterceptor csrfChannelInterceptor = new CsrfChannelInterceptor();
private ChannelInterceptor csrfChannelInterceptor = new XorCsrfChannelInterceptor();
private AuthorizationManager<Message<?>> authorizationManager = ANY_MESSAGE_AUTHENTICATED;
@@ -90,6 +92,12 @@ final class WebSocketMessageBrokerSecurityConfiguration
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
ChannelInterceptor csrfChannelInterceptor = getBeanOrNull(CSRF_CHANNEL_INTERCEPTOR_BEAN_NAME,
ChannelInterceptor.class);
if (csrfChannelInterceptor != null) {
this.csrfChannelInterceptor = csrfChannelInterceptor;
}
AuthorizationManager<Message<?>> manager = this.authorizationManager;
if (!this.observationRegistry.isNoop()) {
manager = new ObservationAuthorizationManager<>(this.observationRegistry, manager);
@@ -0,0 +1,38 @@
/*
* Copyright 2002-2023 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.aot.hint;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
/**
* {@link RuntimeHintsRegistrar} for
* {@link org.springframework.security.config.web.server.ServerHttpSecurity}
*
* @author Marcus Da Coregio
* @since 6.0.2
*/
class OAuth2LoginRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
hints.reflection().registerTypeIfPresent(classLoader, "org.springframework.security.oauth2.jwt.JwtDecoder",
MemberCategory.INVOKE_PUBLIC_METHODS);
}
}
@@ -80,6 +80,8 @@ final class AuthenticationConfigBuilder {
private final Log logger = LogFactory.getLog(getClass());
private static final boolean webMvcPresent;
private static final String ATT_REALM = "realm";
private static final String DEF_REALM = "Realm";
@@ -213,6 +215,11 @@ final class AuthenticationConfigBuilder {
private final List<BeanDefinition> csrfIgnoreRequestMatchers = new ManagedList<>();
static {
ClassLoader classLoader = AuthenticationConfigBuilder.class.getClassLoader();
webMvcPresent = ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", classLoader);
}
AuthenticationConfigBuilder(Element element, boolean forceAutoConfig, ParserContext pc,
SessionCreationPolicy sessionPolicy, BeanReference requestCache, BeanReference authenticationManager,
BeanMetadataElement authenticationFilterSecurityContextHolderStrategyRef,
@@ -409,9 +416,7 @@ final class AuthenticationConfigBuilder {
if (!this.oauth2LoginEnabled && !this.oauth2ClientEnabled) {
return;
}
boolean webmvcPresent = ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet",
getClass().getClassLoader());
if (webmvcPresent) {
if (webMvcPresent) {
this.pc.getReaderContext()
.registerWithGeneratedName(new RootBeanDefinition(OAuth2ClientWebMvcSecurityPostProcessor.class));
}
@@ -36,12 +36,19 @@ import org.springframework.web.filter.CorsFilter;
*/
public class CorsBeanDefinitionParser {
private static final String HANDLER_MAPPING_INTROSPECTOR = "org.springframework.web.servlet.handler.HandlerMappingIntrospector";
private static final String ATT_SOURCE = "configuration-source-ref";
private static final String ATT_REF = "ref";
private static final String HANDLER_MAPPING_INTROSPECTOR = "org.springframework.web.servlet.handler.HandlerMappingIntrospector";
private static final boolean mvcPresent;
static {
mvcPresent = ClassUtils.isPresent(HANDLER_MAPPING_INTROSPECTOR,
CorsBeanDefinitionParser.class.getClassLoader());
}
public BeanMetadataElement parse(Element element, ParserContext parserContext) {
if (element == null) {
return null;
@@ -64,7 +71,6 @@ public class CorsBeanDefinitionParser {
if (StringUtils.hasText(configurationSourceRef)) {
return new RuntimeBeanReference(configurationSourceRef);
}
boolean mvcPresent = ClassUtils.isPresent(HANDLER_MAPPING_INTROSPECTOR, getClass().getClassLoader());
if (!mvcPresent) {
return null;
}
@@ -66,14 +66,19 @@ public class CsrfBeanDefinitionParser implements BeanDefinitionParser {
private static final String REQUEST_DATA_VALUE_PROCESSOR = "requestDataValueProcessor";
private static final String DISPATCHER_SERVLET_CLASS_NAME = "org.springframework.web.servlet.DispatcherServlet";
private static final String ATT_MATCHER = "request-matcher-ref";
private static final String ATT_REPOSITORY = "token-repository-ref";
private static final String ATT_REQUEST_HANDLER = "request-handler-ref";
private static final boolean webMvcPresent;
static {
ClassLoader classLoader = CsrfBeanDefinitionParser.class.getClassLoader();
webMvcPresent = ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", classLoader);
}
private String csrfRepositoryRef;
private BeanDefinition csrfFilter;
@@ -90,8 +95,7 @@ public class CsrfBeanDefinitionParser implements BeanDefinitionParser {
if (disabled) {
return null;
}
boolean webmvcPresent = ClassUtils.isPresent(DISPATCHER_SERVLET_CLASS_NAME, getClass().getClassLoader());
if (webmvcPresent) {
if (webMvcPresent) {
if (!pc.getRegistry().containsBeanDefinition(REQUEST_DATA_VALUE_PROCESSOR)) {
RootBeanDefinition beanDefinition = new RootBeanDefinition(CsrfRequestDataValueProcessor.class);
BeanComponentDefinition componentDefinition = new BeanComponentDefinition(beanDefinition,
@@ -42,6 +42,8 @@ public class EmbeddedLdapServerContextSourceFactoryBean
private static final String UNBOUNDID_CLASSNAME = "com.unboundid.ldap.listener.InMemoryDirectoryServer";
private static final boolean unboundIdPresent;
private static final int DEFAULT_PORT = 33389;
private static final int RANDOM_PORT = 0;
@@ -60,6 +62,11 @@ public class EmbeddedLdapServerContextSourceFactoryBean
private EmbeddedLdapServerContainer container;
static {
ClassLoader classLoader = EmbeddedLdapServerContextSourceFactoryBean.class.getClassLoader();
unboundIdPresent = ClassUtils.isPresent(UNBOUNDID_CLASSNAME, classLoader);
}
/**
* Create an EmbeddedLdapServerContextSourceFactoryBean that will use an embedded LDAP
* server to perform LDAP authentication. This requires a dependency on
@@ -120,7 +127,7 @@ public class EmbeddedLdapServerContextSourceFactoryBean
@Override
public DefaultSpringSecurityContextSource getObject() throws Exception {
if (!ClassUtils.isPresent(UNBOUNDID_CLASSNAME, getClass().getClassLoader())) {
if (!unboundIdPresent) {
throw new IllegalStateException("Embedded LDAP server is not provided");
}
this.container = getContainer();
@@ -156,7 +163,7 @@ public class EmbeddedLdapServerContextSourceFactoryBean
}
private EmbeddedLdapServerContainer getContainer() {
if (!ClassUtils.isPresent(UNBOUNDID_CLASSNAME, getClass().getClassLoader())) {
if (!unboundIdPresent) {
throw new IllegalStateException("Embedded LDAP server is not provided");
}
UnboundIdContainer unboundIdContainer = new UnboundIdContainer(this.root, this.ldif);
@@ -86,6 +86,16 @@ public class LdapServerBeanDefinitionParser implements BeanDefinitionParser {
private static final String UNBOUNDID_CONTAINER_CLASSNAME = "org.springframework.security.ldap.server.UnboundIdContainer";
private static final boolean unboundIdPresent;
private static final boolean apacheDsPresent;
static {
ClassLoader classLoader = LdapServerBeanDefinitionParser.class.getClassLoader();
unboundIdPresent = ClassUtils.isPresent(UNBOUNID_CLASSNAME, classLoader);
apacheDsPresent = ClassUtils.isPresent(APACHEDS_CLASSNAME, classLoader);
}
@Override
public BeanDefinition parse(Element elt, ParserContext parserContext) {
String url = elt.getAttribute(ATT_URL);
@@ -184,11 +194,11 @@ public class LdapServerBeanDefinitionParser implements BeanDefinitionParser {
}
private boolean isApacheDsEnabled(String mode) {
return "apacheds".equals(mode) || ClassUtils.isPresent(APACHEDS_CLASSNAME, getClass().getClassLoader());
return "apacheds".equals(mode) || apacheDsPresent;
}
private boolean isUnboundidEnabled(String mode) {
return "unboundid".equals(mode) || ClassUtils.isPresent(UNBOUNID_CLASSNAME, getClass().getClassLoader());
return "unboundid".equals(mode) || unboundIdPresent;
}
private String getPort(Element element) {
@@ -222,11 +232,11 @@ public class LdapServerBeanDefinitionParser implements BeanDefinitionParser {
}
private int getPort() {
if (ClassUtils.isPresent(APACHEDS_CLASSNAME, getClass().getClassLoader())) {
if (apacheDsPresent) {
ApacheDSContainer apacheDSContainer = this.applicationContext.getBean(ApacheDSContainer.class);
return apacheDSContainer.getLocalPort();
}
if (ClassUtils.isPresent(UNBOUNID_CLASSNAME, getClass().getClassLoader())) {
if (unboundIdPresent) {
UnboundIdContainer unboundIdContainer = this.applicationContext.getBean(UnboundIdContainer.class);
return unboundIdContainer.getPort();
}
@@ -1,2 +1,5 @@
org.springframework.beans.factory.aot.BeanRegistrationAotProcessor=\
org.springframework.security.config.annotation.authentication.configuration.AuthenticationManagerBeanRegistrationAotProcessor
org.springframework.aot.hint.RuntimeHintsRegistrar=\
org.springframework.security.config.aot.hint.OAuth2LoginRuntimeHints
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2023 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.
@@ -78,8 +78,7 @@ public class EnableReactiveMethodSecurityTests {
.withMessage("The returnType class java.lang.String on public abstract java.lang.String "
+ "org.springframework.security.config.annotation.method.configuration.ReactiveMessageService"
+ ".notPublisherPreAuthorizeFindById(long) must return an instance of org.reactivestreams"
+ ".Publisher (i.e. Mono / Flux) or the function must be a Kotlin coroutine "
+ "function in order to support Reactor Context");
+ ".Publisher (for example, a Mono or Flux) in order to support Reactor Context");
}
@Test
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,6 +29,7 @@ import org.assertj.core.api.ListAssert;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@@ -44,12 +45,29 @@ import org.springframework.security.web.context.request.async.WebAsyncManagerInt
import org.springframework.security.web.header.HeaderWriterFilter;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@ExtendWith(SpringTestContextExtension.class)
public class HttpSecurityDeferAddFilterTest {
public final SpringTestContext spring = new SpringTestContext(this);
@Test
public void addFilterAfterFilterNotRegisteredYetThenThrowIllegalArgument() {
assertThatExceptionOfType(UnsatisfiedDependencyException.class)
.isThrownBy(
() -> this.spring.register(MyOtherFilterAfterMyFilterNotRegisteredYetConfig.class).autowire())
.havingRootCause().isInstanceOf(IllegalArgumentException.class);
}
@Test
public void addFilterBeforeFilterNotRegisteredYetThenThrowIllegalArgument() {
assertThatExceptionOfType(UnsatisfiedDependencyException.class)
.isThrownBy(
() -> this.spring.register(MyOtherFilterBeforeMyFilterNotRegisteredYetConfig.class).autowire())
.havingRootCause().isInstanceOf(IllegalArgumentException.class);
}
@Test
public void addFilterAfterWhenSameFilterDifferentPlacesThenOrderCorrect() {
this.spring.register(MyFilterMultipleAfterConfig.class).autowire();
@@ -216,6 +234,34 @@ public class HttpSecurityDeferAddFilterTest {
}
@Configuration
@EnableWebSecurity
static class MyOtherFilterAfterMyFilterNotRegisteredYetConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.addFilterAfter(new MyOtherFilter(), MyFilter.class);
// @formatter:on
return http.build();
}
}
@EnableWebSecurity
static class MyOtherFilterBeforeMyFilterNotRegisteredYetConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.addFilterBefore(new MyOtherFilter(), MyFilter.class);
// @formatter:on
return http.build();
}
}
@EnableWebSecurity
static class MyOtherFilterRelativeToMyFilterBeforeConfig {
@@ -18,6 +18,8 @@ package org.springframework.security.config.annotation.web.builders;
import java.io.IOException;
import io.micrometer.observation.ObservationRegistry;
import io.micrometer.observation.ObservationTextPublisher;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.AfterEach;
@@ -104,6 +106,15 @@ public class WebSecurityTests {
@Test
public void requestRejectedHandlerInvoked() throws ServletException, IOException {
loadConfig(DefaultConfig.class);
this.request.setServletPath("/spring");
this.request.setRequestURI("/spring/\u0019path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_BAD_REQUEST);
}
@Test
public void customRequestRejectedHandlerInvoked() throws ServletException, IOException {
loadConfig(RequestRejectedHandlerConfig.class);
this.request.setServletPath("/spring");
this.request.setRequestURI("/spring/\u0019path");
@@ -111,6 +122,16 @@ public class WebSecurityTests {
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_BAD_REQUEST);
}
// gh-12548
@Test
public void requestRejectedHandlerInvokedWhenOperationalObservationRegistry() throws ServletException, IOException {
loadConfig(ObservationRegistryConfig.class);
this.request.setServletPath("/spring");
this.request.setRequestURI("/spring/\u0019path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_BAD_REQUEST);
}
@Test
public void ignoringMvcMatcherServletPath() throws Exception {
loadConfig(MvcMatcherServletPathConfig.class, LegacyMvcMatchingConfig.class);
@@ -143,6 +164,11 @@ public class WebSecurityTests {
this.context.getAutowireCapableBeanFactory().autowireBean(this);
}
@EnableWebSecurity
static class DefaultConfig {
}
@EnableWebSecurity
@Configuration
@EnableWebMvc
@@ -243,4 +269,17 @@ public class WebSecurityTests {
}
@Configuration
@EnableWebSecurity
static class ObservationRegistryConfig {
@Bean
ObservationRegistry observationRegistry() {
ObservationRegistry observationRegistry = ObservationRegistry.create();
observationRegistry.observationConfig().observationHandler(new ObservationTextPublisher());
return observationRegistry;
}
}
}
@@ -125,6 +125,18 @@ public class SessionManagementConfigurerTests {
this.mvc.perform(get("/"));
}
@Test
public void sessionManagementWhenSecurityContextRepositoryIsConfiguredThenUseIt() throws Exception {
SessionManagementSecurityContextRepositoryConfig.SECURITY_CONTEXT_REPO = mock(SecurityContextRepository.class);
given(SessionManagementSecurityContextRepositoryConfig.SECURITY_CONTEXT_REPO
.loadDeferredContext(any(HttpServletRequest.class)))
.willReturn(new TestDeferredSecurityContext(mock(SecurityContext.class), false));
this.spring.register(SessionManagementSecurityContextRepositoryConfig.class).autowire();
this.mvc.perform(get("/"));
verify(SessionManagementSecurityContextRepositoryConfig.SECURITY_CONTEXT_REPO)
.containsContext(any(HttpServletRequest.class));
}
@Test
public void sessionManagementWhenInvokedTwiceThenUsesOriginalSessionCreationPolicy() throws Exception {
this.spring.register(InvokeTwiceDoesNotOverride.class).autowire();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2023 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.
@@ -60,6 +60,7 @@ import org.springframework.security.messaging.context.SecurityContextChannelInte
import org.springframework.security.messaging.web.csrf.CsrfChannelInterceptor;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.DefaultCsrfToken;
import org.springframework.security.web.csrf.DeferredCsrfToken;
import org.springframework.security.web.csrf.MissingCsrfTokenException;
import org.springframework.stereotype.Controller;
import org.springframework.test.util.ReflectionTestUtils;
@@ -78,6 +79,7 @@ import org.springframework.web.socket.sockjs.transport.session.WebSocketServerSo
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.springframework.security.web.csrf.CsrfTokenAssert.assertThatCsrfToken;
public class AbstractSecurityWebSocketMessageBrokerConfigurerTests {
@@ -283,7 +285,7 @@ public class AbstractSecurityWebSocketMessageBrokerConfigurerTests {
private void assertHandshake(HttpServletRequest request) {
TestHandshakeHandler handshakeHandler = this.context.getBean(TestHandshakeHandler.class);
assertThat(handshakeHandler.attributes.get(CsrfToken.class.getName())).isSameAs(this.token);
assertThatCsrfToken(handshakeHandler.attributes.get(CsrfToken.class.getName())).isEqualTo(this.token);
assertThat(handshakeHandler.attributes.get(this.sessionAttr))
.isEqualTo(request.getSession().getAttribute(this.sessionAttr));
}
@@ -305,7 +307,7 @@ public class AbstractSecurityWebSocketMessageBrokerConfigurerTests {
request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "/289/tpyx6mde/websocket");
request.setRequestURI(mapping + "/289/tpyx6mde/websocket");
request.getSession().setAttribute(this.sessionAttr, "sessionValue");
request.setAttribute(CsrfToken.class.getName(), this.token);
request.setAttribute(DeferredCsrfToken.class.getName(), new TestDeferredCsrfToken(this.token));
return request;
}
@@ -0,0 +1,43 @@
/*
* Copyright 2002-2023 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.socket;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.DeferredCsrfToken;
/**
* @author Steve Riesenberg
*/
final class TestDeferredCsrfToken implements DeferredCsrfToken {
private final CsrfToken csrfToken;
TestDeferredCsrfToken(CsrfToken csrfToken) {
this.csrfToken = csrfToken;
}
@Override
public CsrfToken get() {
return this.csrfToken;
}
@Override
public boolean isGenerated() {
return false;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
@@ -66,9 +66,10 @@ import org.springframework.security.messaging.access.intercept.AuthorizationChan
import org.springframework.security.messaging.access.intercept.MessageAuthorizationContext;
import org.springframework.security.messaging.access.intercept.MessageMatcherDelegatingAuthorizationManager;
import org.springframework.security.messaging.context.SecurityContextChannelInterceptor;
import org.springframework.security.messaging.web.csrf.CsrfChannelInterceptor;
import org.springframework.security.messaging.web.csrf.XorCsrfChannelInterceptor;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.DefaultCsrfToken;
import org.springframework.security.web.csrf.DeferredCsrfToken;
import org.springframework.security.web.csrf.MissingCsrfTokenException;
import org.springframework.stereotype.Controller;
import org.springframework.test.util.ReflectionTestUtils;
@@ -91,9 +92,12 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.fail;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.verify;
import static org.springframework.security.web.csrf.CsrfTokenAssert.assertThatCsrfToken;
public class WebSocketMessageBrokerSecurityConfigurationTests {
private static final String XOR_CSRF_TOKEN_VALUE = "wpe7zB62-NCpcA==";
AnnotationConfigWebApplicationContext context;
Authentication messageUser;
@@ -196,7 +200,7 @@ public class WebSocketMessageBrokerSecurityConfigurationTests {
MessageChannel messageChannel = clientInboundChannel();
Stream<Class<? extends ChannelInterceptor>> interceptors = ((AbstractMessageChannel) messageChannel)
.getInterceptors().stream().map(ChannelInterceptor::getClass);
assertThat(interceptors).contains(CsrfChannelInterceptor.class);
assertThat(interceptors).contains(XorCsrfChannelInterceptor.class);
}
@Test
@@ -236,7 +240,7 @@ public class WebSocketMessageBrokerSecurityConfigurationTests {
public void messagesContextWebSocketUseSecurityContextHolderStrategy() {
loadConfig(WebSocketSecurityConfig.class, SecurityContextChangedListenerConfig.class);
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT);
headers.setNativeHeader(this.token.getHeaderName(), this.token.getToken());
headers.setNativeHeader(this.token.getHeaderName(), XOR_CSRF_TOKEN_VALUE);
Message<?> message = message(headers, "/authenticated");
headers.getSessionAttributes().put(CsrfToken.class.getName(), this.token);
MessageChannel messageChannel = clientInboundChannel();
@@ -366,7 +370,7 @@ public class WebSocketMessageBrokerSecurityConfigurationTests {
private void assertHandshake(HttpServletRequest request) {
TestHandshakeHandler handshakeHandler = this.context.getBean(TestHandshakeHandler.class);
assertThat(handshakeHandler.attributes.get(CsrfToken.class.getName())).isSameAs(this.token);
assertThatCsrfToken(handshakeHandler.attributes.get(CsrfToken.class.getName())).isEqualTo(this.token);
assertThat(handshakeHandler.attributes.get(this.sessionAttr))
.isEqualTo(request.getSession().getAttribute(this.sessionAttr));
}
@@ -388,7 +392,7 @@ public class WebSocketMessageBrokerSecurityConfigurationTests {
request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "/289/tpyx6mde/websocket");
request.setRequestURI(mapping + "/289/tpyx6mde/websocket");
request.getSession().setAttribute(this.sessionAttr, "sessionValue");
request.setAttribute(CsrfToken.class.getName(), this.token);
request.setAttribute(DeferredCsrfToken.class.getName(), new TestDeferredCsrfToken(this.token));
return request;
}
@@ -0,0 +1,53 @@
/*
* Copyright 2002-2023 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.aot.hint;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.util.ClassUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link OAuth2LoginRuntimeHints}
*
* @author Marcus Da Coregio
*/
class OAuth2LoginRuntimeHintsTests {
private final RuntimeHints hints = new RuntimeHints();
@BeforeEach
void setup() {
SpringFactoriesLoader.forResourceLocation("META-INF/spring/aot.factories").load(RuntimeHintsRegistrar.class)
.forEach((registrar) -> registrar.registerHints(this.hints, ClassUtils.getDefaultClassLoader()));
}
@Test
void jwtDecoderHasHints() {
assertThat(RuntimeHintsPredicates.reflection().onType(JwtDecoder.class)
.withMemberCategories(MemberCategory.INVOKE_PUBLIC_METHODS)).accepts(this.hints);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
@@ -61,6 +61,7 @@ import org.springframework.security.test.context.annotation.SecurityTestExecutio
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.DefaultCsrfToken;
import org.springframework.security.web.csrf.DeferredCsrfToken;
import org.springframework.security.web.csrf.InvalidCsrfTokenException;
import org.springframework.stereotype.Controller;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@@ -77,6 +78,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.verify;
import static org.springframework.security.web.csrf.CsrfTokenAssert.assertThatCsrfToken;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
/**
@@ -381,12 +383,14 @@ public class WebSocketMessageBrokerConfigTests {
MockMvc mvc = MockMvcBuilders.webAppContextSetup(context).build();
String csrfAttributeName = CsrfToken.class.getName();
String customAttributeName = this.getClass().getName();
MvcResult result = mvc.perform(get("/app").requestAttr(csrfAttributeName, this.token)
.sessionAttr(customAttributeName, "attributeValue")).andReturn();
MvcResult result = mvc.perform(
get("/app").requestAttr(DeferredCsrfToken.class.getName(), new TestDeferredCsrfToken(this.token))
.sessionAttr(customAttributeName, "attributeValue"))
.andReturn();
CsrfToken handshakeToken = (CsrfToken) this.testHandshakeHandler.attributes.get(csrfAttributeName);
String handshakeValue = (String) this.testHandshakeHandler.attributes.get(customAttributeName);
String sessionValue = (String) result.getRequest().getSession().getAttribute(customAttributeName);
assertThat(handshakeToken).isEqualTo(this.token).withFailMessage("CsrfToken is populated");
assertThatCsrfToken(handshakeToken).isEqualTo(this.token).withFailMessage("CsrfToken is populated");
assertThat(handshakeValue).isEqualTo(sessionValue)
.withFailMessage("Explicitly listed session variables are not overridden");
}
@@ -398,12 +402,13 @@ public class WebSocketMessageBrokerConfigTests {
MockMvc mvc = MockMvcBuilders.webAppContextSetup(context).build();
String csrfAttributeName = CsrfToken.class.getName();
String customAttributeName = this.getClass().getName();
MvcResult result = mvc.perform(get("/app/289/tpyx6mde/websocket").requestAttr(csrfAttributeName, this.token)
MvcResult result = mvc.perform(get("/app/289/tpyx6mde/websocket")
.requestAttr(DeferredCsrfToken.class.getName(), new TestDeferredCsrfToken(this.token))
.sessionAttr(customAttributeName, "attributeValue")).andReturn();
CsrfToken handshakeToken = (CsrfToken) this.testHandshakeHandler.attributes.get(csrfAttributeName);
String handshakeValue = (String) this.testHandshakeHandler.attributes.get(customAttributeName);
String sessionValue = (String) result.getRequest().getSession().getAttribute(customAttributeName);
assertThat(handshakeToken).isEqualTo(this.token).withFailMessage("CsrfToken is populated");
assertThatCsrfToken(handshakeToken).isEqualTo(this.token).withFailMessage("CsrfToken is populated");
assertThat(handshakeValue).isEqualTo(sessionValue)
.withFailMessage("Explicitly listed session variables are not overridden");
}
@@ -526,6 +531,26 @@ public class WebSocketMessageBrokerConfigTests {
return SecurityContextHolder.getContextHolderStrategy();
}
private static final class TestDeferredCsrfToken implements DeferredCsrfToken {
private final CsrfToken csrfToken;
TestDeferredCsrfToken(CsrfToken csrfToken) {
this.csrfToken = csrfToken;
}
@Override
public CsrfToken get() {
return this.csrfToken;
}
@Override
public boolean isGenerated() {
return false;
}
}
@Controller
static class MessageController {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2023 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.
@@ -38,7 +38,8 @@ import org.springframework.test.context.junit.jupiter.SpringExtension
@ExtendWith(SpringExtension::class)
@ContextConfiguration
class KotlinEnableReactiveMethodSecurityTests {
// no authorization manager due to https://github.com/spring-projects/spring-security/issues/12080
class KotlinEnableReactiveMethodSecurityNoAuthorizationManagerTests {
private lateinit var delegate: KotlinReactiveMessageService
@@ -211,7 +212,7 @@ class KotlinEnableReactiveMethodSecurityTests {
}
@Configuration
@EnableReactiveMethodSecurity
@EnableReactiveMethodSecurity(useAuthorizationManager = false)
open class Config {
var delegate = mockk<KotlinReactiveMessageService>()
@@ -18,6 +18,7 @@ package org.springframework.security.authentication;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor;
import reactor.core.publisher.Mono;
import org.springframework.security.core.Authentication;
@@ -48,13 +49,16 @@ public class ObservationReactiveAuthenticationManager implements ReactiveAuthent
AuthenticationObservationContext context = new AuthenticationObservationContext();
context.setAuthenticationRequest(authentication);
context.setAuthenticationManagerClass(this.delegate.getClass());
Observation observation = Observation.createNotStarted(this.convention, () -> context, this.registry).start();
return this.delegate.authenticate(authentication).doOnSuccess((result) -> {
context.setAuthenticationResult(result);
observation.stop();
}).doOnCancel(observation::stop).doOnError((t) -> {
observation.error(t);
observation.stop();
return Mono.deferContextual((contextView) -> {
Observation observation = Observation.createNotStarted(this.convention, () -> context, this.registry)
.parentObservation(contextView.getOrDefault(ObservationThreadLocalAccessor.KEY, null)).start();
return this.delegate.authenticate(authentication).doOnSuccess((result) -> {
context.setAuthenticationResult(result);
observation.stop();
}).doOnCancel(observation::stop).doOnError((t) -> {
observation.error(t);
observation.stop();
});
});
}
@@ -18,6 +18,7 @@ package org.springframework.security.authorization;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor;
import reactor.core.publisher.Mono;
import org.springframework.security.access.AccessDeniedException;
@@ -50,16 +51,19 @@ public final class ObservationReactiveAuthorizationManager<T> implements Reactiv
context.setAuthentication(auth);
return context.getAuthentication();
});
Observation observation = Observation.createNotStarted(this.convention, () -> context, this.registry).start();
return this.delegate.check(wrapped, object).doOnSuccess((decision) -> {
context.setDecision(decision);
if (decision == null || !decision.isGranted()) {
observation.error(new AccessDeniedException("Access Denied"));
}
observation.stop();
}).doOnCancel(observation::stop).doOnError((t) -> {
observation.error(t);
observation.stop();
return Mono.deferContextual((contextView) -> {
Observation observation = Observation.createNotStarted(this.convention, () -> context, this.registry)
.parentObservation(contextView.getOrDefault(ObservationThreadLocalAccessor.KEY, null)).start();
return this.delegate.check(wrapped, object).doOnSuccess((decision) -> {
context.setDecision(decision);
if (decision == null || !decision.isGranted()) {
observation.error(new AccessDeniedException("Access Denied"));
}
observation.stop();
}).doOnCancel(observation::stop).doOnError((t) -> {
observation.error(t);
observation.stop();
});
});
}
@@ -89,6 +89,26 @@ public final class SecurityJackson2Modules {
private static final String saml2Jackson2ModuleClass = "org.springframework.security.saml2.jackson2.Saml2Jackson2Module";
private static final boolean webServletPresent;
private static final boolean oauth2ClientPresent;
private static final boolean javaTimeJacksonPresent;
private static final boolean ldapJacksonPresent;
private static final boolean saml2JacksonPresent;
static {
ClassLoader classLoader = SecurityJackson2Modules.class.getClassLoader();
webServletPresent = ClassUtils.isPresent("jakarta.servlet.http.Cookie", classLoader);
oauth2ClientPresent = ClassUtils.isPresent("org.springframework.security.oauth2.client.OAuth2AuthorizedClient",
classLoader);
javaTimeJacksonPresent = ClassUtils.isPresent(javaTimeJackson2ModuleClass, classLoader);
ldapJacksonPresent = ClassUtils.isPresent(ldapJackson2ModuleClass, classLoader);
saml2JacksonPresent = ClassUtils.isPresent(saml2Jackson2ModuleClass, classLoader);
}
private SecurityJackson2Modules() {
}
@@ -125,19 +145,19 @@ public final class SecurityJackson2Modules {
for (String className : securityJackson2ModuleClasses) {
addToModulesList(loader, modules, className);
}
if (ClassUtils.isPresent("jakarta.servlet.http.Cookie", loader)) {
if (webServletPresent) {
addToModulesList(loader, modules, webServletJackson2ModuleClass);
}
if (ClassUtils.isPresent("org.springframework.security.oauth2.client.OAuth2AuthorizedClient", loader)) {
if (oauth2ClientPresent) {
addToModulesList(loader, modules, oauth2ClientJackson2ModuleClass);
}
if (ClassUtils.isPresent(javaTimeJackson2ModuleClass, loader)) {
if (javaTimeJacksonPresent) {
addToModulesList(loader, modules, javaTimeJackson2ModuleClass);
}
if (ClassUtils.isPresent(ldapJackson2ModuleClass, loader)) {
if (ldapJacksonPresent) {
addToModulesList(loader, modules, ldapJackson2ModuleClass);
}
if (ClassUtils.isPresent(saml2Jackson2ModuleClass, loader)) {
if (saml2JacksonPresent) {
addToModulesList(loader, modules, saml2Jackson2ModuleClass);
}
return modules;
+8 -8
View File
@@ -10,12 +10,12 @@ dependencies {
api platform("org.springframework:spring-framework-bom:$springFrameworkVersion")
api platform("io.projectreactor:reactor-bom:$reactorVersion")
api platform("io.rsocket:rsocket-bom:1.1.3")
api platform("org.junit:junit-bom:5.9.1")
api platform("org.junit:junit-bom:5.9.2")
api platform("org.mockito:mockito-bom:4.8.1")
api platform("org.springframework.data:spring-data-bom:2022.0.0")
api platform("org.springframework.data:spring-data-bom:2022.0.2")
api platform("org.jetbrains.kotlin:kotlin-bom:$kotlinVersion")
api platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.6.4")
api platform("com.fasterxml.jackson:jackson-bom:2.14.1")
api platform("com.fasterxml.jackson:jackson-bom:2.14.2")
constraints {
api "ch.qos.logback:logback-classic:1.4.5"
api "com.google.inject:guice:3.0"
@@ -25,12 +25,12 @@ dependencies {
api "com.squareup.okhttp3:okhttp:3.14.9"
api "com.unboundid:unboundid-ldapsdk:6.0.7"
api "commons-collections:commons-collections:3.2.2"
api "io.mockk:mockk:1.13.3"
api "io.mockk:mockk:1.13.4"
api "io.micrometer:micrometer-observation:$micrometerVersion"
api "jakarta.annotation:jakarta.annotation-api:2.1.1"
api "jakarta.inject:jakarta.inject-api:2.0.1"
api "jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api:3.0.0"
api "jakarta.servlet.jsp:jakarta.servlet.jsp-api:3.1.0"
api "jakarta.servlet.jsp:jakarta.servlet.jsp-api:3.1.1"
api "jakarta.servlet:jakarta.servlet-api:6.0.0"
api "jakarta.xml.bind:jakarta.xml.bind-api:4.0.0"
api "jakarta.persistence:jakarta.persistence-api:3.1.0"
@@ -55,7 +55,7 @@ dependencies {
api "org.eclipse.jetty:jetty-servlet:11.0.13"
api "jakarta.persistence:jakarta.persistence-api:3.1.0"
api "org.hamcrest:hamcrest:2.2"
api "org.hibernate.orm:hibernate-core:6.1.6.Final"
api "org.hibernate.orm:hibernate-core:6.1.7.Final"
api "org.hsqldb:hsqldb:2.7.1"
api "org.jasig.cas.client:cas-client-core:3.6.4"
api "org.opensaml:opensaml-core:$openSamlVersion"
@@ -68,12 +68,12 @@ dependencies {
api "org.skyscreamer:jsonassert:1.5.1"
api "org.slf4j:log4j-over-slf4j:1.7.36"
api "org.slf4j:slf4j-api:2.0.6"
api "org.springframework.ldap:spring-ldap-core:3.0.0"
api "org.springframework.ldap:spring-ldap-core:3.0.1"
api "org.synchronoss.cloud:nio-multipart-parser:1.1.0"
api 'org.apache.maven.resolver:maven-resolver-connector-basic:1.8.2'
api 'org.apache.maven.resolver:maven-resolver-impl:1.8.2'
api 'org.apache.maven.resolver:maven-resolver-transport-http:1.8.2'
api 'org.apache.maven:maven-resolver-provider:3.8.6'
api 'org.apache.maven:maven-resolver-provider:3.8.7'
}
}
+1 -2
View File
@@ -1,2 +1 @@
/package-lock.json
/node_modules/
/*-antora-playbook.yml
+8 -2
View File
@@ -6,7 +6,13 @@ nav:
ext:
collector:
run:
command: gradlew -q -PbuildSrc.skipTests=true "-Dorg.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError" :spring-security-docs:generateAntora
command: gradlew -q -PbuildSrc.skipTests=true "-Dorg.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError" :spring-security-docs:generateAntoraYml
local: true
scan:
dir: ./build/generateAntora
dir: ./build/generated-antora-resources
asciidoc:
attributes:
icondir: icons
gh-old-samples-url: 'https://github.com/spring-projects/spring-security/tree/5.4.x/samples'
gh-samples-url: "https://github.com/spring-projects/spring-security-samples/tree/{gh-tag}"
gh-url: "https://github.com/spring-projects/spring-security/tree/{gh-tag}"
-23
View File
@@ -1,23 +0,0 @@
# The purpose of this Antora playbook is to generate a preview of the docs in the current branch.
antora:
extensions:
- '@antora/collector-extension'
site:
title: Spring Security
url: https://docs.spring.io/spring-security/reference
content:
sources:
- url: ./..
branches: HEAD
start_path: docs
worktrees: true
asciidoc:
attributes:
page-pagination: ''
hide-uri-scheme: '@'
urls:
latest_version_segment: ''
ui:
bundle:
url: https://github.com/spring-io/antora-ui-spring/releases/download/latest/ui-bundle.zip
snapshot: true
Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 89 KiB

+1
View File
@@ -5,6 +5,7 @@
* xref:migration/index.adoc[Migrating to 6.0]
** xref:migration/servlet/index.adoc[Servlet Migrations]
*** xref:migration/servlet/session-management.adoc[Session Management]
*** xref:migration/servlet/exploits.adoc[Exploit Protection]
*** xref:migration/servlet/authentication.adoc[Authentication]
*** xref:migration/servlet/authorization.adoc[Authorization]
** xref:migration/reactive.adoc[Reactive Migrations]
@@ -336,7 +336,7 @@ https://docs.spring.io/spring-security/site/docs/5.0.x/api/org/springframework/s
The `BCryptPasswordEncoder` implementation uses the widely supported https://en.wikipedia.org/wiki/Bcrypt[bcrypt] algorithm to hash the passwords.
To make it more resistant to password cracking, bcrypt is deliberately slow.
Like other adaptive one-way functions, it should be tuned to take about 1 second to verify a password on your system.
The default implementationThe default implementation of `BCryptPasswordEncoder` uses strength 10 as mentioned in the Javadoc of https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/crypto/bcrypt/BCryptPasswordEncoder.html[`BCryptPasswordEncoder`]. You are encouraged to
The default implementation of `BCryptPasswordEncoder` uses strength 10 as mentioned in the Javadoc of https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/crypto/bcrypt/BCryptPasswordEncoder.html[`BCryptPasswordEncoder`]. You are encouraged to
tune and test the strength parameter on your own system so that it takes roughly 1 second to verify a password.
.BCryptPasswordEncoder
+2 -2
View File
@@ -4,12 +4,12 @@
The Spring Security team has prepared the 5.8 release to simplify upgrading to Spring Security 6.0.
Use 5.8 and
ifdef::spring-security-version[]
xref:5.8.0@migration/index.adoc[its preparation steps]
xref:5.8.2@migration/index.adoc[its preparation steps]
endif::[]
ifndef::spring-security-version[]
its preparation steps
endif::[]
to simplify updating to 6.0
to simplify updating to 6.0.
After updating to 5.8, follow this guide to perform any remaining migration or cleanup steps.
@@ -7,7 +7,7 @@ The following steps relate to how to finish migrating authentication support.
{security-api-url}org/springframework/security/web/authentication/AuthenticationFilter.html[`AuthenticationFilter`] propagates {security-api-url}org/springframework/security/authentication/AuthenticationServiceException.html[``AuthenticationServiceException``]s to the {security-api-url}org/springframework/security/authentication/AuthenticationEntryPoint.html[`AuthenticationEntryPoint`].
Because ``AuthenticationServiceException``s represent a server-side error instead of a client-side error, in 6.0, this changes to propagate them to the container.
So, if you opted into this behavior by setting `rethrowAuthenticationServiceException` too `true`, you can now remove it like so:
So, if you opted into this behavior by setting `rethrowAuthenticationServiceException` to `true`, you can now remove it like so:
====
.Java
@@ -0,0 +1,43 @@
= Exploit Protection Migrations
The 5.8 migration guide contains several steps for
ifdef::spring-security-version[]
xref:5.8.2@migration/servlet/exploits.adoc[exploit protection migrations] when updating to 6.0.
endif::[]
ifndef::spring-security-version[]
exploit protection migrations when updating to 6.0.
endif::[]
You are encouraged to follow those steps first.
The following steps relate to how to finish migrating exploit protection support.
== Defer Loading CsrfToken
In Spring Security 5.8, the default `CsrfTokenRequestHandler` for making the `CsrfToken` available to the application is `CsrfTokenRequestAttributeHandler`.
The default for the field `csrfRequestAttributeName` is `null`, which causes the CSRF token to be loaded on every request.
In Spring Security 6, `csrfRequestAttributeName` defaults to `_csrf`.
If you configured the following only for the purpose of updating to 6.0, you can now remove it:
requestHandler.setCsrfRequestAttributeName("_csrf");
== Protect against CSRF BREACH
In Spring Security 5.8, the default `CsrfTokenRequestHandler` for making the `CsrfToken` available to the application is `CsrfTokenRequestAttributeHandler`.
`XorCsrfTokenRequestAttributeHandler` was added to allow opting into CSRF BREACH support.
In Spring Security 6, `XorCsrfTokenRequestAttributeHandler` is the default `CsrfTokenRequestHandler` for making the `CsrfToken` available.
If you configured the `XorCsrfTokenRequestAttributeHandler` only for the purpose of updating to 6.0, you can remove it completely.
[NOTE]
====
If you have set the `csrfRequestAttributeName` to `null` in order to opt out of deferred tokens, or if you have configured a `CsrfTokenRequestHandler` for any other reason, you can leave the configuration in place.
====
== CSRF BREACH with WebSocket support
In Spring Security 5.8, the default `ChannelInterceptor` for making the `CsrfToken` available with xref:servlet/integrations/websocket.adoc[WebSocket Security] is `CsrfChannelInterceptor`.
`XorCsrfChannelInterceptor` was added to allow opting into CSRF BREACH support.
In Spring Security 6, `XorCsrfChannelInterceptor` is the default `ChannelInterceptor` for making the `CsrfToken` available.
If you configured the `XorCsrfChannelInterceptor` only for the purpose of updating to 6.0, you can remove it completely.
@@ -109,14 +109,14 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
[[webflux-csrf-configure-request-handler]]
==== Configure ServerCsrfTokenRequestHandler
Spring Security's https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/server/csrf/CsrfWebFilter.html[`CsrfWebFilter`] exposes a https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/csrf/CsrfToken.html[`Mono<CsrfToken>`] as a `ServerWebExchange` attribute named `org.springframework.security.web.server.csrf.CsrfToken` with the help of a https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/server/csrf/ServerCsrfTokenRequestHandler.html[`ServerCsrfTokenRequestHandler`].
The default implementation is `ServerCsrfTokenRequestAttributeHandler`.
Spring Security's https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/server/csrf/CsrfWebFilter.html[`CsrfWebFilter`] exposes a https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/server/csrf/CsrfToken.html[`Mono<CsrfToken>`] as a `ServerWebExchange` attribute named `org.springframework.security.web.server.csrf.CsrfToken` with the help of a https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/server/csrf/ServerCsrfTokenRequestHandler.html[`ServerCsrfTokenRequestHandler`].
In 5.8, the default implementation was `ServerCsrfTokenRequestAttributeHandler`, which simply makes the `Mono<CsrfToken>` available as an exchange attribute.
An alternate implementation `XorServerCsrfTokenRequestAttributeHandler` is available to provide protection for BREACH (see https://github.com/spring-projects/spring-security/issues/4001[gh-4001]).
As of 6.0, the default implementation is `XorServerCsrfTokenRequestAttributeHandler`, which provides protection for BREACH (see https://github.com/spring-projects/spring-security/issues/4001[gh-4001]).
You can configure `XorServerCsrfTokenRequestAttributeHandler` using the following Java configuration:
If you wish to disable BREACH protection of the `CsrfToken` and revert to the 5.8 default, you can configure `ServerCsrfTokenRequestAttributeHandler` using the following Java configuration:
.Configure BREACH protection
.Disable BREACH protection
====
.Java
[source,java,role="primary"]
@@ -126,7 +126,7 @@ public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http)
http
// ...
.csrf(csrf -> csrf
.csrfTokenRequestHandler(new XorServerCsrfTokenRequestAttributeHandler())
.csrfTokenRequestHandler(new ServerCsrfTokenRequestAttributeHandler())
)
return http.build();
}
@@ -140,7 +140,7 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
return http {
// ...
csrf {
csrfTokenRequestHandler = XorServerCsrfTokenRequestAttributeHandler()
csrfTokenRequestHandler = ServerCsrfTokenRequestAttributeHandler()
}
}
}
@@ -271,6 +271,60 @@ The code below demonstrates how to customize the `RequestCache` implementation t
include::partial$servlet/architecture/request-cache-continue.adoc[]
[[requestcache-prevent-saved-request]]
==== Prevent the Request From Being Saved
There are a number of reasons you may want to not store the user's unauthenticated request in the session.
You may want to offload that storage onto the user's browser or store it in a database.
Or you may want to shut off this feature since you always want to redirect the user to the home page instead of the page they tried to visit before login.
To do that, you can use {security-api-url}org/springframework/security/web/savedrequest/NullRequestCache.html[the `NullRequestCache` implementation].
.Prevent the Request From Being Saved
====
.Java
[source,java,role="primary"]
----
@Bean
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
RequestCache nullRequestCache = new NullRequestCache();
http
// ...
.requestCache((cache) -> cache
.requestCache(nullRequestCache)
);
return http.build();
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Bean
open fun springSecurity(http: HttpSecurity): SecurityFilterChain {
val nullRequestCache = NullRequestCache()
http {
requestCache {
requestCache = nullRequestCache
}
}
return http.build()
}
----
.XML
[source,xml,role="secondary"]
----
<http auto-config="true">
<!-- ... -->
<request-cache ref="nullRequestCache"/>
</http>
<b:bean id="nullRequestCache" class="org.springframework.security.web.savedrequest.NullRequestCache"/>
----
====
[[requestcacheawarefilter]]
=== RequestCacheAwareFilter
@@ -53,7 +53,7 @@ image:{icondir}/number_4.png[] If authentication is successful, then __Success__
. `SessionAuthenticationStrategy` is notified of a new login.
See the {security-api-url}springframework/security/web/authentication/session/SessionAuthenticationStrategy.html[`SessionAuthenticationStrategy`] interface in the Javadoc.
. The ref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[Authentication] is set on the xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontextholder[SecurityContextHolder].
. The xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[Authentication] is set on the xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontextholder[SecurityContextHolder].
See the {security-api-url}springframework/security/web/context/SecurityContextPersistenceFilter.html[`SecurityContextPersistenceFilter`] class in the Javadoc.
. `RememberMeServices.loginSuccess` is invoked.
If remember me is not configured, this is a no-op.
File diff suppressed because it is too large Load Diff
@@ -396,10 +396,6 @@ fun doSomething(contact: Contact?)
----
====
Here we are accessing another built-in expression, `authentication`, which is the `Authentication` stored in the security context.
You can also access its "principal" property directly, using the expression `principal`.
The value will often be a `UserDetails` instance, so you might use an expression like `principal.username` or `principal.enabled`.
.[[el-pre-post-annotations-post]]
Here, we access another built-in expression, `authentication`, which is the `Authentication` stored in the security context.
You can also access its `principal` property directly, by using the `principal` expression.
@@ -562,7 +562,7 @@ class MethodSecurityConfig {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public Advisor customAuthorize(AuthorizationManager<MethodInvocationResult> rules) {
AnnotationMethodMatcher pattern = new AnnotationMethodMatcher(MySecurityAnnotation.class);
AnnotationMatchingPointcut pattern = new AnnotationMatchingPointcut(MySecurityAnnotation.class);
AuthorizationManagerAfterMethodInterceptor interceptor = new AuthorizationManagerAfterMethodInterceptor(pattern, rules);
interceptor.setOrder(AuthorizationInterceptorsOrder.POST_AUTHORIZE_ADVISOR_ORDER.getOrder() + 1);
return interceptor;
@@ -579,7 +579,7 @@ class MethodSecurityConfig {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
fun customAuthorize(rules : AuthorizationManager<MethodInvocationResult>) : Advisor {
val pattern = AnnotationMethodMatcher(MySecurityAnnotation::class.java);
val pattern = AnnotationMatchingPointcut(MySecurityAnnotation::class.java);
val interceptor = AuthorizationManagerAfterMethodInterceptor(pattern, rules);
interceptor.setOrder(AuthorizationInterceptorsOrder.POST_AUTHORIZE_ADVISOR_ORDER.getOrder() + 1);
return interceptor;
@@ -168,13 +168,13 @@ class SecurityConfig {
==== Configure CsrfTokenRequestHandler
Spring Security's https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/csrf/CsrfFilter.html[`CsrfFilter`] exposes a https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/csrf/CsrfToken.html[`CsrfToken`] as an `HttpServletRequest` attribute named `_csrf` with the help of a https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/csrf/CsrfTokenRequestHandler.html[CsrfTokenRequestHandler].
The default implementation is `CsrfTokenRequestAttributeHandler`.
In 5.8, the default implementation was `CsrfTokenRequestAttributeHandler` which simply makes the `_csrf` attribute available as a request attribute.
An alternate implementation `XorCsrfTokenRequestAttributeHandler` is available to provide protection for BREACH (see https://github.com/spring-projects/spring-security/issues/4001[gh-4001]).
As of 6.0, the default implementation is `XorCsrfTokenRequestAttributeHandler`, which provides protection for BREACH (see https://github.com/spring-projects/spring-security/issues/4001[gh-4001]).
You can configure `XorCsrfTokenRequestAttributeHandler` in XML using the following:
If you wish to disable BREACH protection of the `CsrfToken` and revert to the 5.8 default, you can configure `CsrfTokenRequestAttributeHandler` in XML using the following:
.Configure BREACH protection XML Configuration
.Disable BREACH protection XML Configuration
====
[source,xml]
----
@@ -183,13 +183,13 @@ You can configure `XorCsrfTokenRequestAttributeHandler` in XML using the followi
<csrf request-handler-ref="requestHandler"/>
</http>
<b:bean id="requestHandler"
class="org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler"/>
class="org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler"/>
----
====
You can configure `XorCsrfTokenRequestAttributeHandler` in Java Configuration using the following:
You can configure `CsrfTokenRequestAttributeHandler` in Java Configuration using the following:
.Configure BREACH protection
.Disable BREACH protection
====
.Java
[source,java,role="primary"]
@@ -201,7 +201,7 @@ public class WebSecurityConfig {
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf
.csrfTokenRequestHandler(new XorCsrfTokenRequestAttributeHandler())
.csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler())
);
return http.build();
}
@@ -218,7 +218,7 @@ class SecurityConfig {
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
csrf {
csrfTokenRequestHandler = XorCsrfTokenRequestAttributeHandler()
csrfTokenRequestHandler = CsrfTokenRequestAttributeHandler()
}
}
return http.build()
@@ -418,7 +418,7 @@ Now that we have a tenant-aware processor and a tenant-aware validator, we can p
JwtDecoder jwtDecoder(JWTProcessor jwtProcessor, OAuth2TokenValidator<Jwt> jwtValidator) {
NimbusJwtDecoder decoder = new NimbusJwtDecoder(processor);
OAuth2TokenValidator<Jwt> validator = new DelegatingOAuth2TokenValidator<>
(JwtValidators.createDefault(), this.jwtValidator);
(JwtValidators.createDefault(), jwtValidator);
decoder.setJwtValidator(validator);
return decoder;
}
@@ -296,7 +296,7 @@ fun foo(@AuthenticationPrincipal oauth2User: OAuth2User): String? {
----
====
In that case, we can tell Spring Security to include a default `OAuth2User` using the `oauth2User` xref:servlet/test/mockmvc/request-post-processors.adoc[`RequestPostProcessor`], like so:
In that case, we can tell Spring Security to include a default `OAuth2User` using the `oauth2Login` xref:servlet/test/mockmvc/request-post-processors.adoc[`RequestPostProcessor`], like so:
====
.Java
+37 -69
View File
@@ -1,78 +1,25 @@
plugins {
id 'org.antora' version '1.0.0-alpha.3'
id 'org.antora' version '1.0.0'
id 'io.spring.antora.generate-antora-yml' version '0.0.1'
}
apply plugin: 'io.spring.convention.docs'
apply plugin: 'java'
antora {
version = '3.2.0-alpha.2'
playbook = file('local-antora-playbook.yml')
options = ['--clean', '--stacktrace']
environment = [
'ALGOLIA_API_KEY': '82c7ead946afbac3cf98c32446154691',
'ALGOLIA_APP_ID': '244V8V9FGG',
'ALGOLIA_INDEX_NAME': 'security-docs'
]
dependencies = [
'@antora/collector-extension': '1.0.0-alpha.3'
]
playbook = 'cached-antora-playbook.yml'
playbookProvider {
repository = 'spring-projects/spring-security'
branch = 'docs-build'
path = 'lib/antora/templates/per-branch-antora-playbook.yml'
checkLocalBranch = true
}
options = [clean: true, fetch: !project.gradle.startParameter.offline, stacktrace: true]
}
tasks.register('generateAntora') {
group = 'Documentation'
description = 'Generates the antora.yml for dynamic properties'
doLast {
def docsTag = snapshotBuild ? 'current' : project.version
def ghTag = snapshotBuild ? 'main' : project.version
def ghUrl = "https://github.com/spring-projects/spring-security/tree/$ghTag"
def ghOldSamplesUrl = 'https://github.com/spring-projects/spring-security/tree/5.4.x/samples'
def ghSamplesUrl = "https://github.com/spring-projects/spring-security-samples/tree/$samplesBranch"
def securityDocsUrl = "https://docs.spring.io/spring-security/site/docs/$docsTag"
def securityApiUrl = "$securityDocsUrl/api/"
def securityReferenceUrl = "$securityDocsUrl/reference/html5/"
def springFrameworkApiUrl = "https://docs.spring.io/spring-framework/docs/$springFrameworkVersion/javadoc-api/"
def springFrameworkReferenceUrl = "https://docs.spring.io/spring-framework/docs/$springFrameworkVersion/reference/html/"
def ymlVersions = resolvedVersions(project.configurations.testRuntimeClasspath).call()
.collect(v -> " ${v.getKey()}: ${v.getValue()}")
.join('\n')
def outputFile = layout.buildDirectory.file('generateAntora/antora.yml').get().asFile
mkdir(outputFile.getParentFile())
def mainVersion = project.version
def prerelease = null
def versionComponents = mainVersion.split(/(?=-)/)
if (versionComponents.length > 1) {
if (versionComponents[1] == '-SNAPSHOT') {
mainVersion = versionComponents[0]
prerelease = "'-SNAPSHOT'"
} else {
prerelease = 'true'
}
}
def antoraYmlText = file('antora.yml').text
layout.buildDirectory.file('.antora.yml').get().asFile.text = antoraYmlText
antoraYmlText = antoraYmlText.lines().collect { l ->
if (l.startsWith('version: ')) {
return prerelease == null ? "version: '${mainVersion}'" : "version: '${mainVersion}'\nprerelease: ${prerelease}"
}
if (l.startsWith('title: ')) return "title: ${project.parent.description}"
return l == 'ext:' || l.getAt(0) == ' ' ? null : l
}.findAll(Objects::nonNull).join('\n')
outputFile.text = """$antoraYmlText
asciidoc:
attributes:
icondir: icons
gh-old-samples-url: $ghOldSamplesUrl
gh-samples-url: $ghSamplesUrl
gh-url: $ghUrl
security-api-url: $securityApiUrl
security-reference-url: $securityReferenceUrl
spring-framework-api-url: $springFrameworkApiUrl
spring-framework-reference-url: $springFrameworkReferenceUrl
spring-security-version: ${project.version}
${ymlVersions}
"""
}
tasks.named("generateAntoraYml") {
asciidocAttributes = project.provider( { generateAttributes() } )
asciidocAttributes.putAll(providers.provider( { resolvedVersions(project.configurations.testRuntimeClasspath) }))
}
dependencies {
@@ -82,12 +29,33 @@ dependencies {
testImplementation 'org.springframework:spring-core'
}
def generateAttributes() {
def docsTag = snapshotBuild ? 'current' : project.version
def ghTag = snapshotBuild ? 'main' : project.version
def ghUrl = "https://github.com/spring-projects/spring-security/tree/$ghTag"
def ghOldSamplesUrl = 'https://github.com/spring-projects/spring-security/tree/5.4.x/samples'
def ghSamplesUrl = "https://github.com/spring-projects/spring-security-samples/tree/$samplesBranch"
def securityDocsUrl = "https://docs.spring.io/spring-security/site/docs/$docsTag"
def securityApiUrl = "$securityDocsUrl/api/"
def securityReferenceUrl = "$securityDocsUrl/reference/html5/"
def springFrameworkApiUrl = "https://docs.spring.io/spring-framework/docs/$springFrameworkVersion/javadoc-api/"
def springFrameworkReferenceUrl = "https://docs.spring.io/spring-framework/docs/$springFrameworkVersion/reference/html/"
return ['gh-old-samples-url': ghOldSamplesUrl.toString(),
'gh-samples-url': ghSamplesUrl.toString(),
'gh-url': ghUrl.toString(),
'security-api-url': securityApiUrl.toString(),
'security-reference-url': securityReferenceUrl.toString(),
'spring-framework-api-url': springFrameworkApiUrl.toString(),
'spring-framework-reference-url': springFrameworkReferenceUrl.toString(),
'spring-security-version': project.version]
+ resolvedVersions(project.configurations.testRuntimeClasspath)
}
def resolvedVersions(Configuration configuration) {
return {
configuration.resolvedConfiguration
return configuration.resolvedConfiguration
.resolvedArtifacts
.collectEntries { [(it.name + '-version'): it.moduleVersion.id.version] }
}
}
repositories {
+5 -5
View File
@@ -1,11 +1,11 @@
aspectjVersion=1.9.9.1
reactorVersion=2022.0.1
aspectjVersion=1.9.19
reactorVersion=2022.0.3
springJavaformatVersion=0.0.35
springBootVersion=2.4.2
springFrameworkVersion=6.0.3
micrometerVersion=1.10.2
springFrameworkVersion=6.0.5
micrometerVersion=1.10.4
openSamlVersion=4.1.1
version=6.0.1
version=6.0.2
kotlinVersion=1.7.22
samplesBranch=main
org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError
@@ -0,0 +1,86 @@
/*
* Copyright 2002-2023 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.messaging.web.csrf;
import java.security.MessageDigest;
import java.util.Map;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.simp.SimpMessageType;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.security.crypto.codec.Utf8;
import org.springframework.security.messaging.util.matcher.MessageMatcher;
import org.springframework.security.messaging.util.matcher.SimpMessageTypeMatcher;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.InvalidCsrfTokenException;
import org.springframework.security.web.csrf.MissingCsrfTokenException;
/**
* {@link ChannelInterceptor} that validates a CSRF token masked by the
* {@link org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler} in
* the header of any {@link SimpMessageType#CONNECT} message.
*
* @author Steve Riesenberg
* @since 5.8
*/
public final class XorCsrfChannelInterceptor implements ChannelInterceptor {
private final MessageMatcher<Object> matcher = new SimpMessageTypeMatcher(SimpMessageType.CONNECT);
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
if (!this.matcher.matches(message)) {
return message;
}
Map<String, Object> sessionAttributes = SimpMessageHeaderAccessor.getSessionAttributes(message.getHeaders());
CsrfToken expectedToken = (sessionAttributes != null)
? (CsrfToken) sessionAttributes.get(CsrfToken.class.getName()) : null;
if (expectedToken == null) {
throw new MissingCsrfTokenException(null);
}
String actualToken = SimpMessageHeaderAccessor.wrap(message)
.getFirstNativeHeader(expectedToken.getHeaderName());
String actualTokenValue = XorCsrfTokenUtils.getTokenValue(actualToken, expectedToken.getToken());
boolean csrfCheckPassed = equalsConstantTime(expectedToken.getToken(), actualTokenValue);
if (!csrfCheckPassed) {
throw new InvalidCsrfTokenException(expectedToken, actualToken);
}
return message;
}
/**
* 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);
}
}
@@ -0,0 +1,72 @@
/*
* Copyright 2002-2023 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.messaging.web.csrf;
import java.util.Base64;
import org.springframework.security.crypto.codec.Utf8;
/**
* Copied from
* {@link org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler}.
*
* @see <a href=
* "https://github.com/spring-projects/spring-security/issues/12378">gh-12378</a>
*/
final class XorCsrfTokenUtils {
private XorCsrfTokenUtils() {
}
static String getTokenValue(String actualToken, String token) {
byte[] actualBytes;
try {
actualBytes = Base64.getUrlDecoder().decode(actualToken);
}
catch (Exception ex) {
return null;
}
byte[] tokenBytes = Utf8.encode(token);
int tokenSize = tokenBytes.length;
if (actualBytes.length < tokenSize) {
return null;
}
// extract token and random bytes
int randomBytesSize = actualBytes.length - tokenSize;
byte[] xoredCsrf = new byte[tokenSize];
byte[] randomBytes = new byte[randomBytesSize];
System.arraycopy(actualBytes, 0, randomBytes, 0, randomBytesSize);
System.arraycopy(actualBytes, randomBytesSize, xoredCsrf, 0, tokenSize);
byte[] csrfBytes = xorCsrf(randomBytes, xoredCsrf);
return Utf8.decode(csrfBytes);
}
private static byte[] xorCsrf(byte[] randomBytes, byte[] csrfBytes) {
int len = Math.min(randomBytes.length, csrfBytes.length);
byte[] xoredCsrf = new byte[len];
System.arraycopy(csrfBytes, 0, xoredCsrf, 0, csrfBytes.length);
for (int i = 0; i < len; i++) {
xoredCsrf[i] ^= randomBytes[i];
}
return xoredCsrf;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2023 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.
@@ -24,15 +24,18 @@ import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.DefaultCsrfToken;
import org.springframework.security.web.csrf.DeferredCsrfToken;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;
/**
* Copies a CsrfToken from the HttpServletRequest's attributes to the WebSocket
* attributes. This is used as the expected CsrfToken when validating connection requests
* to ensure only the same origin connects.
* Loads a CsrfToken from the HttpServletRequest and HttpServletResponse to populate the
* WebSocket attributes. This is used as the expected CsrfToken when validating connection
* requests to ensure only the same origin connects.
*
* @author Rob Winch
* @author Steve Riesenberg
* @since 4.0
*/
public final class CsrfTokenHandshakeInterceptor implements HandshakeInterceptor {
@@ -41,11 +44,19 @@ public final class CsrfTokenHandshakeInterceptor implements HandshakeInterceptor
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
Map<String, Object> attributes) {
HttpServletRequest httpRequest = ((ServletServerHttpRequest) request).getServletRequest();
CsrfToken token = (CsrfToken) httpRequest.getAttribute(CsrfToken.class.getName());
if (token == null) {
DeferredCsrfToken deferredCsrfToken = (DeferredCsrfToken) httpRequest
.getAttribute(DeferredCsrfToken.class.getName());
if (deferredCsrfToken == null) {
return true;
}
attributes.put(CsrfToken.class.getName(), token);
CsrfToken csrfToken = deferredCsrfToken.get();
// Ensure the values of the CsrfToken are copied into a new token so the old token
// is available for garbage collection.
// This is required because the original token could hold a reference to the
// HttpServletRequest/Response of the handshake request.
CsrfToken resolvedCsrfToken = new DefaultCsrfToken(csrfToken.getHeaderName(), csrfToken.getParameterName(),
csrfToken.getToken());
attributes.put(CsrfToken.class.getName(), resolvedCsrfToken);
return true;
}
@@ -0,0 +1,148 @@
/*
* Copyright 2002-2023 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.messaging.web.csrf;
import java.util.HashMap;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.simp.SimpMessageType;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.DefaultCsrfToken;
import org.springframework.security.web.csrf.InvalidCsrfTokenException;
import org.springframework.security.web.csrf.MissingCsrfTokenException;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link XorCsrfChannelInterceptor}.
*
* @author Steve Riesenberg
*/
public class XorCsrfChannelInterceptorTests {
private static final String XOR_CSRF_TOKEN_VALUE = "wpe7zB62-NCpcA==";
private static final String INVALID_XOR_CSRF_TOKEN_VALUE = "KneoaygbRZtfHQ==";
private CsrfToken token;
private SimpMessageHeaderAccessor messageHeaders;
private MessageChannel channel;
private XorCsrfChannelInterceptor interceptor;
@BeforeEach
public void setup() {
this.token = new DefaultCsrfToken("header", "param", "token");
this.messageHeaders = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT);
this.messageHeaders.setSessionAttributes(new HashMap<>());
this.channel = mock(MessageChannel.class);
this.interceptor = new XorCsrfChannelInterceptor();
}
@Test
public void preSendWhenConnectWithValidTokenThenSuccess() {
this.messageHeaders.setNativeHeader(this.token.getHeaderName(), XOR_CSRF_TOKEN_VALUE);
this.messageHeaders.getSessionAttributes().put(CsrfToken.class.getName(), this.token);
this.interceptor.preSend(message(), this.channel);
}
@Test
public void preSendWhenConnectWithInvalidTokenThenThrowsInvalidCsrfTokenException() {
this.messageHeaders.setNativeHeader(this.token.getHeaderName(), INVALID_XOR_CSRF_TOKEN_VALUE);
this.messageHeaders.getSessionAttributes().put(CsrfToken.class.getName(), this.token);
// @formatter:off
assertThatExceptionOfType(InvalidCsrfTokenException.class)
.isThrownBy(() -> this.interceptor.preSend(message(), mock(MessageChannel.class)));
// @formatter:on
}
@Test
public void preSendWhenConnectWithNoTokenThenThrowsInvalidCsrfTokenException() {
this.messageHeaders.getSessionAttributes().put(CsrfToken.class.getName(), this.token);
// @formatter:off
assertThatExceptionOfType(InvalidCsrfTokenException.class)
.isThrownBy(() -> this.interceptor.preSend(message(), mock(MessageChannel.class)));
// @formatter:on
}
@Test
public void preSendWhenConnectWithMissingTokenThenThrowsMissingCsrfTokenException() {
// @formatter:off
assertThatExceptionOfType(MissingCsrfTokenException.class)
.isThrownBy(() -> this.interceptor.preSend(message(), mock(MessageChannel.class)));
// @formatter:on
}
@Test
public void preSendWhenConnectWithNullSessionAttributesThenThrowsMissingCsrfTokenException() {
this.messageHeaders.setSessionAttributes(null);
// @formatter:off
assertThatExceptionOfType(MissingCsrfTokenException.class)
.isThrownBy(() -> this.interceptor.preSend(message(), mock(MessageChannel.class)));
// @formatter:on
}
@Test
public void preSendWhenAckThenIgnores() {
this.messageHeaders = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT_ACK);
this.interceptor.preSend(message(), this.channel);
}
@Test
public void preSendWhenDisconnectThenIgnores() {
this.messageHeaders = SimpMessageHeaderAccessor.create(SimpMessageType.DISCONNECT);
this.interceptor.preSend(message(), this.channel);
}
@Test
public void preSendWhenHeartbeatThenIgnores() {
this.messageHeaders = SimpMessageHeaderAccessor.create(SimpMessageType.HEARTBEAT);
this.interceptor.preSend(message(), this.channel);
}
@Test
public void preSendWhenMessageThenIgnores() {
this.messageHeaders = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
this.interceptor.preSend(message(), this.channel);
}
@Test
public void preSendWhenOtherThenIgnores() {
this.messageHeaders = SimpMessageHeaderAccessor.create(SimpMessageType.OTHER);
this.interceptor.preSend(message(), this.channel);
}
@Test
public void preSendWhenUnsubscribeThenIgnores() {
this.messageHeaders = SimpMessageHeaderAccessor.create(SimpMessageType.UNSUBSCRIBE);
this.interceptor.preSend(message(), this.channel);
}
private Message<String> message() {
return MessageBuilder.withPayload("message").copyHeaders(this.messageHeaders.toMap()).build();
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2023 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 org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.DefaultCsrfToken;
import org.springframework.security.web.csrf.DeferredCsrfToken;
import org.springframework.web.socket.WebSocketHandler;
import static org.assertj.core.api.Assertions.assertThat;
@@ -72,10 +73,38 @@ public class CsrfTokenHandshakeInterceptorTests {
@Test
public void beforeHandshake() throws Exception {
CsrfToken token = new DefaultCsrfToken("header", "param", "token");
this.httpRequest.setAttribute(CsrfToken.class.getName(), token);
this.httpRequest.setAttribute(DeferredCsrfToken.class.getName(), new TestDeferredCsrfToken(token));
this.interceptor.beforeHandshake(this.request, this.response, this.wsHandler, this.attributes);
assertThat(this.attributes.keySet()).containsOnly(CsrfToken.class.getName());
assertThat(this.attributes.values()).containsOnly(token);
CsrfToken csrfToken = (CsrfToken) this.attributes.get(CsrfToken.class.getName());
assertThat(csrfToken.getHeaderName()).isEqualTo(token.getHeaderName());
assertThat(csrfToken.getParameterName()).isEqualTo(token.getParameterName());
assertThat(csrfToken.getToken()).isEqualTo(token.getToken());
// Ensure the values of the CsrfToken are copied into a new token so the old token
// is available for garbage collection.
// This is required because the original token could hold a reference to the
// HttpServletRequest/Response of the handshake request.
assertThat(csrfToken).isNotSameAs(token);
}
private static final class TestDeferredCsrfToken implements DeferredCsrfToken {
private final CsrfToken csrfToken;
private TestDeferredCsrfToken(CsrfToken csrfToken) {
this.csrfToken = csrfToken;
}
@Override
public CsrfToken get() {
return this.csrfToken;
}
@Override
public boolean isGenerated() {
return false;
}
}
}
@@ -25,7 +25,6 @@ import io.r2dbc.h2.H2ConnectionFactory;
import io.r2dbc.spi.ConnectionFactory;
import io.r2dbc.spi.Result;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -115,14 +114,12 @@ public class R2dbcReactiveOAuth2AuthorizedClientServiceTests {
}
@Test
@Disabled // until https://github.com/reactor/reactor-core/issues/3307 is resolved
public void loadAuthorizedClientWhenDoesNotExistThenReturnNull() {
this.authorizedClientService.loadAuthorizedClient("registration-not-found", "principalName")
.as(StepVerifier::create).expectNextCount(0).verifyComplete();
}
@Test
@Disabled // until https://github.com/reactor/reactor-core/issues/3307 is resolved
public void loadAuthorizedClientWhenExistsThenReturnAuthorizedClient() {
Authentication principal = createPrincipal();
OAuth2AuthorizedClient expected = createAuthorizedClient(principal, this.clientRegistration);
@@ -153,7 +150,6 @@ public class R2dbcReactiveOAuth2AuthorizedClientServiceTests {
}
@Test
@Disabled // until https://github.com/reactor/reactor-core/issues/3307 is resolved
public void loadAuthorizedClientWhenExistsButNotFoundInClientRegistrationRepositoryThenThrowDataRetrievalFailureException() {
given(this.clientRegistrationRepository.findByRegistrationId(any())).willReturn(Mono.empty());
Authentication principal = createPrincipal();
@@ -190,7 +186,6 @@ public class R2dbcReactiveOAuth2AuthorizedClientServiceTests {
}
@Test
@Disabled // until https://github.com/reactor/reactor-core/issues/3307 is resolved
public void saveAuthorizedClientWhenSaveThenLoadReturnsSaved() {
Authentication principal = createPrincipal();
final OAuth2AuthorizedClient expected = createAuthorizedClient(principal, this.clientRegistration);
@@ -249,7 +244,6 @@ public class R2dbcReactiveOAuth2AuthorizedClientServiceTests {
}
@Test
@Disabled // until https://github.com/reactor/reactor-core/issues/3307 is resolved
public void saveAuthorizedClientWhenSaveClientWithExistingPrimaryKeyThenUpdate() {
// Given a saved authorized client
Authentication principal = createPrincipal();
@@ -303,7 +297,6 @@ public class R2dbcReactiveOAuth2AuthorizedClientServiceTests {
}
@Test
@Disabled // until https://github.com/reactor/reactor-core/issues/3307 is resolved
public void removeAuthorizedClientWhenExistsThenRemoved() {
Authentication principal = createPrincipal();
OAuth2AuthorizedClient authorizedClient = createAuthorizedClient(principal, this.clientRegistration);
@@ -27,6 +27,7 @@ import org.springframework.util.ClassUtils;
* Utility methods for {@link HttpMessageConverter}'s.
*
* @author Joe Grandja
* @author luamas
* @since 5.1
*/
final class HttpMessageConverters {
@@ -42,7 +43,7 @@ final class HttpMessageConverters {
jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader)
&& ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
jsonbPresent = ClassUtils.isPresent("javax.json.bind.Jsonb", classLoader);
jsonbPresent = ClassUtils.isPresent("jakarta.json.bind.Jsonb", classLoader);
}
private HttpMessageConverters() {
@@ -60,6 +60,7 @@ import org.mockito.ArgumentCaptor;
import org.springframework.cache.Cache;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.cache.support.SimpleValueWrapper;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
@@ -700,6 +701,7 @@ public class NimbusJwtDecoderTests {
RestOperations restOperations = mock(RestOperations.class);
Cache cache = mock(Cache.class);
given(cache.get(eq(JWK_SET_URI), eq(String.class))).willReturn(JWK_SET);
given(cache.get(eq(JWK_SET_URI))).willReturn(new SimpleValueWrapper(JWK_SET));
given(restOperations.exchange(any(RequestEntity.class), eq(String.class)))
.willReturn(new ResponseEntity<>(NEW_KID_JWK_SET, HttpStatus.OK));
@@ -23,6 +23,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
@@ -674,7 +675,7 @@ public final class OpenSaml4AuthenticationProvider implements AuthenticationProv
attributeMap.addAll(attribute.getName(), attributeValues);
}
}
return attributeMap;
return new LinkedHashMap<>(attributeMap); // gh-11785
}
private static List<String> getSessionIndexes(Assertion assertion) {
@@ -32,6 +32,7 @@ import java.util.function.Consumer;
import javax.xml.namespace.QName;
import com.fasterxml.jackson.databind.ObjectMapper;
import net.shibboleth.utilities.java.support.xml.SerializeSupport;
import org.junit.jupiter.api.Test;
import org.opensaml.core.xml.XMLObject;
@@ -68,6 +69,7 @@ import org.w3c.dom.Element;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.core.Authentication;
import org.springframework.security.jackson2.SecurityJackson2Modules;
import org.springframework.security.saml2.Saml2Exception;
import org.springframework.security.saml2.core.Saml2Error;
import org.springframework.security.saml2.core.Saml2ErrorCodes;
@@ -349,6 +351,23 @@ public class OpenSaml4AuthenticationProviderTests {
assertThat(principal.getSessionIndexes()).contains("session-index");
}
// gh-11785
@Test
public void deserializeWhenAssertionContainsAttributesThenWorks() throws Exception {
ObjectMapper mapper = new ObjectMapper();
ClassLoader loader = getClass().getClassLoader();
mapper.registerModules(SecurityJackson2Modules.getModules(loader));
Response response = response();
Assertion assertion = assertion();
List<AttributeStatement> attributes = TestOpenSamlObjects.attributeStatements();
assertion.getAttributeStatements().addAll(attributes);
response.getAssertions().add(signed(assertion));
Saml2AuthenticationToken token = token(response, verifying(registration()));
Authentication authentication = this.provider.authenticate(token);
String result = mapper.writeValueAsString(authentication);
mapper.readValue(result, Authentication.class);
}
@Test
public void authenticateWhenAssertionContainsCustomAttributesThenItSucceeds() {
Response response = response();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
@@ -23,6 +23,7 @@ import org.springframework.security.web.context.SecurityContextHolderFilter;
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.test.context.aot.TestRuntimeHintsRegistrar;
import org.springframework.util.ClassUtils;
/**
* {@link TestRuntimeHintsRegistrar} implementation that register runtime hints for
@@ -35,6 +36,9 @@ class WebTestUtilsTestRuntimeHints implements TestRuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, Class<?> testClass, ClassLoader classLoader) {
if (!ClassUtils.isPresent("jakarta.servlet.Filter", classLoader)) {
return;
}
registerFilterChainProxyHints(hints);
registerSecurityContextRepositoryHints(hints);
registerCsrfTokenRepositoryHints(hints);
@@ -43,14 +43,18 @@ import org.springframework.util.StringUtils;
*/
final class WithUserDetailsSecurityContextFactory implements WithSecurityContextFactory<WithUserDetails> {
private static final boolean reactorPresent = ClassUtils.isPresent("reactor.core.publisher.Mono",
WithUserDetailsSecurityContextFactory.class.getClassLoader());
private static final boolean reactorPresent;
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
.getContextHolderStrategy();
private BeanFactory beans;
static {
reactorPresent = ClassUtils.isPresent("reactor.core.publisher.Mono",
WithUserDetailsSecurityContextFactory.class.getClassLoader());
}
@Autowired
WithUserDetailsSecurityContextFactory(BeanFactory beans) {
this.beans = beans;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
@@ -182,7 +182,7 @@ public final class ObservationFilterChainDecorator implements FilterChainProxy.F
parentBefore.setFilterName(this.name);
parentBefore.setChainPosition(this.position);
}
parent.before().event(Observation.Event.of(this.name + " before"));
parent.before().event(Observation.Event.of(this.name + ".before", "before " + this.name));
this.filter.doFilter(request, response, chain);
parent.start();
if (parent.after().getContext() instanceof FilterChainObservationContext parentAfter) {
@@ -190,7 +190,7 @@ public final class ObservationFilterChainDecorator implements FilterChainProxy.F
parentAfter.setFilterName(this.name);
parentAfter.setChainPosition(this.size - this.position + 1);
}
parent.after().event(Observation.Event.of(this.name + " after"));
parent.after().event(Observation.Event.of(this.name + ".after", "after " + this.name));
}
private AroundFilterObservation parent(HttpServletRequest request) {
@@ -59,6 +59,8 @@ import org.springframework.security.web.authentication.AuthenticationSuccessHand
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.security.web.context.RequestAttributeSecurityContextRepository;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.security.web.util.UrlUtils;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
@@ -146,6 +148,8 @@ public class SwitchUserFilter extends GenericFilterBean implements ApplicationEv
private AuthenticationFailureHandler failureHandler;
private SecurityContextRepository securityContextRepository = new RequestAttributeSecurityContextRepository();
@Override
public void afterPropertiesSet() {
Assert.notNull(this.userDetailsService, "userDetailsService must be specified");
@@ -183,6 +187,7 @@ public class SwitchUserFilter extends GenericFilterBean implements ApplicationEv
context.setAuthentication(targetUser);
this.securityContextHolderStrategy.setContext(context);
this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", targetUser));
this.securityContextRepository.saveContext(context, request, response);
// redirect to target url
this.successHandler.onAuthenticationSuccess(request, response, targetUser);
}
@@ -200,6 +205,7 @@ public class SwitchUserFilter extends GenericFilterBean implements ApplicationEv
context.setAuthentication(originalUser);
this.securityContextHolderStrategy.setContext(context);
this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", originalUser));
this.securityContextRepository.saveContext(context, request, response);
// redirect to target url
this.successHandler.onAuthenticationSuccess(request, response, originalUser);
return;
@@ -525,6 +531,19 @@ public class SwitchUserFilter extends GenericFilterBean implements ApplicationEv
this.securityContextHolderStrategy = securityContextHolderStrategy;
}
/**
* Sets the {@link SecurityContextRepository} to save the {@link SecurityContext} on
* switch user success. The default is
* {@link RequestAttributeSecurityContextRepository}.
* @param securityContextRepository the {@link SecurityContextRepository} to use.
* Cannot be null.
* @since 5.7.7
*/
public void setSecurityContextRepository(SecurityContextRepository securityContextRepository) {
Assert.notNull(securityContextRepository, "securityContextRepository cannot be null");
this.securityContextRepository = securityContextRepository;
}
private static RequestMatcher createMatcher(String pattern) {
return new AntPathRequestMatcher(pattern, "POST", true, new UrlPathHelper());
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
@@ -107,6 +107,7 @@ public final class CsrfFilter extends OncePerRequestFilter {
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
DeferredCsrfToken deferredCsrfToken = this.tokenRepository.loadDeferredToken(request, response);
request.setAttribute(DeferredCsrfToken.class.getName(), deferredCsrfToken);
this.requestHandler.handle(request, response, deferredCsrfToken::get);
if (!this.requireCsrfProtectionMatcher.matches(request)) {
if (this.logger.isTraceEnabled()) {
@@ -170,7 +171,7 @@ public final class CsrfFilter extends OncePerRequestFilter {
* {@link CsrfToken} available as a request attribute.
*
* <p>
* The default is {@link CsrfTokenRequestAttributeHandler}.
* The default is {@link XorCsrfTokenRequestAttributeHandler}.
* </p>
* @param requestHandler the {@link CsrfTokenRequestHandler} to use
* @since 5.8
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
@@ -225,7 +225,8 @@ public class DefaultSavedRequest implements SavedRequest {
if (!propertyEquals(this.pathInfo, request.getPathInfo())) {
return false;
}
if (!propertyEquals(this.queryString, request.getQueryString())) {
if (!propertyEquals(createQueryString(this.queryString, this.matchingRequestParameterName),
request.getQueryString())) {
return false;
}
if (!propertyEquals(this.requestURI, request.getRequestURI())) {
@@ -27,6 +27,7 @@ import io.micrometer.common.KeyValues;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationConvention;
import io.micrometer.observation.ObservationRegistry;
import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor;
import reactor.core.publisher.Mono;
import org.springframework.lang.Nullable;
@@ -73,20 +74,22 @@ public final class ObservationWebFilterChainDecorator implements WebFilterChainP
}
private WebFilterChain wrapSecured(WebFilterChain original) {
return (exchange) -> {
return (exchange) -> Mono.deferContextual((contextView) -> {
AroundWebFilterObservation parent = observation(exchange);
Observation parentObservation = contextView.getOrDefault(ObservationThreadLocalAccessor.KEY, null);
Observation observation = Observation.createNotStarted(SECURED_OBSERVATION_NAME, this.registry)
.contextualName("secured request");
.contextualName("secured request").parentObservation(parentObservation);
return parent.wrap(WebFilterObservation.create(observation).wrap(original)).filter(exchange);
};
});
}
private WebFilterChain wrapUnsecured(WebFilterChain original) {
return (exchange) -> {
return (exchange) -> Mono.deferContextual((contextView) -> {
Observation parentObservation = contextView.getOrDefault(ObservationThreadLocalAccessor.KEY, null);
Observation observation = Observation.createNotStarted(UNSECURED_OBSERVATION_NAME, this.registry)
.contextualName("unsecured request");
.contextualName("unsecured request").parentObservation(parentObservation);
return WebFilterObservation.create(observation).wrap(original).filter(exchange);
};
});
}
private List<ObservationWebFilter> wrap(List<WebFilter> filters) {
@@ -186,8 +189,11 @@ public final class ObservationWebFilterChainDecorator implements WebFilterChainP
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
if (this.position == 1) {
AroundWebFilterObservation parent = parent(exchange);
return parent.wrap(this::wrapFilter).filter(exchange, chain);
return Mono.deferContextual((contextView) -> {
Observation parentObservation = contextView.getOrDefault(ObservationThreadLocalAccessor.KEY, null);
AroundWebFilterObservation parent = parent(exchange, parentObservation);
return parent.wrap(this::wrapFilter).filter(exchange, chain);
});
}
else {
return wrapFilter(exchange, chain);
@@ -211,11 +217,13 @@ public final class ObservationWebFilterChainDecorator implements WebFilterChainP
});
}
private AroundWebFilterObservation parent(ServerWebExchange exchange) {
private AroundWebFilterObservation parent(ServerWebExchange exchange, Observation parentObservation) {
WebFilterChainObservationContext beforeContext = WebFilterChainObservationContext.before();
WebFilterChainObservationContext afterContext = WebFilterChainObservationContext.after();
Observation before = Observation.createNotStarted(this.convention, () -> beforeContext, this.registry);
Observation after = Observation.createNotStarted(this.convention, () -> afterContext, this.registry);
Observation before = Observation.createNotStarted(this.convention, () -> beforeContext, this.registry)
.parentObservation(parentObservation);
Observation after = Observation.createNotStarted(this.convention, () -> afterContext, this.registry)
.parentObservation(parentObservation);
AroundWebFilterObservation parent = AroundWebFilterObservation.create(before, after);
exchange.getAttributes().put(ATTRIBUTE, parent);
return parent;
@@ -104,7 +104,7 @@ public class CsrfWebFilter implements WebFilter {
* Specifies a {@link ServerCsrfTokenRequestHandler} that is used to make the
* {@code CsrfToken} available as an exchange attribute.
* <p>
* The default is {@link ServerCsrfTokenRequestAttributeHandler}.
* The default is {@link XorServerCsrfTokenRequestAttributeHandler}.
* @param requestHandler the {@link ServerCsrfTokenRequestHandler} to use
* @since 5.8
*/
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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,17 +16,24 @@
package org.springframework.security.web;
import java.util.List;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationHandler;
import io.micrometer.observation.ObservationRegistry;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
@@ -61,4 +68,23 @@ public class ObservationFilterChainDecoratorTests {
verifyNoInteractions(handler);
}
@Test
void decorateFiltersWhenDefaultsThenObserves() throws Exception {
ObservationHandler<?> handler = mock(ObservationHandler.class);
given(handler.supportsContext(any())).willReturn(true);
ObservationRegistry registry = ObservationRegistry.create();
registry.observationConfig().observationHandler(handler);
ObservationFilterChainDecorator decorator = new ObservationFilterChainDecorator(registry);
FilterChain chain = mock(FilterChain.class);
Filter filter = mock(Filter.class);
FilterChain decorated = decorator.decorate(chain, List.of(filter));
decorated.doFilter(new MockHttpServletRequest("GET", "/"), new MockHttpServletResponse());
verify(handler, times(2)).onStart(any());
ArgumentCaptor<Observation.Event> event = ArgumentCaptor.forClass(Observation.Event.class);
verify(handler, times(2)).onEvent(event.capture(), any());
List<Observation.Event> events = event.getAllValues();
assertThat(events.get(0).getName()).isEqualTo(filter.getClass().getSimpleName() + ".before");
assertThat(events.get(1).getName()).isEqualTo(filter.getClass().getSimpleName() + ".after");
}
}
@@ -16,14 +16,17 @@
package org.springframework.security.web.authentication.switchuser;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.authentication.AccountExpiredException;
@@ -46,11 +49,15 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.util.FieldUtils;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.security.web.context.RequestAttributeSecurityContextRepository;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -502,6 +509,59 @@ public class SwitchUserFilterTests {
filter.setSwitchFailureUrl("/foo");
}
@Test
void filterWhenDefaultSecurityContextRepositoryThenRequestAttributeRepository() {
SwitchUserFilter switchUserFilter = new SwitchUserFilter();
assertThat(ReflectionTestUtils.getField(switchUserFilter, "securityContextRepository"))
.isInstanceOf(RequestAttributeSecurityContextRepository.class);
}
@Test
void doFilterWhenSwitchUserThenSaveSecurityContext() throws ServletException, IOException {
SecurityContextRepository securityContextRepository = mock(SecurityContextRepository.class);
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
MockFilterChain filterChain = new MockFilterChain();
request.setParameter(SwitchUserFilter.SPRING_SECURITY_SWITCH_USERNAME_KEY, "jacklord");
request.setRequestURI("/login/impersonate");
SwitchUserFilter filter = new SwitchUserFilter();
filter.setSecurityContextRepository(securityContextRepository);
filter.setUserDetailsService(new MockUserDetailsService());
filter.setTargetUrl("/target");
filter.afterPropertiesSet();
filter.doFilter(request, response, filterChain);
verify(securityContextRepository).saveContext(any(), any(), any());
}
@Test
void doFilterWhenExitUserThenSaveSecurityContext() throws ServletException, IOException {
UsernamePasswordAuthenticationToken source = UsernamePasswordAuthenticationToken.authenticated("dano",
"hawaii50", ROLES_12);
// set current user (Admin)
List<GrantedAuthority> adminAuths = new ArrayList<>(ROLES_12);
adminAuths.add(new SwitchUserGrantedAuthority("PREVIOUS_ADMINISTRATOR", source));
UsernamePasswordAuthenticationToken admin = UsernamePasswordAuthenticationToken.authenticated("jacklord",
"hawaii50", adminAuths);
SecurityContextHolder.getContext().setAuthentication(admin);
SecurityContextRepository securityContextRepository = mock(SecurityContextRepository.class);
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
MockFilterChain filterChain = new MockFilterChain();
request.setParameter(SwitchUserFilter.SPRING_SECURITY_SWITCH_USERNAME_KEY, "jacklord");
request.setRequestURI("/logout/impersonate");
SwitchUserFilter filter = new SwitchUserFilter();
filter.setSecurityContextRepository(securityContextRepository);
filter.setUserDetailsService(new MockUserDetailsService());
filter.setTargetUrl("/target");
filter.afterPropertiesSet();
filter.doFilter(request, response, filterChain);
verify(securityContextRepository).saveContext(any(), any(), any());
}
private class MockUserDetailsService implements UserDetailsService {
private String password = "hawaii50";
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
@@ -127,11 +127,12 @@ public class CsrfFilterTests {
@Test
public void doFilterAccessDeniedNoTokenPresent() throws ServletException, IOException {
given(this.requestMatcher.matches(this.request)).willReturn(true);
given(this.tokenRepository.loadDeferredToken(this.request, this.response))
.willReturn(new TestDeferredCsrfToken(this.token, false));
DeferredCsrfToken deferredCsrfToken = new TestDeferredCsrfToken(this.token, false);
given(this.tokenRepository.loadDeferredToken(this.request, this.response)).willReturn(deferredCsrfToken);
this.filter.doFilter(this.request, this.response, this.filterChain);
assertThatCsrfToken(this.request.getAttribute(this.csrfAttrName)).isNotNull();
assertThatCsrfToken(this.request.getAttribute(CsrfToken.class.getName())).isNotNull();
assertThat(this.request.getAttribute(DeferredCsrfToken.class.getName())).isSameAs(deferredCsrfToken);
verify(this.deniedHandler).handle(eq(this.request), eq(this.response), any(InvalidCsrfTokenException.class));
verifyNoMoreInteractions(this.filterChain);
}
@@ -139,12 +140,13 @@ public class CsrfFilterTests {
@Test
public void doFilterAccessDeniedIncorrectTokenPresent() throws ServletException, IOException {
given(this.requestMatcher.matches(this.request)).willReturn(true);
given(this.tokenRepository.loadDeferredToken(this.request, this.response))
.willReturn(new TestDeferredCsrfToken(this.token, false));
DeferredCsrfToken deferredCsrfToken = new TestDeferredCsrfToken(this.token, false);
given(this.tokenRepository.loadDeferredToken(this.request, this.response)).willReturn(deferredCsrfToken);
this.request.setParameter(this.token.getParameterName(), this.token.getToken() + " INVALID");
this.filter.doFilter(this.request, this.response, this.filterChain);
assertThatCsrfToken(this.request.getAttribute(this.csrfAttrName)).isNotNull();
assertThatCsrfToken(this.request.getAttribute(CsrfToken.class.getName())).isNotNull();
assertThat(this.request.getAttribute(DeferredCsrfToken.class.getName())).isSameAs(deferredCsrfToken);
verify(this.deniedHandler).handle(eq(this.request), eq(this.response), any(InvalidCsrfTokenException.class));
verifyNoMoreInteractions(this.filterChain);
}
@@ -152,12 +154,13 @@ public class CsrfFilterTests {
@Test
public void doFilterAccessDeniedIncorrectTokenPresentHeader() throws ServletException, IOException {
given(this.requestMatcher.matches(this.request)).willReturn(true);
given(this.tokenRepository.loadDeferredToken(this.request, this.response))
.willReturn(new TestDeferredCsrfToken(this.token, false));
DeferredCsrfToken deferredCsrfToken = new TestDeferredCsrfToken(this.token, false);
given(this.tokenRepository.loadDeferredToken(this.request, this.response)).willReturn(deferredCsrfToken);
this.request.addHeader(this.token.getHeaderName(), this.token.getToken() + " INVALID");
this.filter.doFilter(this.request, this.response, this.filterChain);
assertThatCsrfToken(this.request.getAttribute(this.csrfAttrName)).isNotNull();
assertThatCsrfToken(this.request.getAttribute(CsrfToken.class.getName())).isNotNull();
assertThat(this.request.getAttribute(DeferredCsrfToken.class.getName())).isSameAs(deferredCsrfToken);
verify(this.deniedHandler).handle(eq(this.request), eq(this.response), any(InvalidCsrfTokenException.class));
verifyNoMoreInteractions(this.filterChain);
}
@@ -166,8 +169,8 @@ public class CsrfFilterTests {
public void doFilterAccessDeniedIncorrectTokenPresentHeaderPreferredOverParameter()
throws ServletException, IOException {
given(this.requestMatcher.matches(this.request)).willReturn(true);
given(this.tokenRepository.loadDeferredToken(this.request, this.response))
.willReturn(new TestDeferredCsrfToken(this.token, false));
DeferredCsrfToken deferredCsrfToken = new TestDeferredCsrfToken(this.token, false);
given(this.tokenRepository.loadDeferredToken(this.request, this.response)).willReturn(deferredCsrfToken);
CsrfTokenRequestHandler handler = new XorCsrfTokenRequestAttributeHandler();
handler.handle(this.request, this.response, () -> this.token);
CsrfToken csrfToken = (CsrfToken) this.request.getAttribute(CsrfToken.class.getName());
@@ -176,6 +179,7 @@ public class CsrfFilterTests {
this.filter.doFilter(this.request, this.response, this.filterChain);
assertThatCsrfToken(this.request.getAttribute(this.csrfAttrName)).isNotNull();
assertThatCsrfToken(this.request.getAttribute(CsrfToken.class.getName())).isNotNull();
assertThat(this.request.getAttribute(DeferredCsrfToken.class.getName())).isSameAs(deferredCsrfToken);
verify(this.deniedHandler).handle(eq(this.request), eq(this.response), any(InvalidCsrfTokenException.class));
verifyNoMoreInteractions(this.filterChain);
}
@@ -183,11 +187,12 @@ public class CsrfFilterTests {
@Test
public void doFilterNotCsrfRequestExistingToken() throws ServletException, IOException {
given(this.requestMatcher.matches(this.request)).willReturn(false);
given(this.tokenRepository.loadDeferredToken(this.request, this.response))
.willReturn(new TestDeferredCsrfToken(this.token, false));
DeferredCsrfToken deferredCsrfToken = new TestDeferredCsrfToken(this.token, false);
given(this.tokenRepository.loadDeferredToken(this.request, this.response)).willReturn(deferredCsrfToken);
this.filter.doFilter(this.request, this.response, this.filterChain);
assertThatCsrfToken(this.request.getAttribute(this.csrfAttrName)).isNotNull();
assertThatCsrfToken(this.request.getAttribute(CsrfToken.class.getName())).isNotNull();
assertThat(this.request.getAttribute(DeferredCsrfToken.class.getName())).isSameAs(deferredCsrfToken);
verify(this.filterChain).doFilter(this.request, this.response);
verifyNoMoreInteractions(this.deniedHandler);
}
@@ -195,11 +200,12 @@ public class CsrfFilterTests {
@Test
public void doFilterNotCsrfRequestGenerateToken() throws ServletException, IOException {
given(this.requestMatcher.matches(this.request)).willReturn(false);
given(this.tokenRepository.loadDeferredToken(this.request, this.response))
.willReturn(new TestDeferredCsrfToken(this.token, true));
DeferredCsrfToken deferredCsrfToken = new TestDeferredCsrfToken(this.token, true);
given(this.tokenRepository.loadDeferredToken(this.request, this.response)).willReturn(deferredCsrfToken);
this.filter.doFilter(this.request, this.response, this.filterChain);
assertThatCsrfToken(this.request.getAttribute(this.csrfAttrName)).isNotNull();
assertThatCsrfToken(this.request.getAttribute(CsrfToken.class.getName())).isNotNull();
assertThat(this.request.getAttribute(DeferredCsrfToken.class.getName())).isSameAs(deferredCsrfToken);
verify(this.filterChain).doFilter(this.request, this.response);
verifyNoMoreInteractions(this.deniedHandler);
}
@@ -207,8 +213,8 @@ public class CsrfFilterTests {
@Test
public void doFilterIsCsrfRequestExistingTokenHeader() throws ServletException, IOException {
given(this.requestMatcher.matches(this.request)).willReturn(true);
given(this.tokenRepository.loadDeferredToken(this.request, this.response))
.willReturn(new TestDeferredCsrfToken(this.token, false));
DeferredCsrfToken deferredCsrfToken = new TestDeferredCsrfToken(this.token, false);
given(this.tokenRepository.loadDeferredToken(this.request, this.response)).willReturn(deferredCsrfToken);
CsrfTokenRequestHandler handler = new XorCsrfTokenRequestAttributeHandler();
handler.handle(this.request, this.response, () -> this.token);
CsrfToken csrfToken = (CsrfToken) this.request.getAttribute(CsrfToken.class.getName());
@@ -216,6 +222,7 @@ public class CsrfFilterTests {
this.filter.doFilter(this.request, this.response, this.filterChain);
assertThatCsrfToken(this.request.getAttribute(this.csrfAttrName)).isNotNull();
assertThatCsrfToken(this.request.getAttribute(CsrfToken.class.getName())).isNotNull();
assertThat(this.request.getAttribute(DeferredCsrfToken.class.getName())).isSameAs(deferredCsrfToken);
verify(this.filterChain).doFilter(this.request, this.response);
verifyNoMoreInteractions(this.deniedHandler);
}
@@ -224,8 +231,8 @@ public class CsrfFilterTests {
public void doFilterIsCsrfRequestExistingTokenHeaderPreferredOverInvalidParam()
throws ServletException, IOException {
given(this.requestMatcher.matches(this.request)).willReturn(true);
given(this.tokenRepository.loadDeferredToken(this.request, this.response))
.willReturn(new TestDeferredCsrfToken(this.token, false));
DeferredCsrfToken deferredCsrfToken = new TestDeferredCsrfToken(this.token, false);
given(this.tokenRepository.loadDeferredToken(this.request, this.response)).willReturn(deferredCsrfToken);
CsrfTokenRequestHandler handler = new XorCsrfTokenRequestAttributeHandler();
handler.handle(this.request, this.response, () -> this.token);
CsrfToken csrfToken = (CsrfToken) this.request.getAttribute(CsrfToken.class.getName());
@@ -234,6 +241,7 @@ public class CsrfFilterTests {
this.filter.doFilter(this.request, this.response, this.filterChain);
assertThatCsrfToken(this.request.getAttribute(this.csrfAttrName)).isNotNull();
assertThatCsrfToken(this.request.getAttribute(CsrfToken.class.getName())).isNotNull();
assertThat(this.request.getAttribute(DeferredCsrfToken.class.getName())).isSameAs(deferredCsrfToken);
verify(this.filterChain).doFilter(this.request, this.response);
verifyNoMoreInteractions(this.deniedHandler);
}
@@ -241,8 +249,8 @@ public class CsrfFilterTests {
@Test
public void doFilterIsCsrfRequestExistingToken() throws ServletException, IOException {
given(this.requestMatcher.matches(this.request)).willReturn(true);
given(this.tokenRepository.loadDeferredToken(this.request, this.response))
.willReturn(new TestDeferredCsrfToken(this.token, false));
DeferredCsrfToken deferredCsrfToken = new TestDeferredCsrfToken(this.token, false);
given(this.tokenRepository.loadDeferredToken(this.request, this.response)).willReturn(deferredCsrfToken);
CsrfTokenRequestHandler handler = new XorCsrfTokenRequestAttributeHandler();
handler.handle(this.request, this.response, () -> this.token);
CsrfToken csrfToken = (CsrfToken) this.request.getAttribute(CsrfToken.class.getName());
@@ -250,6 +258,7 @@ public class CsrfFilterTests {
this.filter.doFilter(this.request, this.response, this.filterChain);
assertThatCsrfToken(this.request.getAttribute(this.csrfAttrName)).isNotNull();
assertThatCsrfToken(this.request.getAttribute(CsrfToken.class.getName())).isNotNull();
assertThat(this.request.getAttribute(DeferredCsrfToken.class.getName())).isSameAs(deferredCsrfToken);
verify(this.filterChain).doFilter(this.request, this.response);
verifyNoMoreInteractions(this.deniedHandler);
verify(this.tokenRepository, never()).saveToken(any(CsrfToken.class), any(HttpServletRequest.class),
@@ -259,8 +268,8 @@ public class CsrfFilterTests {
@Test
public void doFilterIsCsrfRequestGenerateToken() throws ServletException, IOException {
given(this.requestMatcher.matches(this.request)).willReturn(true);
given(this.tokenRepository.loadDeferredToken(this.request, this.response))
.willReturn(new TestDeferredCsrfToken(this.token, true));
DeferredCsrfToken deferredCsrfToken = new TestDeferredCsrfToken(this.token, true);
given(this.tokenRepository.loadDeferredToken(this.request, this.response)).willReturn(deferredCsrfToken);
CsrfTokenRequestHandler handler = new XorCsrfTokenRequestAttributeHandler();
handler.handle(this.request, this.response, () -> this.token);
CsrfToken csrfToken = (CsrfToken) this.request.getAttribute(CsrfToken.class.getName());
@@ -268,6 +277,7 @@ public class CsrfFilterTests {
this.filter.doFilter(this.request, this.response, this.filterChain);
assertThatCsrfToken(this.request.getAttribute(this.csrfAttrName)).isNotNull();
assertThatCsrfToken(this.request.getAttribute(CsrfToken.class.getName())).isNotNull();
assertThat(this.request.getAttribute(DeferredCsrfToken.class.getName())).isSameAs(deferredCsrfToken);
// LazyCsrfTokenRepository requires the response as an attribute
assertThat(this.request.getAttribute(HttpServletResponse.class.getName())).isEqualTo(this.response);
verify(this.filterChain).doFilter(this.request, this.response);
@@ -332,11 +342,12 @@ public class CsrfFilterTests {
this.filter = new CsrfFilter(this.tokenRepository);
this.filter.setRequireCsrfProtectionMatcher(this.requestMatcher);
given(this.requestMatcher.matches(this.request)).willReturn(true);
given(this.tokenRepository.loadDeferredToken(this.request, this.response))
.willReturn(new TestDeferredCsrfToken(this.token, false));
DeferredCsrfToken deferredCsrfToken = new TestDeferredCsrfToken(this.token, false);
given(this.tokenRepository.loadDeferredToken(this.request, this.response)).willReturn(deferredCsrfToken);
this.filter.doFilter(this.request, this.response, this.filterChain);
assertThatCsrfToken(this.request.getAttribute(this.csrfAttrName)).isNotNull();
assertThatCsrfToken(this.request.getAttribute(CsrfToken.class.getName())).isNotNull();
assertThat(this.request.getAttribute(DeferredCsrfToken.class.getName())).isSameAs(deferredCsrfToken);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN);
verifyNoMoreInteractions(this.filterChain);
}
@@ -360,22 +371,24 @@ public class CsrfFilterTests {
given(token.getToken()).willReturn(null);
given(token.getHeaderName()).willReturn(this.token.getHeaderName());
given(token.getParameterName()).willReturn(this.token.getParameterName());
given(this.tokenRepository.loadDeferredToken(this.request, this.response))
.willReturn(new TestDeferredCsrfToken(token, false));
DeferredCsrfToken deferredCsrfToken = new TestDeferredCsrfToken(token, false);
given(this.tokenRepository.loadDeferredToken(this.request, this.response)).willReturn(deferredCsrfToken);
given(this.requestMatcher.matches(this.request)).willReturn(true);
filter.doFilterInternal(this.request, this.response, this.filterChain);
assertThat(this.request.getAttribute(DeferredCsrfToken.class.getName())).isSameAs(deferredCsrfToken);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
}
@Test
public void doFilterWhenRequestHandlerThenUsed() throws Exception {
given(this.tokenRepository.loadDeferredToken(this.request, this.response))
.willReturn(new TestDeferredCsrfToken(this.token, false));
DeferredCsrfToken deferredCsrfToken = new TestDeferredCsrfToken(this.token, false);
given(this.tokenRepository.loadDeferredToken(this.request, this.response)).willReturn(deferredCsrfToken);
CsrfTokenRequestHandler requestHandler = mock(CsrfTokenRequestHandler.class);
this.filter = createCsrfFilter(this.tokenRepository);
this.filter.setRequestHandler(requestHandler);
this.request.setParameter(this.token.getParameterName(), this.token.getToken());
this.filter.doFilter(this.request, this.response, this.filterChain);
assertThat(this.request.getAttribute(DeferredCsrfToken.class.getName())).isSameAs(deferredCsrfToken);
verify(this.tokenRepository).loadDeferredToken(this.request, this.response);
verify(requestHandler).handle(eq(this.request), eq(this.response), any());
verify(this.filterChain).doFilter(this.request, this.response);
@@ -384,11 +397,12 @@ public class CsrfFilterTests {
@Test
public void doFilterWhenXorCsrfTokenRequestAttributeHandlerAndValidTokenThenSuccess() throws Exception {
given(this.requestMatcher.matches(this.request)).willReturn(false);
given(this.tokenRepository.loadDeferredToken(this.request, this.response))
.willReturn(new TestDeferredCsrfToken(this.token, false));
DeferredCsrfToken deferredCsrfToken = new TestDeferredCsrfToken(this.token, false);
given(this.tokenRepository.loadDeferredToken(this.request, this.response)).willReturn(deferredCsrfToken);
this.filter.doFilter(this.request, this.response, this.filterChain);
assertThat(this.request.getAttribute(CsrfToken.class.getName())).isNotNull();
assertThat(this.request.getAttribute("_csrf")).isNotNull();
assertThat(this.request.getAttribute(DeferredCsrfToken.class.getName())).isSameAs(deferredCsrfToken);
verify(this.filterChain).doFilter(this.request, this.response);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
@@ -407,10 +421,11 @@ public class CsrfFilterTests {
@Test
public void doFilterWhenXorCsrfTokenRequestAttributeHandlerAndRawTokenThenAccessDeniedException() throws Exception {
given(this.requestMatcher.matches(this.request)).willReturn(true);
given(this.tokenRepository.loadDeferredToken(this.request, this.response))
.willReturn(new TestDeferredCsrfToken(this.token, false));
DeferredCsrfToken deferredCsrfToken = new TestDeferredCsrfToken(this.token, false);
given(this.tokenRepository.loadDeferredToken(this.request, this.response)).willReturn(deferredCsrfToken);
this.request.setParameter(this.token.getParameterName(), this.token.getToken());
this.filter.doFilter(this.request, this.response, this.filterChain);
assertThat(this.request.getAttribute(DeferredCsrfToken.class.getName())).isSameAs(deferredCsrfToken);
verify(this.deniedHandler).handle(eq(this.request), eq(this.response), any(AccessDeniedException.class));
verifyNoMoreInteractions(this.filterChain);
}
@@ -435,10 +450,11 @@ public class CsrfFilterTests {
requestHandler.setCsrfRequestAttributeName(csrfAttrName);
filter.setRequestHandler(requestHandler);
CsrfToken expectedCsrfToken = mock(CsrfToken.class);
given(this.tokenRepository.loadDeferredToken(this.request, this.response))
.willReturn(new TestDeferredCsrfToken(expectedCsrfToken, true));
DeferredCsrfToken deferredCsrfToken = new TestDeferredCsrfToken(expectedCsrfToken, true);
given(this.tokenRepository.loadDeferredToken(this.request, this.response)).willReturn(deferredCsrfToken);
filter.doFilter(this.request, this.response, this.filterChain);
assertThat(this.request.getAttribute(DeferredCsrfToken.class.getName())).isSameAs(deferredCsrfToken);
verifyNoInteractions(expectedCsrfToken);
CsrfToken tokenFromRequest = (CsrfToken) this.request.getAttribute(csrfAttrName);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2023 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.
@@ -114,6 +114,23 @@ public class HttpSessionRequestCacheTests {
cache.setMatchingRequestParameterName("success");
cache.saveRequest(request, new MockHttpServletResponse());
MockHttpServletRequest requestToMatch = new MockHttpServletRequest();
requestToMatch.setQueryString("success"); // gh-12665
requestToMatch.setParameter("success", "");
requestToMatch.setSession(request.getSession());
HttpServletRequest matchingRequest = cache.getMatchingRequest(requestToMatch, new MockHttpServletResponse());
assertThat(matchingRequest).isNotNull();
}
// gh-12665
@Test
public void getMatchingRequestWhenMatchingRequestParameterNameSetAndParameterExistAndQueryThenLookedUp() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setQueryString("param=true");
HttpSessionRequestCache cache = new HttpSessionRequestCache();
cache.setMatchingRequestParameterName("success");
cache.saveRequest(request, new MockHttpServletResponse());
MockHttpServletRequest requestToMatch = new MockHttpServletRequest();
requestToMatch.setQueryString("param=true&success");
requestToMatch.setParameter("success", "");
requestToMatch.setSession(request.getSession());
HttpServletRequest matchingRequest = cache.getMatchingRequest(requestToMatch, new MockHttpServletResponse());
@@ -128,6 +145,7 @@ public class HttpSessionRequestCacheTests {
cache.saveRequest(request, new MockHttpServletResponse());
assertThat(request.getSession().getAttribute(HttpSessionRequestCache.SAVED_REQUEST)).isNotNull();
MockHttpServletRequest requestToMatch = new MockHttpServletRequest();
requestToMatch.setQueryString("success");
requestToMatch.setParameter("success", "");
requestToMatch.setSession(request.getSession());
HttpServletRequest matchingRequest = cache.getMatchingRequest(requestToMatch, new MockHttpServletResponse());