1
0
mirror of synced 2026-07-08 12:20:02 +00:00

Compare commits

...

222 Commits

Author SHA1 Message Date
github-actions[bot] a5cd7ce122 Release 6.2.8 2024-11-18 15:23:13 +00:00
Joe Grandja a8c4d6cead Require Locale argument for toLower/toUpperCase usage 2024-11-18 04:22:26 -05:00
dependabot[bot] 5f838b0e93 Bump io.micrometer:micrometer-observation from 1.12.12 to 1.12.13
Bumps [io.micrometer:micrometer-observation](https://github.com/micrometer-metrics/micrometer) from 1.12.12 to 1.12.13.
- [Release notes](https://github.com/micrometer-metrics/micrometer/releases)
- [Commits](https://github.com/micrometer-metrics/micrometer/compare/v1.12.12...v1.12.13)

---
updated-dependencies:
- dependency-name: io.micrometer:micrometer-observation
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-17 19:53:46 -08:00
dependabot[bot] bc3e6f2919 Bump org.springframework.data:spring-data-bom
Bumps [org.springframework.data:spring-data-bom](https://github.com/spring-projects/spring-data-bom) from 2023.1.11 to 2023.1.12.
- [Release notes](https://github.com/spring-projects/spring-data-bom/releases)
- [Commits](https://github.com/spring-projects/spring-data-bom/compare/2023.1.11...2023.1.12)

---
updated-dependencies:
- dependency-name: org.springframework.data:spring-data-bom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-17 19:51:06 -08:00
dependabot[bot] 810d83e2f8 Bump @antora/collector-extension in /docs
---
updated-dependencies:
- dependency-name: "@antora/collector-extension"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-17 16:59:03 -08:00
dependabot[bot] 127ed4b7cf Bump org.springframework:spring-framework-bom from 6.1.14 to 6.1.15
Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 6.1.14 to 6.1.15.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v6.1.14...v6.1.15)

---
updated-dependencies:
- dependency-name: org.springframework:spring-framework-bom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-14 20:08:07 -08:00
dependabot[bot] da345a3bac Bump org.springframework.ldap:spring-ldap-core from 3.2.7 to 3.2.8
Bumps [org.springframework.ldap:spring-ldap-core](https://github.com/spring-projects/spring-ldap) from 3.2.7 to 3.2.8.
- [Release notes](https://github.com/spring-projects/spring-ldap/releases)
- [Changelog](https://github.com/spring-projects/spring-ldap/blob/main/changelog.txt)
- [Commits](https://github.com/spring-projects/spring-ldap/compare/3.2.7...3.2.8)

---
updated-dependencies:
- dependency-name: org.springframework.ldap:spring-ldap-core
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-14 19:56:04 -08:00
dependabot[bot] 0790978590 Bump io.projectreactor:reactor-bom from 2023.0.11 to 2023.0.12
Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2023.0.11 to 2023.0.12.
- [Release notes](https://github.com/reactor/reactor/releases)
- [Commits](https://github.com/reactor/reactor/compare/2023.0.11...2023.0.12)

---
updated-dependencies:
- dependency-name: io.projectreactor:reactor-bom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-12 20:04:22 -08:00
dependabot[bot] 0d8b8ee04b Bump io.micrometer:micrometer-observation from 1.12.11 to 1.12.12
Bumps [io.micrometer:micrometer-observation](https://github.com/micrometer-metrics/micrometer) from 1.12.11 to 1.12.12.
- [Release notes](https://github.com/micrometer-metrics/micrometer/releases)
- [Commits](https://github.com/micrometer-metrics/micrometer/compare/v1.12.11...v1.12.12)

---
updated-dependencies:
- dependency-name: io.micrometer:micrometer-observation
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-11 20:04:38 -08:00
Rob Winch 81e74e65d4 Support ServerExchangeRejectedHandler @Bean
Closes gh-16061
2024-11-11 14:58:00 -06:00
Steve Riesenberg 86f3cd6dc7 Polish gh-15940
Closes gh-15885
2024-11-04 13:34:12 -06:00
Hyeongi Jeong 4c6fef82b9 Fix error when Bearer token is requested with empty string
Issue gh-15885
2024-11-04 13:33:58 -06:00
dependabot[bot] 18129f3af3 Bump org.hsqldb:hsqldb from 2.7.3 to 2.7.4
Bumps org.hsqldb:hsqldb from 2.7.3 to 2.7.4.

---
updated-dependencies:
- dependency-name: org.hsqldb:hsqldb
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-03 20:01:04 -08:00
dependabot[bot] fed3c99c1d Bump @antora/collector-extension in /docs
---
updated-dependencies:
- dependency-name: "@antora/collector-extension"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-03 16:24:16 -08:00
Rob Winch 1ba6301afa Support ServerWebExchangeFirewall @Bean
Closes gh-15987
2024-10-25 12:13:41 -05:00
Josh Cummings addc7c53b2 Merge branch '5.8.x' into 6.2.x
Closes gh-15985
2024-10-24 11:56:16 -06:00
DingHao 1399a82ea9 Return Null Request When Cookie Is Malformed
Closes gh-15905
2024-10-24 10:55:36 -07:00
Steve Riesenberg 3592253b8e Merge branch '5.8.x' into 6.2.x 2024-10-24 11:16:49 -05:00
github-actions[bot] 0b83830fb3 Update Antora Spring UI to v0.4.17 2024-10-24 11:14:43 -05:00
github-actions[bot] ec33e40748 Update Antora Spring UI to v0.4.17 2024-10-24 11:12:30 -05:00
Scott Murphy Heiberg 18dba34bde Make RequestMatcherDelegatingAuthorizationManager Post-Processable
Closes gh-15948
2024-10-23 15:15:10 -07:00
Steve Riesenberg e9bbe31b1a Merge branch '5.8.x' into 6.2.x 2024-10-21 13:52:08 -05:00
github-actions[bot] f9f533499b Next development version 2024-10-21 17:51:39 +00:00
github-actions[bot] 7209155482 Next development version 2024-10-21 17:42:46 +00:00
github-actions[bot] 72a208e02c Release 5.8.15 2024-10-21 17:16:06 +00:00
github-actions[bot] 77afcd3e53 Release 6.2.7 2024-10-21 17:11:06 +00:00
Rob Winch 0e257b56ce Add Firewall for WebFlux
Closes gh-15967
2024-10-21 09:42:24 -05:00
Rob Winch 542071b1f8 Merge Add Firewall for WebFlux
Closes gh-15967
2024-10-21 08:56:42 -05:00
Rob Winch 4ce7cde155 Add Firewall for WebFlux
Closes gh-15967
2024-10-21 08:46:13 -05:00
dependabot[bot] ee5e11a294 Bump org.springframework.data:spring-data-bom
Bumps [org.springframework.data:spring-data-bom](https://github.com/spring-projects/spring-data-bom) from 2023.1.10 to 2023.1.11.
- [Release notes](https://github.com/spring-projects/spring-data-bom/releases)
- [Commits](https://github.com/spring-projects/spring-data-bom/compare/2023.1.10...2023.1.11)

---
updated-dependencies:
- dependency-name: org.springframework.data:spring-data-bom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-20 20:30:49 -07:00
Steve Riesenberg a504b2f125 Merge branch '5.8.x' into 6.2.x 2024-10-18 12:17:06 -05:00
dependabot[bot] 52438b4973 Bump org.springframework:spring-framework-bom from 6.1.13 to 6.1.14
Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 6.1.13 to 6.1.14.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v6.1.13...v6.1.14)

---
updated-dependencies:
- dependency-name: org.springframework:spring-framework-bom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-17 21:15:29 -07:00
dependabot[bot] adf1755064 Bump org.springframework.ldap:spring-ldap-core from 3.2.6 to 3.2.7
Bumps [org.springframework.ldap:spring-ldap-core](https://github.com/spring-projects/spring-ldap) from 3.2.6 to 3.2.7.
- [Release notes](https://github.com/spring-projects/spring-ldap/releases)
- [Changelog](https://github.com/spring-projects/spring-ldap/blob/main/changelog.txt)
- [Commits](https://github.com/spring-projects/spring-ldap/compare/3.2.6...3.2.7)

---
updated-dependencies:
- dependency-name: org.springframework.ldap:spring-ldap-core
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-17 21:03:31 -07:00
dependabot[bot] c66ee4a21a Bump org.springframework.ldap:spring-ldap-core from 2.4.1 to 2.4.2
Bumps [org.springframework.ldap:spring-ldap-core](https://github.com/spring-projects/spring-ldap) from 2.4.1 to 2.4.2.
- [Release notes](https://github.com/spring-projects/spring-ldap/releases)
- [Changelog](https://github.com/spring-projects/spring-ldap/blob/main/changelog.txt)
- [Commits](https://github.com/spring-projects/spring-ldap/compare/2.4.1...2.4.2)

---
updated-dependencies:
- dependency-name: org.springframework.ldap:spring-ldap-core
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-17 20:35:06 -07:00
dependabot[bot] 517ea7f42a Bump io.projectreactor.tools:blockhound
Bumps [io.projectreactor.tools:blockhound](https://github.com/reactor/BlockHound) from 1.0.9.RELEASE to 1.0.10.RELEASE.
- [Release notes](https://github.com/reactor/BlockHound/releases)
- [Commits](https://github.com/reactor/BlockHound/compare/1.0.9.RELEASE...1.0.10.RELEASE)

---
updated-dependencies:
- dependency-name: io.projectreactor.tools:blockhound
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-15 20:59:35 -07:00
dependabot[bot] ea3cc42f0b Bump io.projectreactor:reactor-bom from 2023.0.10 to 2023.0.11
Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2023.0.10 to 2023.0.11.
- [Release notes](https://github.com/reactor/reactor/releases)
- [Commits](https://github.com/reactor/reactor/compare/2023.0.10...2023.0.11)

---
updated-dependencies:
- dependency-name: io.projectreactor:reactor-bom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-15 20:31:09 -07:00
dependabot[bot] beecb2e06b Bump io.micrometer:micrometer-observation from 1.12.10 to 1.12.11
Bumps [io.micrometer:micrometer-observation](https://github.com/micrometer-metrics/micrometer) from 1.12.10 to 1.12.11.
- [Release notes](https://github.com/micrometer-metrics/micrometer/releases)
- [Commits](https://github.com/micrometer-metrics/micrometer/compare/v1.12.10...v1.12.11)

---
updated-dependencies:
- dependency-name: io.micrometer:micrometer-observation
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-14 21:16:13 -07:00
github-actions[bot] 49e39144d7 Bump Gradle Wrapper from 8.10.1 to 8.10.2 2024-10-14 13:14:02 -07:00
github-actions[bot] f082494e1b Merge branch '5.8.x' into 6.2.x 2024-10-14 01:09:39 +00:00
dependabot[bot] fe79766aa2 Bump @antora/collector-extension in /docs
---
updated-dependencies:
- dependency-name: "@antora/collector-extension"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-13 18:08:50 -07:00
dependabot[bot] ef70561ac9 Bump @antora/collector-extension in /docs
---
updated-dependencies:
- dependency-name: "@antora/collector-extension"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-13 18:03:15 -07:00
dependabot[bot] 48af71ae5e Bump io.mockk:mockk from 1.13.12 to 1.13.13
Bumps [io.mockk:mockk](https://github.com/mockk/mockk) from 1.13.12 to 1.13.13.
- [Release notes](https://github.com/mockk/mockk/releases)
- [Commits](https://github.com/mockk/mockk/compare/1.13.12...1.13.13)

---
updated-dependencies:
- dependency-name: io.mockk:mockk
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-09 21:05:06 -07:00
dependabot[bot] 73ee0cf7ec Bump org.junit:junit-bom from 5.10.4 to 5.10.5
Bumps [org.junit:junit-bom](https://github.com/junit-team/junit5) from 5.10.4 to 5.10.5.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.10.4...r5.10.5)

---
updated-dependencies:
- dependency-name: org.junit:junit-bom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-06 20:29:49 -07:00
Josh Cummings c1857c0308 Fix Formatting
Issue gh-15771
2024-09-30 16:19:26 -07:00
chao.wang 690e012fb1 Improve OidcBackChannelLogoutTokenValidator error when provider issuer is missing
Closes gh-15771
2024-09-30 16:19:26 -07:00
Tran Ngoc Nhan f7b85ed314 Fix Broken Resource Server Doc Links 2024-09-26 13:08:12 -06:00
dependabot[bot] 8b63817f02 Bump org.junit:junit-bom from 5.10.3 to 5.10.4
Bumps [org.junit:junit-bom](https://github.com/junit-team/junit5) from 5.10.3 to 5.10.4.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.10.3...r5.10.4)

---
updated-dependencies:
- dependency-name: org.junit:junit-bom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-24 21:01:50 -07:00
dependabot[bot] 95cee40224 Bump @springio/asciidoctor-extensions in /docs
Bumps [@springio/asciidoctor-extensions](https://github.com/spring-io/asciidoctor-extensions) from 1.0.0-alpha.13 to 1.0.0-alpha.14.
- [Changelog](https://github.com/spring-io/asciidoctor-extensions/blob/main/CHANGELOG.adoc)
- [Commits](https://github.com/spring-io/asciidoctor-extensions/compare/v1.0.0-alpha.13...v1.0.0-alpha.14)

---
updated-dependencies:
- dependency-name: "@springio/asciidoctor-extensions"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-22 18:06:59 -07:00
github-actions[bot] bcf0a7f55d Merge branch '5.8.x' into 6.2.x 2024-09-23 00:55:51 +00:00
dependabot[bot] 755e9f2f69 Bump @springio/asciidoctor-extensions in /docs
Bumps [@springio/asciidoctor-extensions](https://github.com/spring-io/asciidoctor-extensions) from 1.0.0-alpha.13 to 1.0.0-alpha.14.
- [Changelog](https://github.com/spring-io/asciidoctor-extensions/blob/main/CHANGELOG.adoc)
- [Commits](https://github.com/spring-io/asciidoctor-extensions/compare/v1.0.0-alpha.13...v1.0.0-alpha.14)

---
updated-dependencies:
- dependency-name: "@springio/asciidoctor-extensions"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-22 17:55:03 -07:00
Steve Riesenberg 551c483ee6 Merge branch '5.8.x' into 6.2.x
Closes gh-15828
2024-09-19 16:22:37 -05:00
Steve Riesenberg 7b7a3044cf Polish gh-15533 2024-09-19 16:13:03 -05:00
陈圳佳 8a791028b1 Fix array values of additionalParameters
Closes gh-15468
2024-09-19 16:10:00 -05:00
Josh Cummings 1a0203ecf6 Fix AuthorizationManager API Documentation Typo
Closes gh-15704
2024-09-17 18:25:08 -06:00
github-actions[bot] c4ce3f61fd Bump Gradle Wrapper from 8.10 to 8.10.1 2024-09-17 07:13:15 -03:00
dependabot[bot] 8a48c6903a Bump org.springframework.data:spring-data-bom from 2023.1.9 to 2023.1.10
Bumps [org.springframework.data:spring-data-bom](https://github.com/spring-projects/spring-data-bom) from 2023.1.9 to 2023.1.10.
- [Release notes](https://github.com/spring-projects/spring-data-bom/releases)
- [Commits](https://github.com/spring-projects/spring-data-bom/compare/2023.1.9...2023.1.10)

---
updated-dependencies:
- dependency-name: org.springframework.data:spring-data-bom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-15 21:01:04 -07:00
Steve Riesenberg 1782f17e7f Merge branch '5.8.x' into 6.2.x
Closes gh-15807
2024-09-13 16:08:04 -05:00
Steve Riesenberg 0a4eb0f09a Update credential erasure examples
Closes gh-15683
2024-09-13 16:07:47 -05:00
dependabot[bot] f3061d9c1a Bump org.springframework:spring-framework-bom from 6.1.12 to 6.1.13
Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 6.1.12 to 6.1.13.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v6.1.12...v6.1.13)

---
updated-dependencies:
- dependency-name: org.springframework:spring-framework-bom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-12 20:43:14 -07:00
dependabot[bot] e86dac8a64 Bump io.projectreactor:reactor-bom from 2023.0.9 to 2023.0.10
Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2023.0.9 to 2023.0.10.
- [Release notes](https://github.com/reactor/reactor/releases)
- [Commits](https://github.com/reactor/reactor/compare/2023.0.9...2023.0.10)

---
updated-dependencies:
- dependency-name: io.projectreactor:reactor-bom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-10 20:55:48 -07:00
Tran Ngoc Nhan b9f051d15b docs: update 2024-09-10 11:50:11 -07:00
dependabot[bot] 44afdcec64 Bump io.micrometer:micrometer-observation from 1.12.9 to 1.12.10
Bumps [io.micrometer:micrometer-observation](https://github.com/micrometer-metrics/micrometer) from 1.12.9 to 1.12.10.
- [Release notes](https://github.com/micrometer-metrics/micrometer/releases)
- [Commits](https://github.com/micrometer-metrics/micrometer/compare/v1.12.9...v1.12.10)

---
updated-dependencies:
- dependency-name: io.micrometer:micrometer-observation
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-09 20:39:13 -07:00
Marcus Hert Da Coregio a268b78473 Merge branch '5.8.x' into 6.2.x
Closes gh-15766
2024-09-09 08:53:39 -03:00
github-actions[bot] b2c0cc3809 Merge branch '5.8.x' into 6.2.x 2024-09-09 11:51:53 +00:00
Marcus Hert Da Coregio a0e6c17512 Do not log exception if CasJackson2Module is not present
Closes gh-15749
2024-09-09 08:32:40 -03:00
dependabot[bot] e251779b31 Bump @springio/asciidoctor-extensions in /docs
Bumps [@springio/asciidoctor-extensions](https://github.com/spring-io/asciidoctor-extensions) from 1.0.0-alpha.12 to 1.0.0-alpha.13.
- [Changelog](https://github.com/spring-io/asciidoctor-extensions/blob/main/CHANGELOG.adoc)
- [Commits](https://github.com/spring-io/asciidoctor-extensions/compare/v1.0.0-alpha.12...v1.0.0-alpha.13)

---
updated-dependencies:
- dependency-name: "@springio/asciidoctor-extensions"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-08 18:12:55 -07:00
dependabot[bot] 51b0f1058b Bump @springio/asciidoctor-extensions in /docs
Bumps [@springio/asciidoctor-extensions](https://github.com/spring-io/asciidoctor-extensions) from 1.0.0-alpha.12 to 1.0.0-alpha.13.
- [Changelog](https://github.com/spring-io/asciidoctor-extensions/blob/main/CHANGELOG.adoc)
- [Commits](https://github.com/spring-io/asciidoctor-extensions/compare/v1.0.0-alpha.12...v1.0.0-alpha.13)

---
updated-dependencies:
- dependency-name: "@springio/asciidoctor-extensions"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-08 17:51:19 -07:00
dependabot[bot] c44eed0d15 Bump org-eclipse-jetty from 11.0.23 to 11.0.24
Bumps `org-eclipse-jetty` from 11.0.23 to 11.0.24.

Updates `org.eclipse.jetty:jetty-server` from 11.0.23 to 11.0.24

Updates `org.eclipse.jetty:jetty-servlet` from 11.0.23 to 11.0.24

---
updated-dependencies:
- dependency-name: org.eclipse.jetty:jetty-server
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.eclipse.jetty:jetty-servlet
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-03 20:55:25 -07:00
dependabot[bot] 7ad6f38d5b Bump org-eclipse-jetty from 9.4.55.v20240627 to 9.4.56.v20240826
Bumps `org-eclipse-jetty` from 9.4.55.v20240627 to 9.4.56.v20240826.

Updates `org.eclipse.jetty:jetty-server` from 9.4.55.v20240627 to 9.4.56.v20240826

Updates `org.eclipse.jetty:jetty-servlet` from 9.4.55.v20240627 to 9.4.56.v20240826

---
updated-dependencies:
- dependency-name: org.eclipse.jetty:jetty-server
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.eclipse.jetty:jetty-servlet
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-03 20:40:27 -07:00
Josh Cummings b22061d0b6 Merge branch '5.8.x' into 6.2.x 2024-09-03 16:33:22 -06:00
Josh Cummings 97cefa6830 Update Formatting
Issue gh-15714
2024-09-03 15:32:59 -07:00
tugjg f836efb912 Address unnecessary method invocation
Closes gh-15714
2024-09-03 15:32:59 -07:00
Bryan Kelly 69231c5853 Avoid running the VerifyDependenciesVersionsTask and CheckExpecedBranchVersionTask when inputs are stable #15713 2024-09-02 08:45:48 -03:00
Hero Wanders 2ba9b6821a Fix OIDC Logout Code Snippets 2024-08-26 15:32:35 -07:00
Hero Wanders f372f5cf52 Replace OidcSessionStrategy References with OidcSessionRegistry 2024-08-26 15:32:35 -07:00
Junhyunny 47723f6d39 Fix code format in OIDC Logout docs 2024-08-26 15:14:02 -07:00
dependabot[bot] c3a5cf54d4 Bump jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api
Bumps [jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api](https://github.com/eclipse-ee4j/jstl-api) from 3.0.1 to 3.0.2.
- [Release notes](https://github.com/eclipse-ee4j/jstl-api/releases)
- [Commits](https://github.com/eclipse-ee4j/jstl-api/commits)

---
updated-dependencies:
- dependency-name: jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-26 12:40:49 -07:00
Josh Cummings a3b88a8d4b Enable Runtime Method Parameter Reflection
Several method security tests rely on method parameters
being preserved, in order to demonstrate the difference
between relying on runtime reflection and using the @P
annotation.

Closes gh-15680
2024-08-22 19:44:11 -06:00
Josh Cummings 3ee5a96e53 Merge branch '5.8.x' into 6.2.x
Closes gh-15675
2024-08-22 12:24:56 -06:00
Josh Cummings 5c604b95fb Correct PostFilterAuthorizationMethodInterceptor Target Type
Previously, `postFilterAuthorizationMethodInterceptor` mistakenly
was published as an `Advisor`. Because `MethodSecurityAdvisorRegistrar`
re-publishes each pre/post annotation interceptor also as an `Advisor`,
this resulted in a duplicate advisor for `@PostFilter`.

Closes gh-15651
2024-08-22 12:10:25 -06:00
Marcus Hert Da Coregio c06543daf3 Merge branch '5.8.x' into 6.2.x 2024-08-22 11:06:19 -03:00
Marcus Hert Da Coregio e92a945a2d Replace Env Variable with Expression
Issue gh-15659
2024-08-22 10:17:55 -03:00
Steve Riesenberg fb054198af Migrate slack notifications to GChat (2nd attempt)
Closes gh-15667
2024-08-21 16:16:50 -05:00
Steve Riesenberg 035f86bdb3 Merge branch '5.8.x' into 6.2.x
Closes gh-15667
2024-08-21 15:45:39 -05:00
Steve Riesenberg 92809cef01 Migrate slack notifications to GChat (2nd attempt)
Closes gh-15503
2024-08-21 15:44:44 -05:00
dependabot[bot] 439b797eb8 Bump org-eclipse-jetty from 11.0.22 to 11.0.23
Bumps `org-eclipse-jetty` from 11.0.22 to 11.0.23.

Updates `org.eclipse.jetty:jetty-server` from 11.0.22 to 11.0.23

Updates `org.eclipse.jetty:jetty-servlet` from 11.0.22 to 11.0.23

---
updated-dependencies:
- dependency-name: org.eclipse.jetty:jetty-server
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.eclipse.jetty:jetty-servlet
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-20 21:02:01 -07:00
github-actions[bot] f4585d8e4a Bump Gradle Wrapper from 8.7 to 8.10 2024-08-20 09:40:53 -03:00
Marcus Hert Da Coregio 83eceea58f Merge branch '5.8.x' into 6.2.x 2024-08-20 09:22:51 -03:00
github-actions[bot] 097c927d8d Next development version 2024-08-19 21:10:35 +00:00
github-actions[bot] 38093090c2 Next development version 2024-08-19 21:06:57 +00:00
github-actions[bot] 0115e8aea1 Release 5.8.14 2024-08-19 18:22:56 +00:00
github-actions[bot] 182cbbbbb7 Release 6.2.6 2024-08-19 18:22:43 +00:00
Daniel Garnier-Moiroux b92ed92548 Fix checkstyle errors with @Deprecated 2024-08-19 10:55:28 -03:00
Daniel Garnier-Moiroux 79fb0113c8 Bump io-spring-javaformat from 0.0.42 to 0.0.43
Bumps `io-spring-javaformat` from 0.0.42 to 0.0.43.

Updates `io.spring.javaformat:spring-javaformat-checkstyle` from 0.0.42 to 0.0.43
- [Release notes](https://github.com/spring-io/spring-javaformat/releases)
- [Commits](spring-io/spring-javaformat@v0.0.42...v0.0.43)

Updates `io.spring.javaformat:spring-javaformat-gradle-plugin` from 0.0.42 to 0.0.43
- [Release notes](https://github.com/spring-io/spring-javaformat/releases)
- [Commits](spring-io/spring-javaformat@v0.0.42...v0.0.43)

---
updated-dependencies:
- dependency-name: io.spring.javaformat:spring-javaformat-checkstyle
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: io.spring.javaformat:spring-javaformat-gradle-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

---
Manual updates:
- Adhere to rule where `@Deprecated` annotations and `@deprecated` javadoc comments MUST
  be used together

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-19 09:11:05 -03:00
dependabot[bot] 6118ef696f Bump org.apache.maven:maven-resolver-provider from 3.9.8 to 3.9.9
Bumps [org.apache.maven:maven-resolver-provider](https://github.com/apache/maven) from 3.9.8 to 3.9.9.
- [Release notes](https://github.com/apache/maven/releases)
- [Commits](https://github.com/apache/maven/compare/maven-3.9.8...maven-3.9.9)

---
updated-dependencies:
- dependency-name: org.apache.maven:maven-resolver-provider
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-18 20:45:35 -07:00
dependabot[bot] 23d8601db1 Bump org.springframework.data:spring-data-bom from 2023.1.8 to 2023.1.9
Bumps [org.springframework.data:spring-data-bom](https://github.com/spring-projects/spring-data-bom) from 2023.1.8 to 2023.1.9.
- [Release notes](https://github.com/spring-projects/spring-data-bom/releases)
- [Commits](https://github.com/spring-projects/spring-data-bom/compare/2023.1.8...2023.1.9)

---
updated-dependencies:
- dependency-name: org.springframework.data:spring-data-bom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-18 20:45:12 -07:00
dependabot[bot] f10a9b8b33 Bump @antora/collector-extension in /docs
---
updated-dependencies:
- dependency-name: "@antora/collector-extension"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-18 17:43:42 -07:00
dependabot[bot] 2923bc161d Bump @springio/antora-extensions from 1.13.1 to 1.14.2 in /docs
Bumps [@springio/antora-extensions](https://github.com/spring-io/antora-extensions) from 1.13.1 to 1.14.2.
- [Changelog](https://github.com/spring-io/antora-extensions/blob/main/CHANGELOG.adoc)
- [Commits](https://github.com/spring-io/antora-extensions/compare/v1.13.1...v1.14.2)

---
updated-dependencies:
- dependency-name: "@springio/antora-extensions"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-18 17:43:29 -07:00
github-actions[bot] cedd068851 Merge branch '5.8.x' into 6.2.x 2024-08-19 00:35:55 +00:00
dependabot[bot] 6b69edb9a7 Bump @antora/collector-extension in /docs
---
updated-dependencies:
- dependency-name: "@antora/collector-extension"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-18 17:35:26 -07:00
dependabot[bot] 0ba26176db Bump @springio/antora-extensions from 1.13.1 to 1.14.2 in /docs
Bumps [@springio/antora-extensions](https://github.com/spring-io/antora-extensions) from 1.13.1 to 1.14.2.
- [Changelog](https://github.com/spring-io/antora-extensions/blob/main/CHANGELOG.adoc)
- [Commits](https://github.com/spring-io/antora-extensions/compare/v1.13.1...v1.14.2)

---
updated-dependencies:
- dependency-name: "@springio/antora-extensions"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-18 17:35:04 -07:00
dependabot[bot] e4b38bbfab Bump org.springframework.ldap:spring-ldap-core from 3.2.4 to 3.2.6
Bumps [org.springframework.ldap:spring-ldap-core](https://github.com/spring-projects/spring-ldap) from 3.2.4 to 3.2.6.
- [Release notes](https://github.com/spring-projects/spring-ldap/releases)
- [Changelog](https://github.com/spring-projects/spring-ldap/blob/main/changelog.txt)
- [Commits](https://github.com/spring-projects/spring-ldap/compare/3.2.4...3.2.6)

---
updated-dependencies:
- dependency-name: org.springframework.ldap:spring-ldap-core
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-15 20:53:26 -07:00
github-actions[bot] 62d5c61cfa Merge branch '5.8.x' into 6.2.x 2024-08-15 03:33:53 +00:00
dependabot[bot] 85775fe794 Bump org.springframework:spring-framework-bom from 5.3.37 to 5.3.39
Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 5.3.37 to 5.3.39.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v5.3.37...v5.3.39)

---
updated-dependencies:
- dependency-name: org.springframework:spring-framework-bom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-14 20:33:05 -07:00
dependabot[bot] 1b8607b5e3 Bump org.springframework:spring-framework-bom from 6.1.11 to 6.1.12
Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 6.1.11 to 6.1.12.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v6.1.11...v6.1.12)

---
updated-dependencies:
- dependency-name: org.springframework:spring-framework-bom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-14 20:27:45 -07:00
github-actions[bot] 5102098b9e Merge branch '5.8.x' into 6.2.x 2024-08-14 04:22:18 +00:00
dependabot[bot] 1ca53164ee Bump io.projectreactor.netty:reactor-netty from 1.0.47 to 1.0.48
Bumps [io.projectreactor.netty:reactor-netty](https://github.com/reactor/reactor-netty) from 1.0.47 to 1.0.48.
- [Release notes](https://github.com/reactor/reactor-netty/releases)
- [Commits](https://github.com/reactor/reactor-netty/compare/v1.0.47...v1.0.48)

---
updated-dependencies:
- dependency-name: io.projectreactor.netty:reactor-netty
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-13 21:21:27 -07:00
github-actions[bot] a5ab4e32ef Merge branch '5.8.x' into 6.2.x 2024-08-14 04:06:49 +00:00
dependabot[bot] 47a521ca26 Bump io.projectreactor:reactor-bom from 2020.0.46 to 2020.0.47
Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2020.0.46 to 2020.0.47.
- [Release notes](https://github.com/reactor/reactor/releases)
- [Commits](https://github.com/reactor/reactor/compare/2020.0.46...2020.0.47)

---
updated-dependencies:
- dependency-name: io.projectreactor:reactor-bom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-13 21:06:03 -07:00
dependabot[bot] 4da172be00 Bump io.projectreactor:reactor-bom from 2023.0.8 to 2023.0.9
Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2023.0.8 to 2023.0.9.
- [Release notes](https://github.com/reactor/reactor/releases)
- [Commits](https://github.com/reactor/reactor/compare/2023.0.8...2023.0.9)

---
updated-dependencies:
- dependency-name: io.projectreactor:reactor-bom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-13 20:54:31 -07:00
dependabot[bot] 86fc752afa Bump io.micrometer:micrometer-observation from 1.12.8 to 1.12.9
Bumps [io.micrometer:micrometer-observation](https://github.com/micrometer-metrics/micrometer) from 1.12.8 to 1.12.9.
- [Release notes](https://github.com/micrometer-metrics/micrometer/releases)
- [Commits](https://github.com/micrometer-metrics/micrometer/compare/v1.12.8...v1.12.9)

---
updated-dependencies:
- dependency-name: io.micrometer:micrometer-observation
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-12 21:06:10 -07:00
dependabot[bot] 4e84e9f1ba Bump jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api
Bumps [jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api](https://github.com/eclipse-ee4j/jstl-api) from 3.0.0 to 3.0.1.
- [Release notes](https://github.com/eclipse-ee4j/jstl-api/releases)
- [Commits](https://github.com/eclipse-ee4j/jstl-api/commits)

---
updated-dependencies:
- dependency-name: jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-12 20:58:58 -07:00
dependabot[bot] ccc36f967d Bump org.slf4j:slf4j-api from 2.0.15 to 2.0.16
Bumps org.slf4j:slf4j-api from 2.0.15 to 2.0.16.

---
updated-dependencies:
- dependency-name: org.slf4j:slf4j-api
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-11 20:31:19 -07:00
dependabot[bot] 0c931afa6a Bump @springio/antora-extensions from 1.13.0 to 1.13.1 in /docs
Bumps [@springio/antora-extensions](https://github.com/spring-io/antora-extensions) from 1.13.0 to 1.13.1.
- [Changelog](https://github.com/spring-io/antora-extensions/blob/main/CHANGELOG.adoc)
- [Commits](https://github.com/spring-io/antora-extensions/compare/v1.13.0...v1.13.1)

---
updated-dependencies:
- dependency-name: "@springio/antora-extensions"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-11 17:35:20 -07:00
dependabot[bot] d2c430b752 Bump @antora/collector-extension in /docs
---
updated-dependencies:
- dependency-name: "@antora/collector-extension"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-11 17:34:50 -07:00
github-actions[bot] 4887eff69e Merge branch '5.8.x' into 6.2.x 2024-08-12 00:30:23 +00:00
dependabot[bot] 751c6a762a Bump @springio/antora-extensions from 1.13.0 to 1.13.1 in /docs
Bumps [@springio/antora-extensions](https://github.com/spring-io/antora-extensions) from 1.13.0 to 1.13.1.
- [Changelog](https://github.com/spring-io/antora-extensions/blob/main/CHANGELOG.adoc)
- [Commits](https://github.com/spring-io/antora-extensions/compare/v1.13.0...v1.13.1)

---
updated-dependencies:
- dependency-name: "@springio/antora-extensions"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-11 17:30:01 -07:00
dependabot[bot] 7d7b59bea4 Bump @antora/collector-extension in /docs
---
updated-dependencies:
- dependency-name: "@antora/collector-extension"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-11 17:29:17 -07:00
Steve Riesenberg 5be8322f7a Merge branch '5.8.x' into 6.2.x
# Conflicts:
#	.github/workflows/continuous-integration-workflow.yml
#	.github/workflows/update-scheduled-release-version.yml
2024-08-09 10:48:08 -05:00
Steve Riesenberg 7149622efd Revert "Migrate slack notifications to GChat"
This reverts commits:
- 5d335ccf8d
- a5b1dec99a
2024-08-09 10:39:34 -05:00
dependabot[bot] 41bec3c838 Bump org.slf4j:slf4j-api from 2.0.14 to 2.0.15
Bumps org.slf4j:slf4j-api from 2.0.14 to 2.0.15.

---
updated-dependencies:
- dependency-name: org.slf4j:slf4j-api
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-08 20:26:02 -07:00
dependabot[bot] 1490061bc1 Bump org-apache-maven-resolver from 1.9.21 to 1.9.22
Bumps `org-apache-maven-resolver` from 1.9.21 to 1.9.22.

Updates `org.apache.maven.resolver:maven-resolver-connector-basic` from 1.9.21 to 1.9.22
- [Release notes](https://github.com/apache/maven-resolver/releases)
- [Commits](https://github.com/apache/maven-resolver/compare/maven-resolver-1.9.21...maven-resolver-1.9.22)

Updates `org.apache.maven.resolver:maven-resolver-impl` from 1.9.21 to 1.9.22
- [Release notes](https://github.com/apache/maven-resolver/releases)
- [Commits](https://github.com/apache/maven-resolver/compare/maven-resolver-1.9.21...maven-resolver-1.9.22)

Updates `org.apache.maven.resolver:maven-resolver-transport-http` from 1.9.21 to 1.9.22

---
updated-dependencies:
- dependency-name: org.apache.maven.resolver:maven-resolver-connector-basic
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.apache.maven.resolver:maven-resolver-impl
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.apache.maven.resolver:maven-resolver-transport-http
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-08 20:24:34 -07:00
dependabot[bot] f1bcdd2a41 Bump org.slf4j:slf4j-api from 2.0.13 to 2.0.14
Bumps org.slf4j:slf4j-api from 2.0.13 to 2.0.14.

---
updated-dependencies:
- dependency-name: org.slf4j:slf4j-api
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-06 20:23:55 -07:00
github-actions[bot] 1b708a1aec Merge branch '5.8.x' into 6.2.x 2024-08-05 01:29:19 +00:00
dependabot[bot] e9e0ec89fb Bump @springio/antora-extensions from 1.12.0 to 1.13.0 in /docs
Bumps [@springio/antora-extensions](https://github.com/spring-io/antora-extensions) from 1.12.0 to 1.13.0.
- [Changelog](https://github.com/spring-io/antora-extensions/blob/main/CHANGELOG.adoc)
- [Commits](https://github.com/spring-io/antora-extensions/compare/v1.12.0...v1.13.0)

---
updated-dependencies:
- dependency-name: "@springio/antora-extensions"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-04 18:28:27 -07:00
github-actions[bot] 154a540682 Merge branch '5.8.x' into 6.2.x 2024-08-05 01:14:00 +00:00
dependabot[bot] 596c5f9b0c Bump @springio/asciidoctor-extensions in /docs
Bumps [@springio/asciidoctor-extensions](https://github.com/spring-io/asciidoctor-extensions) from 1.0.0-alpha.11 to 1.0.0-alpha.12.
- [Changelog](https://github.com/spring-io/asciidoctor-extensions/blob/main/CHANGELOG.adoc)
- [Commits](https://github.com/spring-io/asciidoctor-extensions/compare/v1.0.0-alpha.11...v1.0.0-alpha.12)

---
updated-dependencies:
- dependency-name: "@springio/asciidoctor-extensions"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-04 18:13:16 -07:00
dependabot[bot] a6c4031b6b Bump @springio/antora-extensions from 1.12.0 to 1.13.0 in /docs
Bumps [@springio/antora-extensions](https://github.com/spring-io/antora-extensions) from 1.12.0 to 1.13.0.
- [Changelog](https://github.com/spring-io/antora-extensions/blob/main/CHANGELOG.adoc)
- [Commits](https://github.com/spring-io/antora-extensions/compare/v1.12.0...v1.13.0)

---
updated-dependencies:
- dependency-name: "@springio/antora-extensions"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-04 17:40:29 -07:00
dependabot[bot] 64b938ad80 Bump @springio/asciidoctor-extensions in /docs
Bumps [@springio/asciidoctor-extensions](https://github.com/spring-io/asciidoctor-extensions) from 1.0.0-alpha.11 to 1.0.0-alpha.12.
- [Changelog](https://github.com/spring-io/asciidoctor-extensions/blob/main/CHANGELOG.adoc)
- [Commits](https://github.com/spring-io/asciidoctor-extensions/compare/v1.0.0-alpha.11...v1.0.0-alpha.12)

---
updated-dependencies:
- dependency-name: "@springio/asciidoctor-extensions"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-04 17:31:57 -07:00
Steve Riesenberg d2ebde2418 Revert merged file
Issue gh-15503
2024-07-31 17:04:09 -05:00
Steve Riesenberg 5895f19a73 Merge branch '5.8.x' into 6.2.x 2024-07-31 17:00:07 -05:00
Steve Riesenberg 5d335ccf8d Fix job dependencies
Issue gh-15503
2024-07-31 16:59:35 -05:00
Josh Cummings 8d43f1bd7d Merge branch '5.8.x' into 6.2.x 2024-07-31 15:48:44 -06:00
Josh Cummings 5cdcdc9bcb Suppress Node Files From Nohttp Analysis
Given that we have no control over the contents of
third-party code, it isn't helpful to have nohttp
generate errors for the usage of http:// in that code.
2024-07-31 15:48:28 -06:00
Steve Riesenberg dae2674dbe Merge branch '5.8.x' into 6.2.x
Closes gh-15504
2024-07-31 15:42:08 -05:00
Steve Riesenberg a5b1dec99a Migrate slack notifications to GChat
Closes gh-15503
2024-07-31 14:59:04 -05:00
Josh Cummings e51507e32d Polish Inline Code Formatting 2024-07-29 14:08:01 -07:00
Junhyunny bfee6927c2 Correct Explanation for HttpSessionCsrfTokenRepository 2024-07-29 14:08:01 -07:00
Josh Cummings 9a83986b91 Merge branch '5.8.x' into 6.2.x
Closes gh-15494
2024-07-29 15:04:49 -06:00
Roman Zabaluev ea2ec04633 Clarify url Parameter Usage in AD Provider
Closes gh-7760
2024-07-29 14:04:13 -07:00
baezzys 3d4bcf1b44 fix: Restrict automatic CORS configuration to UrlBasedCorsConfigurationSource
- Update CORS configuration logic to automatically enable .cors() only if a UrlBasedCorsConfigurationSource bean is present.
- Modify applyCorsIfAvailable method to check for UrlBasedCorsConfigurationSource instances.
2024-07-29 14:55:55 -03:00
dependabot[bot] 0190763a02 Bump antora from 3.2.0-alpha.5 to 3.2.0-alpha.6 in /docs
Bumps [antora](https://gitlab.com/antora/antora) from 3.2.0-alpha.5 to 3.2.0-alpha.6.
- [Changelog](https://gitlab.com/antora/antora/blob/main/CHANGELOG.adoc)
- [Commits](https://gitlab.com/antora/antora/compare/v3.2.0-alpha.5...v3.2.0-alpha.6)

---
updated-dependencies:
- dependency-name: antora
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-28 18:06:53 -07:00
dependabot[bot] f27d7d28f4 Bump @antora/collector-extension in /docs
---
updated-dependencies:
- dependency-name: "@antora/collector-extension"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-28 18:06:45 -07:00
github-actions[bot] aeec67e8cb Merge branch '5.8.x' into 6.2.x 2024-07-29 00:40:15 +00:00
github-actions[bot] cf8c6cd051 Merge branch '5.8.x' into 6.2.x 2024-07-29 00:39:21 +00:00
dependabot[bot] c6bc1ec969 Bump antora from 3.2.0-alpha.5 to 3.2.0-alpha.6 in /docs
Bumps [antora](https://gitlab.com/antora/antora) from 3.2.0-alpha.5 to 3.2.0-alpha.6.
- [Changelog](https://gitlab.com/antora/antora/blob/main/CHANGELOG.adoc)
- [Commits](https://gitlab.com/antora/antora/compare/v3.2.0-alpha.5...v3.2.0-alpha.6)

---
updated-dependencies:
- dependency-name: antora
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-28 17:39:11 -07:00
dependabot[bot] 9ac5ee9bdb Bump @antora/collector-extension in /docs
---
updated-dependencies:
- dependency-name: "@antora/collector-extension"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-28 17:38:32 -07:00
Marcus Hert Da Coregio 82c77adacd Merge branch '5.8.x' into 6.2.x 2024-07-26 10:25:38 -03:00
Marcus Hert Da Coregio 31babecfbd Only Merge PRs from Original Repository 2024-07-26 10:25:30 -03:00
Marcus Hert Da Coregio 8512dab3dc Merge branch '5.8.x' into 6.2.x 2024-07-26 10:19:07 -03:00
Marcus Hert Da Coregio 4c12940304 Change Skip Condition 2024-07-26 10:18:48 -03:00
dependabot[bot] 5089b88db3 Bump com.gradle.develocity from 3.17.5 to 3.17.6
Bumps com.gradle.develocity from 3.17.5 to 3.17.6.

---
updated-dependencies:
- dependency-name: com.gradle.develocity
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-22 21:06:35 -07:00
github-actions[bot] 52414d3832 Merge branch '5.8.x' into 6.2.x 2024-07-23 03:59:34 +00:00
dependabot[bot] bb3738dbfd Bump com.gradle.develocity from 3.17.5 to 3.17.6
Bumps com.gradle.develocity from 3.17.5 to 3.17.6.

---
updated-dependencies:
- dependency-name: com.gradle.develocity
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-22 20:58:46 -07:00
Josh Cummings 4406462346 Merge branch '5.8.x' into 6.2.x
Closes gh-15459
2024-07-22 15:34:08 -06:00
Josh Cummings 5c1a108b8b Remove Stray JavaDoc Statement
As of the 5.2 release, a separate registrar bean was no longer necessary

Closes gh-15425
2024-07-22 15:33:57 -06:00
dependabot[bot] 41e140730b Bump org.jetbrains.kotlin:kotlin-bom from 1.9.24 to 1.9.25
Bumps [org.jetbrains.kotlin:kotlin-bom](https://github.com/JetBrains/kotlin) from 1.9.24 to 1.9.25.
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/v1.9.25/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v1.9.24...v1.9.25)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlin:kotlin-bom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-21 20:19:23 -07:00
dependabot[bot] 93bc82d621 Bump org.jetbrains.kotlin:kotlin-gradle-plugin from 1.9.24 to 1.9.25
Bumps [org.jetbrains.kotlin:kotlin-gradle-plugin](https://github.com/JetBrains/kotlin) from 1.9.24 to 1.9.25.
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/v1.9.25/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v1.9.24...v1.9.25)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlin:kotlin-gradle-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-21 20:18:13 -07:00
github-actions[bot] 06d1c0822c Merge branch '5.8.x' into 6.2.x 2024-07-22 00:43:44 +00:00
dependabot[bot] 1c4a63387b Bump @antora/collector-extension in /docs
---
updated-dependencies:
- dependency-name: "@antora/collector-extension"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-21 17:42:57 -07:00
dependabot[bot] 385011f4b1 Bump @antora/collector-extension in /docs
---
updated-dependencies:
- dependency-name: "@antora/collector-extension"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-21 17:14:00 -07:00
Josh Cummings 3daeeb8789 Merge branch '5.8.x' into 6.2.x
Closes gh-15439
2024-07-18 15:50:58 -06:00
Josh Cummings dab48d25b0 Improve Error Message When Registration Missing
Closes gh-15363
2024-07-18 15:50:41 -06:00
dependabot[bot] 055090c112 Bump io.mockk:mockk from 1.13.11 to 1.13.12
Bumps [io.mockk:mockk](https://github.com/mockk/mockk) from 1.13.11 to 1.13.12.
- [Release notes](https://github.com/mockk/mockk/releases)
- [Commits](https://github.com/mockk/mockk/compare/1.13.11...1.13.12)

---
updated-dependencies:
- dependency-name: io.mockk:mockk
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-15 20:21:01 -07:00
dependabot[bot] be5c072ef2 Bump org.springframework.data:spring-data-bom from 2023.1.7 to 2023.1.8
Bumps [org.springframework.data:spring-data-bom](https://github.com/spring-projects/spring-data-bom) from 2023.1.7 to 2023.1.8.
- [Release notes](https://github.com/spring-projects/spring-data-bom/releases)
- [Commits](https://github.com/spring-projects/spring-data-bom/compare/2023.1.7...2023.1.8)

---
updated-dependencies:
- dependency-name: org.springframework.data:spring-data-bom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-14 20:45:25 -07:00
github-actions[bot] 2ba6562473 Merge branch '5.8.x' into 6.2.x 2024-07-15 01:14:59 +00:00
dependabot[bot] caa325f92e Bump @springio/antora-extensions from 1.11.1 to 1.12.0 in /docs
Bumps [@springio/antora-extensions](https://github.com/spring-io/antora-extensions) from 1.11.1 to 1.12.0.
- [Changelog](https://github.com/spring-io/antora-extensions/blob/main/CHANGELOG.adoc)
- [Commits](https://github.com/spring-io/antora-extensions/compare/v1.11.1...v1.12.0)

---
updated-dependencies:
- dependency-name: "@springio/antora-extensions"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-14 18:14:10 -07:00
github-actions[bot] 2b8ae64f49 Merge branch '5.8.x' into 6.2.x 2024-07-15 00:57:13 +00:00
dependabot[bot] 174e3ef356 Bump @springio/asciidoctor-extensions in /docs
Bumps [@springio/asciidoctor-extensions](https://github.com/spring-io/asciidoctor-extensions) from 1.0.0-alpha.10 to 1.0.0-alpha.11.
- [Changelog](https://github.com/spring-io/asciidoctor-extensions/blob/main/CHANGELOG.adoc)
- [Commits](https://github.com/spring-io/asciidoctor-extensions/compare/v1.0.0-alpha.10...v1.0.0-alpha.11)

---
updated-dependencies:
- dependency-name: "@springio/asciidoctor-extensions"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-14 17:56:21 -07:00
dependabot[bot] 43aaa68bb3 Bump @springio/antora-extensions from 1.11.1 to 1.12.0 in /docs
Bumps [@springio/antora-extensions](https://github.com/spring-io/antora-extensions) from 1.11.1 to 1.12.0.
- [Changelog](https://github.com/spring-io/antora-extensions/blob/main/CHANGELOG.adoc)
- [Commits](https://github.com/spring-io/antora-extensions/compare/v1.11.1...v1.12.0)

---
updated-dependencies:
- dependency-name: "@springio/antora-extensions"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-14 17:47:38 -07:00
dependabot[bot] 083c41244c Bump @springio/asciidoctor-extensions in /docs
Bumps [@springio/asciidoctor-extensions](https://github.com/spring-io/asciidoctor-extensions) from 1.0.0-alpha.10 to 1.0.0-alpha.11.
- [Changelog](https://github.com/spring-io/asciidoctor-extensions/blob/main/CHANGELOG.adoc)
- [Commits](https://github.com/spring-io/asciidoctor-extensions/compare/v1.0.0-alpha.10...v1.0.0-alpha.11)

---
updated-dependencies:
- dependency-name: "@springio/asciidoctor-extensions"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-14 17:35:52 -07:00
Josh Cummings 7422a1134a Allow logout+jwt JWT type
Closes gh-15003
2024-07-12 10:03:40 -07:00
dependabot[bot] 82b6d0806b Bump org.springframework:spring-framework-bom from 6.1.10 to 6.1.11
Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 6.1.10 to 6.1.11.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v6.1.10...v6.1.11)

---
updated-dependencies:
- dependency-name: org.springframework:spring-framework-bom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-11 21:06:14 -07:00
Josh Cummings e1fdc61360 Merge branch '5.8.x' into 6.2.x 2024-07-10 17:12:43 -06:00
Josh Cummings 32e2735f45 Clarify Valid Metadata Locations 2024-07-10 17:10:49 -06:00
Marcus Hert Da Coregio dff7e83e3f Merge branch '5.8.x' into 6.2.x 2024-07-10 12:48:45 -03:00
Marcus Hert Da Coregio 9cfc9bb8a8 Use in:title
Issue gh-14484
2024-07-10 12:48:39 -03:00
github-actions[bot] e95100a179 Merge branch '5.8.x' into 6.2.x 2024-07-10 04:35:51 +00:00
dependabot[bot] 341f58d411 Bump io.projectreactor:reactor-bom from 2020.0.45 to 2020.0.46
Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2020.0.45 to 2020.0.46.
- [Release notes](https://github.com/reactor/reactor/releases)
- [Commits](https://github.com/reactor/reactor/compare/2020.0.45...2020.0.46)

---
updated-dependencies:
- dependency-name: io.projectreactor:reactor-bom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-09 21:35:04 -07:00
github-actions[bot] 3aac1bcdc8 Merge branch '5.8.x' into 6.2.x 2024-07-10 04:20:16 +00:00
dependabot[bot] c00197bdf5 Bump io.projectreactor.netty:reactor-netty from 1.0.46 to 1.0.47
Bumps [io.projectreactor.netty:reactor-netty](https://github.com/reactor/reactor-netty) from 1.0.46 to 1.0.47.
- [Release notes](https://github.com/reactor/reactor-netty/releases)
- [Commits](https://github.com/reactor/reactor-netty/compare/v1.0.46...v1.0.47)

---
updated-dependencies:
- dependency-name: io.projectreactor.netty:reactor-netty
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-09 21:19:30 -07:00
dependabot[bot] 902d60d019 Bump io.projectreactor:reactor-bom from 2023.0.7 to 2023.0.8
Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2023.0.7 to 2023.0.8.
- [Release notes](https://github.com/reactor/reactor/releases)
- [Commits](https://github.com/reactor/reactor/compare/2023.0.7...2023.0.8)

---
updated-dependencies:
- dependency-name: io.projectreactor:reactor-bom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-09 21:13:44 -07:00
Marcus Hert Da Coregio 90ff25337d Merge branch '5.8.x' into 6.2.x 2024-07-09 08:16:48 -03:00
Marcus Hert Da Coregio 01e1813020 Remove single quotes from boolean value
Issue gh-14484
2024-07-09 08:16:06 -03:00
dependabot[bot] b4f3966c79 Bump io.micrometer:micrometer-observation from 1.12.7 to 1.12.8
Bumps [io.micrometer:micrometer-observation](https://github.com/micrometer-metrics/micrometer) from 1.12.7 to 1.12.8.
- [Release notes](https://github.com/micrometer-metrics/micrometer/releases)
- [Commits](https://github.com/micrometer-metrics/micrometer/compare/v1.12.7...v1.12.8)

---
updated-dependencies:
- dependency-name: io.micrometer:micrometer-observation
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-08 21:08:38 -07:00
Seungrae f4cbaaa2dd Fix typos and formatting in documentation
Closes gh-15353
2024-07-08 20:52:06 -06:00
Marcus Hert Da Coregio cd404aaf04 Merge branch '5.8.x' into 6.2.x 2024-07-08 11:09:17 -03:00
Marcus Hert Da Coregio 65c037853e Debug PR event payload
Issue gh-14484
2024-07-08 11:08:36 -03:00
github-actions[bot] a34e24a7c6 Merge branch '5.8.x' into 6.2.x 2024-07-08 04:00:21 +00:00
dependabot[bot] 7fceda4d7b Bump com.github.spullara.mustache.java:compiler from 0.9.13 to 0.9.14
Bumps [com.github.spullara.mustache.java:compiler](https://github.com/spullara/mustache.java) from 0.9.13 to 0.9.14.
- [Commits](https://github.com/spullara/mustache.java/compare/mustache.java-0.9.13...mustache.java-0.9.14)

---
updated-dependencies:
- dependency-name: com.github.spullara.mustache.java:compiler
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-07 21:00:11 -07:00
dependabot[bot] 36f31949e0 Bump org-apache-maven-resolver from 1.9.20 to 1.9.21
Bumps `org-apache-maven-resolver` from 1.9.20 to 1.9.21.

Updates `org.apache.maven.resolver:maven-resolver-connector-basic` from 1.9.20 to 1.9.21
- [Release notes](https://github.com/apache/maven-resolver/releases)
- [Commits](https://github.com/apache/maven-resolver/compare/maven-resolver-1.9.20...maven-resolver-1.9.21)

Updates `org.apache.maven.resolver:maven-resolver-impl` from 1.9.20 to 1.9.21
- [Release notes](https://github.com/apache/maven-resolver/releases)
- [Commits](https://github.com/apache/maven-resolver/compare/maven-resolver-1.9.20...maven-resolver-1.9.21)

Updates `org.apache.maven.resolver:maven-resolver-transport-http` from 1.9.20 to 1.9.21

---
updated-dependencies:
- dependency-name: org.apache.maven.resolver:maven-resolver-connector-basic
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.apache.maven.resolver:maven-resolver-impl
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.apache.maven.resolver:maven-resolver-transport-http
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-07 20:59:39 -07:00
dependabot[bot] 05256da801 Bump com.github.spullara.mustache.java:compiler from 0.9.13 to 0.9.14
Bumps [com.github.spullara.mustache.java:compiler](https://github.com/spullara/mustache.java) from 0.9.13 to 0.9.14.
- [Commits](https://github.com/spullara/mustache.java/compare/mustache.java-0.9.13...mustache.java-0.9.14)

---
updated-dependencies:
- dependency-name: com.github.spullara.mustache.java:compiler
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-07 20:59:36 -07:00
github-actions[bot] a8fe03dad5 Merge branch '5.8.x' into 6.2.x 2024-07-04 04:09:20 +00:00
dependabot[bot] 2bdb57a8a8 Bump org-eclipse-jetty from 9.4.54.v20240208 to 9.4.55.v20240627
Bumps `org-eclipse-jetty` from 9.4.54.v20240208 to 9.4.55.v20240627.

Updates `org.eclipse.jetty:jetty-server` from 9.4.54.v20240208 to 9.4.55.v20240627

Updates `org.eclipse.jetty:jetty-servlet` from 9.4.54.v20240208 to 9.4.55.v20240627

---
updated-dependencies:
- dependency-name: org.eclipse.jetty:jetty-server
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.eclipse.jetty:jetty-servlet
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-03 21:08:34 -07:00
dependabot[bot] 138daec14c Bump org-eclipse-jetty from 11.0.21 to 11.0.22
Bumps `org-eclipse-jetty` from 11.0.21 to 11.0.22.

Updates `org.eclipse.jetty:jetty-server` from 11.0.21 to 11.0.22

Updates `org.eclipse.jetty:jetty-servlet` from 11.0.21 to 11.0.22

---
updated-dependencies:
- dependency-name: org.eclipse.jetty:jetty-server
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.eclipse.jetty:jetty-servlet
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-03 20:32:09 -07:00
Marcus Hert Da Coregio cdb8403ba7 Merge branch '5.8.x' into 6.2.x 2024-07-03 13:48:17 -03:00
Marcus Hert Da Coregio a3095527a0 Label superseded dependabot updates as duplicates
Issue gh-14484
2024-07-03 13:47:35 -03:00
Antoine Rey 99cda31579 Update prerequisites documentation
Raises the minimum version of the Java runtime for Spring
Security from 8 to 17

Closes gh-15323
2024-07-01 17:19:22 -06:00
github-actions[bot] 174435beff Merge branch '5.8.x' into 6.2.x 2024-07-01 04:16:28 +00:00
dependabot[bot] 9189916287 Bump org.skyscreamer:jsonassert from 1.5.1 to 1.5.3
Bumps [org.skyscreamer:jsonassert](https://github.com/skyscreamer/JSONassert) from 1.5.1 to 1.5.3.
- [Release notes](https://github.com/skyscreamer/JSONassert/releases)
- [Changelog](https://github.com/skyscreamer/JSONassert/blob/master/CHANGELOG.md)
- [Commits](https://github.com/skyscreamer/JSONassert/compare/jsonassert-1.5.1...jsonassert-1.5.3)

---
updated-dependencies:
- dependency-name: org.skyscreamer:jsonassert
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-30 21:15:38 -07:00
dependabot[bot] f564385004 Bump org.skyscreamer:jsonassert from 1.5.1 to 1.5.3
Bumps [org.skyscreamer:jsonassert](https://github.com/skyscreamer/JSONassert) from 1.5.1 to 1.5.3.
- [Release notes](https://github.com/skyscreamer/JSONassert/releases)
- [Changelog](https://github.com/skyscreamer/JSONassert/blob/master/CHANGELOG.md)
- [Commits](https://github.com/skyscreamer/JSONassert/compare/jsonassert-1.5.1...jsonassert-1.5.3)

---
updated-dependencies:
- dependency-name: org.skyscreamer:jsonassert
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-30 20:43:21 -07:00
github-actions[bot] 523080c061 Merge branch '5.8.x' into 6.2.x 2024-07-01 01:20:50 +00:00
dependabot[bot] 97e6ac76af Bump antora from 3.2.0-alpha.4 to 3.2.0-alpha.5 in /docs
Bumps [antora](https://gitlab.com/antora/antora) from 3.2.0-alpha.4 to 3.2.0-alpha.5.
- [Changelog](https://gitlab.com/antora/antora/blob/main/CHANGELOG.adoc)
- [Commits](https://gitlab.com/antora/antora/compare/v3.2.0-alpha.4...v3.2.0-alpha.5)

---
updated-dependencies:
- dependency-name: antora
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-30 18:20:05 -07:00
dependabot[bot] 260411acac Bump antora from 3.2.0-alpha.4 to 3.2.0-alpha.5 in /docs
Bumps [antora](https://gitlab.com/antora/antora) from 3.2.0-alpha.4 to 3.2.0-alpha.5.
- [Changelog](https://gitlab.com/antora/antora/blob/main/CHANGELOG.adoc)
- [Commits](https://gitlab.com/antora/antora/compare/v3.2.0-alpha.4...v3.2.0-alpha.5)

---
updated-dependencies:
- dependency-name: antora
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-30 17:48:57 -07:00
Marcus Hert Da Coregio 462ce1ee91 Merge branch '5.8.x' into 6.2.x
Closes gh-15320
2024-06-28 15:33:59 -03:00
Marcus Hert Da Coregio 779030b6cd Document the role of CredentialsContainer
Closes gh-15319
2024-06-28 15:33:34 -03:00
dependabot[bot] ef85ed5460 Bump org.junit:junit-bom from 5.10.2 to 5.10.3
Bumps [org.junit:junit-bom](https://github.com/junit-team/junit5) from 5.10.2 to 5.10.3.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.10.2...r5.10.3)

---
updated-dependencies:
- dependency-name: org.junit:junit-bom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-28 05:44:05 -07:00
Marcus Hert Da Coregio 24f573dd4c Merge branch '5.8.x' into 6.2.x 2024-06-28 09:32:25 -03:00
Marcus Hert Da Coregio 1135ad5a5a Skip checkExpectedBranchVersion task on PR Build workflow
Issue gh-15226
2024-06-28 09:31:48 -03:00
Marcus Hert Da Coregio 87e3c23d3c Merge branch '5.8.x' into 6.2.x
Closes gh-15309
2024-06-27 14:35:45 -03:00
Marcus Hert Da Coregio e3d642ce7c Add Task to Verify Branch Version Matches the Project Version
Closes gh-15226
2024-06-27 14:34:41 -03:00
dependabot[bot] 2c28a396e6 Bump org.apache.maven:maven-resolver-provider from 3.9.7 to 3.9.8
Bumps [org.apache.maven:maven-resolver-provider](https://github.com/apache/maven) from 3.9.7 to 3.9.8.
- [Release notes](https://github.com/apache/maven/releases)
- [Commits](https://github.com/apache/maven/compare/maven-3.9.7...maven-3.9.8)

---
updated-dependencies:
- dependency-name: org.apache.maven:maven-resolver-provider
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-24 12:13:46 -07:00
Josh Cummings c4f70bee4b Fix Broken Link
Closes gh-15288
2024-06-24 13:01:49 -06:00
Marcus Hert Da Coregio 06065761a3 Merge branch '5.8.x' into 6.2.x 2024-06-20 15:48:41 -03:00
github-actions[bot] 92269118e2 Update Antora Spring UI to v0.4.16 2024-06-20 15:48:21 -03:00
github-actions[bot] 00247350d0 Update Antora Spring UI to v0.4.16 2024-06-20 15:47:54 -03:00
dependabot[bot] de87115462 Bump org.springframework:spring-framework-bom from 6.1.9 to 6.1.10
Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 6.1.9 to 6.1.10.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v6.1.9...v6.1.10)

---
updated-dependencies:
- dependency-name: org.springframework:spring-framework-bom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-19 20:52:56 -07:00
Marcus Hert Da Coregio 5bf1ba59ee Merge branch '5.8.x' into 6.2.x 2024-06-17 16:04:03 -03:00
github-actions[bot] aa55e71d96 Next development version 2024-06-17 18:14:31 +00:00
github-actions[bot] 9e2cadb475 Next development version 2024-06-17 16:00:03 +00:00
github-actions[bot] 20332fedb0 Release 5.8.13 2024-06-17 15:22:54 +00:00
github-actions[bot] 2966a7210b Release 6.2.5 2024-06-17 15:22:22 +00:00
100 changed files with 2990 additions and 398 deletions
@@ -113,20 +113,13 @@ jobs:
artifact-path: org/springframework/security/spring-security-core
slack-announcing-id: spring-security-announcing
secrets: inherit
notify_result:
name: Check for failures
send-notification:
name: Send Notification
needs: [ perform-release ]
if: failure()
if: ${{ failure() || cancelled() }}
runs-on: ubuntu-latest
permissions:
actions: read
steps:
- name: Send Slack message
# Workaround while waiting for Gamesight/slack-workflow-status#38 to be fixed
# See https://github.com/Gamesight/slack-workflow-status/issues/38
uses: sjohnr/slack-workflow-status@v1-beta
- name: Send Notification
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@v1
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
slack_webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }}
channel: '#spring-security-ci'
name: 'CI Notifier'
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
@@ -0,0 +1,45 @@
name: Mark Duplicate Dependabot PRs
on:
pull_request:
types: [closed]
jobs:
check_duplicate_prs:
runs-on: ubuntu-latest
if: github.event.pull_request.merged == true && github.event.pull_request.user.login == 'dependabot[bot]'
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Extract Dependency Name from PR Title
id: extract
run: |
PR_TITLE="${{ github.event.pull_request.title }}"
DEPENDENCY_NAME=$(echo "$PR_TITLE" | awk -F ' from ' '{print $1}')
echo "dependency_name=$DEPENDENCY_NAME" >> $GITHUB_OUTPUT
- name: Find PRs
id: find_duplicates
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PRS=$(gh pr list --search 'milestone:${{ github.event.pull_request.milestone.title }} is:merged in:title "${{ steps.extract.outputs.dependency_name }}"' --json number --jq 'map(.number) | join(",")')
echo "prs=$PRS" >> $GITHUB_OUTPUT
- name: Label Duplicate PRs
if: steps.find_duplicates.outputs.prs != ''
env:
PRS: ${{ steps.find_duplicates.outputs.prs }}
CURRENT_PR_NUMBER: ${{ github.event.pull_request.number }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: bash
run: |
for i in ${PRS//,/ }
do
if [ ! $i -eq "$CURRENT_PR_NUMBER" ]; then
echo "Marking PR $i as duplicate"
gh pr edit "$i" --add-label "status: duplicate"
gh pr comment "$i" --body "Duplicate of #$CURRENT_PR_NUMBER"
fi
done
+12 -1
View File
@@ -8,8 +8,9 @@ permissions: write-all
jobs:
merge-dependabot-pr:
name: Merge Dependabot PR
runs-on: ubuntu-latest
if: github.actor == 'dependabot[bot]'
if: ${{ github.event.pull_request.user.login == 'dependabot[bot]' && github.repository == 'spring-projects/spring-security' }}
steps:
- uses: actions/checkout@v4
@@ -50,3 +51,13 @@ jobs:
run: gh pr merge ${{ github.event.pull_request.number }} --auto --rebase
env:
GH_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
send-notification:
name: Send Notification
needs: [ merge-dependabot-pr ]
if: ${{ failure() || cancelled() }}
runs-on: ubuntu-latest
steps:
- name: Send Notification
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@v1
with:
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
@@ -23,18 +23,13 @@ jobs:
export TOOL_VERSION=0.1.1
wget "https://repo.maven.apache.org/maven2/io/spring/releasetrain/spring-release-train-tools/$TOOL_VERSION/spring-release-train-tools-$TOOL_VERSION.jar"
java -cp "spring-release-train-tools-$TOOL_VERSION.jar" io.spring.releasetrain.CheckMilestoneDueOnMain --dueOn "$DUE_ON" --expectedDayOfWeek MONDAY --expectedMondayCount 3
notify_result:
name: Check for failures
needs: [spring-releasetrain-checks]
if: failure()
send-notification:
name: Send Notification
needs: [ spring-releasetrain-checks ]
if: ${{ failure() || cancelled() }}
runs-on: ubuntu-latest
permissions:
actions: read
steps:
- name: Send Slack message
uses: Gamesight/slack-workflow-status@v1.3.0
- name: Send Notification
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@v1
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
slack_webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }}
channel: '#spring-security-ci'
name: 'CI Notifier'
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
+11 -1
View File
@@ -21,7 +21,7 @@ jobs:
java-version: '17'
distribution: 'temurin'
- name: Build with Gradle
run: ./gradlew clean build --continue
run: ./gradlew clean build -PskipCheckExpectedBranchVersion --continue
generate-docs:
name: Generate Docs
runs-on: ubuntu-latest
@@ -42,3 +42,13 @@ jobs:
name: docs
path: docs/build/site
overwrite: true
send-notification:
name: Send Notification
needs: [ build, generate-docs ]
if: ${{ failure() && github.event.pull_request.user.login == 'dependabot[bot]' && github.repository == 'spring-projects/spring-security' }}
runs-on: ubuntu-latest
steps:
- name: Send Notification
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@v1
with:
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
@@ -11,3 +11,13 @@ jobs:
name: Update Scheduled Release Version
uses: spring-io/spring-security-release-tools/.github/workflows/update-scheduled-release-version.yml@v1
secrets: inherit
send-notification:
name: Send Notification
needs: [ update-scheduled-release-version ]
if: ${{ failure() || cancelled() }}
runs-on: ubuntu-latest
steps:
- name: Send Notification
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@v1
with:
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
+1
View File
@@ -24,6 +24,7 @@ apply plugin: 's101'
apply plugin: 'io.spring.convention.root'
apply plugin: 'org.jetbrains.kotlin.jvm'
apply plugin: 'org.springframework.security.versions.verify-dependencies-versions'
apply plugin: 'org.springframework.security.check-expected-branch-version'
apply plugin: 'io.spring.security.release'
group = 'org.springframework.security'
+4
View File
@@ -47,6 +47,10 @@ gradlePlugin {
id = "org.springframework.security.versions.verify-dependencies-versions"
implementationClass = "org.springframework.security.convention.versions.VerifyDependenciesVersionsPlugin"
}
checkExpectedBranchVersion {
id = "org.springframework.security.check-expected-branch-version"
implementationClass = "org.springframework.security.CheckExpectedBranchVersionPlugin"
}
}
}
@@ -63,6 +63,7 @@ class EclipsePlugin implements Plugin<Project> {
// test sources. Relax those from error to warning
properties['org.eclipse.jdt.core.circularClasspath'] = 'warning'
properties['org.eclipse.jdt.core.incompleteClasspath'] = 'warning'
properties['org.eclipse.jdt.core.compiler.codegen.methodParameters'] = 'generate'
}
}
}
@@ -0,0 +1,115 @@
/*
* Copyright 2002-2024 the original author 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;
import org.gradle.api.DefaultTask;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.plugins.JavaBasePlugin;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.CacheableTask;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;
import org.gradle.api.tasks.TaskExecutionException;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.api.tasks.VerificationException;
import java.io.IOException;
import java.nio.file.Files;
/**
* @author Marcus da Coregio
*/
public class CheckExpectedBranchVersionPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
TaskProvider<CheckExpectedBranchVersionTask> checkExpectedBranchVersionTask = project.getTasks().register("checkExpectedBranchVersion", CheckExpectedBranchVersionTask.class, (task) -> {
task.setGroup("Build");
task.setDescription("Check if the project version matches the branch version");
task.onlyIf("skipCheckExpectedBranchVersion property is false or not present", CheckExpectedBranchVersionPlugin::skipPropertyFalseOrNotPresent);
task.getVersion().convention(project.provider(() -> project.getVersion().toString()));
task.getBranchName().convention(project.getProviders().exec(execSpec -> execSpec.setCommandLine("git", "symbolic-ref", "--short", "HEAD")).getStandardOutput().getAsText());
task.getOutputFile().convention(project.getLayout().getBuildDirectory().file("check-expected-branch-version"));
});
project.getTasks().named(JavaBasePlugin.CHECK_TASK_NAME, checkTask -> checkTask.dependsOn(checkExpectedBranchVersionTask));
}
private static boolean skipPropertyFalseOrNotPresent(Task task) {
return task.getProject()
.getProviders()
.gradleProperty("skipCheckExpectedBranchVersion")
.orElse("false")
.map("false"::equalsIgnoreCase)
.get();
}
@CacheableTask
public static abstract class CheckExpectedBranchVersionTask extends DefaultTask {
@Input
abstract Property<String> getVersion();
@Input
abstract Property<String> getBranchName();
@OutputFile
abstract RegularFileProperty getOutputFile();
@TaskAction
public void run() {
String version = getVersion().get();
String branchVersion = getBranchName().map(String::trim).get();
if (!branchVersion.matches("^[0-9]+\\.[0-9]+\\.x$")) {
String msg = String.format("Branch version [%s] does not match *.x, ignoring", branchVersion);
getLogger().warn(msg);
writeExpectedVersionOutput(msg);
return;
}
if (!versionsMatch(version, branchVersion)) {
String msg = String.format("Project version [%s] does not match branch version [%s]. " +
"Please verify that the branch contains the right version.", version, branchVersion);
writeExpectedVersionOutput(msg);
throw new VerificationException(msg);
}
writeExpectedVersionOutput(version);
}
private void writeExpectedVersionOutput(String fileContent) {
try {
Files.writeString(getOutputFile().get().getAsFile().toPath(), fileContent);
} catch (IOException e) {
throw new TaskExecutionException(this, e);
}
}
private boolean versionsMatch(String projectVersion, String branchVersion) {
String[] projectVersionParts = projectVersion.split("\\.");
String[] branchVersionParts = branchVersion.split("\\.");
if (projectVersionParts.length < 2 || branchVersionParts.length < 2) {
return false;
}
return projectVersionParts[0].equals(branchVersionParts[0]) && projectVersionParts[1].equals(branchVersionParts[1]);
}
}
}
@@ -19,52 +19,81 @@ package org.springframework.security.convention.versions;
import org.gradle.api.DefaultTask;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.artifacts.MinimalExternalModuleDependency;
import org.gradle.api.artifacts.VersionCatalog;
import org.gradle.api.artifacts.VersionCatalogsExtension;
import org.gradle.api.file.RegularFile;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.plugins.JavaBasePlugin;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.CacheableTask;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;
import org.gradle.api.tasks.TaskExecutionException;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.api.tasks.VerificationException;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Optional;
public class VerifyDependenciesVersionsPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
VersionCatalog versionCatalog = project.getExtensions().getByType(VersionCatalogsExtension.class).named("libs");
Optional<Provider<MinimalExternalModuleDependency>> oauth2OidcSdk = versionCatalog.findLibrary("com-nimbusds-oauth2-oidc-sdk");
Optional<Provider<MinimalExternalModuleDependency>> nimbusJoseJwt = versionCatalog.findLibrary("com-nimbusds-nimbus-jose-jwt");
if (oauth2OidcSdk.isEmpty()) {
throw new VerificationException("Library [com-nimbusds-oauth2-oidc-sdk] does not exist in the version catalog named libs.");
}
if (nimbusJoseJwt.isEmpty()) {
throw new VerificationException("Library [com-nimbusds-nimbus-jose-jwt] does not exist in the version catalog named libs.");
}
TaskProvider<VerifyDependenciesVersionsTask> verifyDependenciesVersionsTaskProvider = project.getTasks().register("verifyDependenciesVersions", VerifyDependenciesVersionsTask.class, (task) -> {
task.setGroup("Verification");
task.setDescription("Verify that specific dependencies are using the same version");
VersionCatalog versionCatalog = project.getExtensions().getByType(VersionCatalogsExtension.class).named("libs");
MinimalExternalModuleDependency oauth2OidcSdk = versionCatalog.findLibrary("com-nimbusds-oauth2-oidc-sdk").get().get();
MinimalExternalModuleDependency nimbusJoseJwt = versionCatalog.findLibrary("com-nimbusds-nimbus-jose-jwt").get().get();
task.setOauth2OidcSdkVersion(oauth2OidcSdk.getVersion());
task.setExpectedNimbusJoseJwtVersion(nimbusJoseJwt.getVersion());
task.getOauth2OidcSdkVersion().convention(oauth2OidcSdk.get().map(Dependency::getVersion));
task.getExpectedNimbusJoseJwtVersion().convention(nimbusJoseJwt.get().map(Dependency::getVersion));
task.getOutputFile().convention(project.getLayout().getBuildDirectory().file("verify-dependencies-versions"));
});
project.getTasks().named(JavaBasePlugin.CHECK_TASK_NAME, checkTask -> checkTask.dependsOn(verifyDependenciesVersionsTaskProvider));
}
public static class VerifyDependenciesVersionsTask extends DefaultTask {
@CacheableTask
public abstract static class VerifyDependenciesVersionsTask extends DefaultTask {
private String oauth2OidcSdkVersion;
@Input
abstract Property<String> getOauth2OidcSdkVersion();
private String expectedNimbusJoseJwtVersion;
@Input
abstract Property<String> getExpectedNimbusJoseJwtVersion();
public void setOauth2OidcSdkVersion(String oauth2OidcSdkVersion) {
this.oauth2OidcSdkVersion = oauth2OidcSdkVersion;
}
public void setExpectedNimbusJoseJwtVersion(String expectedNimbusJoseJwtVersion) {
this.expectedNimbusJoseJwtVersion = expectedNimbusJoseJwtVersion;
}
@OutputFile
abstract RegularFileProperty getOutputFile();
@TaskAction
public void verify() {
String transitiveNimbusJoseJwtVersion = TransitiveDependencyLookupUtils.lookupJwtVersion(this.oauth2OidcSdkVersion);
if (!transitiveNimbusJoseJwtVersion.equals(this.expectedNimbusJoseJwtVersion)) {
String message = String.format("Found transitive nimbus-jose-jwt:%s in oauth2-oidc-sdk:%s, but the project contains a different version of nimbus-jose-jwt [%s]. Please align the versions.", transitiveNimbusJoseJwtVersion, this.oauth2OidcSdkVersion, this.expectedNimbusJoseJwtVersion);
throw new IllegalStateException(message);
public void verify() {
String oauth2OidcSdkVersion = this.getOauth2OidcSdkVersion().get();
String transitiveNimbusJoseJwtVersion = TransitiveDependencyLookupUtils.lookupJwtVersion(oauth2OidcSdkVersion);
String expectedNimbusJoseJwtVersion = this.getExpectedNimbusJoseJwtVersion().get();
if (!transitiveNimbusJoseJwtVersion.equals(expectedNimbusJoseJwtVersion)) {
String message = String.format("Found transitive nimbus-jose-jwt:%s in oauth2-oidc-sdk:%s, but the project contains a different version of nimbus-jose-jwt [%s]. Please align the versions.", transitiveNimbusJoseJwtVersion, oauth2OidcSdkVersion, expectedNimbusJoseJwtVersion);
throw new VerificationException(message);
}
String message = String.format("Found transitive nimbus-jose-jwt:%s in oauth2-oidc-sdk:%s, the project contains expected version of nimbus-jose-jwt [%s]. Verified all versions align.", transitiveNimbusJoseJwtVersion, oauth2OidcSdkVersion, expectedNimbusJoseJwtVersion);
try {
Files.writeString(getOutputFile().get().getAsFile().toPath(), message);
} catch (IOException e) {
throw new TaskExecutionException(this, e);
}
}
}
}
@@ -18,6 +18,7 @@ package org.springframework.security.cas.userdetails;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import org.apereo.cas.client.validation.Assertion;
@@ -73,7 +74,8 @@ public final class GrantedAuthorityFromAssertionAttributesUserDetailsService
}
private SimpleGrantedAuthority createSimpleGrantedAuthority(Object o) {
return new SimpleGrantedAuthority(this.convertToUpperCase ? o.toString().toUpperCase() : o.toString());
return new SimpleGrantedAuthority(
this.convertToUpperCase ? o.toString().toUpperCase(Locale.ROOT) : o.toString());
}
/**
@@ -315,7 +315,7 @@ public abstract class AbstractRequestMatcherRegistry<C> {
}
}
private String computeErrorMessage(Collection<? extends ServletRegistration> registrations) {
private static String computeErrorMessage(Collection<? extends ServletRegistration> registrations) {
String template = "This method cannot decide whether these patterns are Spring MVC patterns or not. "
+ "If this endpoint is a Spring MVC endpoint, please use requestMatchers(MvcRequestMatcher); "
+ "otherwise, please use requestMatchers(AntPathRequestMatcher).\n\n"
@@ -509,7 +509,8 @@ public abstract class AbstractRequestMatcherRegistry<C> {
public boolean matches(HttpServletRequest request) {
String name = request.getHttpServletMapping().getServletName();
ServletRegistration registration = this.servletContext.getServletRegistration(name);
Assert.notNull(name, "Failed to find servlet [" + name + "] in the servlet context");
Assert.notNull(registration,
() -> computeErrorMessage(this.servletContext.getServletRegistrations().values()));
try {
Class<?> clazz = Class.forName(registration.getClassName());
return DispatcherServlet.class.isAssignableFrom(clazz);
@@ -551,18 +552,12 @@ public abstract class AbstractRequestMatcherRegistry<C> {
@Override
public boolean matches(HttpServletRequest request) {
if (this.dispatcherServlet.matches(request)) {
return this.mvc.matches(request);
}
return this.ant.matches(request);
return requestMatcher(request).matches(request);
}
@Override
public MatchResult matcher(HttpServletRequest request) {
if (this.dispatcherServlet.matches(request)) {
return this.mvc.matcher(request);
}
return this.ant.matcher(request);
return requestMatcher(request).matcher(request);
}
@Override
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -47,7 +47,7 @@ import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;
import org.springframework.web.accept.ContentNegotiationStrategy;
import org.springframework.web.accept.HeaderContentNegotiationStrategy;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import static org.springframework.security.config.Customizer.withDefaults;
@@ -55,6 +55,7 @@ import static org.springframework.security.config.Customizer.withDefaults;
* {@link Configuration} that exposes the {@link HttpSecurity} bean.
*
* @author Eleftheria Stein
* @author Jinwoo Bae
* @since 5.4
*/
@Configuration(proxyBeanMethods = false)
@@ -131,8 +132,7 @@ class HttpSecurityConfiguration {
}
private void applyCorsIfAvailable(HttpSecurity http) throws Exception {
String[] beanNames = this.context.getBeanNamesForType(CorsConfigurationSource.class);
if (beanNames.length == 1) {
if (this.context.getBeanNamesForType(UrlBasedCorsConfigurationSource.class).length > 0) {
http.cors(withDefaults());
}
}
@@ -171,7 +171,8 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
Assert.state(this.mappingCount > 0,
"At least one mapping is required (for example, authorizeHttpRequests().anyRequest().authenticated())");
ObservationRegistry registry = getObservationRegistry();
RequestMatcherDelegatingAuthorizationManager manager = postProcess(this.managerBuilder.build());
AuthorizationManager<HttpServletRequest> manager = postProcess(
(AuthorizationManager<HttpServletRequest>) this.managerBuilder.build());
if (registry.isNoop()) {
return manager;
}
@@ -16,6 +16,11 @@
package org.springframework.security.config.annotation.web.configurers.oauth2.client;
import com.nimbusds.jose.JOSEObjectType;
import com.nimbusds.jose.proc.DefaultJOSEObjectTypeVerifier;
import com.nimbusds.jose.proc.JOSEObjectTypeVerifier;
import com.nimbusds.jose.proc.SecurityContext;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
@@ -26,11 +31,14 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.converter.ClaimTypeConverter;
import org.springframework.security.oauth2.jwt.BadJwtException;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtDecoderFactory;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* An {@link AuthenticationProvider} that authenticates an OIDC Logout Token; namely
@@ -56,9 +64,27 @@ final class OidcBackChannelLogoutAuthenticationProvider implements Authenticatio
* Construct an {@link OidcBackChannelLogoutAuthenticationProvider}
*/
OidcBackChannelLogoutAuthenticationProvider() {
OidcIdTokenDecoderFactory logoutTokenDecoderFactory = new OidcIdTokenDecoderFactory();
logoutTokenDecoderFactory.setJwtValidatorFactory(new DefaultOidcLogoutTokenValidatorFactory());
this.logoutTokenDecoderFactory = logoutTokenDecoderFactory;
DefaultOidcLogoutTokenValidatorFactory jwtValidator = new DefaultOidcLogoutTokenValidatorFactory();
this.logoutTokenDecoderFactory = (clientRegistration) -> {
String jwkSetUri = clientRegistration.getProviderDetails().getJwkSetUri();
if (!StringUtils.hasText(jwkSetUri)) {
OAuth2Error oauth2Error = new OAuth2Error("missing_signature_verifier",
"Failed to find a Signature Verifier for Client Registration: '"
+ clientRegistration.getRegistrationId()
+ "'. Check to ensure you have configured the JwkSet URI.",
null);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
JOSEObjectTypeVerifier<SecurityContext> typeVerifier = new DefaultJOSEObjectTypeVerifier<>(null,
JOSEObjectType.JWT, new JOSEObjectType("logout+jwt"));
NimbusJwtDecoder decoder = NimbusJwtDecoder.withJwkSetUri(jwkSetUri)
.jwtProcessorCustomizer((processor) -> processor.setJWSTypeVerifier(typeVerifier))
.build();
decoder.setJwtValidator(jwtValidator.apply(clientRegistration));
decoder.setClaimSetConverter(
new ClaimTypeConverter(OidcIdTokenDecoderFactory.createDefaultClaimTypeConverters()));
return decoder;
};
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -30,6 +30,7 @@ import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.util.Assert;
/**
* A {@link OAuth2TokenValidator} that validates OIDC Logout Token claims in conformance
@@ -57,7 +58,9 @@ final class OidcBackChannelLogoutTokenValidator implements OAuth2TokenValidator<
OidcBackChannelLogoutTokenValidator(ClientRegistration clientRegistration) {
this.audience = clientRegistration.getClientId();
this.issuer = clientRegistration.getProviderDetails().getIssuerUri();
String issuer = clientRegistration.getProviderDetails().getIssuerUri();
Assert.hasText(issuer, "Provider issuer cannot be null");
this.issuer = issuer;
}
@Override
@@ -21,6 +21,7 @@ import java.util.List;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.context.ApplicationContext;
@@ -33,6 +34,8 @@ import org.springframework.security.web.reactive.result.view.CsrfRequestDataValu
import org.springframework.security.web.server.ObservationWebFilterChainDecorator;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.WebFilterChainProxy;
import org.springframework.security.web.server.firewall.ServerExchangeRejectedHandler;
import org.springframework.security.web.server.firewall.ServerWebExchangeFirewall;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.web.reactive.result.view.AbstractView;
@@ -79,11 +82,14 @@ class WebFluxSecurityConfiguration {
@Bean(SPRING_SECURITY_WEBFILTERCHAINFILTER_BEAN_NAME)
@Order(WEB_FILTER_CHAIN_FILTER_ORDER)
WebFilterChainProxy springSecurityWebFilterChainFilter() {
WebFilterChainProxy springSecurityWebFilterChainFilter(ObjectProvider<ServerWebExchangeFirewall> firewall,
ObjectProvider<ServerExchangeRejectedHandler> rejectedHandler) {
WebFilterChainProxy proxy = new WebFilterChainProxy(getSecurityWebFilterChains());
if (!this.observationRegistry.isNoop()) {
proxy.setFilterChainDecorator(new ObservationWebFilterChainDecorator(this.observationRegistry));
}
firewall.ifUnique(proxy::setFirewall);
rejectedHandler.ifUnique(proxy::setExchangeRejectedHandler);
return proxy;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.security.config.http;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import io.micrometer.observation.ObservationRegistry;
import jakarta.servlet.ServletRequest;
@@ -313,7 +314,7 @@ class HttpConfigurationBuilder {
// Needed to account for placeholders
static String createPath(String path, boolean lowerCase) {
return lowerCase ? path.toLowerCase() : path;
return lowerCase ? path.toLowerCase(Locale.ENGLISH) : path;
}
BeanMetadataElement getSecurityContextHolderStrategyForAuthenticationFilters() {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -30,6 +30,7 @@ import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.util.Assert;
/**
* A {@link OAuth2TokenValidator} that validates OIDC Logout Token claims in conformance
@@ -57,7 +58,9 @@ final class OidcBackChannelLogoutTokenValidator implements OAuth2TokenValidator<
OidcBackChannelLogoutTokenValidator(ClientRegistration clientRegistration) {
this.audience = clientRegistration.getClientId();
this.issuer = clientRegistration.getProviderDetails().getIssuerUri();
String issuer = clientRegistration.getProviderDetails().getIssuerUri();
Assert.hasText(issuer, "Provider issuer cannot be null");
this.issuer = issuer;
}
@Override
@@ -73,6 +73,8 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
@@ -447,6 +449,18 @@ public class PrePostMethodSecurityConfigurationTests {
.autowire();
}
// gh-15651
@Test
@WithMockUser(roles = "ADMIN")
public void adviseWhenPrePostEnabledThenEachInterceptorRunsExactlyOnce() {
this.spring.register(MethodSecurityServiceConfig.class, CustomMethodSecurityExpressionHandlerConfig.class)
.autowire();
MethodSecurityExpressionHandler expressionHandler = this.spring.getContext()
.getBean(MethodSecurityExpressionHandler.class);
this.methodSecurityService.manyAnnotations(new ArrayList<>(Arrays.asList("harold", "jonathan", "tim", "bo")));
verify(expressionHandler, times(4)).createEvaluationContext(any(Supplier.class), any());
}
private static Consumer<ConfigurableWebApplicationContext> disallowBeanOverriding() {
return (context) -> ((AnnotationConfigWebApplicationContext) context).setAllowBeanDefinitionOverriding(false);
}
@@ -523,6 +537,19 @@ public class PrePostMethodSecurityConfigurationTests {
}
@Configuration
@EnableMethodSecurity
static class CustomMethodSecurityExpressionHandlerConfig {
private final MethodSecurityExpressionHandler expressionHandler = spy(
new DefaultMethodSecurityExpressionHandler());
@Bean
MethodSecurityExpressionHandler methodSecurityExpressionHandler() {
return this.expressionHandler;
}
}
@EnableMethodSecurity
static class CustomPermissionEvaluatorConfig {
@@ -341,6 +341,19 @@ public class AbstractRequestMatcherRegistryTests {
verifyNoMoreInteractions(mvc);
}
@Test
public void matchesWhenNoMappingThenException() {
MockServletContext servletContext = new MockServletContext();
servletContext.addServlet("default", DispatcherServlet.class).addMapping("/");
servletContext.addServlet("path", Servlet.class).addMapping("/services/*");
MvcRequestMatcher mvc = mock(MvcRequestMatcher.class);
AntPathRequestMatcher ant = mock(AntPathRequestMatcher.class);
DispatcherServletDelegatingRequestMatcher requestMatcher = new DispatcherServletDelegatingRequestMatcher(ant,
mvc, servletContext);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/services/endpoint");
assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> requestMatcher.matcher(request));
}
private void mockMvcIntrospector(boolean isPresent) {
ApplicationContext context = this.matcherRegistry.getApplicationContext();
given(context.containsBean("mvcHandlerMappingIntrospector")).willReturn(isPresent);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -362,7 +362,7 @@ public class HttpSecurityConfigurationTests {
}
@Test
public void configureWhenCorsConfigurationSourceThenApplyCors() {
public void configureWhenCorsConfigurationSourceThenApplyCors() throws Exception {
this.spring.register(CorsConfigurationSourceConfig.class, DefaultWithFilterChainConfig.class).autowire();
SecurityFilterChain filterChain = this.spring.getContext().getBean(SecurityFilterChain.class);
CorsFilter corsFilter = (CorsFilter) filterChain.getFilters()
@@ -374,6 +374,16 @@ public class HttpSecurityConfigurationTests {
assertThat(configSource).isInstanceOf(UrlBasedCorsConfigurationSource.class);
}
// gh-15378
@Test
public void configureWhenNoUrlBasedCorsConfigThenNoCorsAppliedAndVaryHeaderNotPresent() throws Exception {
this.spring.register(NonUrlBasedCorsConfig.class, DefaultWithFilterChainConfig.class).autowire();
SecurityFilterChain filterChain = this.spring.getContext().getBean(SecurityFilterChain.class);
assertThat(filterChain.getFilters()).noneMatch((filter) -> filter instanceof CorsFilter);
this.mockMvc.perform(get("/")).andExpect(header().doesNotExist("Vary"));
}
@Test
public void configureWhenAddingCustomDslUsingWithThenApplied() throws Exception {
this.spring.register(WithCustomDslConfig.class, UserDetailsConfig.class).autowire();
@@ -673,6 +683,33 @@ public class HttpSecurityConfigurationTests {
}
@Configuration
@EnableWebSecurity
static class NonUrlBasedCorsConfig {
@Bean
CorsConfigurationSource corsConfigurationSource() {
return new CustomCorsConfigurationSource();
}
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http.build();
}
}
static class CustomCorsConfigurationSource implements CorsConfigurationSource {
@Override
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(List.of("http://localhost:8080"));
return configuration;
}
}
static class DefaultConfigurer extends AbstractHttpConfigurer<DefaultConfigurer, HttpSecurity> {
boolean init;
@@ -153,6 +153,7 @@ public class AuthorizeHttpRequestsConfigurerTests {
this.spring.register(ObjectPostProcessorConfig.class).autowire();
ObjectPostProcessor objectPostProcessor = this.spring.getContext().getBean(ObjectPostProcessor.class);
verify(objectPostProcessor).postProcess(any(RequestMatcherDelegatingAuthorizationManager.class));
verify(objectPostProcessor).postProcess(any(AuthorizationManager.class));
verify(objectPostProcessor).postProcess(any(AuthorizationFilter.class));
}
@@ -650,7 +650,7 @@ public class OAuth2LoginConfigurerTests {
}
@Test
public void configureWhenOidcSessionStrategyThenUses() {
public void configureWhenOidcSessionRegistryThenUses() {
this.spring.register(OAuth2LoginWithOidcSessionRegistry.class).autowire();
OidcSessionRegistry registry = this.spring.getContext().getBean(OidcSessionRegistry.class);
this.spring.getContext().publishEvent(new HttpSessionDestroyedEvent(this.request.getSession()));
@@ -73,6 +73,8 @@ import org.springframework.security.oauth2.client.registration.TestClientRegistr
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
import org.springframework.security.oauth2.core.oidc.TestOidcIdTokens;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.JwsHeader;
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.JwtEncoderParameters;
@@ -89,6 +91,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.hamcrest.Matchers.containsString;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.willThrow;
@@ -257,6 +260,22 @@ public class OidcLogoutConfigurerTests {
verify(sessionRegistry).removeSessionInformation(any(OidcLogoutToken.class));
}
@Test
void logoutWhenProviderIssuerMissingThenThrowIllegalArgumentException() throws Exception {
this.spring.register(WebServerConfig.class, OidcProviderConfig.class, ProviderIssuerMissingConfig.class)
.autowire();
String registrationId = this.clientRegistration.getRegistrationId();
MockHttpSession session = login();
String logoutToken = this.mvc.perform(get("/token/logout").session(session))
.andExpect(status().isOk())
.andReturn()
.getResponse()
.getContentAsString();
assertThatIllegalArgumentException().isThrownBy(
() -> this.mvc.perform(post(this.web.url("/logout/connect/back-channel/" + registrationId).toString())
.param("logout_token", logoutToken)));
}
private MockHttpSession login() throws Exception {
MockMvcDispatcher dispatcher = (MockMvcDispatcher) this.web.getDispatcher();
this.mvc.perform(get("/token/logout")).andExpect(status().isUnauthorized());
@@ -408,6 +427,54 @@ public class OidcLogoutConfigurerTests {
}
@Configuration
static class ProviderIssuerMissingRegistrationConfig {
@Autowired(required = false)
MockWebServer web;
@Bean
ClientRegistration clientRegistration() {
if (this.web == null) {
return TestClientRegistrations.clientRegistration().issuerUri(null).build();
}
String issuer = this.web.url("/").toString();
return TestClientRegistrations.clientRegistration()
.issuerUri(null)
.jwkSetUri(issuer + "jwks")
.tokenUri(issuer + "token")
.userInfoUri(issuer + "user")
.scope("openid")
.build();
}
@Bean
ClientRegistrationRepository clientRegistrationRepository(ClientRegistration clientRegistration) {
return new InMemoryClientRegistrationRepository(clientRegistration);
}
}
@Configuration
@EnableWebSecurity
@Import(ProviderIssuerMissingRegistrationConfig.class)
static class ProviderIssuerMissingConfig {
@Bean
@Order(1)
SecurityFilterChain filters(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated())
.oauth2Login(Customizer.withDefaults())
.oidcLogout((oidc) -> oidc.backChannel(Customizer.withDefaults()));
// @formatter:on
return http.build();
}
}
@Configuration
@EnableWebSecurity
@EnableWebMvc
@@ -446,6 +513,9 @@ public class OidcLogoutConfigurerTests {
@Autowired
ClientRegistration registration;
@Autowired(required = false)
MockWebServer web;
@Bean
@Order(0)
SecurityFilterChain authorizationServer(HttpSecurity http, ClientRegistration registration) throws Exception {
@@ -482,7 +552,7 @@ public class OidcLogoutConfigurerTests {
HttpSession session = request.getSession();
JwtEncoderParameters parameters = JwtEncoderParameters
.from(JwtClaimsSet.builder().id("id").subject(this.username)
.issuer(this.registration.getProviderDetails().getIssuerUri()).issuedAt(Instant.now())
.issuer(getIssuerUri()).issuedAt(Instant.now())
.expiresAt(Instant.now().plusSeconds(86400)).claim("scope", "openid").build());
String token = this.encoder.encode(parameters).getTokenValue();
return new OIDCTokens(idToken(session.getId()), new BearerAccessToken(token, 86400, new Scope("openid")), null)
@@ -490,7 +560,7 @@ public class OidcLogoutConfigurerTests {
}
String idToken(String sessionId) {
OidcIdToken token = TestOidcIdTokens.idToken().issuer(this.registration.getProviderDetails().getIssuerUri())
OidcIdToken token = TestOidcIdTokens.idToken().issuer(getIssuerUri())
.subject(this.username).expiresAt(Instant.now().plusSeconds(86400))
.audience(List.of(this.registration.getClientId())).nonce(this.nonce)
.claim(LogoutTokenClaimNames.SID, sessionId).build();
@@ -499,6 +569,13 @@ public class OidcLogoutConfigurerTests {
return this.encoder.encode(parameters).getTokenValue();
}
private String getIssuerUri() {
if (this.web == null) {
return TestClientRegistrations.clientRegistration().build().getProviderDetails().getIssuerUri();
}
return this.web.url("/").toString();
}
@GetMapping("/user")
Map<String, Object> userinfo() {
return Map.of("sub", this.username, "id", this.username);
@@ -513,8 +590,9 @@ public class OidcLogoutConfigurerTests {
String logoutToken(@AuthenticationPrincipal OidcUser user) {
OidcLogoutToken token = TestOidcLogoutTokens.withUser(user)
.audience(List.of(this.registration.getClientId())).build();
JwtEncoderParameters parameters = JwtEncoderParameters
.from(JwtClaimsSet.builder().claims((claims) -> claims.putAll(token.getClaims())).build());
JwsHeader header = JwsHeader.with(SignatureAlgorithm.RS256).type("logout+jwt").build();
JwtClaimsSet claims = JwtClaimsSet.builder().claims((c) -> c.putAll(token.getClaims())).build();
JwtEncoderParameters parameters = JwtEncoderParameters.from(header, claims);
return this.encoder.encode(parameters).getTokenValue();
}
@@ -523,8 +601,9 @@ public class OidcLogoutConfigurerTests {
OidcLogoutToken token = TestOidcLogoutTokens.withUser(user)
.audience(List.of(this.registration.getClientId()))
.claims((claims) -> claims.remove(LogoutTokenClaimNames.SID)).build();
JwtEncoderParameters parameters = JwtEncoderParameters
.from(JwtClaimsSet.builder().claims((claims) -> claims.putAll(token.getClaims())).build());
JwsHeader header = JwsHeader.with(SignatureAlgorithm.RS256).type("JWT").build();
JwtClaimsSet claims = JwtClaimsSet.builder().claims((c) -> c.putAll(token.getClaims())).build();
JwtEncoderParameters parameters = JwtEncoderParameters.from(header, claims);
return this.encoder.encode(parameters).getTokenValue();
}
}
@@ -16,14 +16,26 @@
package org.springframework.security.config.annotation.web.reactive;
import java.util.Collections;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import reactor.core.publisher.Mono;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.config.users.ReactiveAuthenticationTestConfiguration;
import org.springframework.security.web.server.WebFilterChainProxy;
import org.springframework.security.web.server.firewall.HttpStatusExchangeRejectedHandler;
import org.springframework.security.web.server.firewall.ServerExchangeRejectedHandler;
import org.springframework.security.web.server.firewall.ServerWebExchangeFirewall;
import org.springframework.web.server.handler.DefaultWebFilterChain;
import static org.assertj.core.api.Assertions.assertThat;
@@ -47,6 +59,46 @@ public class WebFluxSecurityConfigurationTests {
assertThat(webFilterChainProxy).isNotNull();
}
@Test
void loadConfigWhenDefaultThenFirewalled() throws Exception {
this.spring
.register(ServerHttpSecurityConfiguration.class, ReactiveAuthenticationTestConfiguration.class,
WebFluxSecurityConfiguration.class)
.autowire();
WebFilterChainProxy webFilterChainProxy = this.spring.getContext().getBean(WebFilterChainProxy.class);
MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/;/").build());
DefaultWebFilterChain chain = emptyChain();
webFilterChainProxy.filter(exchange, chain).block();
assertThat(exchange.getResponse().getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
}
@Test
void loadConfigWhenCustomRejectedHandler() throws Exception {
this.spring
.register(ServerHttpSecurityConfiguration.class, ReactiveAuthenticationTestConfiguration.class,
WebFluxSecurityConfiguration.class, CustomServerExchangeRejectedHandlerConfig.class)
.autowire();
WebFilterChainProxy webFilterChainProxy = this.spring.getContext().getBean(WebFilterChainProxy.class);
MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/;/").build());
DefaultWebFilterChain chain = emptyChain();
webFilterChainProxy.filter(exchange, chain).block();
assertThat(exchange.getResponse().getStatusCode())
.isEqualTo(CustomServerExchangeRejectedHandlerConfig.EXPECTED_STATUS);
}
@Test
void loadConfigWhenFirewallBeanThenCustomized() throws Exception {
this.spring
.register(ServerHttpSecurityConfiguration.class, ReactiveAuthenticationTestConfiguration.class,
WebFluxSecurityConfiguration.class, NoOpFirewallConfig.class)
.autowire();
WebFilterChainProxy webFilterChainProxy = this.spring.getContext().getBean(WebFilterChainProxy.class);
MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/;/").build());
DefaultWebFilterChain chain = emptyChain();
webFilterChainProxy.filter(exchange, chain).block();
assertThat(exchange.getResponse().getStatusCode()).isNotEqualTo(HttpStatus.BAD_REQUEST);
}
@Test
public void loadConfigWhenBeanProxyingEnabledAndSubclassThenWebFilterChainProxyExists() {
this.spring
@@ -57,6 +109,32 @@ public class WebFluxSecurityConfigurationTests {
assertThat(webFilterChainProxy).isNotNull();
}
private static @NotNull DefaultWebFilterChain emptyChain() {
return new DefaultWebFilterChain((webExchange) -> Mono.empty(), Collections.emptyList());
}
@Configuration
static class NoOpFirewallConfig {
@Bean
ServerWebExchangeFirewall noOpFirewall() {
return ServerWebExchangeFirewall.INSECURE_NOOP;
}
}
@Configuration
static class CustomServerExchangeRejectedHandlerConfig {
static HttpStatus EXPECTED_STATUS = HttpStatus.I_AM_A_TEAPOT;
@Bean
ServerExchangeRejectedHandler rejectedHandler() {
return new HttpStatusExchangeRejectedHandler(EXPECTED_STATUS);
}
}
@Configuration
static class SubclassConfig extends WebFluxSecurityConfiguration {
@@ -323,6 +323,30 @@ public class OidcLogoutSpecTests {
verify(sessionRegistry, atLeastOnce()).removeSessionInformation(any(OidcLogoutToken.class));
}
@Test
void logoutWhenProviderIssuerMissingThen5xxServerError() {
this.spring.register(WebServerConfig.class, OidcProviderConfig.class, ProviderIssuerMissingConfig.class)
.autowire();
String registrationId = this.clientRegistration.getRegistrationId();
String session = login();
String logoutToken = this.test.mutateWith(session(session))
.get()
.uri("/token/logout")
.exchange()
.expectStatus()
.isOk()
.returnResult(String.class)
.getResponseBody()
.blockFirst();
this.test.post()
.uri(this.web.url("/logout/connect/back-channel/" + registrationId).toString())
.body(BodyInserters.fromFormData("logout_token", logoutToken))
.exchange()
.expectStatus()
.is5xxServerError();
this.test.mutateWith(session(session)).get().uri("/token/logout").exchange().expectStatus().isOk();
}
private String login() {
this.test.get().uri("/token/logout").exchange().expectStatus().isUnauthorized();
String registrationId = this.clientRegistration.getRegistrationId();
@@ -499,6 +523,54 @@ public class OidcLogoutSpecTests {
}
@Configuration
static class ProviderIssuerMissingRegistrationConfig {
@Autowired(required = false)
MockWebServer web;
@Bean
ClientRegistration clientRegistration() {
if (this.web == null) {
return TestClientRegistrations.clientRegistration().issuerUri(null).build();
}
String issuer = this.web.url("/").toString();
return TestClientRegistrations.clientRegistration()
.issuerUri(null)
.jwkSetUri(issuer + "jwks")
.tokenUri(issuer + "token")
.userInfoUri(issuer + "user")
.scope("openid")
.build();
}
@Bean
ReactiveClientRegistrationRepository clientRegistrationRepository(ClientRegistration clientRegistration) {
return new InMemoryReactiveClientRegistrationRepository(clientRegistration);
}
}
@Configuration
@EnableWebFluxSecurity
@Import(ProviderIssuerMissingRegistrationConfig.class)
static class ProviderIssuerMissingConfig {
@Bean
@Order(1)
SecurityWebFilterChain filters(ServerHttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeExchange((authorize) -> authorize.anyExchange().authenticated())
.oauth2Login(Customizer.withDefaults())
.oidcLogout((oidc) -> oidc.backChannel(Customizer.withDefaults()));
// @formatter:on
return http.build();
}
}
@Configuration
@EnableWebFluxSecurity
@EnableWebFlux
@@ -537,6 +609,9 @@ public class OidcLogoutSpecTests {
@Autowired
ClientRegistration registration;
@Autowired(required = false)
MockWebServer web;
static ServerWebExchangeMatcher or(String... patterns) {
List<ServerWebExchangeMatcher> matchers = new ArrayList<>();
for (String pattern : patterns) {
@@ -581,7 +656,7 @@ public class OidcLogoutSpecTests {
Map<String, Object> accessToken(WebSession session) {
JwtEncoderParameters parameters = JwtEncoderParameters
.from(JwtClaimsSet.builder().id("id").subject(this.username)
.issuer(this.registration.getProviderDetails().getIssuerUri()).issuedAt(Instant.now())
.issuer(getIssuerUri()).issuedAt(Instant.now())
.expiresAt(Instant.now().plusSeconds(86400)).claim("scope", "openid").build());
String token = this.encoder.encode(parameters).getTokenValue();
return new OIDCTokens(idToken(session.getId()), new BearerAccessToken(token, 86400, new Scope("openid")), null)
@@ -589,7 +664,7 @@ public class OidcLogoutSpecTests {
}
String idToken(String sessionId) {
OidcIdToken token = TestOidcIdTokens.idToken().issuer(this.registration.getProviderDetails().getIssuerUri())
OidcIdToken token = TestOidcIdTokens.idToken().issuer(getIssuerUri())
.subject(this.username).expiresAt(Instant.now().plusSeconds(86400))
.audience(List.of(this.registration.getClientId())).nonce(this.nonce)
.claim(LogoutTokenClaimNames.SID, sessionId).build();
@@ -598,6 +673,13 @@ public class OidcLogoutSpecTests {
return this.encoder.encode(parameters).getTokenValue();
}
private String getIssuerUri() {
if (this.web == null) {
return TestClientRegistrations.clientRegistration().build().getProviderDetails().getIssuerUri();
}
return this.web.url("/").toString();
}
@GetMapping("/user")
Map<String, Object> userinfo() {
return Map.of("sub", this.username, "id", this.username);
@@ -34,6 +34,7 @@ import org.springframework.security.authorization.AuthorizationManager;
* {@code <method-security>} and {@code <intercept-methods>} instead or use
* annotation-based or {@link AuthorizationManager}-based authorization
*/
@Deprecated
public interface MethodSecurityMetadataSource extends SecurityMetadataSource {
Collection<ConfigAttribute> getAttributes(Method method, Class<?> targetClass);
@@ -26,6 +26,7 @@ import org.springframework.security.authorization.AuthorizationManager;
* @see org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor
* @deprecated Use delegation with {@link AuthorizationManager}
*/
@Deprecated
public interface PrePostInvocationAttributeFactory extends AopInfrastructureBean {
PreInvocationAttribute createPreInvocationAttribute(String preFilterAttribute, String filterObject,
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,8 @@
package org.springframework.security.authentication;
import java.util.Locale;
import io.micrometer.common.KeyValues;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationConvention;
@@ -53,7 +55,7 @@ public final class AuthenticationObservationConvention
if (authenticationType.endsWith("Authentication")) {
authenticationType = authenticationType.substring(0, authenticationType.lastIndexOf("Authentication"));
}
return "authenticate " + authenticationType.toLowerCase();
return "authenticate " + authenticationType.toLowerCase(Locale.ENGLISH);
}
return "authenticate";
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -79,10 +79,10 @@ public class SimpleAttributes2GrantedAuthoritiesMapper
*/
private GrantedAuthority getGrantedAuthority(String attribute) {
if (isConvertAttributeToLowerCase()) {
attribute = attribute.toLowerCase(Locale.getDefault());
attribute = attribute.toLowerCase(Locale.ROOT);
}
else if (isConvertAttributeToUpperCase()) {
attribute = attribute.toUpperCase(Locale.getDefault());
attribute = attribute.toUpperCase(Locale.ROOT);
}
if (isAddPrefixIfAlreadyExisting() || !attribute.startsWith(getAttributePrefix())) {
return new SimpleGrantedAuthority(getAttributePrefix() + attribute);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.security.core.authority.mapping;
import java.util.Collection;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import org.springframework.beans.factory.InitializingBean;
@@ -71,10 +72,10 @@ public final class SimpleAuthorityMapper implements GrantedAuthoritiesMapper, In
private GrantedAuthority mapAuthority(String name) {
if (this.convertToUpperCase) {
name = name.toUpperCase();
name = name.toUpperCase(Locale.ROOT);
}
else if (this.convertToLowerCase) {
name = name.toLowerCase();
name = name.toLowerCase(Locale.ROOT);
}
if (this.prefix.length() > 0 && !name.startsWith(this.prefix)) {
name = this.prefix + name;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.security.core.userdetails;
import java.util.Arrays;
import java.util.Collection;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -91,7 +92,7 @@ public class MapReactiveUserDetailsService implements ReactiveUserDetailsService
}
private String getKey(String username) {
return username.toLowerCase();
return username.toLowerCase(Locale.ROOT);
}
}
@@ -19,6 +19,7 @@ package org.springframework.security.core.userdetails.memory;
import java.beans.PropertyEditorSupport;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import org.springframework.util.StringUtils;
@@ -45,10 +46,10 @@ public class UserAttributeEditor extends PropertyEditorSupport {
userAttrib.setPassword(currentToken);
}
else {
if (currentToken.toLowerCase().equals("enabled")) {
if (currentToken.toLowerCase(Locale.ENGLISH).equals("enabled")) {
userAttrib.setEnabled(true);
}
else if (currentToken.toLowerCase().equals("disabled")) {
else if (currentToken.toLowerCase(Locale.ENGLISH).equals("disabled")) {
userAttrib.setEnabled(false);
}
else {
@@ -75,7 +75,6 @@ public final class SecurityJackson2Modules {
private static final List<String> securityJackson2ModuleClasses = Arrays.asList(
"org.springframework.security.jackson2.CoreJackson2Module",
"org.springframework.security.cas.jackson2.CasJackson2Module",
"org.springframework.security.web.jackson2.WebJackson2Module",
"org.springframework.security.web.server.jackson2.WebServerJackson2Module");
@@ -89,6 +88,8 @@ public final class SecurityJackson2Modules {
private static final String saml2Jackson2ModuleClass = "org.springframework.security.saml2.jackson2.Saml2Jackson2Module";
private static final String casJackson2ModuleClass = "org.springframework.security.cas.jackson2.CasJackson2Module";
private static final boolean webServletPresent;
private static final boolean oauth2ClientPresent;
@@ -99,6 +100,8 @@ public final class SecurityJackson2Modules {
private static final boolean saml2JacksonPresent;
private static final boolean casJacksonPresent;
static {
ClassLoader classLoader = SecurityJackson2Modules.class.getClassLoader();
webServletPresent = ClassUtils.isPresent("jakarta.servlet.http.Cookie", classLoader);
@@ -107,6 +110,7 @@ public final class SecurityJackson2Modules {
javaTimeJacksonPresent = ClassUtils.isPresent(javaTimeJackson2ModuleClass, classLoader);
ldapJacksonPresent = ClassUtils.isPresent(ldapJackson2ModuleClass, classLoader);
saml2JacksonPresent = ClassUtils.isPresent(saml2Jackson2ModuleClass, classLoader);
casJacksonPresent = ClassUtils.isPresent(casJackson2ModuleClass, classLoader);
}
private SecurityJackson2Modules() {
@@ -160,6 +164,9 @@ public final class SecurityJackson2Modules {
if (saml2JacksonPresent) {
addToModulesList(loader, modules, saml2Jackson2ModuleClass);
}
if (casJacksonPresent) {
addToModulesList(loader, modules, casJackson2ModuleClass);
}
return modules;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@ package org.springframework.security.provisioning;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
@@ -96,23 +97,23 @@ public class InMemoryUserDetailsManager implements UserDetailsManager, UserDetai
@Override
public void createUser(UserDetails user) {
Assert.isTrue(!userExists(user.getUsername()), "user should not exist");
this.users.put(user.getUsername().toLowerCase(), new MutableUser(user));
this.users.put(user.getUsername().toLowerCase(Locale.ROOT), new MutableUser(user));
}
@Override
public void deleteUser(String username) {
this.users.remove(username.toLowerCase());
this.users.remove(username.toLowerCase(Locale.ROOT));
}
@Override
public void updateUser(UserDetails user) {
Assert.isTrue(userExists(user.getUsername()), "user should exist");
this.users.put(user.getUsername().toLowerCase(), new MutableUser(user));
this.users.put(user.getUsername().toLowerCase(Locale.ROOT), new MutableUser(user));
}
@Override
public boolean userExists(String username) {
return this.users.containsKey(username.toLowerCase());
return this.users.containsKey(username.toLowerCase(Locale.ROOT));
}
@Override
@@ -143,14 +144,14 @@ public class InMemoryUserDetailsManager implements UserDetailsManager, UserDetai
@Override
public UserDetails updatePassword(UserDetails user, String newPassword) {
String username = user.getUsername();
MutableUserDetails mutableUser = this.users.get(username.toLowerCase());
MutableUserDetails mutableUser = this.users.get(username.toLowerCase(Locale.ROOT));
mutableUser.setPassword(newPassword);
return mutableUser;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserDetails user = this.users.get(username.toLowerCase());
UserDetails user = this.users.get(username.toLowerCase(Locale.ROOT));
if (user == null) {
throw new UsernameNotFoundException(username);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.security.crypto.password;
import java.security.MessageDigest;
import java.util.Base64;
import java.util.Locale;
import org.springframework.security.crypto.codec.Utf8;
import org.springframework.security.crypto.keygen.BytesKeyGenerator;
@@ -50,11 +51,11 @@ public class LdapShaPasswordEncoder implements PasswordEncoder {
private static final String SSHA_PREFIX = "{SSHA}";
private static final String SSHA_PREFIX_LC = SSHA_PREFIX.toLowerCase();
private static final String SSHA_PREFIX_LC = SSHA_PREFIX.toLowerCase(Locale.ENGLISH);
private static final String SHA_PREFIX = "{SHA}";
private static final String SHA_PREFIX_LC = SHA_PREFIX.toLowerCase();
private static final String SHA_PREFIX_LC = SHA_PREFIX.toLowerCase(Locale.ENGLISH);
private BytesKeyGenerator saltGenerator;
+1 -1
View File
@@ -30,7 +30,7 @@ urls:
redirect_facility: httpd
ui:
bundle:
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.15/ui-bundle.zip
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.17/ui-bundle.zip
snapshot: true
runtime:
log:
+2
View File
@@ -38,6 +38,7 @@
***** xref:servlet/authentication/passwords/in-memory.adoc[In Memory]
***** xref:servlet/authentication/passwords/jdbc.adoc[JDBC]
***** xref:servlet/authentication/passwords/user-details.adoc[UserDetails]
***** xref:servlet/authentication/passwords/credentials-container.adoc[CredentialsContainer]
***** xref:servlet/authentication/passwords/user-details-service.adoc[UserDetailsService]
***** xref:servlet/authentication/passwords/password-encoder.adoc[PasswordEncoder]
***** xref:servlet/authentication/passwords/dao-authentication-provider.adoc[DaoAuthenticationProvider]
@@ -152,6 +153,7 @@
*** xref:reactive/exploits/csrf.adoc[CSRF]
*** xref:reactive/exploits/headers.adoc[Headers]
*** xref:reactive/exploits/http.adoc[HTTP Requests]
*** xref:reactive/exploits/firewall.adoc[]
** Integrations
*** xref:reactive/integrations/cors.adoc[CORS]
*** xref:reactive/integrations/rsocket.adoc[RSocket]
+1 -1
View File
@@ -1,7 +1,7 @@
[[prerequisites]]
= Prerequisites
Spring Security requires a Java 8 or higher Runtime Environment.
Spring Security requires a Java 17 or higher Runtime Environment.
As Spring Security aims to operate in a self-contained manner, you do not need to place any special configuration files in your Java Runtime Environment.
In particular, you need not configure a special Java Authentication and Authorization Service (JAAS) policy file or place Spring Security into common classpath locations.
@@ -0,0 +1,234 @@
[[webflux-serverwebexchangefirewall]]
= ServerWebExchangeFirewall
There are various ways a request can be created by malicious users that can exploit applications.
Spring Security provides the `ServerWebExchangeFirewall` to allow rejecting requests that look malicious.
The default implementation is `StrictServerWebExchangeFirewall` which rejects malicious requests.
For example a request could contain path-traversal sequences (such as `/../`) or multiple forward slashes (`//`) that could also cause pattern-matches to fail.
Some containers normalize these out before performing the servlet mapping, but others do not.
To protect against issues like these, `WebFilterChainProxy` uses a `ServerWebExchangeFirewall` strategy to check and wrap the request.
By default, un-normalized requests are automatically rejected, and path parameters are removed for matching purposes.
(So, for example, an original request path of `/secure;hack=1/somefile.html;hack=2` is returned as `/secure/somefile.html`.)
It is, therefore, essential that a `WebFilterChainProxy` is used.
In practice, we recommend that you use method security at your service layer, to control access to your application, rather than rely entirely on the use of security constraints defined at the web-application level.
URLs change, and it is difficult to take into account all the possible URLs that an application might support and how requests might be manipulated.
You should restrict yourself to using a few simple patterns that are simple to understand.
Always try to use a "`deny-by-default`" approach, where you have a catch-all wildcard (`/**` or `**`) defined last to deny access.
Security defined at the service layer is much more robust and harder to bypass, so you should always take advantage of Spring Security's method security options.
You can customize the `ServerWebExchangeFirewall` by exposing it as a Bean.
.Allow Matrix Variables
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
public StrictServerWebExchangeFirewall httpFirewall() {
StrictServerWebExchangeFirewall firewall = new StrictServerWebExchangeFirewall();
firewall.setAllowSemicolon(true);
return firewall;
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
fun httpFirewall(): StrictServerWebExchangeFirewall {
val firewall = StrictServerWebExchangeFirewall()
firewall.setAllowSemicolon(true)
return firewall
}
----
======
To protect against https://www.owasp.org/index.php/Cross_Site_Tracing[Cross Site Tracing (XST)] and https://www.owasp.org/index.php/Test_HTTP_Methods_(OTG-CONFIG-006)[HTTP Verb Tampering], the `StrictServerWebExchangeFirewall` provides an allowed list of valid HTTP methods that are allowed.
The default valid methods are `DELETE`, `GET`, `HEAD`, `OPTIONS`, `PATCH`, `POST`, and `PUT`.
If your application needs to modify the valid methods, you can configure a custom `StrictServerWebExchangeFirewall` bean.
The following example allows only HTTP `GET` and `POST` methods:
.Allow Only GET & POST
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
public StrictServerWebExchangeFirewall httpFirewall() {
StrictServerWebExchangeFirewall firewall = new StrictServerWebExchangeFirewall();
firewall.setAllowedHttpMethods(Arrays.asList("GET", "POST"));
return firewall;
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
fun httpFirewall(): StrictServerWebExchangeFirewall {
val firewall = StrictServerWebExchangeFirewall()
firewall.setAllowedHttpMethods(listOf("GET", "POST"))
return firewall
}
----
======
If you must allow any HTTP method (not recommended), you can use `StrictServerWebExchangeFirewall.setUnsafeAllowAnyHttpMethod(true)`.
Doing so entirely disables validation of the HTTP method.
[[webflux-serverwebexchangefirewall-headers-parameters]]
`StrictServerWebExchangeFirewall` also checks header names and values and parameter names.
It requires that each character have a defined code point and not be a control character.
This requirement can be relaxed or adjusted as necessary by using the following methods:
* `StrictServerWebExchangeFirewall#setAllowedHeaderNames(Predicate)`
* `StrictServerWebExchangeFirewall#setAllowedHeaderValues(Predicate)`
* `StrictServerWebExchangeFirewall#setAllowedParameterNames(Predicate)`
[NOTE]
====
Parameter values can be also controlled with `setAllowedParameterValues(Predicate)`.
====
For example, to switch off this check, you can wire your `StrictServerWebExchangeFirewall` with `Predicate` instances that always return `true`:
.Allow Any Header Name, Header Value, and Parameter Name
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
public StrictServerWebExchangeFirewall httpFirewall() {
StrictServerWebExchangeFirewall firewall = new StrictServerWebExchangeFirewall();
firewall.setAllowedHeaderNames((header) -> true);
firewall.setAllowedHeaderValues((header) -> true);
firewall.setAllowedParameterNames((parameter) -> true);
return firewall;
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
fun httpFirewall(): StrictServerWebExchangeFirewall {
val firewall = StrictServerWebExchangeFirewall()
firewall.setAllowedHeaderNames { true }
firewall.setAllowedHeaderValues { true }
firewall.setAllowedParameterNames { true }
return firewall
}
----
======
Alternatively, there might be a specific value that you need to allow.
For example, iPhone Xʀ uses a `User-Agent` that includes a character that is not in the ISO-8859-1 charset.
Due to this fact, some application servers parse this value into two separate characters, the latter being an undefined character.
You can address this with the `setAllowedHeaderValues` method:
.Allow Certain User Agents
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
public StrictServerWebExchangeFirewall httpFirewall() {
StrictServerWebExchangeFirewall firewall = new StrictServerWebExchangeFirewall();
Pattern allowed = Pattern.compile("[\\p{IsAssigned}&&[^\\p{IsControl}]]*");
Pattern userAgent = ...;
firewall.setAllowedHeaderValues((header) -> allowed.matcher(header).matches() || userAgent.matcher(header).matches());
return firewall;
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
fun httpFirewall(): StrictServerWebExchangeFirewall {
val firewall = StrictServerWebExchangeFirewall()
val allowed = Pattern.compile("[\\p{IsAssigned}&&[^\\p{IsControl}]]*")
val userAgent = Pattern.compile(...)
firewall.setAllowedHeaderValues { allowed.matcher(it).matches() || userAgent.matcher(it).matches() }
return firewall
}
----
======
In the case of header values, you may instead consider parsing them as UTF-8 at verification time:
.Parse Headers As UTF-8
[tabs]
======
Java::
+
[source,java,role="primary"]
----
firewall.setAllowedHeaderValues((header) -> {
String parsed = new String(header.getBytes(ISO_8859_1), UTF_8);
return allowed.matcher(parsed).matches();
});
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
firewall.setAllowedHeaderValues {
val parsed = String(header.getBytes(ISO_8859_1), UTF_8)
return allowed.matcher(parsed).matches()
}
----
======
The `ServerExchangeRejectedHandler` interface is used to handle `ServerExchangeRejectedException` throw by Spring Security's `ServerWebExchangeFirewall`.
By default `HttpStatusExchangeRejectedHandler` is used to send an HTTP 400 response to clients when a request is rejected.
To customize the behavior, users can expose a `ServerExchangeRejectedHandler` Bean.
For example, the following will send an HTTP 404 when the request is rejected:
.Send 404 on Request Rejected
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
ServerExchangeRejectedHandler rejectedHandler() {
return new HttpStatusExchangeRejectedHandler(HttpStatus.NOT_FOUND);
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
fun rejectedHandler(): ServerExchangeRejectedHandler {
return HttpStatusExchangeRejectedHandler(HttpStatus.NOT_FOUND)
}
----
======
Handling can be completely customized by creating a custom `ServerExchangeRejectedHandler` implementation.
@@ -17,7 +17,7 @@ Java::
[source,java,role="primary"]
----
@Bean
CorsConfigurationSource corsConfigurationSource() {
UrlBasedCorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("https://example.com"));
configuration.setAllowedMethods(Arrays.asList("GET","POST"));
@@ -32,7 +32,7 @@ Kotlin::
[source,kotlin,role="secondary"]
----
@Bean
fun corsConfigurationSource(): CorsConfigurationSource {
fun corsConfigurationSource(): UrlBasedCorsConfigurationSource {
val configuration = CorsConfiguration()
configuration.allowedOrigins = listOf("https://example.com")
configuration.allowedMethods = listOf("GET", "POST")
@@ -135,7 +135,7 @@ To enable this, you can stand up the Back-Channel Logout endpoint in the DSL lik
======
Java::
+
[source=java,role="primary"]
[source,java,role="primary"]
----
@Bean
public SecurityWebFilterChain filterChain(ServerHttpSecurity http) throws Exception {
@@ -153,7 +153,7 @@ public SecurityWebFilterChain filterChain(ServerHttpSecurity http) throws Except
Kotlin::
+
[source=kotlin,role="secondary"]
[source,kotlin,role="secondary"]
----
@Bean
open fun filterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
@@ -187,7 +187,7 @@ Consider a `ClientRegistration` whose identifier is `registrationId`.
The overall flow for a Back-Channel logout is like this:
1. At login time, Spring Security correlates the ID Token, CSRF Token, and Provider Session ID (if any) to your application's session id in its `ReactiveOidcSessionStrategy` implementation.
1. At login time, Spring Security correlates the ID Token, CSRF Token, and Provider Session ID (if any) to your application's session id in its `ReactiveOidcSessionRegistry` implementation.
2. Then at logout time, your OIDC Provider makes an API call to `/logout/connect/back-channel/registrationId` including a Logout Token that indicates either the `sub` (the End User) or the `sid` (the Provider Session ID) to logout.
3. Spring Security validates the token's signature and claims.
4. If the token contains a `sid` claim, then only the Client's session that correlates to that provider session is terminated.
@@ -197,38 +197,38 @@ The overall flow for a Back-Channel logout is like this:
Remember that Spring Security's OIDC support is multi-tenant.
This means that it will only terminate sessions whose Client matches the `aud` claim in the Logout Token.
=== Customizing the OIDC Provider Session Strategy
=== Customizing the OIDC Provider Session Registry
By default, Spring Security stores in-memory all links between the OIDC Provider session and the Client session.
There are a number of circumstances, like a clustered application, where it would be nice to store this instead in a separate location, like a database.
You can achieve this by configuring a custom `ReactiveOidcSessionStrategy`, like so:
You can achieve this by configuring a custom `ReactiveOidcSessionRegistry`, like so:
[tabs]
======
Java::
+
[source=java,role="primary"]
[source,java,role="primary"]
----
@Component
public final class MySpringDataOidcSessionStrategy implements OidcSessionStrategy {
public final class MySpringDataOidcSessionRegistry implements ReactiveOidcSessionRegistry {
private final OidcProviderSessionRepository sessions;
// ...
@Override
public void saveSessionInformation(OidcSessionInformation info) {
this.sessions.save(info);
public Mono<void> saveSessionInformation(OidcSessionInformation info) {
return this.sessions.save(info);
}
@Override
public OidcSessionInformation(String clientSessionId) {
public Mono<OidcSessionInformation> removeSessionInformation(String clientSessionId) {
return this.sessions.removeByClientSessionId(clientSessionId);
}
@Override
public Iterable<OidcSessionInformation> removeSessionInformation(OidcLogoutToken token) {
public Flux<OidcSessionInformation> removeSessionInformation(OidcLogoutToken token) {
return token.getSessionId() != null ?
this.sessions.removeBySessionIdAndIssuerAndAudience(...) :
this.sessions.removeBySubjectAndIssuerAndAudience(...);
@@ -238,10 +238,10 @@ public final class MySpringDataOidcSessionStrategy implements OidcSessionStrateg
Kotlin::
+
[source=kotlin,role="secondary"]
[source,kotlin,role="secondary"]
----
@Component
class MySpringDataOidcSessionStrategy: ReactiveOidcSessionStrategy {
class MySpringDataOidcSessionRegistry: ReactiveOidcSessionRegistry {
val sessions: OidcProviderSessionRepository
// ...
@@ -0,0 +1,12 @@
[[servlet-authentication-credentialscontainer]]
= CredentialsContainer
{security-api-url}org/springframework/security/core/CredentialsContainer.html[The `CredentialsContainer`] interface indicates that the implementing object contains sensitive data, and is used internally by Spring Security to erase the authentication credentials after a successful authentication.
This interface is implemented by most of Spring Security internal domain classes, like {security-api-url}org/springframework/security/core/userdetails/User.html[User] and {security-api-url}org/springframework/security/authentication/UsernamePasswordAuthenticationToken.html[UsernamePasswordAuthenticationToken].
The `ProviderManager` manager checks whether the returned `Authentication` implements this interface.
If so, xref:servlet/authentication/architecture.adoc#servlet-authentication-providermanager-erasing-credentials[it calls the `eraseCredentials` method] to remove the credentials from the object.
If you want your custom authentication objects to have their credentials erased after authentication, you should ensure that the classes implement the `CredentialsContainer` interface.
Users who are writing their own `AuthenticationProvider` implementations should create and return an appropriate `Authentication` object there, minus any sensitive data, rather than using this interface.
@@ -326,156 +326,7 @@ Normally, Spring Security builds an `AuthenticationManager` internally composed
In certain cases, it may still be desired to customize the instance of `AuthenticationManager` used by Spring Security.
For example, you may need to simply disable xref:servlet/authentication/architecture.adoc#servlet-authentication-providermanager-erasing-credentials[credential erasure] for cached users.
The recommended way to do this is to simply publish your own `AuthenticationManager` bean, and Spring Security will use it.
You can publish an `AuthenticationManager` using the following configuration:
.Publish `AuthenticationManager` bean for Spring Security
[tabs]
=====
Java::
+
[source,java,role="primary"]
----
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/login").permitAll()
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults())
.formLogin(Customizer.withDefaults());
return http.build();
}
@Bean
public AuthenticationManager authenticationManager(
UserDetailsService userDetailsService,
PasswordEncoder passwordEncoder) {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService);
authenticationProvider.setPasswordEncoder(passwordEncoder);
ProviderManager providerManager = new ProviderManager(authenticationProvider);
providerManager.setEraseCredentialsAfterAuthentication(false);
return providerManager;
}
@Bean
public UserDetailsService userDetailsService() {
UserDetails userDetails = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(userDetails);
}
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
}
----
XML::
+
[source,xml,role="secondary"]
----
<http>
<intercept-url pattern="/login" access="permitAll"/>
<intercept-url pattern="/**" access="authenticated"/>
<form-login />
<http-basic />
<bean id="authenticationManager"
class="org.springframework.security.authentication.ProviderManager">
<constructor-arg>
<bean class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<property name="userDetailsService" ref="userDetailsService" />
<property name="passwordEncoder" ref="passwordEncoder" />
</bean>
</constructor-arg>
</bean>
<user-service id="userDetailsService">
<user name="user"
password="{noop}password"
authorities="ROLE_USER" />
</user-service>
<bean id="passwordEncoder"
class="org.springframework.security.crypto.factory.PasswordEncoderFactories" factory-method="createDelegatingPasswordEncoder"/>
</http>
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
import org.springframework.security.config.annotation.web.invoke
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeHttpRequests {
authorize("/login", permitAll)
authorize(anyRequest, authenticated)
}
formLogin { }
httpBasic { }
}
return http.build()
}
@Bean
fun authenticationManager(
userDetailsService: UserDetailsService,
passwordEncoder: PasswordEncoder): AuthenticationManager {
val authenticationProvider = DaoAuthenticationProvider()
authenticationProvider.setUserDetailsService(userDetailsService)
authenticationProvider.setPasswordEncoder(passwordEncoder)
val providerManager = ProviderManager(authenticationProvider)
providerManager.eraseCredentialsAfterAuthentication = false
return providerManager
}
@Bean
fun userDetailsService(): UserDetailsService {
val user = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build()
return InMemoryUserDetailsManager(user)
}
@Bean
fun passwordEncoder(): PasswordEncoder {
return PasswordEncoderFactories.createDelegatingPasswordEncoder()
}
}
----
=====
Alternatively, you can take advantage of the fact that the `AuthenticationManagerBuilder` used to build Spring Security's global `AuthenticationManager` is published as a bean.
To do this, you can take advantage of the fact that the `AuthenticationManagerBuilder` used to build Spring Security's global `AuthenticationManager` is published as a bean.
You can configure the builder as follows:
.Configure global `AuthenticationManagerBuilder`
@@ -539,3 +390,142 @@ class SecurityConfig {
}
----
=====
Alternatively, you may configure a local `AuthenticationManager` to override the global one.
.Configure local `AuthenticationManager` for Spring Security
[tabs]
=====
Java::
+
[source,java,role="primary"]
----
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults())
.formLogin(Customizer.withDefaults())
.authenticationManager(authenticationManager());
return http.build();
}
private AuthenticationManager authenticationManager() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService());
authenticationProvider.setPasswordEncoder(passwordEncoder());
ProviderManager providerManager = new ProviderManager(authenticationProvider);
providerManager.setEraseCredentialsAfterAuthentication(false);
return providerManager;
}
private UserDetailsService userDetailsService() {
UserDetails userDetails = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(userDetails);
}
private PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
}
----
XML::
+
[source,xml,role="secondary"]
----
<http authentication-manager-ref="authenticationManager">
<intercept-url pattern="/**" access="authenticated"/>
<form-login />
<http-basic />
<bean id="authenticationManager"
class="org.springframework.security.authentication.ProviderManager">
<constructor-arg>
<bean class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<property name="userDetailsService" ref="userDetailsService" />
<property name="passwordEncoder" ref="passwordEncoder" />
</bean>
</constructor-arg>
</bean>
<user-service id="userDetailsService">
<user name="user"
password="{noop}password"
authorities="ROLE_USER" />
</user-service>
<bean id="passwordEncoder"
class="org.springframework.security.crypto.factory.PasswordEncoderFactories" factory-method="createDelegatingPasswordEncoder"/>
</http>
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
import org.springframework.security.config.annotation.web.invoke
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeHttpRequests {
authorize(anyRequest, authenticated)
}
formLogin { }
httpBasic { }
authenticationManager = authenticationManager()
}
return http.build()
}
@Bean
fun authenticationManager(): AuthenticationManager {
val authenticationProvider = DaoAuthenticationProvider()
authenticationProvider.setUserDetailsService(userDetailsService())
authenticationProvider.setPasswordEncoder(passwordEncoder())
val providerManager = ProviderManager(authenticationProvider)
providerManager.eraseCredentialsAfterAuthentication = false
return providerManager
}
private fun userDetailsService(): UserDetailsService {
val user = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build()
return InMemoryUserDetailsManager(user)
}
private fun passwordEncoder(): PasswordEncoder {
return PasswordEncoderFactories.createDelegatingPasswordEncoder()
}
}
----
=====
@@ -101,7 +101,7 @@ The `AuthorizationManager` interface contains two methods:
----
AuthorizationDecision check(Supplier<Authentication> authentication, Object secureObject);
default AuthorizationDecision verify(Supplier<Authentication> authentication, Object secureObject)
default void verify(Supplier<Authentication> authentication, Object secureObject)
throws AccessDeniedException {
// ...
}
@@ -116,7 +116,7 @@ A given invocation to `MyCustomerService#readCustomer` may look something like t
image::{figures}/methodsecurity.png[]
1. Spring AOP invokes its proxy method for `readCustomer`. Among the proxy's other advisors, it invokes an {security-api-url}org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptor/html[`AuthorizationManagerBeforeMethodInterceptor`] that matches <<annotation-method-pointcuts,the `@PreAuthorize` pointcut>>
1. Spring AOP invokes its proxy method for `readCustomer`. Among the proxy's other advisors, it invokes an {security-api-url}org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptor.html[`AuthorizationManagerBeforeMethodInterceptor`] that matches <<annotation-method-pointcuts,the `@PreAuthorize` pointcut>>
2. The interceptor invokes {security-api-url}org/springframework/security/authorization/method/PreAuthorizeAuthorizationManager.html[`PreAuthorizeAuthorizationManager#check`]
3. The authorization manager uses a `MethodSecurityExpressionHandler` to parse the annotation's <<authorization-expressions,SpEL expression>> and constructs a corresponding `EvaluationContext` from a `MethodSecurityExpressionRoot` containing xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[a `Supplier<Authentication>`] and `MethodInvocation`.
4. The interceptor uses this context to evaluate the expression; specifically, it reads xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[the `Authentication`] from the `Supplier` and checks whether it has `permission:read` in its collection of xref:servlet/authorization/architecture.adoc#authz-authorities[authorities]
@@ -239,7 +239,7 @@ static RoleHierarchy roleHierarchy() {
Kotlin::
+
[source,java,role="secondary"]
[source,kotlin,role="secondary"]
----
companion object {
@Bean
@@ -130,7 +130,7 @@ You can also specify <<csrf-token-repository-custom,your own implementation>> to
By default, Spring Security stores the expected CSRF token in the `HttpSession` by using {security-api-url}org/springframework/security/web/csrf/HttpSessionCsrfTokenRepository.html[`HttpSessionCsrfTokenRepository`], so no additional code is necessary.
The `HttpSessionCsrfTokenRepository` reads the token from an HTTP request header named `X-CSRF-TOKEN` or the request parameter `_csrf` by default.
The `HttpSessionCsrfTokenRepository` reads the token from a session (whether in-memory, cache, or database). If you need to access the session attribute directly, please first configure the session attribute name using `HttpSessionCsrfTokenRepository#setSessionAttributeName`.
You can specify the default configuration explicitly using the following configuration:
@@ -6,7 +6,7 @@ CORS must be processed before Spring Security, because the pre-flight request do
If the request does not contain any cookies and Spring Security is first, the request determines that the user is not authenticated (since there are no cookies in the request) and rejects it.
The easiest way to ensure that CORS is handled first is to use the `CorsFilter`.
Users can integrate the `CorsFilter` with Spring Security by providing a `CorsConfigurationSource`.
Users can integrate the `CorsFilter` with Spring Security by providing a `CorsConfigurationSource`. Note that Spring Security will automatically configure CORS only if a `UrlBasedCorsConfigurationSource` instance is present.
For example, the following will integrate CORS support within Spring Security:
[tabs]
@@ -16,7 +16,7 @@ Java::
[source,java,role="primary"]
----
@Bean
CorsConfigurationSource corsConfigurationSource() {
UrlBasedCorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("https://example.com"));
configuration.setAllowedMethods(Arrays.asList("GET","POST"));
@@ -31,7 +31,7 @@ Kotlin::
[source,kotlin,role="secondary"]
----
@Bean
fun corsConfigurationSource(): CorsConfigurationSource {
fun corsConfigurationSource(): UrlBasedCorsConfigurationSource {
val configuration = CorsConfiguration()
configuration.allowedOrigins = listOf("https://example.com")
configuration.allowedMethods = listOf("GET", "POST")
@@ -147,7 +147,7 @@ public class WebSecurityConfig {
return http.build();
}
CorsConfigurationSource apiConfigurationSource() {
UrlBasedCorsConfigurationSource apiConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("https://api.example.com"));
configuration.setAllowedMethods(Arrays.asList("GET","POST"));
@@ -156,7 +156,7 @@ public class WebSecurityConfig {
return source;
}
CorsConfigurationSource myWebsiteConfigurationSource() {
UrlBasedCorsConfigurationSource myWebsiteConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("https://example.com"));
configuration.setAllowedMethods(Arrays.asList("GET","POST"));
@@ -173,7 +173,7 @@ Kotlin::
[source,kotlin,role="secondary"]
----
@Bean
fun corsConfigurationSource(): CorsConfigurationSource {
fun corsConfigurationSource(): UrlBasedCorsConfigurationSource {
val configuration = CorsConfiguration()
configuration.allowedOrigins = listOf("https://example.com")
configuration.allowedMethods = listOf("GET", "POST")
@@ -134,7 +134,7 @@ To enable this, you can stand up the Back-Channel Logout endpoint in the DSL lik
======
Java::
+
[source=java,role="primary"]
[source,java,role="primary"]
----
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
@@ -152,7 +152,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
Kotlin::
+
[source=kotlin,role="secondary"]
[source,kotlin,role="secondary"]
----
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
@@ -176,7 +176,7 @@ Then, you need a way listen to events published by Spring Security to remove old
======
Java::
+
[source=java,role="primary"]
[source,java,role="primary"]
----
@Bean
public HttpSessionEventPublisher sessionEventPublisher() {
@@ -186,7 +186,7 @@ public HttpSessionEventPublisher sessionEventPublisher() {
Kotlin::
+
[source=kotlin,role="secondary"]
[source,kotlin,role="secondary"]
----
@Bean
open fun sessionEventPublisher(): HttpSessionEventPublisher {
@@ -213,7 +213,7 @@ Consider a `ClientRegistration` whose identifier is `registrationId`.
The overall flow for a Back-Channel logout is like this:
1. At login time, Spring Security correlates the ID Token, CSRF Token, and Provider Session ID (if any) to your application's session id in its `OidcSessionStrategy` implementation.
1. At login time, Spring Security correlates the ID Token, CSRF Token, and Provider Session ID (if any) to your application's session id in its `OidcSessionRegistry` implementation.
2. Then at logout time, your OIDC Provider makes an API call to `/logout/connect/back-channel/registrationId` including a Logout Token that indicates either the `sub` (the End User) or the `sid` (the Provider Session ID) to logout.
3. Spring Security validates the token's signature and claims.
4. If the token contains a `sid` claim, then only the Client's session that correlates to that provider session is terminated.
@@ -223,22 +223,22 @@ The overall flow for a Back-Channel logout is like this:
Remember that Spring Security's OIDC support is multi-tenant.
This means that it will only terminate sessions whose Client matches the `aud` claim in the Logout Token.
=== Customizing the OIDC Provider Session Strategy
=== Customizing the OIDC Provider Session Registry
By default, Spring Security stores in-memory all links between the OIDC Provider session and the Client session.
There are a number of circumstances, like a clustered application, where it would be nice to store this instead in a separate location, like a database.
You can achieve this by configuring a custom `OidcSessionStrategy`, like so:
You can achieve this by configuring a custom `OidcSessionRegistry`, like so:
[tabs]
======
Java::
+
[source=java,role="primary"]
[source,java,role="primary"]
----
@Component
public final class MySpringDataOidcSessionStrategy implements OidcSessionStrategy {
public final class MySpringDataOidcSessionRegistry implements OidcSessionRegistry {
private final OidcProviderSessionRepository sessions;
// ...
@@ -249,7 +249,7 @@ public final class MySpringDataOidcSessionStrategy implements OidcSessionStrateg
}
@Override
public OidcSessionInformation(String clientSessionId) {
public OidcSessionInformation removeSessionInformation(String clientSessionId) {
return this.sessions.removeByClientSessionId(clientSessionId);
}
@@ -264,10 +264,10 @@ public final class MySpringDataOidcSessionStrategy implements OidcSessionStrateg
Kotlin::
+
[source=kotlin,role="secondary"]
[source,kotlin,role="secondary"]
----
@Component
class MySpringDataOidcSessionStrategy: OidcSessionStrategy {
class MySpringDataOidcSessionRegistry: OidcSessionRegistry {
val sessions: OidcProviderSessionRepository
// ...
@@ -89,12 +89,12 @@ Next, let's see the architectural components that Spring Security uses to suppor
{security-api-url}org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationProvider.html[`JwtAuthenticationProvider`] is an xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationprovider[`AuthenticationProvider`] implementation that leverages a <<oauth2resourceserver-jwt-decoder,`JwtDecoder`>> and <<oauth2resourceserver-jwt-authorization-extraction,`JwtAuthenticationConverter`>> to authenticate a JWT.
Let's take a look at how `JwtAuthenticationProvider` works within Spring Security.
The figure explains details of how the xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationmanager[`AuthenticationManager`] in figures from <<oauth2resourceserver-authentication-bearertokenauthenticationfilter,Reading the Bearer Token>> works.
The figure explains details of how the xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationmanager[`AuthenticationManager`] in figures from xref:servlet/oauth2/resource-server/index.adoc#oauth2resourceserver-authentication-bearertokenauthenticationfilter[Reading the Bearer Token] works.
.`JwtAuthenticationProvider` Usage
image::{figures}/jwtauthenticationprovider.png[]
image:{icondir}/number_1.png[] The authentication `Filter` from <<oauth2resourceserver-authentication-bearertokenauthenticationfilter,Reading the Bearer Token>> passes a `BearerTokenAuthenticationToken` to the `AuthenticationManager` which is implemented by xref:servlet/authentication/architecture.adoc#servlet-authentication-providermanager[`ProviderManager`].
image:{icondir}/number_1.png[] The authentication `Filter` from xref:servlet/oauth2/resource-server/index.adoc#oauth2resourceserver-authentication-bearertokenauthenticationfilter[Reading the Bearer Token] passes a `BearerTokenAuthenticationToken` to the `AuthenticationManager` which is implemented by xref:servlet/authentication/architecture.adoc#servlet-authentication-providermanager[`ProviderManager`].
image:{icondir}/number_2.png[] The `ProviderManager` is configured to use an xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationprovider[AuthenticationProvider] of type `JwtAuthenticationProvider`.
@@ -85,12 +85,12 @@ Next, let's see the architectural components that Spring Security uses to suppor
{security-api-url}org/springframework/security/oauth2/server/resource/authentication/OpaqueTokenAuthenticationProvider.html[`OpaqueTokenAuthenticationProvider`] is an xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationprovider[`AuthenticationProvider`] implementation that leverages a <<oauth2resourceserver-opaque-introspector,`OpaqueTokenIntrospector`>> to authenticate an opaque token.
Let's take a look at how `OpaqueTokenAuthenticationProvider` works within Spring Security.
The figure explains details of how the xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationmanager[`AuthenticationManager`] in figures from <<oauth2resourceserver-authentication-bearertokenauthenticationfilter,Reading the Bearer Token>> works.
The figure explains details of how the xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationmanager[`AuthenticationManager`] in figures from xref:servlet/oauth2/resource-server/index.adoc#oauth2resourceserver-authentication-bearertokenauthenticationfilter[Reading the Bearer Token] works.
.`OpaqueTokenAuthenticationProvider` Usage
image::{figures}/opaquetokenauthenticationprovider.png[]
image:{icondir}/number_1.png[] The authentication `Filter` from <<oauth2resourceserver-authentication-bearertokenauthenticationfilter,Reading the Bearer Token>> passes a `BearerTokenAuthenticationToken` to the `AuthenticationManager` which is implemented by xref:servlet/authentication/architecture.adoc#servlet-authentication-providermanager[`ProviderManager`].
image:{icondir}/number_1.png[] The authentication `Filter` from xref:servlet/oauth2/resource-server/index.adoc#oauth2resourceserver-authentication-bearertokenauthenticationfilter[Reading the Bearer Token] passes a `BearerTokenAuthenticationToken` to the `AuthenticationManager` which is implemented by xref:servlet/authentication/architecture.adoc#servlet-authentication-providermanager[`ProviderManager`].
image:{icondir}/number_2.png[] The `ProviderManager` is configured to use an xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationprovider[AuthenticationProvider] of type `OpaqueTokenAuthenticationProvider`.
@@ -87,7 +87,7 @@ RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.wit
Kotlin::
+
[source,java,role="secondary"]
[source,kotlin,role="secondary"]
----
var relyingPartyRegistration: RelyingPartyRegistration =
RelyingPartyRegistration.withRegistrationId("okta")
@@ -96,7 +96,7 @@ var relyingPartyRegistration: RelyingPartyRegistration =
// ...
.wantAuthnRequestsSigned(false)
}
.build();
.build()
----
======
@@ -141,7 +141,7 @@ var relyingPartyRegistration: RelyingPartyRegistration =
)
}
}
.build();
.build()
----
======
@@ -949,12 +949,12 @@ Collection<RelyingPartyRegistration> registrations = RelyingPartyRegistrations
.entityId("https://example.org/saml2/sp")
.build()
)
.collect(Collectors.toList()));
.collect(Collectors.toList());
----
Kotlin::
+
[source,java,role="secondary"]
[source,kotlin,role="secondary"]
----
var registrations: Collection<RelyingPartyRegistration> = RelyingPartyRegistrations
.collectionFromMetadataLocation("https://example.org/saml2/idp/metadata.xml")
@@ -964,7 +964,7 @@ var registrations: Collection<RelyingPartyRegistration> = RelyingPartyRegistrati
.assertionConsumerServiceLocation("{baseUrl}/login/saml2/sso")
.build()
}
.collect(Collectors.toList()));
.collect(Collectors.toList())
----
======
+4 -4
View File
@@ -1,10 +1,10 @@
{
"dependencies": {
"antora": "3.2.0-alpha.4",
"antora": "3.2.0-alpha.6",
"@antora/atlas-extension": "1.0.0-alpha.2",
"@antora/collector-extension": "1.0.0-alpha.4",
"@antora/collector-extension": "1.0.0-beta.5",
"@asciidoctor/tabs": "1.0.0-beta.6",
"@springio/antora-extensions": "1.11.1",
"@springio/asciidoctor-extensions": "1.0.0-alpha.10"
"@springio/antora-extensions": "1.14.2",
"@springio/asciidoctor-extensions": "1.0.0-alpha.14"
}
}
@@ -40,4 +40,8 @@
<!-- Lambdas that we can't replace with a method reference because a closure is required -->
<suppress files="BearerTokenAuthenticationFilter\.java" checks="SpringLambda"/>
<!-- Ignore String.toUpperCase() and String.toLowerCase() checks in tests -->
<suppress files="[\\/]src[\\/]test[\\/]" checks="RegexpSinglelineJava" id="toLowerCaseWithoutLocale"/>
<suppress files="[\\/]src[\\/]test[\\/]" checks="RegexpSinglelineJava" id="toUpperCaseWithoutLocale"/>
</suppressions>
+16
View File
@@ -30,5 +30,21 @@
<property name="message" value="Please use assertThatExceptionOfType." />
<property name="ignoreComments" value="true" />
</module>
<module name="com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineJavaCheck">
<property name="id" value="toLowerCaseWithoutLocale"/>
<property name="format" value="\.toLowerCase\(\)"/>
<property name="maximum" value="0"/>
<property name="message"
value="String.toLowerCase() should be String.toLowerCase(Locale.ROOT) or String.toLowerCase(Locale.ENGLISH)"/>
<property name="ignoreComments" value="true"/>
</module>
<module name="com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineJavaCheck">
<property name="id" value="toUpperCaseWithoutLocale"/>
<property name="format" value="\.toUpperCase\(\)"/>
<property name="maximum" value="0"/>
<property name="message"
value="String.toUpperCase() should be String.toUpperCase(Locale.ROOT) or String.toUpperCase(Locale.ENGLISH)"/>
<property name="ignoreComments" value="true"/>
</module>
</module>
</module>
+1
View File
@@ -10,6 +10,7 @@ org.eclipse.jdt.core.codeComplete.staticFieldSuffixes=
org.eclipse.jdt.core.codeComplete.staticFinalFieldPrefixes=
org.eclipse.jdt.core.codeComplete.staticFinalFieldSuffixes=
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.methodParameters=generate
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.6
+3
View File
@@ -12,4 +12,7 @@
<property name="optional" value="true"/>
</module>
<module name="SuppressWithPlainTextCommentFilter"/>
<module name="BeforeExecutionExclusionFileFilter">
<property name="fileNamePattern" value=".*[\\/]node(_modules|js)[\\/].*$"/>
</module>
</module>
+1 -1
View File
@@ -1,5 +1,5 @@
springBootVersion=3.1.1
version=6.2.5-SNAPSHOT
version=6.2.8
samplesBranch=6.2.x
org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError
org.gradle.parallel=true
+18 -18
View File
@@ -1,19 +1,19 @@
[versions]
com-squareup-okhttp3 = "3.14.9"
io-rsocket = "1.1.4"
io-spring-javaformat = "0.0.42"
io-spring-javaformat = "0.0.43"
io-spring-nohttp = "0.0.11"
jakarta-websocket = "2.1.1"
org-apache-directory-server = "1.5.5"
org-apache-maven-resolver = "1.9.20"
org-apache-maven-resolver = "1.9.22"
org-aspectj = "1.9.22.1"
org-bouncycastle = "1.70"
org-eclipse-jetty = "11.0.21"
org-jetbrains-kotlin = "1.9.24"
org-eclipse-jetty = "11.0.24"
org-jetbrains-kotlin = "1.9.25"
org-jetbrains-kotlinx = "1.7.3"
org-mockito = "5.5.0"
org-opensaml = "4.3.2"
org-springframework = "6.1.9"
org-springframework = "6.1.15"
[libraries]
ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.4.14"
@@ -26,9 +26,9 @@ com-squareup-okhttp3-mockwebserver = { module = "com.squareup.okhttp3:mockwebser
com-squareup-okhttp3-okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "com-squareup-okhttp3" }
com-unboundid-unboundid-ldapsdk = "com.unboundid:unboundid-ldapsdk:6.0.11"
commons-collections = "commons-collections:commons-collections:3.2.2"
io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.12.7"
io-mockk = "io.mockk:mockk:1.13.11"
io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2023.0.7"
io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.12.13"
io-mockk = "io.mockk:mockk:1.13.13"
io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2023.0.12"
io-rsocket-rsocket-bom = { module = "io.rsocket:rsocket-bom", version.ref = "io-rsocket" }
io-spring-javaformat-spring-javaformat-checkstyle = { module = "io.spring.javaformat:spring-javaformat-checkstyle", version.ref = "io-spring-javaformat" }
io-spring-javaformat-spring-javaformat-gradle-plugin = { module = "io.spring.javaformat:spring-javaformat-gradle-plugin", version.ref = "io-spring-javaformat" }
@@ -40,7 +40,7 @@ jakarta-inject-jakarta-inject-api = "jakarta.inject:jakarta.inject-api:2.0.1"
jakarta-persistence-jakarta-persistence-api = "jakarta.persistence:jakarta.persistence-api:3.1.0"
jakarta-servlet-jakarta-servlet-api = "jakarta.servlet:jakarta.servlet-api:6.0.0"
jakarta-servlet-jsp-jakarta-servlet-jsp-api = "jakarta.servlet.jsp:jakarta.servlet.jsp-api:3.1.1"
jakarta-servlet-jsp-jstl-jakarta-servlet-jsp-jstl-api = "jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api:3.0.0"
jakarta-servlet-jsp-jstl-jakarta-servlet-jsp-jstl-api = "jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api:3.0.2"
jakarta-websocket-jakarta-websocket-api = { module = "jakarta.websocket:jakarta.websocket-api", version.ref = "jakarta-websocket" }
jakarta-websocket-jakarta-websocket-client-api = { module = "jakarta.websocket:jakarta.websocket-client-api", version.ref = "jakarta-websocket" }
jakarta-xml-bind-jakarta-xml-bind-api = "jakarta.xml.bind:jakarta.xml.bind-api:4.0.2"
@@ -53,7 +53,7 @@ org-apache-directory-server-apacheds-protocol-shared = { module = "org.apache.di
org-apache-directory-server-apacheds-server-jndi = { module = "org.apache.directory.server:apacheds-server-jndi", version.ref = "org-apache-directory-server" }
org-apache-directory-shared-shared-ldap = "org.apache.directory.shared:shared-ldap:0.9.15"
org-apache-httpcomponents-httpclient = "org.apache.httpcomponents:httpclient:4.5.14"
org-apache-maven-maven-resolver-provider = "org.apache.maven:maven-resolver-provider:3.9.7"
org-apache-maven-maven-resolver-provider = "org.apache.maven:maven-resolver-provider:3.9.9"
org-apache-maven-resolver-maven-resolver-connector-basic = { module = "org.apache.maven.resolver:maven-resolver-connector-basic", version.ref = "org-apache-maven-resolver" }
org-apache-maven-resolver-maven-resolver-impl = { module = "org.apache.maven.resolver:maven-resolver-impl", version.ref = "org-apache-maven-resolver" }
org-apache-maven-resolver-maven-resolver-transport-http = { module = "org.apache.maven.resolver:maven-resolver-transport-http", version.ref = "org-apache-maven-resolver" }
@@ -68,11 +68,11 @@ org-eclipse-jetty-jetty-server = { module = "org.eclipse.jetty:jetty-server", ve
org-eclipse-jetty-jetty-servlet = { module = "org.eclipse.jetty:jetty-servlet", version.ref = "org-eclipse-jetty" }
org-hamcrest = "org.hamcrest:hamcrest:2.2"
org-hibernate-orm-hibernate-core = "org.hibernate.orm:hibernate-core:6.3.2.Final"
org-hsqldb = "org.hsqldb:hsqldb:2.7.3"
org-hsqldb = "org.hsqldb:hsqldb:2.7.4"
org-jetbrains-kotlin-kotlin-bom = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "org-jetbrains-kotlin" }
org-jetbrains-kotlin-kotlin-gradle-plugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.24"
org-jetbrains-kotlin-kotlin-gradle-plugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25"
org-jetbrains-kotlinx-kotlinx-coroutines-bom = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-bom", version.ref = "org-jetbrains-kotlinx" }
org-junit-junit-bom = "org.junit:junit-bom:5.10.2"
org-junit-junit-bom = "org.junit:junit-bom:5.10.5"
org-mockito-mockito-bom = { module = "org.mockito:mockito-bom", version.ref = "org-mockito" }
org-opensaml-opensaml-core = { module = "org.opensaml:opensaml-core", version.ref = "org-opensaml" }
org-opensaml-opensaml-saml-api = { module = "org.opensaml:opensaml-saml-api", version.ref = "org-opensaml" }
@@ -81,11 +81,11 @@ org-python-jython = { module = "org.python:jython", version = "2.5.3" }
org-seleniumhq-selenium-htmlunit-driver = "org.seleniumhq.selenium:htmlunit-driver:2.70.0"
org-seleniumhq-selenium-selenium-java = "org.seleniumhq.selenium:selenium-java:3.141.59"
org-seleniumhq-selenium-selenium-support = "org.seleniumhq.selenium:selenium-support:3.141.59"
org-skyscreamer-jsonassert = "org.skyscreamer:jsonassert:1.5.1"
org-skyscreamer-jsonassert = "org.skyscreamer:jsonassert:1.5.3"
org-slf4j-log4j-over-slf4j = "org.slf4j:log4j-over-slf4j:1.7.36"
org-slf4j-slf4j-api = "org.slf4j:slf4j-api:2.0.13"
org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2023.1.7"
org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:3.2.4"
org-slf4j-slf4j-api = "org.slf4j:slf4j-api:2.0.16"
org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2023.1.12"
org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:3.2.8"
org-springframework-spring-framework-bom = { module = "org.springframework:spring-framework-bom", version.ref = "org-springframework" }
org-synchronoss-cloud-nio-multipart-parser = "org.synchronoss.cloud:nio-multipart-parser:1.1.0"
@@ -97,7 +97,7 @@ org-apache-commons-commons-io = "org.apache.commons:commons-io:1.3.2"
io-github-gradle-nexus-publish-plugin = "io.github.gradle-nexus:publish-plugin:1.1.0"
org-gretty-gretty = "org.gretty:gretty:4.0.3"
com-github-ben-manes-gradle-versions-plugin = "com.github.ben-manes:gradle-versions-plugin:0.38.0"
com-github-spullara-mustache-java-compiler = "com.github.spullara.mustache.java:compiler:0.9.13"
com-github-spullara-mustache-java-compiler = "com.github.spullara.mustache.java:compiler:0.9.14"
org-hidetake-gradle-ssh-plugin = "org.hidetake:gradle-ssh-plugin:2.10.1"
org-jfrog-buildinfo-build-info-extractor-gradle = "org.jfrog.buildinfo:build-info-extractor-gradle:4.29.4"
org-sonarsource-scanner-gradle-sonarqube-gradle-plugin = "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.7.1"
Binary file not shown.
+2 -2
View File
@@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=544c35d6bd849ae8a5ed0bcea39ba677dc40f49df7d1835561582da2009b961d
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
distributionSha256Sum=31c55713e40233a8303827ceb42ca48a47267a0ad4bab9177123121e71524c26
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
+5 -2
View File
@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -84,7 +86,8 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
Vendored
+2
View File
@@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@@ -1,5 +1,5 @@
/*
* Copyright 2005-2010 the original author or authors.
* Copyright 2005-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,8 @@
package org.springframework.security.ldap;
import java.util.Locale;
import org.springframework.ldap.BadLdapGrammarException;
/**
@@ -72,7 +74,7 @@ final class LdapEncoder {
}
protected static String toTwoCharHex(char c) {
String raw = Integer.toHexString(c).toUpperCase();
String raw = Integer.toHexString(c).toUpperCase(Locale.ENGLISH);
return (raw.length() > 1) ? raw : "0" + raw;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2005-2010 the original author or authors.
* Copyright 2005-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,8 @@
package org.springframework.security.ldap.authentication;
import java.util.Locale;
import org.springframework.ldap.BadLdapGrammarException;
/**
@@ -72,7 +74,7 @@ final class LdapEncoder {
}
protected static String toTwoCharHex(char c) {
String raw = Integer.toHexString(c).toUpperCase();
String raw = Integer.toHexString(c).toUpperCase(Locale.ENGLISH);
return (raw.length() > 1) ? raw : "0" + raw;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2024 the original author 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 java.util.Collection;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -136,15 +137,17 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLda
ContextFactory contextFactory = new ContextFactory();
/**
* @param domain the domain name (may be null or empty)
* @param url an LDAP url (or multiple URLs)
* @param rootDn the root DN (may be null or empty)
* @param domain the domain name (can be null or empty)
* @param url an LDAP url (or multiple space-delimited URLs).
* @param rootDn the root DN (can be null or empty)
* @see <a href="https://docs.oracle.com/javase/jndi/tutorial/ldap/misc/url.html">JNDI
* URL format documentation</a>
*/
public ActiveDirectoryLdapAuthenticationProvider(String domain, String url, String rootDn) {
Assert.isTrue(StringUtils.hasText(url), "Url cannot be empty");
this.domain = StringUtils.hasText(domain) ? domain.toLowerCase() : null;
this.domain = StringUtils.hasText(domain) ? domain.toLowerCase(Locale.ROOT) : null;
this.url = url;
this.rootDn = StringUtils.hasText(rootDn) ? rootDn.toLowerCase() : null;
this.rootDn = StringUtils.hasText(rootDn) ? rootDn.toLowerCase(Locale.ROOT) : null;
}
/**
@@ -153,7 +156,7 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLda
*/
public ActiveDirectoryLdapAuthenticationProvider(String domain, String url) {
Assert.isTrue(StringUtils.hasText(url), "Url cannot be empty");
this.domain = StringUtils.hasText(domain) ? domain.toLowerCase() : null;
this.domain = StringUtils.hasText(domain) ? domain.toLowerCase(Locale.ROOT) : null;
this.url = url;
this.rootDn = (this.domain != null) ? rootDnFromDomain(this.domain) : null;
}
@@ -348,7 +351,7 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLda
}
String createBindPrincipal(String username) {
if (this.domain == null || username.toLowerCase().endsWith(this.domain)) {
if (this.domain == null || username.toLowerCase(Locale.ROOT).endsWith(this.domain)) {
return username;
}
return username + "@" + this.domain;
@@ -20,6 +20,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
@@ -179,7 +180,7 @@ public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator
return null;
}
if (this.convertToUpperCase) {
role = role.toUpperCase();
role = role.toUpperCase(Locale.ROOT);
}
return new SimpleGrantedAuthority(this.rolePrefix + role);
};
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author 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 java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Locale;
import javax.naming.Context;
import javax.naming.NameNotFoundException;
@@ -124,7 +125,7 @@ public class LdapUserDetailsManager implements UserDetailsManager {
NamingEnumeration<?> ne = roleAttr.getAll();
Object group = ne.next();
String role = group.toString();
return new SimpleGrantedAuthority(this.rolePrefix + role.toUpperCase());
return new SimpleGrantedAuthority(this.rolePrefix + role.toUpperCase(Locale.ROOT));
};
private String[] attributesToRetrieve;
@@ -284,10 +285,12 @@ public class LdapUserDetailsManager implements UserDetailsManager {
* Creates a DN from a group name.
* @param group the name of the group
* @return the DN of the corresponding group, including the groupSearchBase
* @deprecated
*/
@Deprecated
protected DistinguishedName buildGroupDn(String group) {
DistinguishedName dn = new DistinguishedName(this.groupSearchBase);
dn.add(this.groupRoleAttributeName, group.toLowerCase());
dn.add(this.groupRoleAttributeName, group.toLowerCase(Locale.ROOT));
return dn;
}
@@ -17,6 +17,7 @@
package org.springframework.security.ldap.userdetails;
import java.util.Collection;
import java.util.Locale;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -127,7 +128,7 @@ public class LdapUserDetailsMapper implements UserDetailsContextMapper {
protected GrantedAuthority createAuthority(Object role) {
if (role instanceof String) {
if (this.convertToUpperCase) {
role = ((String) role).toUpperCase();
role = ((String) role).toUpperCase(Locale.ROOT);
}
return new SimpleGrantedAuthority(this.rolePrefix + role);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.security.ldap.userdetails;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
@@ -193,7 +194,7 @@ public class NestedLdapAuthoritiesPopulator extends DefaultLdapAuthoritiesPopula
}
for (String role : roles) {
if (isConvertToUpperCase()) {
role = role.toUpperCase();
role = role.toUpperCase(Locale.ROOT);
}
role = getRolePrefix() + role;
// if the group already exist, we will not search for it's parents again.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.security.oauth2.client.web.reactive.function.client;
import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
@@ -539,7 +540,7 @@ public final class ServerOAuth2AuthorizedClientExchangeFilterFunction implements
// @formatter:off
return Stream.of(wwwAuthenticateHeader)
.filter((header) -> StringUtils.hasLength(header))
.filter((header) -> header.toLowerCase().startsWith("bearer"))
.filter((header) -> header.toLowerCase(Locale.ENGLISH).startsWith("bearer"))
.map((header) -> header.substring("bearer".length()))
.map((header) -> header.split(","))
.flatMap(Stream::of)
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.security.oauth2.client.web.reactive.function.client;
import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -654,7 +655,7 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement
private Map<String, String> parseAuthParameters(String wwwAuthenticateHeader) {
// @formatter:off
return Stream.of(wwwAuthenticateHeader).filter((header) -> StringUtils.hasLength(header))
.filter((header) -> header.toLowerCase().startsWith("bearer"))
.filter((header) -> header.toLowerCase(Locale.ENGLISH).startsWith("bearer"))
.map((header) -> header.substring("bearer".length()))
.map((header) -> header.split(","))
.flatMap(Stream::of)
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -440,7 +440,21 @@ public final class OAuth2AuthorizationRequest implements Serializable {
Map<String, Object> parameters = getParameters(); // Not encoded
this.parametersConsumer.accept(parameters);
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
parameters.forEach((k, v) -> queryParams.set(encodeQueryParam(k), encodeQueryParam(String.valueOf(v)))); // Encoded
parameters.forEach((k, v) -> {
String key = encodeQueryParam(k);
if (v instanceof Iterable) {
((Iterable<?>) v).forEach((value) -> queryParams.add(key, encodeQueryParam(String.valueOf(value))));
}
else if (v != null && v.getClass().isArray()) {
Object[] values = (Object[]) v;
for (Object value : values) {
queryParams.add(key, encodeQueryParam(String.valueOf(value)));
}
}
else {
queryParams.set(key, encodeQueryParam(String.valueOf(v)));
}
});
UriBuilder uriBuilder = this.uriBuilderFactory.uriString(this.authorizationUri).queryParams(queryParams);
return this.authorizationRequestUriFunction.apply(uriBuilder).toString();
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@ package org.springframework.security.oauth2.core.endpoint;
import java.net.URI;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
@@ -319,4 +320,49 @@ public class OAuth2AuthorizationRequestTests {
+ "item%20amount=19.95%E2%82%AC&%C3%A2ge=4%C2%BD&item%20name=H%C3%85M%C3%96");
}
@Test
public void buildWhenAdditionalParametersContainsArrayThenProperlyEncoded() {
Map<String, Object> additionalParameters = new LinkedHashMap<>();
additionalParameters.put("item1", new String[] { "1", "2" });
additionalParameters.put("item2", "value2");
OAuth2AuthorizationRequest authorizationRequest = TestOAuth2AuthorizationRequests.request()
.additionalParameters(additionalParameters)
.build();
assertThat(authorizationRequest.getAuthorizationRequestUri()).isNotNull();
assertThat(authorizationRequest.getAuthorizationRequestUri())
.isEqualTo("https://example.com/login/oauth/authorize?response_type=code&client_id=client-id&state=state&"
+ "redirect_uri=https://example.com/authorize/oauth2/code/registration-id&"
+ "item1=1&item1=2&item2=value2");
}
@Test
public void buildWhenAdditionalParametersContainsIterableThenProperlyEncoded() {
Map<String, Object> additionalParameters = new LinkedHashMap<>();
additionalParameters.put("item1", Arrays.asList("1", "2"));
additionalParameters.put("item2", "value2");
OAuth2AuthorizationRequest authorizationRequest = TestOAuth2AuthorizationRequests.request()
.additionalParameters(additionalParameters)
.build();
assertThat(authorizationRequest.getAuthorizationRequestUri()).isNotNull();
assertThat(authorizationRequest.getAuthorizationRequestUri())
.isEqualTo("https://example.com/login/oauth/authorize?response_type=code&client_id=client-id&state=state&"
+ "redirect_uri=https://example.com/authorize/oauth2/code/registration-id&"
+ "item1=1&item1=2&item2=value2");
}
@Test
public void buildWhenAdditionalParametersContainsNullThenAuthorizationRequestUriContainsNull() {
Map<String, Object> additionalParameters = new LinkedHashMap<>();
additionalParameters.put("item1", null);
additionalParameters.put("item2", "value2");
OAuth2AuthorizationRequest authorizationRequest = TestOAuth2AuthorizationRequests.request()
.additionalParameters(additionalParameters)
.build();
assertThat(authorizationRequest.getAuthorizationRequestUri()).isNotNull();
assertThat(authorizationRequest.getAuthorizationRequestUri())
.isEqualTo("https://example.com/login/oauth/authorize?response_type=code&client_id=client-id&state=state&"
+ "redirect_uri=https://example.com/authorize/oauth2/code/registration-id&"
+ "item1=null&item2=value2");
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -57,13 +57,18 @@ public final class DefaultBearerTokenResolver implements BearerTokenResolver {
? resolveFromRequestParameters(request) : null;
if (authorizationHeaderToken != null) {
if (parameterToken != null) {
final BearerTokenError error = BearerTokenErrors
BearerTokenError error = BearerTokenErrors
.invalidRequest("Found multiple bearer tokens in the request");
throw new OAuth2AuthenticationException(error);
}
return authorizationHeaderToken;
}
if (parameterToken != null && isParameterTokenEnabledForRequest(request)) {
if (!StringUtils.hasText(parameterToken)) {
BearerTokenError error = BearerTokenErrors
.invalidRequest("The requested token parameter is an empty string");
throw new OAuth2AuthenticationException(error);
}
return parameterToken;
}
return null;
@@ -50,11 +50,6 @@ import org.springframework.web.reactive.function.client.ExchangeFunction;
* To locate the bearer token, this looks in the Reactor {@link Context} for a key of type
* {@link Authentication}.
*
* Registering
* {@see org.springframework.security.config.annotation.web.configuration.OAuth2ResourceServerConfiguration.OAuth2ResourceServerWebFluxSecurityConfiguration.BearerRequestContextSubscriberRegistrar},
* as a {@code @Bean} will take care of this automatically, but certainly an application
* can supply a {@link Context} of its own to override.
*
* @author Josh Cummings
* @since 5.2
*/
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2024 the original author 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,6 +78,11 @@ public class ServerBearerTokenAuthenticationConverter implements ServerAuthentic
return authorizationHeaderToken;
}
if (parameterToken != null && isParameterTokenSupportedForRequest(request)) {
if (!StringUtils.hasText(parameterToken)) {
BearerTokenError error = BearerTokenErrors
.invalidRequest("The requested token parameter is an empty string");
throw new OAuth2AuthenticationException(error);
}
return parameterToken;
}
return null;
@@ -49,6 +49,8 @@ import static org.mockito.BDDMockito.verify;
/**
* Tests for {@link JwtIssuerAuthenticationManagerResolver}
*
* @deprecated Superseded by {@link JwtIssuerAuthenticationManagerResolverTests}
*/
@Deprecated
public class JwtIssuerAuthenticationManagerResolverDeprecatedTests {
@@ -52,6 +52,8 @@ import static org.mockito.BDDMockito.verify;
/**
* Tests for {@link JwtIssuerReactiveAuthenticationManagerResolver}
*
* @deprecated Superseded by {@link JwtIssuerReactiveAuthenticationManagerResolverTests}
*/
@Deprecated
public class JwtIssuerReactiveAuthenticationManagerResolverDeprecatedTests {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,8 +21,11 @@ import java.util.Base64;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpStatus;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.server.resource.BearerTokenError;
import org.springframework.security.oauth2.server.resource.BearerTokenErrorCodes;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -258,4 +261,35 @@ public class DefaultBearerTokenResolverTests {
assertThat(this.resolver.resolve(request)).isNull();
}
@Test
public void resolveWhenQueryParameterIsPresentAndEmptyStringThenTokenIsNotResolved() {
this.resolver.setAllowUriQueryParameter(true);
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("GET");
request.addParameter("access_token", "");
assertThatExceptionOfType(OAuth2AuthenticationException.class).isThrownBy(() -> this.resolver.resolve(request))
.withMessageContaining("The requested token parameter is an empty string")
.satisfies((e) -> {
BearerTokenError error = (BearerTokenError) e.getError();
assertThat(error.getErrorCode()).isEqualTo(BearerTokenErrorCodes.INVALID_REQUEST);
assertThat(error.getHttpStatus()).isEqualTo(HttpStatus.BAD_REQUEST);
});
}
@Test
public void resolveWhenFormParameterIsPresentAndEmptyStringThenTokenIsNotResolved() {
this.resolver.setAllowFormEncodedBodyParameter(true);
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("POST");
request.setContentType("application/x-www-form-urlencoded");
request.addParameter("access_token", "");
assertThatExceptionOfType(OAuth2AuthenticationException.class).isThrownBy(() -> this.resolver.resolve(request))
.withMessageContaining("The requested token parameter is an empty string")
.satisfies((e) -> {
BearerTokenError error = (BearerTokenError) e.getError();
assertThat(error.getErrorCode()).isEqualTo(BearerTokenErrorCodes.INVALID_REQUEST);
assertThat(error.getHttpStatus()).isEqualTo(HttpStatus.BAD_REQUEST);
});
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -187,9 +187,9 @@ public class ServerBearerTokenAuthenticationConverterTests {
.isThrownBy(() -> convertToToken(request))
.satisfies((ex) -> {
BearerTokenError error = (BearerTokenError) ex.getError();
assertThat(error.getErrorCode()).isEqualTo(BearerTokenErrorCodes.INVALID_TOKEN);
assertThat(error.getErrorCode()).isEqualTo(BearerTokenErrorCodes.INVALID_REQUEST);
assertThat(error.getUri()).isEqualTo("https://tools.ietf.org/html/rfc6750#section-3.1");
assertThat(error.getHttpStatus()).isEqualTo(HttpStatus.UNAUTHORIZED);
assertThat(error.getHttpStatus()).isEqualTo(HttpStatus.BAD_REQUEST);
});
// @formatter:on
}
@@ -45,8 +45,8 @@ public final class RelyingPartyRegistrations {
* Return a {@link RelyingPartyRegistration.Builder} based off of the given SAML 2.0
* Asserting Party (IDP) metadata location.
*
* Valid locations can be classpath- or file-based or they can be HTTP endpoints. Some
* valid endpoints might include:
* Valid locations can be classpath- or file-based or they can be HTTPS endpoints.
* Some valid endpoints might include:
*
* <pre>
* metadataLocation = "classpath:asserting-party-metadata.xml";
@@ -69,8 +69,8 @@ public final class RelyingPartyRegistrations {
* about the asserting party. Thus, you will need to remember to still populate
* anything about the relying party, like any private keys the relying party will use
* for signing AuthnRequests.
* @param metadataLocation The classpath- or file-based locations or HTTP endpoints of
* the asserting party metadata file
* @param metadataLocation The classpath- or file-based locations or HTTPS endpoints
* of the asserting party metadata file
* @return the {@link RelyingPartyRegistration.Builder} for further configuration
*/
public static RelyingPartyRegistration.Builder fromMetadataLocation(String metadataLocation) {
@@ -130,8 +130,8 @@ public final class RelyingPartyRegistrations {
* Return a {@link Collection} of {@link RelyingPartyRegistration.Builder}s based off
* of the given SAML 2.0 Asserting Party (IDP) metadata location.
*
* Valid locations can be classpath- or file-based or they can be HTTP endpoints. Some
* valid endpoints might include:
* Valid locations can be classpath- or file-based or they can be HTTPS endpoints.
* Some valid endpoints might include:
*
* <pre>
* metadataLocation = "classpath:asserting-party-metadata.xml";
@@ -155,7 +155,7 @@ public final class RelyingPartyRegistrations {
* about the asserting party. Thus, you will need to remember to still populate
* anything about the relying party, like any private keys the relying party will use
* for signing AuthnRequests.
* @param location The classpath- or file-based locations or HTTP endpoints of the
* @param location The classpath- or file-based locations or HTTPS endpoints of the
* asserting party metadata file
* @return the {@link Collection} of {@link RelyingPartyRegistration.Builder}s for
* further configuration
+1 -1
View File
@@ -5,7 +5,7 @@ pluginManagement {
}
plugins {
id "com.gradle.develocity" version "3.17.5"
id "com.gradle.develocity" version "3.17.6"
id "io.spring.ge.conventions" version "0.0.17"
}
@@ -1,5 +1,5 @@
/*
* Copyright 2004-2022 the original author or authors.
* Copyright 2004-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
package org.springframework.security.taglibs.authz;
import java.io.IOException;
import java.util.Locale;
import java.util.Map;
import jakarta.servlet.ServletContext;
@@ -169,7 +170,7 @@ public abstract class AbstractAuthorizeTag {
}
public void setMethod(String method) {
this.method = (method != null) ? method.toUpperCase() : null;
this.method = (method != null) ? method.toUpperCase(Locale.ENGLISH) : null;
}
private SecurityContext getContext() {
@@ -16,6 +16,8 @@
package org.springframework.security.web;
import java.util.Locale;
import jakarta.servlet.ServletRequest;
import org.springframework.util.Assert;
@@ -45,7 +47,7 @@ public class PortResolverImpl implements PortResolver {
@Override
public int getServerPort(ServletRequest request) {
int serverPort = request.getServerPort();
String scheme = request.getScheme().toLowerCase();
String scheme = request.getScheme().toLowerCase(Locale.ENGLISH);
Integer mappedPort = getMappedPort(serverPort, scheme);
return (mappedPort != null) ? mappedPort : serverPort;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author 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,9 @@ public class CookieRequestCache implements RequestCache {
return null;
}
String originalURI = decodeCookie(savedRequestCookie.getValue());
if (originalURI == null) {
return null;
}
UriComponents uriComponents = UriComponentsBuilder.fromUriString(originalURI).build();
DefaultSavedRequest.Builder builder = new DefaultSavedRequest.Builder();
int port = getPort(uriComponents);
@@ -122,8 +125,14 @@ public class CookieRequestCache implements RequestCache {
return Base64.getEncoder().encodeToString(cookieValue.getBytes());
}
private static String decodeCookie(String encodedCookieValue) {
return new String(Base64.getDecoder().decode(encodedCookieValue.getBytes()));
private String decodeCookie(String encodedCookieValue) {
try {
return new String(Base64.getDecoder().decode(encodedCookieValue.getBytes()));
}
catch (IllegalArgumentException ex) {
this.logger.debug("Failed decode cookie value " + encodedCookieValue);
return null;
}
}
private static String getCookiePath(HttpServletRequest request) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,11 +20,15 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import jakarta.servlet.FilterChain;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.server.firewall.HttpStatusExchangeRejectedHandler;
import org.springframework.security.web.server.firewall.ServerExchangeRejectedException;
import org.springframework.security.web.server.firewall.ServerExchangeRejectedHandler;
import org.springframework.security.web.server.firewall.ServerWebExchangeFirewall;
import org.springframework.security.web.server.firewall.StrictServerWebExchangeFirewall;
import org.springframework.util.Assert;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
@@ -43,6 +47,10 @@ public class WebFilterChainProxy implements WebFilter {
private WebFilterChainDecorator filterChainDecorator = new DefaultWebFilterChainDecorator();
private ServerWebExchangeFirewall firewall = new StrictServerWebExchangeFirewall();
private ServerExchangeRejectedHandler exchangeRejectedHandler = new HttpStatusExchangeRejectedHandler();
public WebFilterChainProxy(List<SecurityWebFilterChain> filters) {
this.filters = filters;
}
@@ -53,14 +61,41 @@ public class WebFilterChainProxy implements WebFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return this.firewall.getFirewalledExchange(exchange)
.flatMap((firewalledExchange) -> filterFirewalledExchange(firewalledExchange, chain))
.onErrorResume(ServerExchangeRejectedException.class,
(rejected) -> this.exchangeRejectedHandler.handle(exchange, rejected).then(Mono.empty()));
}
private Mono<Void> filterFirewalledExchange(ServerWebExchange firewalledExchange, WebFilterChain chain) {
return Flux.fromIterable(this.filters)
.filterWhen((securityWebFilterChain) -> securityWebFilterChain.matches(exchange))
.filterWhen((securityWebFilterChain) -> securityWebFilterChain.matches(firewalledExchange))
.next()
.switchIfEmpty(
Mono.defer(() -> this.filterChainDecorator.decorate(chain).filter(exchange).then(Mono.empty())))
.switchIfEmpty(Mono
.defer(() -> this.filterChainDecorator.decorate(chain).filter(firewalledExchange).then(Mono.empty())))
.flatMap((securityWebFilterChain) -> securityWebFilterChain.getWebFilters().collectList())
.map((filters) -> this.filterChainDecorator.decorate(chain, filters))
.flatMap((securedChain) -> securedChain.filter(exchange));
.flatMap((securedChain) -> securedChain.filter(firewalledExchange));
}
/**
* Protects the application using the provided
* {@link StrictServerWebExchangeFirewall}.
* @param firewall the {@link StrictServerWebExchangeFirewall} to use. Cannot be null.
* @since 6.4
*/
public void setFirewall(ServerWebExchangeFirewall firewall) {
Assert.notNull(firewall, "firewall cannot be null");
this.firewall = firewall;
}
/**
* Handles {@link ServerExchangeRejectedException} when the
* {@link ServerWebExchangeFirewall} rejects the provided {@link ServerWebExchange}.
* @param exchangeRejectedHandler the {@link ServerExchangeRejectedHandler} to use.
*/
public void setExchangeRejectedHandler(ServerExchangeRejectedHandler exchangeRejectedHandler) {
this.exchangeRejectedHandler = exchangeRejectedHandler;
}
/**
@@ -0,0 +1,66 @@
/*
* Copyright 2002-2024 the original author 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.web.server.firewall;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Mono;
import org.springframework.core.log.LogMessage;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ServerWebExchange;
/**
* A simple implementation of {@link ServerExchangeRejectedHandler} that sends an error
* with configurable status code.
*
* @author Rob Winch
* @since 6.4
*/
public class HttpStatusExchangeRejectedHandler implements ServerExchangeRejectedHandler {
private static final Log logger = LogFactory.getLog(HttpStatusExchangeRejectedHandler.class);
private final HttpStatus status;
/**
* Constructs an instance which uses {@code 400} as response code.
*/
public HttpStatusExchangeRejectedHandler() {
this(HttpStatus.BAD_REQUEST);
}
/**
* Constructs an instance which uses a configurable http code as response.
* @param status http status code to use
*/
public HttpStatusExchangeRejectedHandler(HttpStatus status) {
this.status = status;
}
@Override
public Mono<Void> handle(ServerWebExchange exchange,
ServerExchangeRejectedException serverExchangeRejectedException) {
return Mono.fromRunnable(() -> {
logger.debug(
LogMessage.format("Rejecting request due to: %s", serverExchangeRejectedException.getMessage()),
serverExchangeRejectedException);
exchange.getResponse().setStatusCode(this.status);
});
}
}
@@ -0,0 +1,31 @@
/*
* Copyright 2002-2024 the original author 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.web.server.firewall;
/**
* Thrown when a {@link org.springframework.web.server.ServerWebExchange} is rejected.
*
* @author Rob Winch
* @since 6.4
*/
public class ServerExchangeRejectedException extends RuntimeException {
public ServerExchangeRejectedException(String message) {
super(message);
}
}
@@ -0,0 +1,39 @@
/*
* Copyright 2002-2024 the original author 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.web.server.firewall;
import reactor.core.publisher.Mono;
import org.springframework.web.server.ServerWebExchange;
/**
* Handles {@link ServerExchangeRejectedException} thrown by
* {@link ServerWebExchangeFirewall}.
*
* @author Rob Winch
* @since 6.4
*/
public interface ServerExchangeRejectedHandler {
/**
* Handles an request rejected failure.
* @param exchange the {@link ServerWebExchange} that was rejected
* @param serverExchangeRejectedException that caused the invocation
*/
Mono<Void> handle(ServerWebExchange exchange, ServerExchangeRejectedException serverExchangeRejectedException);
}
@@ -0,0 +1,46 @@
/*
* Copyright 2002-2024 the original author 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.web.server.firewall;
import reactor.core.publisher.Mono;
import org.springframework.web.server.ServerWebExchange;
/**
* Interface which can be used to reject potentially dangerous requests and/or wrap them
* to control their behaviour.
*
* @author Rob Winch
* @since 6.4
*/
public interface ServerWebExchangeFirewall {
/**
* An implementation of {@link StrictServerWebExchangeFirewall} that does nothing.
* This is considered insecure and not recommended.
*/
ServerWebExchangeFirewall INSECURE_NOOP = (exchange) -> Mono.just(exchange);
/**
* Get a {@link ServerWebExchange} that has firewall rules applied to it.
* @param exchange the {@link ServerWebExchange} to apply firewall rules to.
* @return the {@link ServerWebExchange} that has firewall rules applied to it.
* @throws ServerExchangeRejectedException when a rule is broken.
*/
Mono<ServerWebExchange> getFirewalledExchange(ServerWebExchange exchange);
}
@@ -0,0 +1,790 @@
/*
* Copyright 2002-2024 the original author 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.web.server.firewall;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import reactor.core.publisher.Mono;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.ServerWebExchangeDecorator;
/**
* <p>
* A strict implementation of {@link ServerWebExchangeFirewall} that rejects any
* suspicious requests with a {@link ServerExchangeRejectedException}.
* </p>
* <p>
* The following rules are applied to the firewall:
* </p>
* <ul>
* <li>Rejects HTTP methods that are not allowed. This specified to block
* <a href="https://www.owasp.org/index.php/Test_HTTP_Methods_(OTG-CONFIG-006)">HTTP Verb
* tampering and XST attacks</a>. See {@link #setAllowedHttpMethods(Collection)}</li>
* <li>Rejects URLs that are not normalized to avoid bypassing security constraints. There
* is no way to disable this as it is considered extremely risky to disable this
* constraint. A few options to allow this behavior is to normalize the request prior to
* the firewall or using
* {@link org.springframework.security.web.firewall.DefaultHttpFirewall} instead. Please
* keep in mind that normalizing the request is fragile and why requests are rejected
* rather than normalized.</li>
* <li>Rejects URLs that contain characters that are not printable ASCII characters. There
* is no way to disable this as it is considered extremely risky to disable this
* constraint.</li>
* <li>Rejects URLs that contain semicolons. See {@link #setAllowSemicolon(boolean)}</li>
* <li>Rejects URLs that contain a URL encoded slash. See
* {@link #setAllowUrlEncodedSlash(boolean)}</li>
* <li>Rejects URLs that contain a backslash. See {@link #setAllowBackSlash(boolean)}</li>
* <li>Rejects URLs that contain a null character. See {@link #setAllowNull(boolean)}</li>
* <li>Rejects URLs that contain a URL encoded percent. See
* {@link #setAllowUrlEncodedPercent(boolean)}</li>
* <li>Rejects hosts that are not allowed. See {@link #setAllowedHostnames(Predicate)}
* </li>
* <li>Reject headers names that are not allowed. See
* {@link #setAllowedHeaderNames(Predicate)}</li>
* <li>Reject headers values that are not allowed. See
* {@link #setAllowedHeaderValues(Predicate)}</li>
* <li>Reject parameter names that are not allowed. See
* {@link #setAllowedParameterNames(Predicate)}</li>
* <li>Reject parameter values that are not allowed. See
* {@link #setAllowedParameterValues(Predicate)}</li>
* </ul>
*
* @author Rob Winch
* @since 6.4
*/
public class StrictServerWebExchangeFirewall implements ServerWebExchangeFirewall {
/**
* Used to specify to {@link #setAllowedHttpMethods(Collection)} that any HTTP method
* should be allowed.
*/
private static final Set<HttpMethod> ALLOW_ANY_HTTP_METHOD = Collections.emptySet();
private static final String ENCODED_PERCENT = "%25";
private static final String PERCENT = "%";
private static final List<String> FORBIDDEN_ENCODED_PERIOD = Collections
.unmodifiableList(Arrays.asList("%2e", "%2E"));
private static final List<String> FORBIDDEN_SEMICOLON = Collections
.unmodifiableList(Arrays.asList(";", "%3b", "%3B"));
private static final List<String> FORBIDDEN_FORWARDSLASH = Collections
.unmodifiableList(Arrays.asList("%2f", "%2F"));
private static final List<String> FORBIDDEN_DOUBLE_FORWARDSLASH = Collections
.unmodifiableList(Arrays.asList("//", "%2f%2f", "%2f%2F", "%2F%2f", "%2F%2F"));
private static final List<String> FORBIDDEN_BACKSLASH = Collections
.unmodifiableList(Arrays.asList("\\", "%5c", "%5C"));
private static final List<String> FORBIDDEN_NULL = Collections.unmodifiableList(Arrays.asList("\0", "%00"));
private static final List<String> FORBIDDEN_LF = Collections.unmodifiableList(Arrays.asList("\n", "%0a", "%0A"));
private static final List<String> FORBIDDEN_CR = Collections.unmodifiableList(Arrays.asList("\r", "%0d", "%0D"));
private static final List<String> FORBIDDEN_LINE_SEPARATOR = Collections.unmodifiableList(Arrays.asList("\u2028"));
private static final List<String> FORBIDDEN_PARAGRAPH_SEPARATOR = Collections
.unmodifiableList(Arrays.asList("\u2029"));
private Set<String> encodedUrlBlocklist = new HashSet<>();
private Set<String> decodedUrlBlocklist = new HashSet<>();
private Set<HttpMethod> allowedHttpMethods = createDefaultAllowedHttpMethods();
private Predicate<String> allowedHostnames = (hostname) -> true;
private static final Pattern ASSIGNED_AND_NOT_ISO_CONTROL_PATTERN = Pattern
.compile("[\\p{IsAssigned}&&[^\\p{IsControl}]]*");
private static final Predicate<String> ASSIGNED_AND_NOT_ISO_CONTROL_PREDICATE = (
s) -> ASSIGNED_AND_NOT_ISO_CONTROL_PATTERN.matcher(s).matches();
private static final Pattern HEADER_VALUE_PATTERN = Pattern.compile("[\\p{IsAssigned}&&[[^\\p{IsControl}]||\\t]]*");
private static final Predicate<String> HEADER_VALUE_PREDICATE = (s) -> s == null
|| HEADER_VALUE_PATTERN.matcher(s).matches();
private Predicate<String> allowedHeaderNames = ALLOWED_HEADER_NAMES;
public static final Predicate<String> ALLOWED_HEADER_NAMES = ASSIGNED_AND_NOT_ISO_CONTROL_PREDICATE;
private Predicate<String> allowedHeaderValues = ALLOWED_HEADER_VALUES;
public static final Predicate<String> ALLOWED_HEADER_VALUES = HEADER_VALUE_PREDICATE;
private Predicate<String> allowedParameterNames = ALLOWED_PARAMETER_NAMES;
public static final Predicate<String> ALLOWED_PARAMETER_NAMES = ASSIGNED_AND_NOT_ISO_CONTROL_PREDICATE;
private Predicate<String> allowedParameterValues = ALLOWED_PARAMETER_VALUES;
public static final Predicate<String> ALLOWED_PARAMETER_VALUES = (value) -> true;
public StrictServerWebExchangeFirewall() {
urlBlocklistsAddAll(FORBIDDEN_SEMICOLON);
urlBlocklistsAddAll(FORBIDDEN_FORWARDSLASH);
urlBlocklistsAddAll(FORBIDDEN_DOUBLE_FORWARDSLASH);
urlBlocklistsAddAll(FORBIDDEN_BACKSLASH);
urlBlocklistsAddAll(FORBIDDEN_NULL);
urlBlocklistsAddAll(FORBIDDEN_LF);
urlBlocklistsAddAll(FORBIDDEN_CR);
this.encodedUrlBlocklist.add(ENCODED_PERCENT);
this.encodedUrlBlocklist.addAll(FORBIDDEN_ENCODED_PERIOD);
this.decodedUrlBlocklist.add(PERCENT);
this.decodedUrlBlocklist.addAll(FORBIDDEN_LINE_SEPARATOR);
this.decodedUrlBlocklist.addAll(FORBIDDEN_PARAGRAPH_SEPARATOR);
}
public Set<String> getEncodedUrlBlocklist() {
return this.encodedUrlBlocklist;
}
public Set<String> getDecodedUrlBlocklist() {
return this.decodedUrlBlocklist;
}
@Override
public Mono<ServerWebExchange> getFirewalledExchange(ServerWebExchange exchange) {
return Mono.fromCallable(() -> {
ServerHttpRequest request = exchange.getRequest();
rejectForbiddenHttpMethod(request);
rejectedBlocklistedUrls(request);
rejectedUntrustedHosts(request);
if (!isNormalized(request)) {
throw new ServerExchangeRejectedException(
"The request was rejected because the URL was not normalized");
}
exchange.getResponse().beforeCommit(() -> Mono.fromRunnable(() -> {
ServerHttpResponse response = exchange.getResponse();
HttpHeaders headers = response.getHeaders();
for (Map.Entry<String, List<String>> header : headers.entrySet()) {
String headerName = header.getKey();
List<String> headerValues = header.getValue();
for (String headerValue : headerValues) {
validateCrlf(headerName, headerValue);
}
}
}));
return new StrictFirewallServerWebExchange(exchange);
});
}
private static void validateCrlf(String name, String value) {
Assert.isTrue(!hasCrlf(name) && !hasCrlf(value), () -> "Invalid characters (CR/LF) in header " + name);
}
private static boolean hasCrlf(String value) {
return value != null && (value.indexOf('\n') != -1 || value.indexOf('\r') != -1);
}
/**
* Sets if any HTTP method is allowed. If this set to true, then no validation on the
* HTTP method will be performed. This can open the application up to
* <a href="https://www.owasp.org/index.php/Test_HTTP_Methods_(OTG-CONFIG-006)"> HTTP
* Verb tampering and XST attacks</a>
* @param unsafeAllowAnyHttpMethod if true, disables HTTP method validation, else
* resets back to the defaults. Default is false.
* @since 5.1
* @see #setAllowedHttpMethods(Collection)
*/
public void setUnsafeAllowAnyHttpMethod(boolean unsafeAllowAnyHttpMethod) {
this.allowedHttpMethods = unsafeAllowAnyHttpMethod ? ALLOW_ANY_HTTP_METHOD : createDefaultAllowedHttpMethods();
}
/**
* <p>
* Determines which HTTP methods should be allowed. The default is to allow "DELETE",
* "GET", "HEAD", "OPTIONS", "PATCH", "POST", and "PUT".
* </p>
* @param allowedHttpMethods the case-sensitive collection of HTTP methods that are
* allowed.
* @since 5.1
* @see #setUnsafeAllowAnyHttpMethod(boolean)
*/
public void setAllowedHttpMethods(Collection<HttpMethod> allowedHttpMethods) {
Assert.notNull(allowedHttpMethods, "allowedHttpMethods cannot be null");
this.allowedHttpMethods = (allowedHttpMethods != ALLOW_ANY_HTTP_METHOD) ? new HashSet<>(allowedHttpMethods)
: ALLOW_ANY_HTTP_METHOD;
}
/**
* <p>
* Determines if semicolon is allowed in the URL (i.e. matrix variables). The default
* is to disable this behavior because it is a common way of attempting to perform
* <a href="https://www.owasp.org/index.php/Reflected_File_Download">Reflected File
* Download Attacks</a>. It is also the source of many exploits which bypass URL based
* security.
* </p>
* <p>
* For example, the following CVEs are a subset of the issues related to ambiguities
* in the Servlet Specification on how to treat semicolons that led to CVEs:
* </p>
* <ul>
* <li><a href="https://pivotal.io/security/cve-2016-5007">cve-2016-5007</a></li>
* <li><a href="https://pivotal.io/security/cve-2016-9879">cve-2016-9879</a></li>
* <li><a href="https://pivotal.io/security/cve-2018-1199">cve-2018-1199</a></li>
* </ul>
*
* <p>
* If you are wanting to allow semicolons, please reconsider as it is a very common
* source of security bypasses. A few common reasons users want semicolons and
* alternatives are listed below:
* </p>
* <ul>
* <li>Including the JSESSIONID in the path - You should not include session id (or
* any sensitive information) in a URL as it can lead to leaking. Instead use Cookies.
* </li>
* <li>Matrix Variables - Users wanting to leverage Matrix Variables should consider
* using HTTP parameters instead.</li>
* </ul>
* @param allowSemicolon should semicolons be allowed in the URL. Default is false
*/
public void setAllowSemicolon(boolean allowSemicolon) {
if (allowSemicolon) {
urlBlocklistsRemoveAll(FORBIDDEN_SEMICOLON);
}
else {
urlBlocklistsAddAll(FORBIDDEN_SEMICOLON);
}
}
/**
* <p>
* Determines if a slash "/" that is URL encoded "%2F" should be allowed in the path
* or not. The default is to not allow this behavior because it is a common way to
* bypass URL based security.
* </p>
* <p>
* For example, due to ambiguities in the servlet specification, the value is not
* parsed consistently which results in different values in {@code HttpServletRequest}
* path related values which allow bypassing certain security constraints.
* </p>
* @param allowUrlEncodedSlash should a slash "/" that is URL encoded "%2F" be allowed
* in the path or not. Default is false.
*/
public void setAllowUrlEncodedSlash(boolean allowUrlEncodedSlash) {
if (allowUrlEncodedSlash) {
urlBlocklistsRemoveAll(FORBIDDEN_FORWARDSLASH);
}
else {
urlBlocklistsAddAll(FORBIDDEN_FORWARDSLASH);
}
}
/**
* <p>
* Determines if double slash "//" that is URL encoded "%2F%2F" should be allowed in
* the path or not. The default is to not allow.
* </p>
* @param allowUrlEncodedDoubleSlash should a slash "//" that is URL encoded "%2F%2F"
* be allowed in the path or not. Default is false.
*/
public void setAllowUrlEncodedDoubleSlash(boolean allowUrlEncodedDoubleSlash) {
if (allowUrlEncodedDoubleSlash) {
urlBlocklistsRemoveAll(FORBIDDEN_DOUBLE_FORWARDSLASH);
}
else {
urlBlocklistsAddAll(FORBIDDEN_DOUBLE_FORWARDSLASH);
}
}
/**
* <p>
* Determines if a period "." that is URL encoded "%2E" should be allowed in the path
* or not. The default is to not allow this behavior because it is a frequent source
* of security exploits.
* </p>
* <p>
* For example, due to ambiguities in the servlet specification a URL encoded period
* might lead to bypassing security constraints through a directory traversal attack.
* This is because the path is not parsed consistently which results in different
* values in {@code HttpServletRequest} path related values which allow bypassing
* certain security constraints.
* </p>
* @param allowUrlEncodedPeriod should a period "." that is URL encoded "%2E" be
* allowed in the path or not. Default is false.
*/
public void setAllowUrlEncodedPeriod(boolean allowUrlEncodedPeriod) {
if (allowUrlEncodedPeriod) {
this.encodedUrlBlocklist.removeAll(FORBIDDEN_ENCODED_PERIOD);
}
else {
this.encodedUrlBlocklist.addAll(FORBIDDEN_ENCODED_PERIOD);
}
}
/**
* <p>
* Determines if a backslash "\" or a URL encoded backslash "%5C" should be allowed in
* the path or not. The default is not to allow this behavior because it is a frequent
* source of security exploits.
* </p>
* <p>
* For example, due to ambiguities in the servlet specification a URL encoded period
* might lead to bypassing security constraints through a directory traversal attack.
* This is because the path is not parsed consistently which results in different
* values in {@code HttpServletRequest} path related values which allow bypassing
* certain security constraints.
* </p>
* @param allowBackSlash a backslash "\" or a URL encoded backslash "%5C" be allowed
* in the path or not. Default is false
*/
public void setAllowBackSlash(boolean allowBackSlash) {
if (allowBackSlash) {
urlBlocklistsRemoveAll(FORBIDDEN_BACKSLASH);
}
else {
urlBlocklistsAddAll(FORBIDDEN_BACKSLASH);
}
}
/**
* <p>
* Determines if a null "\0" or a URL encoded nul "%00" should be allowed in the path
* or not. The default is not to allow this behavior because it is a frequent source
* of security exploits.
* </p>
* @param allowNull a null "\0" or a URL encoded null "%00" be allowed in the path or
* not. Default is false
* @since 5.4
*/
public void setAllowNull(boolean allowNull) {
if (allowNull) {
urlBlocklistsRemoveAll(FORBIDDEN_NULL);
}
else {
urlBlocklistsAddAll(FORBIDDEN_NULL);
}
}
/**
* <p>
* Determines if a percent "%" that is URL encoded "%25" should be allowed in the path
* or not. The default is not to allow this behavior because it is a frequent source
* of security exploits.
* </p>
* <p>
* For example, this can lead to exploits that involve double URL encoding that lead
* to bypassing security constraints.
* </p>
* @param allowUrlEncodedPercent if a percent "%" that is URL encoded "%25" should be
* allowed in the path or not. Default is false
*/
public void setAllowUrlEncodedPercent(boolean allowUrlEncodedPercent) {
if (allowUrlEncodedPercent) {
this.encodedUrlBlocklist.remove(ENCODED_PERCENT);
this.decodedUrlBlocklist.remove(PERCENT);
}
else {
this.encodedUrlBlocklist.add(ENCODED_PERCENT);
this.decodedUrlBlocklist.add(PERCENT);
}
}
/**
* Determines if a URL encoded Carriage Return is allowed in the path or not. The
* default is not to allow this behavior because it is a frequent source of security
* exploits.
* @param allowUrlEncodedCarriageReturn if URL encoded Carriage Return is allowed in
* the URL or not. Default is false.
*/
public void setAllowUrlEncodedCarriageReturn(boolean allowUrlEncodedCarriageReturn) {
if (allowUrlEncodedCarriageReturn) {
urlBlocklistsRemoveAll(FORBIDDEN_CR);
}
else {
urlBlocklistsAddAll(FORBIDDEN_CR);
}
}
/**
* Determines if a URL encoded Line Feed is allowed in the path or not. The default is
* not to allow this behavior because it is a frequent source of security exploits.
* @param allowUrlEncodedLineFeed if URL encoded Line Feed is allowed in the URL or
* not. Default is false.
*/
public void setAllowUrlEncodedLineFeed(boolean allowUrlEncodedLineFeed) {
if (allowUrlEncodedLineFeed) {
urlBlocklistsRemoveAll(FORBIDDEN_LF);
}
else {
urlBlocklistsAddAll(FORBIDDEN_LF);
}
}
/**
* Determines if a URL encoded paragraph separator is allowed in the path or not. The
* default is not to allow this behavior because it is a frequent source of security
* exploits.
* @param allowUrlEncodedParagraphSeparator if URL encoded paragraph separator is
* allowed in the URL or not. Default is false.
*/
public void setAllowUrlEncodedParagraphSeparator(boolean allowUrlEncodedParagraphSeparator) {
if (allowUrlEncodedParagraphSeparator) {
this.decodedUrlBlocklist.removeAll(FORBIDDEN_PARAGRAPH_SEPARATOR);
}
else {
this.decodedUrlBlocklist.addAll(FORBIDDEN_PARAGRAPH_SEPARATOR);
}
}
/**
* Determines if a URL encoded line separator is allowed in the path or not. The
* default is not to allow this behavior because it is a frequent source of security
* exploits.
* @param allowUrlEncodedLineSeparator if URL encoded line separator is allowed in the
* URL or not. Default is false.
*/
public void setAllowUrlEncodedLineSeparator(boolean allowUrlEncodedLineSeparator) {
if (allowUrlEncodedLineSeparator) {
this.decodedUrlBlocklist.removeAll(FORBIDDEN_LINE_SEPARATOR);
}
else {
this.decodedUrlBlocklist.addAll(FORBIDDEN_LINE_SEPARATOR);
}
}
/**
* <p>
* Determines which header names should be allowed. The default is to reject header
* names that contain ISO control characters and characters that are not defined.
* </p>
* @param allowedHeaderNames the predicate for testing header names
* @since 5.4
* @see Character#isISOControl(int)
* @see Character#isDefined(int)
*/
public void setAllowedHeaderNames(Predicate<String> allowedHeaderNames) {
Assert.notNull(allowedHeaderNames, "allowedHeaderNames cannot be null");
this.allowedHeaderNames = allowedHeaderNames;
}
/**
* <p>
* Determines which header values should be allowed. The default is to reject header
* values that contain ISO control characters and characters that are not defined.
* </p>
* @param allowedHeaderValues the predicate for testing hostnames
* @since 5.4
* @see Character#isISOControl(int)
* @see Character#isDefined(int)
*/
public void setAllowedHeaderValues(Predicate<String> allowedHeaderValues) {
Assert.notNull(allowedHeaderValues, "allowedHeaderValues cannot be null");
this.allowedHeaderValues = allowedHeaderValues;
}
/**
* Determines which parameter names should be allowed. The default is to reject header
* names that contain ISO control characters and characters that are not defined.
* @param allowedParameterNames the predicate for testing parameter names
* @since 5.4
* @see Character#isISOControl(int)
* @see Character#isDefined(int)
*/
public void setAllowedParameterNames(Predicate<String> allowedParameterNames) {
Assert.notNull(allowedParameterNames, "allowedParameterNames cannot be null");
this.allowedParameterNames = allowedParameterNames;
}
/**
* <p>
* Determines which parameter values should be allowed. The default is to allow any
* parameter value.
* </p>
* @param allowedParameterValues the predicate for testing parameter values
* @since 5.4
*/
public void setAllowedParameterValues(Predicate<String> allowedParameterValues) {
Assert.notNull(allowedParameterValues, "allowedParameterValues cannot be null");
this.allowedParameterValues = allowedParameterValues;
}
/**
* <p>
* Determines which hostnames should be allowed. The default is to allow any hostname.
* </p>
* @param allowedHostnames the predicate for testing hostnames
* @since 5.2
*/
public void setAllowedHostnames(Predicate<String> allowedHostnames) {
Assert.notNull(allowedHostnames, "allowedHostnames cannot be null");
this.allowedHostnames = allowedHostnames;
}
private void urlBlocklistsAddAll(Collection<String> values) {
this.encodedUrlBlocklist.addAll(values);
this.decodedUrlBlocklist.addAll(values);
}
private void urlBlocklistsRemoveAll(Collection<String> values) {
this.encodedUrlBlocklist.removeAll(values);
this.decodedUrlBlocklist.removeAll(values);
}
private void rejectNonPrintableAsciiCharactersInFieldName(String toCheck, String propertyName) {
if (!containsOnlyPrintableAsciiCharacters(toCheck)) {
throw new ServerExchangeRejectedException(String
.format("The %s was rejected because it can only contain printable ASCII characters.", propertyName));
}
}
private void rejectForbiddenHttpMethod(ServerHttpRequest request) {
if (this.allowedHttpMethods == ALLOW_ANY_HTTP_METHOD) {
return;
}
if (!this.allowedHttpMethods.contains(request.getMethod())) {
throw new ServerExchangeRejectedException(
"The request was rejected because the HTTP method \"" + request.getMethod()
+ "\" was not included within the list of allowed HTTP methods " + this.allowedHttpMethods);
}
}
private void rejectedBlocklistedUrls(ServerHttpRequest request) {
for (String forbidden : this.encodedUrlBlocklist) {
if (encodedUrlContains(request, forbidden)) {
throw new ServerExchangeRejectedException(
"The request was rejected because the URL contained a potentially malicious String \""
+ forbidden + "\"");
}
}
for (String forbidden : this.decodedUrlBlocklist) {
if (decodedUrlContains(request, forbidden)) {
throw new ServerExchangeRejectedException(
"The request was rejected because the URL contained a potentially malicious String \""
+ forbidden + "\"");
}
}
}
private void rejectedUntrustedHosts(ServerHttpRequest request) {
String hostName = request.getURI().getHost();
if (hostName != null && !this.allowedHostnames.test(hostName)) {
throw new ServerExchangeRejectedException(
"The request was rejected because the domain " + hostName + " is untrusted.");
}
}
private static Set<HttpMethod> createDefaultAllowedHttpMethods() {
Set<HttpMethod> result = new HashSet<>();
result.add(HttpMethod.DELETE);
result.add(HttpMethod.GET);
result.add(HttpMethod.HEAD);
result.add(HttpMethod.OPTIONS);
result.add(HttpMethod.PATCH);
result.add(HttpMethod.POST);
result.add(HttpMethod.PUT);
return result;
}
private boolean isNormalized(ServerHttpRequest request) {
if (!isNormalized(request.getPath().value())) {
return false;
}
if (!isNormalized(request.getURI().getRawPath())) {
return false;
}
if (!isNormalized(request.getURI().getPath())) {
return false;
}
return true;
}
private void validateAllowedHeaderName(String headerNames) {
if (!StrictServerWebExchangeFirewall.this.allowedHeaderNames.test(headerNames)) {
throw new ServerExchangeRejectedException(
"The request was rejected because the header name \"" + headerNames + "\" is not allowed.");
}
}
private void validateAllowedHeaderValue(Object key, String value) {
if (!StrictServerWebExchangeFirewall.this.allowedHeaderValues.test(value)) {
throw new ServerExchangeRejectedException("The request was rejected because the header: \"" + key
+ " \" has a value \"" + value + "\" that is not allowed.");
}
}
private void validateAllowedParameterName(String name) {
if (!StrictServerWebExchangeFirewall.this.allowedParameterNames.test(name)) {
throw new ServerExchangeRejectedException(
"The request was rejected because the parameter name \"" + name + "\" is not allowed.");
}
}
private void validateAllowedParameterValue(String name, String value) {
if (!StrictServerWebExchangeFirewall.this.allowedParameterValues.test(value)) {
throw new ServerExchangeRejectedException("The request was rejected because the parameter: \"" + name
+ " \" has a value \"" + value + "\" that is not allowed.");
}
}
private static boolean encodedUrlContains(ServerHttpRequest request, String value) {
if (valueContains(request.getPath().value(), value)) {
return true;
}
return valueContains(request.getURI().getRawPath(), value);
}
private static boolean decodedUrlContains(ServerHttpRequest request, String value) {
return valueContains(request.getURI().getPath(), value);
}
private static boolean containsOnlyPrintableAsciiCharacters(String uri) {
if (uri == null) {
return true;
}
int length = uri.length();
for (int i = 0; i < length; i++) {
char ch = uri.charAt(i);
if (ch < '\u0020' || ch > '\u007e') {
return false;
}
}
return true;
}
private static boolean valueContains(String value, String contains) {
return value != null && value.contains(contains);
}
/**
* Checks whether a path is normalized (doesn't contain path traversal sequences like
* "./", "/../" or "/.")
* @param path the path to test
* @return true if the path doesn't contain any path-traversal character sequences.
*/
private static boolean isNormalized(String path) {
if (path == null) {
return true;
}
for (int i = path.length(); i > 0;) {
int slashIndex = path.lastIndexOf('/', i - 1);
int gap = i - slashIndex;
if (gap == 2 && path.charAt(slashIndex + 1) == '.') {
return false; // ".", "/./" or "/."
}
if (gap == 3 && path.charAt(slashIndex + 1) == '.' && path.charAt(slashIndex + 2) == '.') {
return false;
}
i = slashIndex;
}
return true;
}
private final class StrictFirewallServerWebExchange extends ServerWebExchangeDecorator {
private StrictFirewallServerWebExchange(ServerWebExchange delegate) {
super(delegate);
}
@Override
public ServerHttpRequest getRequest() {
return new StrictFirewallHttpRequest(super.getRequest());
}
private final class StrictFirewallHttpRequest extends ServerHttpRequestDecorator {
private StrictFirewallHttpRequest(ServerHttpRequest delegate) {
super(delegate);
}
@Override
public HttpHeaders getHeaders() {
return new StrictFirewallHttpHeaders(super.getHeaders());
}
@Override
public MultiValueMap<String, String> getQueryParams() {
MultiValueMap<String, String> queryParams = super.getQueryParams();
for (Map.Entry<String, List<String>> paramEntry : queryParams.entrySet()) {
String paramName = paramEntry.getKey();
validateAllowedParameterName(paramName);
for (String paramValue : paramEntry.getValue()) {
validateAllowedParameterValue(paramName, paramValue);
}
}
return queryParams;
}
private final class StrictFirewallHttpHeaders extends HttpHeaders {
private StrictFirewallHttpHeaders(HttpHeaders delegate) {
super(delegate);
}
@Override
public String getFirst(String headerName) {
validateAllowedHeaderName(headerName);
String headerValue = super.getFirst(headerName);
validateAllowedHeaderValue(headerName, headerValue);
return headerValue;
}
@Override
public List<String> get(Object key) {
if (key instanceof String headerName) {
validateAllowedHeaderName(headerName);
}
List<String> headerValues = super.get(key);
if (headerValues == null) {
return headerValues;
}
for (String headerValue : headerValues) {
validateAllowedHeaderValue(key, headerValue);
}
return headerValues;
}
@Override
public Set<String> keySet() {
Set<String> headerNames = super.keySet();
for (String headerName : headerNames) {
validateAllowedHeaderName(headerName);
}
return headerNames;
}
}
}
}
}
@@ -16,6 +16,7 @@
package org.springframework.security.web.util;
import java.util.Locale;
import java.util.regex.Pattern;
import jakarta.servlet.http.HttpServletRequest;
@@ -49,7 +50,7 @@ public final class UrlUtils {
*/
public static String buildFullRequestUrl(String scheme, String serverName, int serverPort, String requestURI,
String queryString) {
scheme = scheme.toLowerCase();
scheme = scheme.toLowerCase(Locale.ENGLISH);
StringBuilder url = new StringBuilder();
url.append(scheme).append("://").append(serverName);
// Only add port if not default
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
package org.springframework.security.web.util.matcher;
import java.util.Collections;
import java.util.Locale;
import java.util.Map;
import jakarta.servlet.http.HttpServletRequest;
@@ -303,7 +304,7 @@ public final class AntPathRequestMatcher implements RequestMatcher, RequestVaria
private SubpathMatcher(String subpath, boolean caseSensitive) {
Assert.isTrue(!subpath.contains("*"), "subpath cannot contain \"*\"");
this.subpath = caseSensitive ? subpath : subpath.toLowerCase();
this.subpath = caseSensitive ? subpath : subpath.toLowerCase(Locale.ROOT);
this.length = subpath.length();
this.caseSensitive = caseSensitive;
}
@@ -311,7 +312,7 @@ public final class AntPathRequestMatcher implements RequestMatcher, RequestVaria
@Override
public boolean matches(String path) {
if (!this.caseSensitive) {
path = path.toLowerCase();
path = path.toLowerCase(Locale.ROOT);
}
return path.startsWith(this.subpath) && (path.length() == this.length || path.charAt(this.length) == '/');
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -212,4 +212,14 @@ public class CookieRequestCacheTests {
return new String(Base64.getDecoder().decode(encodedCookieValue.getBytes()));
}
// gh-15905
@Test
public void illegalCookieValueReturnNull() {
CookieRequestCache cookieRequestCache = new CookieRequestCache();
MockHttpServletRequest request = new MockHttpServletRequest();
request.setCookies(new Cookie(DEFAULT_COOKIE_NAME, "123^456"));
SavedRequest savedRequest = cookieRequestCache.getRequest(request, new MockHttpServletResponse());
assertThat(savedRequest).isNull();
}
}
@@ -27,12 +27,16 @@ import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import reactor.core.publisher.Mono;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.security.web.server.ObservationWebFilterChainDecorator.WebFilterChainObservationContext;
import org.springframework.security.web.server.ObservationWebFilterChainDecorator.WebFilterChainObservationConvention;
import org.springframework.security.web.server.ObservationWebFilterChainDecorator.WebFilterObservation;
import org.springframework.security.web.server.firewall.ServerExchangeRejectedException;
import org.springframework.security.web.server.firewall.ServerExchangeRejectedHandler;
import org.springframework.security.web.server.firewall.ServerWebExchangeFirewall;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher.MatchResult;
import org.springframework.test.web.reactive.server.WebTestClient;
@@ -48,6 +52,7 @@ import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
/**
* @author Rob Winch
@@ -143,6 +148,77 @@ public class WebFilterChainProxyTests {
assertFilterChainObservation(contexts.next(), "before", 1);
}
@Test
void doFilterWhenFirewallThenBadRequest() {
List<WebFilter> filters = Arrays.asList(new Http200WebFilter());
ServerWebExchangeMatcher notMatch = (exchange) -> MatchResult.notMatch();
MatcherSecurityWebFilterChain chain = new MatcherSecurityWebFilterChain(notMatch, filters);
WebFilterChainProxy filter = new WebFilterChainProxy(chain);
WebTestClient.bindToController(new Object())
.webFilter(filter)
.build()
.method(HttpMethod.valueOf("INVALID"))
.exchange()
.expectStatus()
.isBadRequest();
}
@Test
void doFilterWhenCustomFirewallThenInvoked() {
List<WebFilter> filters = Arrays.asList(new Http200WebFilter());
ServerWebExchangeMatcher notMatch = (exchange) -> MatchResult.notMatch();
MatcherSecurityWebFilterChain chain = new MatcherSecurityWebFilterChain(notMatch, filters);
WebFilterChainProxy filter = new WebFilterChainProxy(chain);
ServerExchangeRejectedHandler handler = mock(ServerExchangeRejectedHandler.class);
ServerWebExchangeFirewall firewall = mock(ServerWebExchangeFirewall.class);
filter.setFirewall(firewall);
filter.setExchangeRejectedHandler(handler);
WebTestClient.bindToController(new Object()).webFilter(filter).build().get().exchange();
verify(firewall).getFirewalledExchange(any());
verifyNoInteractions(handler);
}
@Test
void doFilterWhenCustomExchangeRejectedHandlerThenInvoked() {
List<WebFilter> filters = Arrays.asList(new Http200WebFilter());
ServerWebExchangeMatcher notMatch = (exchange) -> MatchResult.notMatch();
MatcherSecurityWebFilterChain chain = new MatcherSecurityWebFilterChain(notMatch, filters);
WebFilterChainProxy filter = new WebFilterChainProxy(chain);
ServerExchangeRejectedHandler handler = mock(ServerExchangeRejectedHandler.class);
ServerWebExchangeFirewall firewall = mock(ServerWebExchangeFirewall.class);
given(firewall.getFirewalledExchange(any()))
.willReturn(Mono.error(new ServerExchangeRejectedException("Oops")));
filter.setFirewall(firewall);
filter.setExchangeRejectedHandler(handler);
WebTestClient.bindToController(new Object()).webFilter(filter).build().get().exchange();
verify(firewall).getFirewalledExchange(any());
verify(handler).handle(any(), any());
}
@Test
void doFilterWhenDelayedServerExchangeRejectedException() {
List<WebFilter> filters = Arrays.asList(new WebFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
// simulate a delayed error (e.g. reading parameters)
return Mono.error(new ServerExchangeRejectedException("Ooops"));
}
});
ServerWebExchangeMatcher match = (exchange) -> MatchResult.match();
MatcherSecurityWebFilterChain chain = new MatcherSecurityWebFilterChain(match, filters);
WebFilterChainProxy filter = new WebFilterChainProxy(chain);
ServerExchangeRejectedHandler handler = mock(ServerExchangeRejectedHandler.class);
filter.setExchangeRejectedHandler(handler);
// @formatter:off
WebTestClient.bindToController(new Object())
.webFilter(filter)
.build()
.get()
.exchange();
// @formatter:on
verify(handler).handle(any(), any());
}
static void assertFilterChainObservation(Observation.Context context, String filterSection, int chainPosition) {
assertThat(context).isInstanceOf(WebFilterChainObservationContext.class);
WebFilterChainObservationContext filterChainObservationContext = (WebFilterChainObservationContext) context;
@@ -0,0 +1,516 @@
/*
* Copyright 2002-2024 the original author 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.web.server.firewall;
import java.net.URI;
import java.util.Arrays;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseCookie;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.web.server.ServerWebExchange;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link StrictServerWebExchangeFirewall}.
*
* @author Rob Winch
* @since 6.4
*/
class StrictServerWebExchangeFirewallTests {
public String[] unnormalizedPaths = { "http://exploit.example/..", "http://exploit.example/./path/",
"http://exploit.example/path/path/.", "http://exploit.example/path/path//.",
"http://exploit.example/./path/../path//.", "http://exploit.example/./path",
"http://exploit.example/.//path", "http://exploit.example/.", "http://exploit.example//path",
"http://exploit.example//path/path", "http://exploit.example//path//path",
"http://exploit.example/path//path" };
private StrictServerWebExchangeFirewall firewall = new StrictServerWebExchangeFirewall();
private MockServerHttpRequest.BaseBuilder<?> request = get("/");
@Test
void cookieWhenHasNewLineThenThrowsException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> ResponseCookie.from("test").value("Something\nhere").build());
}
@Test
void cookieWhenHasLineFeedThenThrowsException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> ResponseCookie.from("test").value("Something\rhere").build());
}
@Test
void responseHeadersWhenValueHasNewLineThenThrowsException() {
this.request = MockServerHttpRequest.get("/");
ServerWebExchange exchange = getFirewalledExchange();
exchange.getResponse().getHeaders().set("FOO", "new\nline");
assertThatIllegalArgumentException().isThrownBy(() -> exchange.getResponse().setComplete().block());
}
@Test
void responseHeadersWhenValueHasLineFeedThenThrowsException() {
this.request = MockServerHttpRequest.get("/");
ServerWebExchange exchange = getFirewalledExchange();
exchange.getResponse().getHeaders().set("FOO", "line\rfeed");
assertThatIllegalArgumentException().isThrownBy(() -> exchange.getResponse().setComplete().block());
}
@Test
void responseHeadersWhenNameHasNewLineThenThrowsException() {
this.request = MockServerHttpRequest.get("/");
ServerWebExchange exchange = getFirewalledExchange();
exchange.getResponse().getHeaders().set("new\nline", "FOO");
assertThatIllegalArgumentException().isThrownBy(() -> exchange.getResponse().setComplete().block());
}
@Test
void responseHeadersWhenNameHasLineFeedThenThrowsException() {
this.request = MockServerHttpRequest.get("/");
ServerWebExchange exchange = getFirewalledExchange();
exchange.getResponse().getHeaders().set("line\rfeed", "FOO");
assertThatIllegalArgumentException().isThrownBy(() -> exchange.getResponse().setComplete().block());
}
@Test
void getFirewalledExchangeWhenInvalidMethodThenThrowsServerExchangeRejectedException() {
this.request = MockServerHttpRequest.method(HttpMethod.valueOf("INVALID"), "/");
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> getFirewalledExchange());
}
private ServerWebExchange getFirewalledExchange() {
MockServerWebExchange exchange = MockServerWebExchange.from(this.request.build());
return this.firewall.getFirewalledExchange(exchange).block();
}
private MockServerHttpRequest.BodyBuilder get(String uri) {
URI url = URI.create(uri);
return MockServerHttpRequest.method(HttpMethod.GET, url);
}
// blocks XST attacks
@Test
void getFirewalledExchangeWhenTraceMethodThenThrowsServerExchangeRejectedException() {
this.request = MockServerHttpRequest.method(HttpMethod.TRACE, "/");
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> getFirewalledExchange());
}
@Test
// blocks XST attack if request is forwarded to a Microsoft IIS web server
void getFirewalledExchangeWhenTrackMethodThenThrowsServerExchangeRejectedException() {
this.request = MockServerHttpRequest.method(HttpMethod.TRACE, "/");
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> getFirewalledExchange());
}
@Test
// HTTP methods are case sensitive
void getFirewalledExchangeWhenLowercaseGetThenThrowsServerExchangeRejectedException() {
this.request = MockServerHttpRequest.method(HttpMethod.valueOf("get"), "/");
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> getFirewalledExchange());
}
@Test
void getFirewalledExchangeWhenAllowedThenNoException() {
List<String> allowedMethods = Arrays.asList("DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT");
for (String allowedMethod : allowedMethods) {
this.request = MockServerHttpRequest.method(HttpMethod.valueOf(allowedMethod), "/");
getFirewalledExchange();
}
}
@Test
void getFirewalledExchangeWhenInvalidMethodAndAnyMethodThenNoException() {
this.firewall.setUnsafeAllowAnyHttpMethod(true);
this.request = MockServerHttpRequest.method(HttpMethod.valueOf("INVALID"), "/");
getFirewalledExchange();
}
@Test
void getFirewalledExchangeWhenURINotNormalizedThenThrowsServerExchangeRejectedException() {
for (String path : this.unnormalizedPaths) {
this.request = get(path);
assertThatExceptionOfType(ServerExchangeRejectedException.class)
.describedAs("The path '" + path + "' is not normalized")
.isThrownBy(() -> getFirewalledExchange());
}
}
@Test
void getFirewalledExchangeWhenSemicolonInRequestUriThenThrowsServerExchangeRejectedException() {
this.request = get("/path;/");
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> getFirewalledExchange());
}
@Test
void getFirewalledExchangeWhenEncodedSemicolonInRequestUriThenThrowsServerExchangeRejectedException() {
this.request = get("/path%3B/");
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> getFirewalledExchange());
}
@Test
void getFirewalledExchangeWhenLowercaseEncodedSemicolonInRequestUriThenThrowsServerExchangeRejectedException() {
this.request = MockServerHttpRequest.method(HttpMethod.GET, URI.create("/path%3b/"));
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> getFirewalledExchange());
}
@Test
void getFirewalledExchangeWhenSemicolonInRequestUriAndAllowSemicolonThenNoException() {
this.firewall.setAllowSemicolon(true);
this.request = get("/path;/");
getFirewalledExchange();
}
@Test
void getFirewalledExchangeWhenEncodedSemicolonInRequestUriAndAllowSemicolonThenNoException() {
this.firewall.setAllowSemicolon(true);
this.request = get("/path%3B/");
getFirewalledExchange();
}
@Test
void getFirewalledExchangeWhenLowercaseEncodedSemicolonInRequestUriAndAllowSemicolonThenNoException() {
this.firewall.setAllowSemicolon(true);
this.request = get("/path%3b/");
getFirewalledExchange();
}
@Test
void getFirewalledExchangeWhenLowercaseEncodedPeriodInThenThrowsServerExchangeRejectedException() {
this.request = get("/%2e/");
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> getFirewalledExchange());
}
@Test
void getFirewalledExchangeWhenContainsLowerboundAsciiThenNoException() {
this.request = get("/%20");
getFirewalledExchange();
}
@Test
void getFirewalledExchangeWhenContainsUpperboundAsciiThenNoException() {
this.request = get("/~");
getFirewalledExchange();
}
@Test
void getFirewalledExchangeWhenJapaneseCharacterThenNoException() {
// FIXME: .method(HttpMethod.GET to .get and similar methods
this.request = get("/\u3042");
getFirewalledExchange();
}
@Test
void getFirewalledExchangeWhenContainsEncodedNullThenException() {
this.request = get("/something%00/");
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> getFirewalledExchange());
}
@Test
void getFirewalledExchangeWhenContainsLowercaseEncodedLineFeedThenException() {
this.request = get("/something%0a/");
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> getFirewalledExchange());
}
@Test
void getFirewalledExchangeWhenContainsUppercaseEncodedLineFeedThenException() {
this.request = get("/something%0A/");
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> getFirewalledExchange());
}
@Test
void getFirewalledExchangeWhenContainsLowercaseEncodedCarriageReturnThenException() {
this.request = get("/something%0d/");
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> getFirewalledExchange());
}
@Test
void getFirewalledExchangeWhenContainsUppercaseEncodedCarriageReturnThenException() {
this.request = get("/something%0D/");
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> getFirewalledExchange());
}
@Test
void getFirewalledExchangeWhenContainsLowercaseEncodedLineFeedAndAllowedThenNoException() {
this.firewall.setAllowUrlEncodedLineFeed(true);
this.request = get("/something%0a/");
getFirewalledExchange();
}
@Test
void getFirewalledExchangeWhenContainsUppercaseEncodedLineFeedAndAllowedThenNoException() {
this.firewall.setAllowUrlEncodedLineFeed(true);
this.request = get("/something%0A/");
getFirewalledExchange();
}
@Test
void getFirewalledExchangeWhenContainsLowercaseEncodedCarriageReturnAndAllowedThenNoException() {
this.firewall.setAllowUrlEncodedCarriageReturn(true);
this.request = get("/something%0d/");
getFirewalledExchange();
}
@Test
void getFirewalledExchangeWhenContainsUppercaseEncodedCarriageReturnAndAllowedThenNoException() {
this.firewall.setAllowUrlEncodedCarriageReturn(true);
this.request = get("/something%0D/");
getFirewalledExchange();
}
/**
* On WebSphere 8.5 a URL like /context-root/a/b;%2f1/c can bypass a rule on /a/b/c
* because the pathInfo is /a/b;/1/c which ends up being /a/b/1/c while Spring MVC
* will strip the ; content from requestURI before the path is URL decoded.
*/
@Test
void getFirewalledExchangeWhenLowercaseEncodedPathThenException() {
this.request = get("/context-root/a/b;%2f1/c");
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> getFirewalledExchange());
}
@Test
void getFirewalledExchangeWhenUppercaseEncodedPathThenException() {
this.request = get("/context-root/a/b;%2F1/c");
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> getFirewalledExchange());
}
@Test
void getFirewalledExchangeWhenAllowUrlEncodedSlashAndLowercaseEncodedPathThenNoException() {
this.firewall.setAllowUrlEncodedSlash(true);
this.firewall.setAllowSemicolon(true);
this.request = get("/context-root/a/b;%2f1/c");
getFirewalledExchange();
}
@Test
void getFirewalledExchangeWhenAllowUrlEncodedSlashAndUppercaseEncodedPathThenNoException() {
this.firewall.setAllowUrlEncodedSlash(true);
this.firewall.setAllowSemicolon(true);
this.request = get("/context-root/a/b;%2F1/c");
getFirewalledExchange();
}
@Test
void getFirewalledExchangeWhenAllowUrlLowerCaseEncodedDoubleSlashThenNoException() {
this.firewall.setAllowUrlEncodedSlash(true);
this.firewall.setAllowUrlEncodedDoubleSlash(true);
this.request = get("/context-root/a/b%2f%2fc");
getFirewalledExchange();
}
@Test
void getFirewalledExchangeWhenAllowUrlUpperCaseEncodedDoubleSlashThenNoException() {
this.firewall.setAllowUrlEncodedSlash(true);
this.firewall.setAllowUrlEncodedDoubleSlash(true);
this.request = get("/context-root/a/b%2F%2Fc");
getFirewalledExchange();
}
@Test
void getFirewalledExchangeWhenAllowUrlLowerCaseAndUpperCaseEncodedDoubleSlashThenNoException() {
this.firewall.setAllowUrlEncodedSlash(true);
this.firewall.setAllowUrlEncodedDoubleSlash(true);
this.request = get("/context-root/a/b%2f%2Fc");
getFirewalledExchange();
}
@Test
void getFirewalledExchangeWhenAllowUrlUpperCaseAndLowerCaseEncodedDoubleSlashThenNoException() {
this.firewall.setAllowUrlEncodedSlash(true);
this.firewall.setAllowUrlEncodedDoubleSlash(true);
this.request = get("/context-root/a/b%2F%2fc");
getFirewalledExchange();
}
@Test
void getFirewalledExchangeWhenRemoveFromUpperCaseEncodedUrlBlocklistThenNoException() {
this.firewall.setAllowUrlEncodedSlash(true);
this.request = get("/context-root/a/b%2Fc");
this.firewall.getEncodedUrlBlocklist().removeAll(Arrays.asList("%2F%2F"));
getFirewalledExchange();
}
@Test
void getFirewalledExchangeWhenRemoveFromDecodedUrlBlocklistThenNoException() {
this.request = get("/a/b%2F%2Fc");
this.firewall.getDecodedUrlBlocklist().removeAll(Arrays.asList("//"));
this.firewall.getEncodedUrlBlocklist().removeAll(Arrays.asList("%2F%2F"));
this.firewall.getEncodedUrlBlocklist().removeAll(Arrays.asList("%2F"));
getFirewalledExchange();
}
@Test
void getFirewalledExchangeWhenTrustedDomainThenNoException() {
this.request.header("Host", "example.org");
this.firewall.setAllowedHostnames((hostname) -> hostname.equals("example.org"));
getFirewalledExchange();
}
@Test
void getFirewalledExchangeWhenUntrustedDomainThenException() {
this.request = get("https://example.org");
this.firewall.setAllowedHostnames((hostname) -> hostname.equals("myexample.org"));
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> getFirewalledExchange());
}
@Test
void getFirewalledExchangeGetHeaderWhenNotAllowedHeaderNameThenException() {
this.firewall.setAllowedHeaderNames((name) -> !name.equals("bad name"));
ServerWebExchange exchange = getFirewalledExchange();
HttpHeaders headers = exchange.getRequest().getHeaders();
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> headers.get("bad name"));
}
@Test
void getFirewalledExchangeWhenHeaderNameNotAllowedWithAugmentedHeaderNamesThenException() {
this.firewall.setAllowedHeaderNames(
StrictServerWebExchangeFirewall.ALLOWED_HEADER_NAMES.and((name) -> !name.equals("bad name")));
ServerWebExchange exchange = getFirewalledExchange();
HttpHeaders headers = exchange.getRequest().getHeaders();
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> headers.getFirst("bad name"));
}
@Test
void getFirewalledExchangeGetHeaderWhenNotAllowedHeaderValueThenException() {
this.request.header("good name", "bad value");
this.firewall.setAllowedHeaderValues((value) -> !value.equals("bad value"));
ServerWebExchange exchange = getFirewalledExchange();
HttpHeaders headers = exchange.getRequest().getHeaders();
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> headers.get("good name"));
}
@Test
void getFirewalledExchangeWhenHeaderValueNotAllowedWithAugmentedHeaderValuesThenException() {
this.request.header("good name", "bad value");
this.firewall.setAllowedHeaderValues(
StrictServerWebExchangeFirewall.ALLOWED_HEADER_VALUES.and((value) -> !value.equals("bad value")));
ServerWebExchange exchange = getFirewalledExchange();
HttpHeaders headers = exchange.getRequest().getHeaders();
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> headers.get("good name"));
}
@Test
void getFirewalledExchangeGetDateHeaderWhenControlCharacterInHeaderNameThenException() {
this.request.header("Bad\0Name", "some value");
ServerWebExchange exchange = getFirewalledExchange();
HttpHeaders headers = exchange.getRequest().getHeaders();
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> headers.get("Bad\0Name"));
}
@Test
void getFirewalledExchangeGetHeaderWhenUndefinedCharacterInHeaderNameThenException() {
this.request.header("Bad\uFFFEName", "some value");
ServerWebExchange exchange = getFirewalledExchange();
HttpHeaders headers = exchange.getRequest().getHeaders();
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> headers.get("Bad\uFFFEName"));
}
@Test
void getFirewalledExchangeGetHeadersWhenControlCharacterInHeaderNameThenException() {
this.request.header("Bad\0Name", "some value");
ServerWebExchange exchange = getFirewalledExchange();
HttpHeaders headers = exchange.getRequest().getHeaders();
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> headers.get("Bad\0Name"));
}
@Test
void getFirewalledExchangeGetHeaderNamesWhenControlCharacterInHeaderNameThenException() {
this.request.header("Bad\0Name", "some value");
ServerWebExchange exchange = getFirewalledExchange();
HttpHeaders headers = exchange.getRequest().getHeaders();
assertThatExceptionOfType(ServerExchangeRejectedException.class)
.isThrownBy(() -> headers.keySet().iterator().next());
}
@Test
void getFirewalledExchangeGetHeaderWhenControlCharacterInHeaderValueThenException() {
this.request.header("Something", "bad\0value");
ServerWebExchange exchange = getFirewalledExchange();
HttpHeaders headers = exchange.getRequest().getHeaders();
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> headers.get("Something"));
}
@Test
void getFirewalledExchangeGetHeaderWhenHorizontalTabInHeaderValueThenNoException() {
this.request.header("Something", "tab\tvalue");
ServerWebExchange exchange = getFirewalledExchange();
HttpHeaders headers = exchange.getRequest().getHeaders();
assertThat(headers.getFirst("Something")).isEqualTo("tab\tvalue");
}
@Test
void getFirewalledExchangeGetHeaderWhenUndefinedCharacterInHeaderValueThenException() {
this.request.header("Something", "bad\uFFFEvalue");
ServerWebExchange exchange = getFirewalledExchange();
HttpHeaders headers = exchange.getRequest().getHeaders();
assertThatExceptionOfType(ServerExchangeRejectedException.class)
.isThrownBy(() -> headers.getFirst("Something"));
}
@Test
void getFirewalledExchangeGetHeadersWhenControlCharacterInHeaderValueThenException() {
this.request.header("Something", "bad\0value");
ServerWebExchange exchange = getFirewalledExchange();
HttpHeaders headers = exchange.getRequest().getHeaders();
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> headers.get("Something"));
}
@Test
void getFirewalledExchangeGetParameterWhenControlCharacterInParameterNameThenException() {
this.request.queryParam("Bad\0Name", "some value");
ServerWebExchange exchange = getFirewalledExchange();
ServerHttpRequest request = exchange.getRequest();
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(request::getQueryParams);
}
@Test
void getFirewalledExchangeGetParameterValuesWhenNotAllowedInParameterValueThenException() {
this.firewall.setAllowedParameterValues((value) -> !value.equals("bad value"));
this.request.queryParam("Something", "bad value");
ServerWebExchange exchange = getFirewalledExchange();
ServerHttpRequest request = exchange.getRequest();
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> request.getQueryParams());
}
@Test
void getFirewalledExchangeGetParameterValuesWhenNotAllowedInParameterNameThenException() {
this.firewall.setAllowedParameterNames((value) -> !value.equals("bad name"));
this.request.queryParam("bad name", "good value");
ServerWebExchange exchange = getFirewalledExchange();
ServerHttpRequest request = exchange.getRequest();
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> request.getQueryParams());
}
// gh-9598
@Test
void getFirewalledExchangeGetHeaderWhenNameIsNullThenNull() {
ServerWebExchange exchange = getFirewalledExchange();
assertThat(exchange.getRequest().getHeaders().get(null)).isNull();
}
}