1
0
mirror of synced 2026-07-10 05:10:03 +00:00

Compare commits

...

86 Commits

Author SHA1 Message Date
github-actions[bot] 147081f771 Release 6.3.8 2025-03-17 20:59:34 +00:00
dependabot[bot] 709d9bc039 Bump org.springframework:spring-framework-bom from 6.1.17 to 6.1.18
Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 6.1.17 to 6.1.18.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v6.1.17...v6.1.18)

---
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>
2025-03-17 13:01:51 -07:00
dependabot[bot] d9bb16e913 Bump io.projectreactor:reactor-bom from 2023.0.15 to 2023.0.16
Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2023.0.15 to 2023.0.16.
- [Release notes](https://github.com/reactor/reactor/releases)
- [Commits](https://github.com/reactor/reactor/compare/2023.0.15...2023.0.16)

---
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>
2025-03-17 12:49:50 -07:00
dependabot[bot] 11114919ec Bump org.springframework.data:spring-data-bom from 2024.0.9 to 2024.0.10
Bumps [org.springframework.data:spring-data-bom](https://github.com/spring-projects/spring-data-bom) from 2024.0.9 to 2024.0.10.
- [Release notes](https://github.com/spring-projects/spring-data-bom/releases)
- [Commits](https://github.com/spring-projects/spring-data-bom/compare/2024.0.9...2024.0.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>
2025-03-17 12:49:20 -07:00
Rob Winch adb303e152 Add testRuntimeOnly junit-platform-launcher
Closes gh-16755
2025-03-17 14:16:44 -05:00
Joe Grandja 46f0dc6dfc Enforce BCrypt password length 2025-03-17 13:23:27 -04:00
Josh Cummings 36ea1b11a7 Fix Compilation Error
Issue gh-16697
2025-03-17 09:43:21 -06:00
Josh Cummings e793a962c5 Remove s101 From Builds
Issue gh-16752
2025-03-17 09:42:49 -06:00
Josh Cummings 46cd94b5f4 SpEL Propagates Authorization Exceptions
Closes gh-16697
2025-03-04 09:51:55 -07:00
dependabot[bot] acd2de4553 Bump io.mockk:mockk from 1.13.16 to 1.13.17
Bumps [io.mockk:mockk](https://github.com/mockk/mockk) from 1.13.16 to 1.13.17.
- [Release notes](https://github.com/mockk/mockk/releases)
- [Commits](https://github.com/mockk/mockk/commits)

---
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>
2025-02-27 19:37:43 -08:00
dependabot[bot] 31f593cca2 Bump org.jfrog.buildinfo:build-info-extractor-gradle
Bumps [org.jfrog.buildinfo:build-info-extractor-gradle](https://github.com/jfrog/build-info) from 4.33.23 to 4.33.24.
- [Release notes](https://github.com/jfrog/build-info/releases)
- [Changelog](https://github.com/jfrog/build-info/blob/master/RELEASE.md)
- [Commits](https://github.com/jfrog/build-info/compare/build-info-gradle-extractor-4.33.23...build-info-gradle-extractor-4.33.24)

---
updated-dependencies:
- dependency-name: org.jfrog.buildinfo:build-info-extractor-gradle
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-26 19:34:57 -08:00
dependabot[bot] 64bdcecdcd Bump org.slf4j:slf4j-api from 2.0.16 to 2.0.17
Bumps org.slf4j:slf4j-api from 2.0.16 to 2.0.17.

---
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>
2025-02-25 19:57:42 -08:00
dependabot[bot] bc0fd60e1a Bump ch.qos.logback:logback-classic from 1.5.16 to 1.5.17
Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.5.16 to 1.5.17.
- [Release notes](https://github.com/qos-ch/logback/releases)
- [Commits](https://github.com/qos-ch/logback/compare/v_1.5.16...v_1.5.17)

---
updated-dependencies:
- dependency-name: ch.qos.logback:logback-classic
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-25 19:55:25 -08:00
Tran Ngoc Nhan a0cfb2777c Fix typo
Signed-off-by: Tran Ngoc Nhan <ngocnhan.tran1996@gmail.com>
2025-02-24 12:49:18 -07:00
dependabot[bot] e8206b42d2 Bump @springio/antora-extensions from 1.14.2 to 1.14.4 in /docs
Bumps [@springio/antora-extensions](https://github.com/spring-io/antora-extensions) from 1.14.2 to 1.14.4.
- [Changelog](https://github.com/spring-io/antora-extensions/blob/main/CHANGELOG.adoc)
- [Commits](https://github.com/spring-io/antora-extensions/compare/v1.14.2...v1.14.4)

---
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>
2025-02-23 17:49:04 -08:00
github-actions[bot] 0737957f94 Next development version 2025-02-18 17:17:05 +00:00
github-actions[bot] 77892d571f Release 6.3.7 2025-02-18 16:52:08 +00:00
dependabot[bot] 06026684e5 Bump org.springframework.data:spring-data-bom from 2024.0.8 to 2024.0.9
Bumps [org.springframework.data:spring-data-bom](https://github.com/spring-projects/spring-data-bom) from 2024.0.8 to 2024.0.9.
- [Release notes](https://github.com/spring-projects/spring-data-bom/releases)
- [Commits](https://github.com/spring-projects/spring-data-bom/compare/2024.0.8...2024.0.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>
2025-02-16 19:29:53 -08:00
dependabot[bot] c3d45ae529 Bump org.springframework:spring-framework-bom from 6.1.16 to 6.1.17
Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 6.1.16 to 6.1.17.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v6.1.16...v6.1.17)

---
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>
2025-02-13 20:28:09 -08:00
dependabot[bot] acf19c1f1f Bump org.springframework.ldap:spring-ldap-core from 3.2.10 to 3.2.11
Bumps [org.springframework.ldap:spring-ldap-core](https://github.com/spring-projects/spring-ldap) from 3.2.10 to 3.2.11.
- [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.10...3.2.11)

---
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>
2025-02-13 20:16:07 -08:00
dependabot[bot] b3c880cc24 Bump com.nimbusds:oauth2-oidc-sdk from 9.43.5 to 9.43.6
Bumps [com.nimbusds:oauth2-oidc-sdk](https://bitbucket.org/connect2id/oauth-2.0-sdk-with-openid-connect-extensions) from 9.43.5 to 9.43.6.
- [Changelog](https://bitbucket.org/connect2id/oauth-2.0-sdk-with-openid-connect-extensions/src/master/CHANGELOG.txt)
- [Commits](https://bitbucket.org/connect2id/oauth-2.0-sdk-with-openid-connect-extensions/branches/compare/9.43.6..9.43.5)

---
updated-dependencies:
- dependency-name: com.nimbusds:oauth2-oidc-sdk
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-13 20:15:46 -08:00
Joe Grandja 33d96d574f Update to oauth2-oidc-sdk 9.43.5
Closes gh-16582
2025-02-12 05:47:03 -05:00
dependabot[bot] b64d5af9c4 Bump io.projectreactor:reactor-bom from 2023.0.14 to 2023.0.15
Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2023.0.14 to 2023.0.15.
- [Release notes](https://github.com/reactor/reactor/releases)
- [Commits](https://github.com/reactor/reactor/compare/2023.0.14...2023.0.15)

---
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>
2025-02-11 19:27:11 -08:00
dependabot[bot] 002dbf355a Bump io.rsocket:rsocket-bom from 1.1.4 to 1.1.5
Bumps [io.rsocket:rsocket-bom](https://github.com/rsocket/rsocket-java) from 1.1.4 to 1.1.5.
- [Release notes](https://github.com/rsocket/rsocket-java/releases)
- [Commits](https://github.com/rsocket/rsocket-java/compare/1.1.4...1.1.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-03 19:59:36 -08:00
dependabot[bot] 7d5414b349 Bump @springio/asciidoctor-extensions in /docs
Bumps [@springio/asciidoctor-extensions](https://github.com/spring-io/asciidoctor-extensions) from 1.0.0-alpha.14 to 1.0.0-alpha.16.
- [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.14...v1.0.0-alpha.16)

---
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>
2025-02-02 16:52:21 -08:00
Josh Cummings 2de2e3803a Update to Gradle 8.12.1
Closes gh-16485
2025-01-27 16:35:13 -07:00
Josh Cummings 43a2fbf5ad Ensure s101 Runs After Assemble
Issue gh-16482
2025-01-24 11:31:22 -07:00
Josh Cummings d6b295ba2c S101 Depends On Assemble
Closes gh-16482
2025-01-24 11:25:26 -07:00
Steve Riesenberg 211fa52649 Favor provided instances over shared objects
Prior to this commit, providing oauth2Login() and oauth2Client() with
clientRegistrationRepository() and authorizedClientRepository() caused
objects to be shared across both configurers.

These configurers will now prefer explicitly provided instances of
those objects when they are available.

Closes gh-16105
2025-01-22 17:07:44 -06:00
dependabot[bot] 7f410ce5b4 Bump org.springframework.data:spring-data-bom from 2024.0.7 to 2024.0.8
Bumps [org.springframework.data:spring-data-bom](https://github.com/spring-projects/spring-data-bom) from 2024.0.7 to 2024.0.8.
- [Release notes](https://github.com/spring-projects/spring-data-bom/releases)
- [Commits](https://github.com/spring-projects/spring-data-bom/compare/2024.0.7...2024.0.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>
2025-01-19 19:52:35 -08:00
Steve Riesenberg a3f6825f9c Fix missing GChat notifications with workaround
This fix was suggested by GitHub Support as a workaround for a bug where
`failure()` is not working for reusable workflows that will be fixed in
a few months.

Closes gh-16423
2025-01-15 11:42:10 -06:00
dependabot[bot] ea0ec9e662 Bump io.projectreactor:reactor-bom from 2023.0.13 to 2023.0.14
Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2023.0.13 to 2023.0.14.
- [Release notes](https://github.com/reactor/reactor/releases)
- [Commits](https://github.com/reactor/reactor/compare/2023.0.13...2023.0.14)

---
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>
2025-01-14 19:59:03 -08:00
dependabot[bot] 2e9e5d0555 Bump io.mockk:mockk from 1.13.14 to 1.13.16
Bumps [io.mockk:mockk](https://github.com/mockk/mockk) from 1.13.14 to 1.13.16.
- [Release notes](https://github.com/mockk/mockk/releases)
- [Commits](https://github.com/mockk/mockk/compare/1.13.14...1.13.16)

---
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>
2025-01-12 20:08:32 -08:00
dependabot[bot] 92161aae2e Bump ch.qos.logback:logback-classic from 1.5.15 to 1.5.16
Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.5.15 to 1.5.16.
- [Commits](https://github.com/qos-ch/logback/compare/v_1.5.15...v_1.5.16)

---
updated-dependencies:
- dependency-name: ch.qos.logback:logback-classic
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-05 19:55:07 -08:00
dependabot[bot] 3ffda83ba1 Bump ch.qos.logback:logback-classic from 1.5.14 to 1.5.15
Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.5.14 to 1.5.15.
- [Commits](https://github.com/qos-ch/logback/compare/v_1.5.14...v_1.5.15)

---
updated-dependencies:
- dependency-name: ch.qos.logback:logback-classic
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-22 20:01:45 -08:00
dependabot[bot] d44923f64e Bump io.mockk:mockk from 1.13.13 to 1.13.14
Bumps [io.mockk:mockk](https://github.com/mockk/mockk) from 1.13.13 to 1.13.14.
- [Release notes](https://github.com/mockk/mockk/releases)
- [Commits](https://github.com/mockk/mockk/compare/1.13.13...1.13.14)

---
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-12-22 20:01:22 -08:00
dependabot[bot] 15faf0f621 Bump ch.qos.logback:logback-classic from 1.5.13 to 1.5.14
Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.5.13 to 1.5.14.
- [Commits](https://github.com/qos-ch/logback/compare/v_1.5.13...v_1.5.14)

---
updated-dependencies:
- dependency-name: ch.qos.logback:logback-classic
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-19 19:42:17 -08:00
Josh Cummings a9f8a23e50 Merge branch '6.2.x' into 6.3.x 2024-12-19 08:55:25 -07:00
Josh Cummings 643a3f1206 Test Setting logoutRequestRepository
Issue gh-16093
2024-12-19 08:55:18 -07:00
Steven Williams 7aafe2ed5a Set Saml2RelyingPartyInitiatedLogoutSuccessHandler#logoutRequestRepository
Closes gh-16093
2024-12-19 08:53:02 -07:00
dependabot[bot] ce90b85945 Bump ch.qos.logback:logback-classic from 1.5.12 to 1.5.13
Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.5.12 to 1.5.13.
- [Commits](https://github.com/qos-ch/logback/commits)

---
updated-dependencies:
- dependency-name: ch.qos.logback:logback-classic
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-18 19:36:46 -08:00
github-actions[bot] 87888d42fc Next development version 2024-12-16 15:50:41 +00:00
github-actions[bot] 85ca9e9a57 Release 6.3.6 2024-12-16 15:23:29 +00:00
dependabot[bot] d9e9e3cdeb Bump org.springframework.data:spring-data-bom from 2024.0.6 to 2024.0.7
Bumps [org.springframework.data:spring-data-bom](https://github.com/spring-projects/spring-data-bom) from 2024.0.6 to 2024.0.7.
- [Release notes](https://github.com/spring-projects/spring-data-bom/releases)
- [Commits](https://github.com/spring-projects/spring-data-bom/compare/2024.0.6...2024.0.7)

---
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-12-15 19:26:03 -08:00
dependabot[bot] 3e43eda42b Bump org.springframework:spring-framework-bom from 6.1.15 to 6.1.16
Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 6.1.15 to 6.1.16.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v6.1.15...v6.1.16)

---
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-12-12 13:24:18 -08:00
dependabot[bot] e7d6dc22b2 Bump org.springframework.ldap:spring-ldap-core from 3.2.8 to 3.2.10
Bumps [org.springframework.ldap:spring-ldap-core](https://github.com/spring-projects/spring-ldap) from 3.2.8 to 3.2.10.
- [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.8...3.2.10)

---
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-12-12 13:11:47 -08:00
Steve Riesenberg da06f6a9e6 Replace GRADLE_ENTERPRISE_SECRET_ACCESS_KEY with DEVELOCITY_ACCESS_KEY
Closes gh-16262
2024-12-11 18:00:31 -06:00
Rob Winch 6a0b683e60 StrictFirewallHttpRequest.buid returns StrictFirewallHttpRequest
Closes gh-16069
2024-12-11 15:46:31 -06:00
dependabot[bot] 2b6d586987 Bump io.projectreactor:reactor-bom from 2023.0.12 to 2023.0.13
Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2023.0.12 to 2023.0.13.
- [Release notes](https://github.com/reactor/reactor/releases)
- [Commits](https://github.com/reactor/reactor/compare/2023.0.12...2023.0.13)

---
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-12-10 19:26:18 -08:00
dependabot[bot] e528923878 Bump io.projectreactor:reactor-bom from 2023.0.12 to 2023.0.13
Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2023.0.12 to 2023.0.13.
- [Release notes](https://github.com/reactor/reactor/releases)
- [Commits](https://github.com/reactor/reactor/compare/2023.0.12...2023.0.13)

---
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-12-10 19:18:45 -08:00
Josh Cummings 8d1b0d9764 Merge branch '6.2.x' into 6.3.x 2024-12-09 15:27:12 -07:00
Josh Cummings 2fe7faea87 Merge branch '5.8.x' into 6.2.x 2024-12-09 15:27:06 -07:00
Josh Cummings eb313ec901 Link to Messaging SpEL Migration Details
Issue gh-12650
2024-12-09 15:26:54 -07:00
Josh Cummings 7873ab8601 Merge branch '6.2.x' into 6.3.x 2024-12-09 15:26:04 -07:00
Josh Cummings 348f064df1 Merge branch '5.8.x' into 6.2.x 2024-12-09 15:25:50 -07:00
Josh Cummings 8b9fe13c88 Document Messaging SpEL Migration
Issue gh-12650
2024-12-09 15:25:33 -07:00
dependabot[bot] 1f7dcc0fa6 Bump org.gretty:gretty from 4.1.5 to 4.1.6
Bumps [org.gretty:gretty](https://github.com/gretty-gradle-plugin/gretty) from 4.1.5 to 4.1.6.
- [Release notes](https://github.com/gretty-gradle-plugin/gretty/releases)
- [Changelog](https://github.com/gretty-gradle-plugin/gretty/blob/master/changes.md)
- [Commits](https://github.com/gretty-gradle-plugin/gretty/compare/v4.1.5...v4.1.6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-08 19:33:25 -08:00
dependabot[bot] 908b9b5a85 Bump antora from 3.2.0-alpha.6 to 3.2.0-alpha.8 in /docs
Bumps [antora](https://gitlab.com/antora/antora) from 3.2.0-alpha.6 to 3.2.0-alpha.8.
- [Changelog](https://gitlab.com/antora/antora/blob/main/CHANGELOG.adoc)
- [Commits](https://gitlab.com/antora/antora/compare/v3.2.0-alpha.6...v3.2.0-alpha.8)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-08 17:10:14 -08:00
dependabot[bot] 807c3dd3ab Bump @antora/collector-extension from 1.0.0 to 1.0.1 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-12-08 17:09:49 -08:00
dependabot[bot] cac03995a3 Bump @antora/collector-extension from 1.0.0 to 1.0.1 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-12-08 17:07:09 -08:00
dependabot[bot] 6ea7da5178 Bump antora from 3.2.0-alpha.6 to 3.2.0-alpha.8 in /docs
Bumps [antora](https://gitlab.com/antora/antora) from 3.2.0-alpha.6 to 3.2.0-alpha.8.
- [Changelog](https://gitlab.com/antora/antora/blob/main/CHANGELOG.adoc)
- [Commits](https://gitlab.com/antora/antora/compare/v3.2.0-alpha.6...v3.2.0-alpha.8)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-08 17:06:45 -08:00
github-actions[bot] 49f1b3554c Merge branch '5.8.x' into 6.3.x 2024-12-09 00:49:42 +00:00
github-actions[bot] 519df3f7d5 Merge branch '5.8.x' into 6.3.x 2024-12-09 00:48:57 +00:00
dependabot[bot] 16272f634c Bump @antora/collector-extension from 1.0.0 to 1.0.1 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-12-08 16:48:56 -08:00
dependabot[bot] 0b9887505e Bump antora from 3.2.0-alpha.6 to 3.2.0-alpha.8 in /docs
Bumps [antora](https://gitlab.com/antora/antora) from 3.2.0-alpha.6 to 3.2.0-alpha.8.
- [Changelog](https://gitlab.com/antora/antora/blob/main/CHANGELOG.adoc)
- [Commits](https://gitlab.com/antora/antora/compare/v3.2.0-alpha.6...v3.2.0-alpha.8)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-08 16:48:02 -08:00
Josh Cummings dd8ee38194 Merge branch '6.2.x' into 6.3.x
Closes gh-16229
2024-12-06 15:18:42 -07:00
Josh Cummings 87de6cea1b Use Reactive JSON Encoder
Closes gh-16177
2024-12-06 15:14:07 -07:00
Josh Cummings 3d1e4b5f18 Polish Tests
Confirm that responses are a valid JSON map

Issue gh-16177
2024-12-06 15:14:07 -07:00
DingHao ef7b11ac01 Delay initialization UserDetailsService in Global Authentication 2024-12-05 12:26:04 -07:00
Josh Cummings 0f85da77be Merge branch '6.2.x' into 6.3.x
Closes gh-16219
2024-12-05 09:52:32 -07:00
Josh Cummings 96a9cf0d2d Restore Previous Behavior for Servlet 5
Closes gh-16173
2024-12-05 09:52:06 -07:00
github-actions[bot] ebb75f5e90 Merge branch '5.8.x' into 6.3.x 2024-12-02 01:09:38 +00:00
dependabot[bot] a6c3d123ed Bump @antora/collector-extension from 1.0.0-rc.1 to 1.0.0 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-12-01 17:08:47 -08:00
dependabot[bot] 324de7af93 Bump @antora/collector-extension from 1.0.0-rc.1 to 1.0.0 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-12-01 16:56:46 -08:00
dependabot[bot] e79ceaeb75 Bump @antora/collector-extension from 1.0.0-rc.1 to 1.0.0 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-12-01 16:27:59 -08:00
dependabot[bot] f361ee72e8 Bump org.jfrog.buildinfo:build-info-extractor-gradle
Bumps [org.jfrog.buildinfo:build-info-extractor-gradle](https://github.com/jfrog/build-info) from 4.33.22 to 4.33.23.
- [Release notes](https://github.com/jfrog/build-info/releases)
- [Changelog](https://github.com/jfrog/build-info/blob/master/RELEASE.md)
- [Commits](https://github.com/jfrog/build-info/compare/build-info-gradle-extractor-4.33.22...build-info-gradle-extractor-4.33.23)

---
updated-dependencies:
- dependency-name: org.jfrog.buildinfo:build-info-extractor-gradle
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-26 19:44:55 -08:00
Steve Riesenberg 21ac1022ef Merge branch '6.2.x' into 6.3.x
Closes gh-16175
2024-11-26 12:12:18 -06:00
Harpreet Singh 68d91916e2 Polish Dark Mode for CSRF and Method Security diagrams
Closes gh-16151
2024-11-26 01:53:56 +05:30
dependabot[bot] 753f8aecc9 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-24 17:01:49 -08:00
github-actions[bot] 84e4c9bc42 Merge branch '5.8.x' into 6.3.x 2024-11-25 00:49:12 +00:00
dependabot[bot] 1dbaa08cd4 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-24 16:48:27 -08:00
dependabot[bot] 1d916c35d1 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-24 16:27:14 -08:00
DingHao e8ba039a61 Delay initialization AuthenticationProvider in Global Authentication 2024-11-22 17:22:14 -07:00
Steve Riesenberg 4b41f8cb5b Merge branch '6.2.x' into 6.3.x
Closes gh-16138
2024-11-20 15:54:29 -06:00
Steve Riesenberg 0eb6acde96 Polish gh-16133 2024-11-20 15:50:29 -06:00
Kai Zander 73f3f75712 Always return current ClientRegistration in loadAuthorizedClient
This changes `InMemoryOAuth2AuthorizedClientService.loadAuthorizedClient`
(and its reactive counterpart) to always return `OAuth2AuthorizedClient`
instances containing the current `ClientRegistration` as obtained from
the `ClientRegistrationRepository`.

Before this change, the first `ClientRegistration` instance was cached,
with the effect that any changes made in the `ClientRegistrationRepository`
(such as a new client secret) would not have taken effect.

Closes gh-15511
2024-11-20 15:50:29 -06:00
66 changed files with 939 additions and 217 deletions
@@ -9,7 +9,7 @@ on:
workflow_dispatch: # Manual trigger
env:
DEVELOCITY_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
permissions:
contents: read
@@ -64,39 +64,23 @@ jobs:
./gradlew publishMavenJavaPublicationToLocalRepository
./gradlew cloneRepository -PrepositoryName="spring-projects/spring-security-samples" -Pref="$samples_branch" -PcloneOutputDirectory="$SAMPLES_DIR"
./gradlew --project-dir "$SAMPLES_DIR" --init-script spring-security-ci.gradle -PlocalRepositoryPath="$LOCAL_REPOSITORY_PATH" -PspringSecurityVersion="$version" :runAllTests
check-tangles:
name: Check for Package Tangles
runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'spring-projects' }}
steps:
- uses: actions/checkout@v4
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v2
with:
java-version: 17
distribution: temurin
- name: Check for package tangles
env:
STRUCTURE101_LICENSEID: ${{ secrets.STRUCTURE101_LICENSEID }}
run: |
./gradlew check s101 -Ps101.licenseId="$STRUCTURE101_LICENSEID" --stacktrace
deploy-artifacts:
name: Deploy Artifacts
needs: [ build, test, check-samples, check-tangles ]
needs: [ build, test, check-samples ]
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-artifacts.yml@v1
with:
should-deploy-artifacts: ${{ needs.build.outputs.should-deploy-artifacts }}
secrets: inherit
deploy-docs:
name: Deploy Docs
needs: [ build, test, check-samples, check-tangles ]
needs: [ build, test, check-samples ]
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-docs.yml@v1
with:
should-deploy-docs: ${{ needs.build.outputs.should-deploy-artifacts }}
secrets: inherit
deploy-schema:
name: Deploy Schema
needs: [ build, test, check-samples, check-tangles ]
needs: [ build, test, check-samples ]
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-schema.yml@v1
with:
should-deploy-schema: ${{ needs.build.outputs.should-deploy-artifacts }}
@@ -116,7 +100,7 @@ jobs:
send-notification:
name: Send Notification
needs: [ perform-release ]
if: ${{ failure() || cancelled() }}
if: ${{ !success() }}
runs-on: ubuntu-latest
steps:
- name: Send Notification
-3
View File
@@ -2,9 +2,6 @@ name: PR Build
on: pull_request
env:
DEVELOCITY_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
permissions:
contents: read
+1
View File
@@ -20,4 +20,5 @@ dependencies {
testImplementation "org.springframework:spring-test"
testRuntimeOnly 'org.hsqldb:hsqldb'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
+2
View File
@@ -27,6 +27,8 @@ dependencies {
testImplementation "org.mockito:mockito-junit-jupiter"
testImplementation "org.springframework:spring-test"
testAspect sourceSets.main.output
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
compileAspectj.ajcOptions.outxmlfile = "META-INF/aop.xml"
-6
View File
@@ -20,7 +20,6 @@ plugins {
apply plugin: 'io.spring.nohttp'
apply plugin: 'locks'
apply plugin: 's101'
apply plugin: 'io.spring.convention.root'
apply plugin: 'org.jetbrains.kotlin.jvm'
apply plugin: 'org.springframework.security.versions.verify-dependencies-versions'
@@ -117,11 +116,6 @@ tasks.register('cloneRepository', IncludeRepoTask) {
outputDirectory = project.hasProperty("cloneOutputDirectory") ? project.file("$cloneOutputDirectory") : defaultDirectory
}
s101 {
repository = 'https://structure101.com/binaries/latest'
configurationDirectory = project.file("etc/s101")
}
wrapperUpgrade {
gradle {
'spring-security' {
+2
View File
@@ -95,6 +95,8 @@ dependencies {
testImplementation 'org.mockito:mockito-core'
testImplementation 'org.mockito:mockito-junit-jupiter'
testImplementation libs.com.squareup.okhttp3.mockwebserver
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
+1 -1
View File
@@ -50,7 +50,7 @@ public class S101Plugin implements Plugin<Project> {
private void configure(JavaExec exec) {
exec.setDescription("Runs Structure101 headless analysis, installing and configuring if necessary");
exec.dependsOn("check");
exec.dependsOn("assemble");
Project project = exec.getProject();
S101PluginExtension extension = project.getExtensions().getByType(S101PluginExtension.class);
exec
@@ -6,5 +6,7 @@ dependencies {
implementation 'org.springframework:spring-core'
testImplementation "org.junit.jupiter:junit-jupiter-api"
testImplementation "org.junit.jupiter:junit-jupiter-engine"
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
@@ -5,4 +5,6 @@ dependencies {
optional 'ch.qos.logback:logback-classic'
testImplementation "org.junit.jupiter:junit-jupiter-api"
testImplementation "org.junit.jupiter:junit-jupiter-engine"
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
+2
View File
@@ -22,4 +22,6 @@ dependencies {
testImplementation "org.mockito:mockito-junit-jupiter"
testImplementation "org.springframework:spring-test"
testImplementation 'org.skyscreamer:jsonassert'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
+1
View File
@@ -115,6 +115,7 @@ dependencies {
testImplementation libs.org.instancio.instancio.junit
testRuntimeOnly 'org.hsqldb:hsqldb'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
def rncToXsd = tasks.named('rncToXsd', RncToXsd)
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 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,8 +16,7 @@
package org.springframework.security.config.annotation.authentication.configuration;
import java.util.ArrayList;
import java.util.List;
import java.util.Arrays;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -63,62 +62,24 @@ class InitializeAuthenticationProviderBeanManagerConfigurer extends GlobalAuthen
if (auth.isConfigured()) {
return;
}
List<BeanWithName<AuthenticationProvider>> authenticationProviders = getBeansWithName(
AuthenticationProvider.class);
if (authenticationProviders.isEmpty()) {
String[] beanNames = InitializeAuthenticationProviderBeanManagerConfigurer.this.context
.getBeanNamesForType(AuthenticationProvider.class);
if (beanNames.length == 0) {
return;
}
else if (authenticationProviders.size() > 1) {
List<String> beanNames = authenticationProviders.stream().map(BeanWithName::getName).toList();
else if (beanNames.length > 1) {
this.logger.info(LogMessage.format("Found %s AuthenticationProvider beans, with names %s. "
+ "Global Authentication Manager will not be configured with AuthenticationProviders. "
+ "Consider publishing a single AuthenticationProvider bean, or wiring your Providers directly "
+ "using the DSL.", authenticationProviders.size(), beanNames));
+ "using the DSL.", beanNames.length, Arrays.toString(beanNames)));
return;
}
AuthenticationProvider authenticationProvider = authenticationProviders.get(0).getBean();
String authenticationProviderBeanName = authenticationProviders.get(0).getName();
AuthenticationProvider authenticationProvider = InitializeAuthenticationProviderBeanManagerConfigurer.this.context
.getBean(beanNames[0], AuthenticationProvider.class);
auth.authenticationProvider(authenticationProvider);
this.logger.info(LogMessage.format(
"Global AuthenticationManager configured with AuthenticationProvider bean with name %s",
authenticationProviderBeanName));
}
/**
* @return a list of beans of the requested class, along with their names. If
* there are no registered beans of that type, the list is empty.
*/
private <T> List<BeanWithName<T>> getBeansWithName(Class<T> type) {
List<BeanWithName<T>> beanWithNames = new ArrayList<>();
String[] beanNames = InitializeAuthenticationProviderBeanManagerConfigurer.this.context
.getBeanNamesForType(type);
for (String beanName : beanNames) {
T bean = InitializeAuthenticationProviderBeanManagerConfigurer.this.context.getBean(beanName, type);
beanWithNames.add(new BeanWithName<T>(bean, beanName));
}
return beanWithNames;
}
static class BeanWithName<T> {
private final T bean;
private final String name;
BeanWithName(T bean, String name) {
this.bean = bean;
this.name = name;
}
T getBean() {
return this.bean;
}
String getName() {
return this.name;
}
beanNames[0]));
}
}
@@ -16,8 +16,7 @@
package org.springframework.security.config.annotation.authentication.configuration;
import java.util.ArrayList;
import java.util.List;
import java.util.Arrays;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -66,9 +65,10 @@ class InitializeUserDetailsBeanManagerConfigurer extends GlobalAuthenticationCon
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
List<BeanWithName<UserDetailsService>> userDetailsServices = getBeansWithName(UserDetailsService.class);
String[] beanNames = InitializeUserDetailsBeanManagerConfigurer.this.context
.getBeanNamesForType(UserDetailsService.class);
if (auth.isConfigured()) {
if (!userDetailsServices.isEmpty()) {
if (beanNames.length > 0) {
this.logger.warn("Global AuthenticationManager configured with an AuthenticationProvider bean. "
+ "UserDetailsService beans will not be used for username/password login. "
+ "Consider removing the AuthenticationProvider bean. "
@@ -78,19 +78,18 @@ class InitializeUserDetailsBeanManagerConfigurer extends GlobalAuthenticationCon
return;
}
if (userDetailsServices.isEmpty()) {
if (beanNames.length == 0) {
return;
}
else if (userDetailsServices.size() > 1) {
List<String> beanNames = userDetailsServices.stream().map(BeanWithName::getName).toList();
else if (beanNames.length > 1) {
this.logger.warn(LogMessage.format("Found %s UserDetailsService beans, with names %s. "
+ "Global Authentication Manager will not use a UserDetailsService for username/password login. "
+ "Consider publishing a single UserDetailsService bean.", userDetailsServices.size(),
beanNames));
+ "Consider publishing a single UserDetailsService bean.", beanNames.length,
Arrays.toString(beanNames)));
return;
}
UserDetailsService userDetailsService = userDetailsServices.get(0).getBean();
String userDetailsServiceBeanName = userDetailsServices.get(0).getName();
UserDetailsService userDetailsService = InitializeUserDetailsBeanManagerConfigurer.this.context
.getBean(beanNames[0], UserDetailsService.class);
PasswordEncoder passwordEncoder = getBeanOrNull(PasswordEncoder.class);
UserDetailsPasswordService passwordManager = getBeanOrNull(UserDetailsPasswordService.class);
CompromisedPasswordChecker passwordChecker = getBeanOrNull(CompromisedPasswordChecker.class);
@@ -111,8 +110,7 @@ class InitializeUserDetailsBeanManagerConfigurer extends GlobalAuthenticationCon
provider.afterPropertiesSet();
auth.authenticationProvider(provider);
this.logger.info(LogMessage.format(
"Global AuthenticationManager configured with UserDetailsService bean with name %s",
userDetailsServiceBeanName));
"Global AuthenticationManager configured with UserDetailsService bean with name %s", beanNames[0]));
}
/**
@@ -127,41 +125,6 @@ class InitializeUserDetailsBeanManagerConfigurer extends GlobalAuthenticationCon
return InitializeUserDetailsBeanManagerConfigurer.this.context.getBean(beanNames[0], type);
}
/**
* @return a list of beans of the requested class, along with their names. If
* there are no registered beans of that type, the list is empty.
*/
private <T> List<BeanWithName<T>> getBeansWithName(Class<T> type) {
List<BeanWithName<T>> beanWithNames = new ArrayList<>();
String[] beanNames = InitializeUserDetailsBeanManagerConfigurer.this.context.getBeanNamesForType(type);
for (String beanName : beanNames) {
T bean = InitializeUserDetailsBeanManagerConfigurer.this.context.getBean(beanName, type);
beanWithNames.add(new BeanWithName<T>(bean, beanName));
}
return beanWithNames;
}
static class BeanWithName<T> {
private final T bean;
private final String name;
BeanWithName(T bean, String name) {
this.bean = bean;
this.name = name;
}
T getBean() {
return this.bean;
}
String getName() {
return this.name;
}
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -98,6 +98,10 @@ public final class OAuth2ClientConfigurer<B extends HttpSecurityBuilder<B>>
private AuthorizationCodeGrantConfigurer authorizationCodeGrantConfigurer = new AuthorizationCodeGrantConfigurer();
private ClientRegistrationRepository clientRegistrationRepository;
private OAuth2AuthorizedClientRepository authorizedClientRepository;
/**
* Sets the repository of client registrations.
* @param clientRegistrationRepository the repository of client registrations
@@ -107,6 +111,7 @@ public final class OAuth2ClientConfigurer<B extends HttpSecurityBuilder<B>>
ClientRegistrationRepository clientRegistrationRepository) {
Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null");
this.getBuilder().setSharedObject(ClientRegistrationRepository.class, clientRegistrationRepository);
this.clientRegistrationRepository = clientRegistrationRepository;
return this;
}
@@ -119,6 +124,7 @@ public final class OAuth2ClientConfigurer<B extends HttpSecurityBuilder<B>>
OAuth2AuthorizedClientRepository authorizedClientRepository) {
Assert.notNull(authorizedClientRepository, "authorizedClientRepository cannot be null");
this.getBuilder().setSharedObject(OAuth2AuthorizedClientRepository.class, authorizedClientRepository);
this.authorizedClientRepository = authorizedClientRepository;
return this;
}
@@ -283,8 +289,7 @@ public final class OAuth2ClientConfigurer<B extends HttpSecurityBuilder<B>>
if (this.authorizationRequestResolver != null) {
return this.authorizationRequestResolver;
}
ClientRegistrationRepository clientRegistrationRepository = OAuth2ClientConfigurerUtils
.getClientRegistrationRepository(getBuilder());
ClientRegistrationRepository clientRegistrationRepository = getClientRegistrationRepository(getBuilder());
return new DefaultOAuth2AuthorizationRequestResolver(clientRegistrationRepository,
OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI);
}
@@ -292,8 +297,8 @@ public final class OAuth2ClientConfigurer<B extends HttpSecurityBuilder<B>>
private OAuth2AuthorizationCodeGrantFilter createAuthorizationCodeGrantFilter(B builder) {
AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
OAuth2AuthorizationCodeGrantFilter authorizationCodeGrantFilter = new OAuth2AuthorizationCodeGrantFilter(
OAuth2ClientConfigurerUtils.getClientRegistrationRepository(builder),
OAuth2ClientConfigurerUtils.getAuthorizedClientRepository(builder), authenticationManager);
getClientRegistrationRepository(builder), getAuthorizedClientRepository(builder),
authenticationManager);
if (this.authorizationRequestRepository != null) {
authorizationCodeGrantFilter.setAuthorizationRequestRepository(this.authorizationRequestRepository);
}
@@ -315,6 +320,18 @@ public final class OAuth2ClientConfigurer<B extends HttpSecurityBuilder<B>>
return (bean != null) ? bean : new DefaultAuthorizationCodeTokenResponseClient();
}
private ClientRegistrationRepository getClientRegistrationRepository(B builder) {
return (OAuth2ClientConfigurer.this.clientRegistrationRepository != null)
? OAuth2ClientConfigurer.this.clientRegistrationRepository
: OAuth2ClientConfigurerUtils.getClientRegistrationRepository(builder);
}
private OAuth2AuthorizedClientRepository getAuthorizedClientRepository(B builder) {
return (OAuth2ClientConfigurer.this.authorizedClientRepository != null)
? OAuth2ClientConfigurer.this.authorizedClientRepository
: OAuth2ClientConfigurerUtils.getAuthorizedClientRepository(builder);
}
@SuppressWarnings("unchecked")
private <T> T getBeanOrNull(ResolvableType type) {
ApplicationContext context = getBuilder().getSharedObject(ApplicationContext.class);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -172,6 +172,10 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
private String loginProcessingUrl = OAuth2LoginAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI;
private ClientRegistrationRepository clientRegistrationRepository;
private OAuth2AuthorizedClientRepository authorizedClientRepository;
/**
* Sets the repository of client registrations.
* @param clientRegistrationRepository the repository of client registrations
@@ -181,6 +185,7 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
ClientRegistrationRepository clientRegistrationRepository) {
Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null");
this.getBuilder().setSharedObject(ClientRegistrationRepository.class, clientRegistrationRepository);
this.clientRegistrationRepository = clientRegistrationRepository;
return this;
}
@@ -194,6 +199,7 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
OAuth2AuthorizedClientRepository authorizedClientRepository) {
Assert.notNull(authorizedClientRepository, "authorizedClientRepository cannot be null");
this.getBuilder().setSharedObject(OAuth2AuthorizedClientRepository.class, authorizedClientRepository);
this.authorizedClientRepository = authorizedClientRepository;
return this;
}
@@ -339,8 +345,7 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
@Override
public void init(B http) throws Exception {
OAuth2LoginAuthenticationFilter authenticationFilter = new OAuth2LoginAuthenticationFilter(
OAuth2ClientConfigurerUtils.getClientRegistrationRepository(this.getBuilder()),
OAuth2ClientConfigurerUtils.getAuthorizedClientRepository(this.getBuilder()), this.loginProcessingUrl);
this.getClientRegistrationRepository(), this.getAuthorizedClientRepository(), this.loginProcessingUrl);
authenticationFilter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
this.setAuthenticationFilter(authenticationFilter);
super.loginProcessingUrl(this.loginProcessingUrl);
@@ -406,8 +411,7 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
authorizationRequestBaseUri = OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI;
}
authorizationRequestFilter = new OAuth2AuthorizationRequestRedirectFilter(
OAuth2ClientConfigurerUtils.getClientRegistrationRepository(this.getBuilder()),
authorizationRequestBaseUri);
this.getClientRegistrationRepository(), authorizationRequestBaseUri);
}
if (this.authorizationEndpointConfig.authorizationRequestRepository != null) {
authorizationRequestFilter
@@ -439,6 +443,16 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
return new AntPathRequestMatcher(loginProcessingUrl);
}
private ClientRegistrationRepository getClientRegistrationRepository() {
return (this.clientRegistrationRepository != null) ? this.clientRegistrationRepository
: OAuth2ClientConfigurerUtils.getClientRegistrationRepository(this.getBuilder());
}
private OAuth2AuthorizedClientRepository getAuthorizedClientRepository() {
return (this.authorizedClientRepository != null) ? this.authorizedClientRepository
: OAuth2ClientConfigurerUtils.getAuthorizedClientRepository(this.getBuilder());
}
@SuppressWarnings("unchecked")
private JwtDecoderFactory<ClientRegistration> getJwtDecoderFactoryBean() {
ResolvableType type = ResolvableType.forClassWithGenerics(JwtDecoderFactory.class, ClientRegistration.class);
@@ -529,8 +543,7 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
@SuppressWarnings("unchecked")
private Map<String, String> getLoginLinks() {
Iterable<ClientRegistration> clientRegistrations = null;
ClientRegistrationRepository clientRegistrationRepository = OAuth2ClientConfigurerUtils
.getClientRegistrationRepository(this.getBuilder());
ClientRegistrationRepository clientRegistrationRepository = this.getClientRegistrationRepository();
ResolvableType type = ResolvableType.forInstance(clientRegistrationRepository).as(Iterable.class);
if (type != ResolvableType.NONE && ClientRegistration.class.isAssignableFrom(type.resolveGenerics()[0])) {
clientRegistrations = (Iterable<ClientRegistration>) clientRegistrationRepository;
@@ -147,6 +147,7 @@ final class Saml2LogoutBeanDefinitionParser implements BeanDefinitionParser {
BeanMetadataElement saml2LogoutRequestSuccessHandler = BeanDefinitionBuilder
.rootBeanDefinition(Saml2RelyingPartyInitiatedLogoutSuccessHandler.class)
.addConstructorArgValue(logoutRequestResolver)
.addPropertyValue("logoutRequestRepository", logoutRequestRepository)
.getBeanDefinition();
this.logoutFilter = BeanDefinitionBuilder.rootBeanDefinition(LogoutFilter.class)
.addConstructorArgValue(saml2LogoutRequestSuccessHandler)
@@ -0,0 +1,65 @@
/*
* 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.config.web.server;
import org.springframework.http.converter.GenericHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.GsonHttpMessageConverter;
import org.springframework.http.converter.json.JsonbHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.util.ClassUtils;
/**
* Utility methods for {@link HttpMessageConverter}'s.
*
* @author Joe Grandja
* @author luamas
* @since 5.1
*/
final class HttpMessageConverters {
private static final boolean jackson2Present;
private static final boolean gsonPresent;
private static final boolean jsonbPresent;
static {
ClassLoader classLoader = HttpMessageConverters.class.getClassLoader();
jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader)
&& ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
jsonbPresent = ClassUtils.isPresent("jakarta.json.bind.Jsonb", classLoader);
}
private HttpMessageConverters() {
}
static GenericHttpMessageConverter<Object> getJsonMessageConverter() {
if (jackson2Present) {
return new MappingJackson2HttpMessageConverter();
}
if (gsonPresent) {
return new GsonHttpMessageConverter();
}
if (jsonbPresent) {
return new JsonbHttpMessageConverter();
}
return null;
}
}
@@ -0,0 +1,95 @@
/*
* 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.config.web.server;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.core.ResolvableType;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageEncoder;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.util.MimeType;
class OAuth2ErrorEncoder implements HttpMessageEncoder<OAuth2Error> {
private final HttpMessageConverter<Object> messageConverter = HttpMessageConverters.getJsonMessageConverter();
@NotNull
@Override
public List<MediaType> getStreamingMediaTypes() {
return List.of();
}
@Override
public boolean canEncode(ResolvableType elementType, MimeType mimeType) {
return getEncodableMimeTypes().contains(mimeType);
}
@NotNull
@Override
public Flux<DataBuffer> encode(Publisher<? extends OAuth2Error> error, DataBufferFactory bufferFactory,
ResolvableType elementType, MimeType mimeType, Map<String, Object> hints) {
return Mono.from(error).flatMap((data) -> {
ByteArrayHttpOutputMessage bytes = new ByteArrayHttpOutputMessage();
try {
this.messageConverter.write(data, MediaType.APPLICATION_JSON, bytes);
return Mono.just(bytes.getBody().toByteArray());
}
catch (IOException ex) {
return Mono.error(ex);
}
}).map(bufferFactory::wrap).flux();
}
@NotNull
@Override
public List<MimeType> getEncodableMimeTypes() {
return List.of(MediaType.APPLICATION_JSON);
}
private static class ByteArrayHttpOutputMessage implements HttpOutputMessage {
private final ByteArrayOutputStream body = new ByteArrayOutputStream();
@NotNull
@Override
public ByteArrayOutputStream getBody() {
return this.body;
}
@NotNull
@Override
public HttpHeaders getHeaders() {
return new HttpHeaders();
}
}
}
@@ -16,16 +16,17 @@
package org.springframework.security.config.web.server;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.core.ResolvableType;
import org.springframework.http.MediaType;
import org.springframework.http.codec.EncoderHttpMessageWriter;
import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
@@ -62,6 +63,9 @@ class OidcBackChannelLogoutWebFilter implements WebFilter {
private ServerLogoutHandler logoutHandler = new OidcBackChannelServerLogoutHandler();
private final HttpMessageWriter<OAuth2Error> errorHttpMessageConverter = new EncoderHttpMessageWriter<>(
new OAuth2ErrorEncoder());
/**
* Construct an {@link OidcBackChannelLogoutWebFilter}
* @param authenticationConverter the {@link AuthenticationConverter} for deriving
@@ -84,7 +88,7 @@ class OidcBackChannelLogoutWebFilter implements WebFilter {
if (ex instanceof AuthenticationServiceException) {
return Mono.error(ex);
}
return handleAuthenticationFailure(exchange.getResponse(), ex).then(Mono.empty());
return handleAuthenticationFailure(exchange, ex).then(Mono.empty());
})
.switchIfEmpty(chain.filter(exchange).then(Mono.empty()))
.flatMap(this.authenticationManager::authenticate)
@@ -93,7 +97,7 @@ class OidcBackChannelLogoutWebFilter implements WebFilter {
if (ex instanceof AuthenticationServiceException) {
return Mono.error(ex);
}
return handleAuthenticationFailure(exchange.getResponse(), ex).then(Mono.empty());
return handleAuthenticationFailure(exchange, ex).then(Mono.empty());
})
.flatMap((authentication) -> {
WebFilterExchange webFilterExchange = new WebFilterExchange(exchange, chain);
@@ -101,19 +105,12 @@ class OidcBackChannelLogoutWebFilter implements WebFilter {
});
}
private Mono<Void> handleAuthenticationFailure(ServerHttpResponse response, Exception ex) {
private Mono<Void> handleAuthenticationFailure(ServerWebExchange exchange, Exception ex) {
this.logger.debug("Failed to process OIDC Back-Channel Logout", ex);
response.setRawStatusCode(HttpServletResponse.SC_BAD_REQUEST);
OAuth2Error error = oauth2Error(ex);
byte[] bytes = String.format("""
{
"error_code": "%s",
"error_description": "%s",
"error_uri: "%s"
}
""", error.getErrorCode(), error.getDescription(), error.getUri()).getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = response.bufferFactory().wrap(bytes);
return response.writeWith(Flux.just(buffer));
exchange.getResponse().setRawStatusCode(HttpServletResponse.SC_BAD_REQUEST);
return this.errorHttpMessageConverter.write(Mono.just(oauth2Error(ex)), ResolvableType.forClass(Object.class),
ResolvableType.forClass(Object.class), MediaType.APPLICATION_JSON, exchange.getRequest(),
exchange.getResponse(), Collections.emptyMap());
}
private OAuth2Error oauth2Error(Exception ex) {
@@ -16,8 +16,8 @@
package org.springframework.security.config.web.server;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
@@ -25,14 +25,15 @@ import java.util.concurrent.atomic.AtomicInteger;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.ResolvableType;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.codec.EncoderHttpMessageWriter;
import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.oidc.authentication.logout.OidcLogoutToken;
import org.springframework.security.oauth2.client.oidc.server.session.InMemoryReactiveOidcSessionRegistry;
@@ -44,6 +45,7 @@ import org.springframework.security.web.server.WebFilterExchange;
import org.springframework.security.web.server.authentication.logout.ServerLogoutHandler;
import org.springframework.util.Assert;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
@@ -63,6 +65,9 @@ final class OidcBackChannelServerLogoutHandler implements ServerLogoutHandler {
private ReactiveOidcSessionRegistry sessionRegistry = new InMemoryReactiveOidcSessionRegistry();
private final HttpMessageWriter<OAuth2Error> errorHttpMessageConverter = new EncoderHttpMessageWriter<>(
new OAuth2ErrorEncoder());
private WebClient web = WebClient.create();
private String logoutUri = "{baseScheme}://localhost{basePort}/logout";
@@ -97,7 +102,7 @@ final class OidcBackChannelServerLogoutHandler implements ServerLogoutHandler {
totalCount.intValue()));
}
if (!list.isEmpty()) {
return handleLogoutFailure(exchange.getExchange().getResponse(), oauth2Error(list));
return handleLogoutFailure(exchange.getExchange(), oauth2Error(list));
}
else {
return Mono.empty();
@@ -148,17 +153,11 @@ final class OidcBackChannelServerLogoutHandler implements ServerLogoutHandler {
"https://openid.net/specs/openid-connect-backchannel-1_0.html#Validation");
}
private Mono<Void> handleLogoutFailure(ServerHttpResponse response, OAuth2Error error) {
response.setRawStatusCode(HttpServletResponse.SC_BAD_REQUEST);
byte[] bytes = String.format("""
{
"error_code": "%s",
"error_description": "%s",
"error_uri: "%s"
}
""", error.getErrorCode(), error.getDescription(), error.getUri()).getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = response.bufferFactory().wrap(bytes);
return response.writeWith(Flux.just(buffer));
private Mono<Void> handleLogoutFailure(ServerWebExchange exchange, OAuth2Error error) {
exchange.getResponse().setRawStatusCode(HttpServletResponse.SC_BAD_REQUEST);
return this.errorHttpMessageConverter.write(Mono.just(error), ResolvableType.forClass(Object.class),
ResolvableType.forClass(Object.class), MediaType.APPLICATION_JSON, exchange.getRequest(),
exchange.getResponse(), Collections.emptyMap());
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -75,6 +75,7 @@ import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.springframework.security.config.Customizer.withDefaults;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
@@ -285,6 +286,49 @@ public class OAuth2ClientConfigurerTests {
verify(authorizationRedirectStrategy).sendRedirect(any(), any(), anyString());
}
@Test
public void configureWhenOAuth2LoginBeansConfiguredThenNotShared() throws Exception {
this.spring.register(OAuth2ClientConfigWithOAuth2Login.class).autowire();
// Setup the Authorization Request in the session
Map<String, Object> attributes = new HashMap<>();
attributes.put(OAuth2ParameterNames.REGISTRATION_ID, this.registration1.getRegistrationId());
// @formatter:off
OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
.authorizationUri(this.registration1.getProviderDetails().getAuthorizationUri())
.clientId(this.registration1.getClientId())
.redirectUri("http://localhost/client-1")
.state("state")
.attributes(attributes)
.build();
// @formatter:on
AuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository = new HttpSessionOAuth2AuthorizationRequestRepository();
MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
MockHttpServletResponse response = new MockHttpServletResponse();
authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, request, response);
MockHttpSession session = (MockHttpSession) request.getSession();
String principalName = "user1";
TestingAuthenticationToken authentication = new TestingAuthenticationToken(principalName, "password");
// @formatter:off
MockHttpServletRequestBuilder clientRequest = get("/client-1")
.param(OAuth2ParameterNames.CODE, "code")
.param(OAuth2ParameterNames.STATE, "state")
.with(authentication(authentication))
.session(session);
this.mockMvc.perform(clientRequest)
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("http://localhost/client-1"));
// @formatter:on
OAuth2AuthorizedClient authorizedClient = authorizedClientRepository
.loadAuthorizedClient(this.registration1.getRegistrationId(), authentication, request);
assertThat(authorizedClient).isNotNull();
// Ensure shared objects set for OAuth2 Client are not used
ClientRegistrationRepository clientRegistrationRepository = this.spring.getContext()
.getBean(ClientRegistrationRepository.class);
OAuth2AuthorizedClientRepository authorizedClientRepository = this.spring.getContext()
.getBean(OAuth2AuthorizedClientRepository.class);
verifyNoInteractions(clientRegistrationRepository, authorizedClientRepository);
}
@EnableWebSecurity
@Configuration
@EnableWebMvc
@@ -362,4 +406,51 @@ public class OAuth2ClientConfigurerTests {
}
@Configuration
@EnableWebSecurity
@EnableWebMvc
static class OAuth2ClientConfigWithOAuth2Login {
private final ClientRegistrationRepository clientRegistrationRepository = mock(
ClientRegistrationRepository.class);
private final OAuth2AuthorizedClientRepository authorizedClientRepository = mock(
OAuth2AuthorizedClientRepository.class);
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.oauth2Client((oauth2Client) -> oauth2Client
.clientRegistrationRepository(OAuth2ClientConfigurerTests.clientRegistrationRepository)
.authorizedClientService(OAuth2ClientConfigurerTests.authorizedClientService)
.authorizationCodeGrant((authorizationCode) -> authorizationCode
.authorizationRequestResolver(authorizationRequestResolver)
.authorizationRedirectStrategy(authorizationRedirectStrategy)
.accessTokenResponseClient(accessTokenResponseClient)
)
)
.oauth2Login((oauth2Login) -> oauth2Login
.clientRegistrationRepository(this.clientRegistrationRepository)
.authorizedClientRepository(this.authorizedClientRepository)
);
// @formatter:on
return http.build();
}
@Bean
ClientRegistrationRepository clientRegistrationRepository() {
return this.clientRegistrationRepository;
}
@Bean
OAuth2AuthorizedClientRepository authorizedClientRepository() {
return this.authorizedClientRepository;
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 the original author 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,7 +73,9 @@ import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository;
import org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizationRequestRepository;
import org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
@@ -115,6 +117,7 @@ import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.springframework.security.config.annotation.SecurityContextChangedListenerArgumentMatchers.setAuthentication;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
@@ -669,6 +672,30 @@ public class OAuth2LoginConfigurerTests {
.collect(Collectors.toList())).isEmpty();
}
@Test
public void oidcLoginWhenOAuth2ClientBeansConfiguredThenNotShared() throws Exception {
this.spring.register(OAuth2LoginConfigWithOAuth2Client.class, JwtDecoderFactoryConfig.class).autowire();
OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest("openid");
this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, this.request, this.response);
this.request.setParameter("code", "code123");
this.request.setParameter("state", authorizationRequest.getState());
this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain);
Authentication authentication = this.securityContextRepository
.loadContext(new HttpRequestResponseHolder(this.request, this.response))
.getAuthentication();
assertThat(authentication.getAuthorities()).hasSize(1);
assertThat(authentication.getAuthorities()).first()
.isInstanceOf(OidcUserAuthority.class)
.hasToString("OIDC_USER");
// Ensure shared objects set for OAuth2 Client are not used
ClientRegistrationRepository clientRegistrationRepository = this.spring.getContext()
.getBean(ClientRegistrationRepository.class);
OAuth2AuthorizedClientRepository authorizedClientRepository = this.spring.getContext()
.getBean(OAuth2AuthorizedClientRepository.class);
verifyNoInteractions(clientRegistrationRepository, authorizedClientRepository);
}
private void loadConfig(Class<?>... configs) {
AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
applicationContext.register(configs);
@@ -1192,6 +1219,45 @@ public class OAuth2LoginConfigurerTests {
}
@Configuration
@EnableWebSecurity
static class OAuth2LoginConfigWithOAuth2Client extends CommonLambdaSecurityFilterChainConfig {
private final ClientRegistrationRepository clientRegistrationRepository = mock(
ClientRegistrationRepository.class);
private final OAuth2AuthorizedClientRepository authorizedClientRepository = mock(
OAuth2AuthorizedClientRepository.class);
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.oauth2Login((oauth2Login) -> oauth2Login
.clientRegistrationRepository(
new InMemoryClientRegistrationRepository(GOOGLE_CLIENT_REGISTRATION))
.authorizedClientRepository(new HttpSessionOAuth2AuthorizedClientRepository())
)
.oauth2Client((oauth2Client) -> oauth2Client
.clientRegistrationRepository(this.clientRegistrationRepository)
.authorizedClientRepository(this.authorizedClientRepository)
);
// @formatter:on
return super.configureFilterChain(http);
}
@Bean
ClientRegistrationRepository clientRegistrationRepository() {
return this.clientRegistrationRepository;
}
@Bean
OAuth2AuthorizedClientRepository authorizedClientRepository() {
return this.authorizedClientRepository;
}
}
private abstract static class CommonSecurityFilterChainConfig {
SecurityFilterChain configureFilterChain(HttpSecurity http) throws Exception {
@@ -484,6 +484,7 @@ public class Saml2LogoutConfigurerTests {
verify(getBean(Saml2LogoutResponseValidator.class)).validate(any());
}
// gh-11363
@Test
public void saml2LogoutWhenCustomLogoutRequestRepositoryThenUses() throws Exception {
this.spring.register(Saml2LogoutComponentsConfig.class).autowire();
@@ -63,6 +63,7 @@ import org.springframework.web.util.UriUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.verify;
@@ -380,6 +381,22 @@ public class Saml2LogoutBeanDefinitionParserTests {
verify(getBean(Saml2LogoutResponseValidator.class)).validate(any());
}
// gh-11363
@Test
public void saml2LogoutWhenCustomLogoutRequestRepositoryThenUses() throws Exception {
this.spring.configLocations(this.xml("CustomComponents")).autowire();
RelyingPartyRegistration registration = this.repository.findByRegistrationId("get");
Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration)
.samlRequest(this.rpLogoutRequest)
.id(this.rpLogoutRequestId)
.relayState(this.rpLogoutRequestRelayState)
.parameters((params) -> params.put("Signature", this.rpLogoutRequestSignature))
.build();
given(getBean(Saml2LogoutRequestResolver.class).resolve(any(), any())).willReturn(logoutRequest);
this.mvc.perform(post("/logout").with(authentication(this.saml2User)).with(csrf()));
verify(getBean(Saml2LogoutRequestRepository.class)).saveLogoutRequest(eq(logoutRequest), any(), any());
}
private <T> T getBean(Class<T> clazz) {
return this.spring.getContext().getBean(clazz);
}
@@ -50,6 +50,7 @@ import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.annotation.Order;
import org.springframework.http.ResponseCookie;
import org.springframework.http.client.reactive.ClientHttpConnector;
@@ -99,6 +100,7 @@ import org.springframework.web.server.WebSession;
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasValue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.atLeastOnce;
@@ -197,7 +199,10 @@ public class OidcLogoutSpecTests {
.body(BodyInserters.fromFormData("logout_token", "invalid"))
.exchange()
.expectStatus()
.isBadRequest();
.isBadRequest()
.expectBody(new ParameterizedTypeReference<Map<String, String>>() {
})
.value(hasValue("invalid_request"));
this.test.get().uri("/token/logout").cookie("SESSION", session).exchange().expectStatus().isOk();
}
@@ -264,9 +269,10 @@ public class OidcLogoutSpecTests {
.exchange()
.expectStatus()
.isBadRequest()
.expectBody(String.class)
.value(containsString("partial_logout"))
.value(containsString("not all sessions were terminated"));
.expectBody(new ParameterizedTypeReference<Map<String, String>>() {
})
.value(hasValue("partial_logout"))
.value(hasValue(containsString("not all sessions were terminated")));
this.test.get().uri("/token/logout").cookie("SESSION", one).exchange().expectStatus().isOk();
}
+1
View File
@@ -33,6 +33,7 @@ dependencies {
testImplementation 'org.springframework:spring-test'
testRuntimeOnly 'org.hsqldb:hsqldb'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
task springVersion(type: org.gradle.api.tasks.WriteProperties) {
@@ -19,6 +19,7 @@ package org.springframework.security.authorization.method;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Expression;
import org.springframework.security.authorization.AuthorizationDeniedException;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.authorization.ExpressionAuthorizationDecision;
@@ -43,9 +44,24 @@ final class ExpressionUtils {
"SpEL expression must return either a Boolean or an AuthorizationDecision");
}
catch (EvaluationException ex) {
AuthorizationDeniedException denied = findAuthorizationException(ex);
if (denied != null) {
throw denied;
}
throw new IllegalArgumentException("Failed to evaluate expression '" + expr.getExpressionString() + "'",
ex);
}
}
static AuthorizationDeniedException findAuthorizationException(EvaluationException ex) {
Throwable cause = ex.getCause();
while (cause != null) {
if (cause instanceof AuthorizationDeniedException denied) {
return denied;
}
cause = cause.getCause();
}
return null;
}
}
@@ -22,9 +22,11 @@ import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationDeniedException;
import org.springframework.security.authorization.ExpressionAuthorizationDecision;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
public class ExpressionUtilsTests {
@@ -48,10 +50,23 @@ public class ExpressionUtilsTests {
assertThat(ExpressionUtils.evaluate(expression, context)).isInstanceOf(ExpressionAuthorizationDecision.class);
}
@Test
public void evaluateWhenExpressionThrowsAuthorizationDeniedExceptionThenPropagates() {
SpelExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("#root.throwException()");
StandardEvaluationContext context = new StandardEvaluationContext(this);
assertThatExceptionOfType(AuthorizationDeniedException.class)
.isThrownBy(() -> ExpressionUtils.evaluate(expression, context));
}
public AuthorizationDecision returnDecision() {
return new AuthorizationDecisionDetails(false, this.details);
}
public Object throwException() {
throw new AuthorizationDeniedException("denied", new AuthorizationDecision(false));
}
public boolean returnResult() {
return false;
}
+2
View File
@@ -13,4 +13,6 @@ dependencies {
testImplementation "org.mockito:mockito-core"
testImplementation "org.mockito:mockito-junit-jupiter"
testImplementation "org.springframework:spring-test"
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
@@ -611,6 +611,9 @@ public class BCrypt {
int rounds, off;
StringBuilder rs = new StringBuilder();
if (passwordb.length > 72) {
throw new IllegalArgumentException("password cannot be more than 72 bytes");
}
if (salt == null) {
throw new IllegalArgumentException("salt cannot be null");
}
@@ -222,4 +222,14 @@ public class BCryptPasswordEncoderTests {
assertThat(encoder.matches("wrong", "$2a$00$9N8N35BVs5TLqGL3pspAte5OWWA2a2aZIs.EGp7At7txYakFERMue")).isFalse();
}
@Test
public void enforcePasswordLength() {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String password72chars = "123456789012345678901234567890123456789012345678901234567890123456789012";
assertThat(encoder.matches(password72chars, encoder.encode(password72chars))).isTrue();
String password73chars = password72chars.concat("a");
assertThatIllegalArgumentException()
.isThrownBy(() -> encoder.matches(password73chars, encoder.encode(password73chars)));
}
}
+2
View File
@@ -15,4 +15,6 @@ dependencies {
testImplementation "org.mockito:mockito-junit-jupiter"
testImplementation "org.springframework:spring-test"
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
@@ -649,7 +649,7 @@ class MyAuthoritiesPopulator : LdapAuthoritiesPopulator {
You would then add a bean of this type to your application context and inject it into the `LdapAuthenticationProvider`. This is covered in the section on configuring LDAP by using explicit Spring beans in the LDAP chapter of the reference manual.
Note that you cannot use the namespace for configuration in this case.
You should also consult the security-api-url[Javadoc] for the relevant classes and interfaces.
You should also consult the {security-api-url}[Javadoc] for the relevant classes and interfaces.
[[appendix-faq-namespace-post-processor]]
@@ -115,6 +115,7 @@ open class MyCustomerService {
A given invocation to `MyCustomerService#readCustomer` may look something like this when Method Security <<activate-method-security,is activated>>:
[.invert-dark]
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>>
@@ -82,6 +82,7 @@ To learn more about CSRF protection for your application, consider the following
CSRF protection is provided by several components that are composed within the {security-api-url}org/springframework/security/web/csrf/CsrfFilter.html[`CsrfFilter`]:
.`CsrfFilter` Components
[.invert-dark]
image::{figures}/csrf.png[]
CSRF protection is divided into two parts:
@@ -90,6 +91,7 @@ CSRF protection is divided into two parts:
2. Determine if the request requires CSRF protection, load and validate the token, and <<csrf-access-denied-handler,handle `AccessDeniedException`>>.
.`CsrfFilter` Processing
[.invert-dark]
image::{figures}/csrf-processing.png[]
* image:{icondir}/number_1.png[] First, the {security-api-url}org/springframework/security/web/csrf/DeferredCsrfToken.html[`DeferredCsrfToken`] is loaded, which holds a reference to the <<csrf-token-repository,`CsrfTokenRepository`>> so that the persisted `CsrfToken` can be loaded later (in image:{icondir}/number_4.png[]).
@@ -205,6 +205,78 @@ This will ensure that:
<5> Any other message of type MESSAGE or SUBSCRIBE is rejected. Due to 6 we do not need this step, but it illustrates how one can match on specific message types.
<6> Any other Message is rejected. This is a good idea to ensure that you do not miss any messages.
[[migrating-spel-expressions]]
=== Migrating SpEL Expressions
If you are migrating from an older version of Spring Security, your destination matchers may include SpEL expressions.
It's recommended that these be changed to using concrete implementations of `AuthorizationManager` since this is independently testable.
However, to ease migration, you can also use a class like the following:
[source,java]
----
public final class MessageExpressionAuthorizationManager implements AuthorizationManager<MessageAuthorizationContext<?>> {
private SecurityExpressionHandler<Message<?>> expressionHandler = new DefaultMessageSecurityExpressionHandler();
private Expression expression;
public MessageExpressionAuthorizationManager(String expressionString) {
Assert.hasText(expressionString, "expressionString cannot be empty");
this.expression = this.expressionHandler.getExpressionParser().parseExpression(expressionString);
}
@Override
public AuthorizationDecision check(Supplier<Authentication> authentication, MessageAuthorizationContext<?> context) {
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication, context.getMessage());
boolean granted = ExpressionUtils.evaluateAsBoolean(this.expression, ctx);
return new ExpressionAuthorizationDecision(granted, this.expression);
}
}
----
And specify an instance for each matcher that you cannot get migrate:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Configuration
public class WebSocketSecurityConfig {
@Bean
public AuthorizationManager<Message<?>> messageAuthorizationManager(MessageMatcherDelegatingAuthorizationManager.Builder messages) {
messages
// ...
.simpSubscribeDestMatchers("/topic/friends/{friend}").access(new MessageExpressionAuthorizationManager("#friends == 'john"));
// ...
return messages.build();
}
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Configuration
open class WebSocketSecurityConfig {
fun messageAuthorizationManager(messages: MessageMatcherDelegatingAuthorizationManager.Builder): AuthorizationManager<Message<?> {
messages
// ..
.simpSubscribeDestMatchers("/topic/friends/{friends}").access(MessageExpressionAuthorizationManager("#friends == 'john"))
// ...
return messages.build()
}
}
----
======
[[websocket-authorization-notes]]
=== WebSocket Authorization Notes
+4 -4
View File
@@ -1,10 +1,10 @@
{
"dependencies": {
"antora": "3.2.0-alpha.6",
"antora": "3.2.0-alpha.8",
"@antora/atlas-extension": "1.0.0-alpha.2",
"@antora/collector-extension": "1.0.0-beta.5",
"@antora/collector-extension": "1.0.1",
"@asciidoctor/tabs": "1.0.0-beta.6",
"@springio/antora-extensions": "1.14.2",
"@springio/asciidoctor-extensions": "1.0.0-alpha.14"
"@springio/antora-extensions": "1.14.4",
"@springio/asciidoctor-extensions": "1.0.0-alpha.16"
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
springBootVersion=3.1.1
version=6.3.6-SNAPSHOT
version=6.3.8
samplesBranch=6.3.x
org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError
org.gradle.parallel=true
+11 -11
View File
@@ -1,6 +1,6 @@
[versions]
com-squareup-okhttp3 = "3.14.9"
io-rsocket = "1.1.4"
io-rsocket = "1.1.5"
io-spring-javaformat = "0.0.43"
io-spring-nohttp = "0.0.11"
jakarta-websocket = "2.1.1"
@@ -13,22 +13,22 @@ org-jetbrains-kotlin = "1.9.25"
org-jetbrains-kotlinx = "1.8.1"
org-mockito = "5.11.0"
org-opensaml = "4.3.2"
org-springframework = "6.1.15"
org-springframework = "6.1.18"
[libraries]
ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.5.12"
ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.5.17"
com-fasterxml-jackson-jackson-bom = "com.fasterxml.jackson:jackson-bom:2.17.3"
com-google-inject-guice = "com.google.inject:guice:3.0"
com-netflix-nebula-nebula-project-plugin = "com.netflix.nebula:nebula-project-plugin:8.2.0"
com-nimbusds-nimbus-jose-jwt = "com.nimbusds:nimbus-jose-jwt:9.37.3"
com-nimbusds-oauth2-oidc-sdk = "com.nimbusds:oauth2-oidc-sdk:9.43.4"
com-nimbusds-oauth2-oidc-sdk = "com.nimbusds:oauth2-oidc-sdk:9.43.6"
com-squareup-okhttp3-mockwebserver = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "com-squareup-okhttp3" }
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.13"
io-mockk = "io.mockk:mockk:1.13.13"
io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2023.0.12"
io-mockk = "io.mockk:mockk:1.13.17"
io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2023.0.16"
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" }
@@ -83,9 +83,9 @@ org-seleniumhq-selenium-selenium-java = "org.seleniumhq.selenium:selenium-java:3
org-seleniumhq-selenium-selenium-support = "org.seleniumhq.selenium:selenium-support:3.141.59"
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.16"
org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2024.0.6"
org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:3.2.8"
org-slf4j-slf4j-api = "org.slf4j:slf4j-api:2.0.17"
org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2024.0.10"
org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:3.2.11"
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"
@@ -95,11 +95,11 @@ net-sourceforge-saxon-saxon = "net.sourceforge.saxon:saxon:9.1.0.8"
org-yaml-snakeyaml = "org.yaml:snakeyaml:1.33"
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.3.0"
org-gretty-gretty = "org.gretty:gretty:4.1.5"
org-gretty-gretty = "org.gretty:gretty:4.1.6"
com-github-ben-manes-gradle-versions-plugin = "com.github.ben-manes:gradle-versions-plugin:0.51.0"
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.33.22"
org-jfrog-buildinfo-build-info-extractor-gradle = "org.jfrog.buildinfo:build-info-extractor-gradle:4.33.24"
org-sonarsource-scanner-gradle-sonarqube-gradle-plugin = "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.8.0.1969"
org-instancio-instancio-junit = "org.instancio:instancio-junit:3.7.1"
+2 -2
View File
@@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=1541fa36599e12857140465f3c91a97409b4512501c26f9631fb113e392c5bd1
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.1-bin.zip
distributionSha256Sum=8d97a97984f6cbd2b85fe4c60a743440a347544bf18818048e611f5288d46c94
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
@@ -22,6 +22,7 @@ dependencies {
testRuntimeOnly project(':spring-security-config')
testRuntimeOnly 'org.aspectj:aspectjweaver'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
System.setProperty('python.cachedir.skip', 'true')
@@ -25,4 +25,6 @@ dependencies {
testImplementation "org.mockito:mockito-core"
testImplementation "org.mockito:mockito-junit-jupiter"
testImplementation "org.springframework:spring-test"
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
@@ -25,4 +25,6 @@ dependencies {
testImplementation "org.mockito:mockito-core"
testImplementation "org.mockito:mockito-junit-jupiter"
testImplementation "org.springframework:spring-test"
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
@@ -18,4 +18,6 @@ dependencies {
testImplementation "org.mockito:mockito-core"
testImplementation "org.mockito:mockito-junit-jupiter"
testImplementation "org.springframework:spring-test"
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
@@ -17,4 +17,6 @@ dependencies {
testImplementation "org.mockito:mockito-core"
testImplementation "org.mockito:mockito-junit-jupiter"
testImplementation "org.springframework:spring-test"
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
@@ -18,4 +18,6 @@ dependencies {
testImplementation "org.mockito:mockito-core"
testImplementation "org.mockito:mockito-junit-jupiter"
testImplementation "org.springframework:spring-test"
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
@@ -25,6 +25,7 @@ dependencies {
testRuntimeOnly project(':spring-security-config')
testRuntimeOnly project(':spring-security-ldap')
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
integrationTest {
+2
View File
@@ -35,6 +35,8 @@ dependencies {
testImplementation "org.mockito:mockito-junit-jupiter"
testImplementation "org.springframework:spring-test"
testImplementation 'org.skyscreamer:jsonassert'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
integrationTest {
@@ -27,4 +27,5 @@ dependencies {
testImplementation "ch.qos.logback:logback-classic"
testRuntimeOnly 'org.hsqldb:hsqldb'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
@@ -33,6 +33,7 @@ dependencies {
testImplementation "org.springframework:spring-test"
testRuntimeOnly 'org.hsqldb:hsqldb'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
provided 'jakarta.servlet:jakarta.servlet-api'
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 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.
@@ -80,7 +80,13 @@ public final class InMemoryOAuth2AuthorizedClientService implements OAuth2Author
if (registration == null) {
return null;
}
return (T) this.authorizedClients.get(new OAuth2AuthorizedClientId(clientRegistrationId, principalName));
OAuth2AuthorizedClient cachedAuthorizedClient = this.authorizedClients
.get(new OAuth2AuthorizedClientId(clientRegistrationId, principalName));
if (cachedAuthorizedClient == null) {
return null;
}
return (T) new OAuth2AuthorizedClient(registration, cachedAuthorizedClient.getPrincipalName(),
cachedAuthorizedClient.getAccessToken(), cachedAuthorizedClient.getRefreshToken());
}
@Override
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 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.
@@ -62,8 +62,15 @@ public final class InMemoryReactiveOAuth2AuthorizedClientService implements Reac
Assert.hasText(clientRegistrationId, "clientRegistrationId cannot be empty");
Assert.hasText(principalName, "principalName cannot be empty");
return (Mono<T>) this.clientRegistrationRepository.findByRegistrationId(clientRegistrationId)
.map((clientRegistration) -> new OAuth2AuthorizedClientId(clientRegistrationId, principalName))
.flatMap((identifier) -> Mono.justOrEmpty(this.authorizedClients.get(identifier)));
.mapNotNull((clientRegistration) -> {
OAuth2AuthorizedClientId id = new OAuth2AuthorizedClientId(clientRegistrationId, principalName);
OAuth2AuthorizedClient cachedAuthorizedClient = this.authorizedClients.get(id);
if (cachedAuthorizedClient == null) {
return null;
}
return new OAuth2AuthorizedClient(clientRegistration, cachedAuthorizedClient.getPrincipalName(),
cachedAuthorizedClient.getAccessToken(), cachedAuthorizedClient.getRefreshToken());
});
}
@Override
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 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,22 +18,25 @@ package org.springframework.security.oauth2.client;
import java.util.Collections;
import java.util.Map;
import java.util.function.Consumer;
import org.junit.jupiter.api.Test;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatObject;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.BDDMockito.mock;
/**
* Tests for {@link InMemoryOAuth2AuthorizedClientService}.
@@ -79,9 +82,11 @@ public class InMemoryOAuth2AuthorizedClientServiceTests {
@Test
public void constructorWhenAuthorizedClientsProvidedThenUseProvidedAuthorizedClients() {
String registrationId = this.registration3.getRegistrationId();
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(this.registration3, this.principalName1,
mock(OAuth2AccessToken.class));
Map<OAuth2AuthorizedClientId, OAuth2AuthorizedClient> authorizedClients = Collections.singletonMap(
new OAuth2AuthorizedClientId(this.registration3.getRegistrationId(), this.principalName1),
mock(OAuth2AuthorizedClient.class));
authorizedClient);
ClientRegistrationRepository clientRegistrationRepository = mock(ClientRegistrationRepository.class);
given(clientRegistrationRepository.findByRegistrationId(eq(registrationId))).willReturn(this.registration3);
InMemoryOAuth2AuthorizedClientService authorizedClientService = new InMemoryOAuth2AuthorizedClientService(
@@ -124,7 +129,35 @@ public class InMemoryOAuth2AuthorizedClientServiceTests {
this.authorizedClientService.saveAuthorizedClient(authorizedClient, authentication);
OAuth2AuthorizedClient loadedAuthorizedClient = this.authorizedClientService
.loadAuthorizedClient(this.registration1.getRegistrationId(), this.principalName1);
assertThat(loadedAuthorizedClient).isEqualTo(authorizedClient);
assertThat(loadedAuthorizedClient).satisfies(isEqualTo(authorizedClient));
}
@Test
public void loadAuthorizedClientWhenClientRegistrationIsUpdatedThenReturnAuthorizedClientWithUpdatedClientRegistration() {
ClientRegistration updatedRegistration = ClientRegistration.withClientRegistration(this.registration1)
.clientSecret("updated secret")
.build();
ClientRegistrationRepository clientRegistrationRepository = mock(ClientRegistrationRepository.class);
given(clientRegistrationRepository.findByRegistrationId(this.registration1.getRegistrationId()))
.willReturn(this.registration1, updatedRegistration);
InMemoryOAuth2AuthorizedClientService authorizedClientService = new InMemoryOAuth2AuthorizedClientService(
clientRegistrationRepository);
OAuth2AuthorizedClient cachedAuthorizedClient = new OAuth2AuthorizedClient(this.registration1,
this.principalName1, mock(OAuth2AccessToken.class), mock(OAuth2RefreshToken.class));
authorizedClientService.saveAuthorizedClient(cachedAuthorizedClient,
new TestingAuthenticationToken(this.principalName1, null));
OAuth2AuthorizedClient authorizedClientWithUpdatedRegistration = new OAuth2AuthorizedClient(updatedRegistration,
this.principalName1, mock(OAuth2AccessToken.class), mock(OAuth2RefreshToken.class));
OAuth2AuthorizedClient firstLoadedClient = authorizedClientService
.loadAuthorizedClient(this.registration1.getRegistrationId(), this.principalName1);
OAuth2AuthorizedClient secondLoadedClient = authorizedClientService
.loadAuthorizedClient(this.registration1.getRegistrationId(), this.principalName1);
assertThat(firstLoadedClient).satisfies(isEqualTo(cachedAuthorizedClient));
assertThat(secondLoadedClient).satisfies(isEqualTo(authorizedClientWithUpdatedRegistration));
}
@Test
@@ -148,7 +181,7 @@ public class InMemoryOAuth2AuthorizedClientServiceTests {
this.authorizedClientService.saveAuthorizedClient(authorizedClient, authentication);
OAuth2AuthorizedClient loadedAuthorizedClient = this.authorizedClientService
.loadAuthorizedClient(this.registration3.getRegistrationId(), this.principalName2);
assertThat(loadedAuthorizedClient).isEqualTo(authorizedClient);
assertThat(loadedAuthorizedClient).satisfies(isEqualTo(authorizedClient));
}
@Test
@@ -180,4 +213,38 @@ public class InMemoryOAuth2AuthorizedClientServiceTests {
assertThat(loadedAuthorizedClient).isNull();
}
private static Consumer<OAuth2AuthorizedClient> isEqualTo(OAuth2AuthorizedClient expected) {
return (actual) -> {
assertThat(actual).isNotNull();
assertThat(actual.getClientRegistration().getRegistrationId())
.isEqualTo(expected.getClientRegistration().getRegistrationId());
assertThat(actual.getClientRegistration().getClientName())
.isEqualTo(expected.getClientRegistration().getClientName());
assertThat(actual.getClientRegistration().getRedirectUri())
.isEqualTo(expected.getClientRegistration().getRedirectUri());
assertThat(actual.getClientRegistration().getAuthorizationGrantType())
.isEqualTo(expected.getClientRegistration().getAuthorizationGrantType());
assertThat(actual.getClientRegistration().getClientAuthenticationMethod())
.isEqualTo(expected.getClientRegistration().getClientAuthenticationMethod());
assertThat(actual.getClientRegistration().getClientId())
.isEqualTo(expected.getClientRegistration().getClientId());
assertThat(actual.getClientRegistration().getClientSecret())
.isEqualTo(expected.getClientRegistration().getClientSecret());
assertThat(actual.getPrincipalName()).isEqualTo(expected.getPrincipalName());
assertThat(actual.getAccessToken().getTokenType()).isEqualTo(expected.getAccessToken().getTokenType());
assertThat(actual.getAccessToken().getTokenValue()).isEqualTo(expected.getAccessToken().getTokenValue());
assertThat(actual.getAccessToken().getIssuedAt()).isEqualTo(expected.getAccessToken().getIssuedAt());
assertThat(actual.getAccessToken().getExpiresAt()).isEqualTo(expected.getAccessToken().getExpiresAt());
assertThat(actual.getAccessToken().getScopes()).isEqualTo(expected.getAccessToken().getScopes());
if (expected.getRefreshToken() != null) {
assertThat(actual.getRefreshToken()).isNotNull();
assertThat(actual.getRefreshToken().getTokenValue())
.isEqualTo(expected.getRefreshToken().getTokenValue());
assertThat(actual.getRefreshToken().getIssuedAt()).isEqualTo(expected.getRefreshToken().getIssuedAt());
assertThat(actual.getRefreshToken().getExpiresAt())
.isEqualTo(expected.getRefreshToken().getExpiresAt());
}
};
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 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,12 +18,14 @@ package org.springframework.security.oauth2.client;
import java.time.Duration;
import java.time.Instant;
import java.util.function.Consumer;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
@@ -34,7 +36,9 @@ import org.springframework.security.oauth2.client.registration.ReactiveClientReg
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.BDDMockito.given;
@@ -56,8 +60,9 @@ public class InMemoryReactiveOAuth2AuthorizedClientServiceTests {
private Authentication principal = new TestingAuthenticationToken(this.principalName, "notused");
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "token", Instant.now(),
Instant.now().plus(Duration.ofDays(1)));
private OAuth2AccessToken accessToken;
private OAuth2RefreshToken refreshToken;
// @formatter:off
private ClientRegistration clientRegistration = ClientRegistration.withRegistrationId(this.clientRegistrationId)
@@ -79,6 +84,11 @@ public class InMemoryReactiveOAuth2AuthorizedClientServiceTests {
public void setup() {
this.authorizedClientService = new InMemoryReactiveOAuth2AuthorizedClientService(
this.clientRegistrationRepository);
Instant issuedAt = Instant.now();
Instant expiresAt = issuedAt.plus(Duration.ofDays(1));
this.accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "token", issuedAt, expiresAt);
this.refreshToken = new OAuth2RefreshToken("refresh", issuedAt, expiresAt);
}
@Test
@@ -153,11 +163,37 @@ public class InMemoryReactiveOAuth2AuthorizedClientServiceTests {
.saveAuthorizedClient(authorizedClient, this.principal)
.then(this.authorizedClientService.loadAuthorizedClient(this.clientRegistrationId, this.principalName));
StepVerifier.create(saveAndLoad)
.expectNext(authorizedClient)
.assertNext(isEqualTo(authorizedClient))
.verifyComplete();
// @formatter:on
}
@Test
@SuppressWarnings("unchecked")
public void loadAuthorizedClientWhenClientRegistrationIsUpdatedThenReturnsAuthorizedClientWithUpdatedClientRegistration() {
ClientRegistration updatedRegistration = ClientRegistration.withClientRegistration(this.clientRegistration)
.clientSecret("updated secret")
.build();
given(this.clientRegistrationRepository.findByRegistrationId(this.clientRegistrationId))
.willReturn(Mono.just(this.clientRegistration), Mono.just(updatedRegistration));
OAuth2AuthorizedClient cachedAuthorizedClient = new OAuth2AuthorizedClient(this.clientRegistration,
this.principalName, this.accessToken, this.refreshToken);
OAuth2AuthorizedClient authorizedClientWithChangedRegistration = new OAuth2AuthorizedClient(updatedRegistration,
this.principalName, this.accessToken, this.refreshToken);
Flux<OAuth2AuthorizedClient> saveAndLoadTwice = this.authorizedClientService
.saveAuthorizedClient(cachedAuthorizedClient, this.principal)
.then(this.authorizedClientService.loadAuthorizedClient(this.clientRegistrationId, this.principalName))
.concatWith(
this.authorizedClientService.loadAuthorizedClient(this.clientRegistrationId, this.principalName));
StepVerifier.create(saveAndLoadTwice)
.assertNext(isEqualTo(cachedAuthorizedClient))
.assertNext(isEqualTo(authorizedClientWithChangedRegistration))
.verifyComplete();
}
@Test
public void saveAuthorizedClientWhenAuthorizedClientNullThenIllegalArgumentException() {
OAuth2AuthorizedClient authorizedClient = null;
@@ -246,4 +282,38 @@ public class InMemoryReactiveOAuth2AuthorizedClientServiceTests {
// @formatter:on
}
private static Consumer<OAuth2AuthorizedClient> isEqualTo(OAuth2AuthorizedClient expected) {
return (actual) -> {
assertThat(actual).isNotNull();
assertThat(actual.getClientRegistration().getRegistrationId())
.isEqualTo(expected.getClientRegistration().getRegistrationId());
assertThat(actual.getClientRegistration().getClientName())
.isEqualTo(expected.getClientRegistration().getClientName());
assertThat(actual.getClientRegistration().getRedirectUri())
.isEqualTo(expected.getClientRegistration().getRedirectUri());
assertThat(actual.getClientRegistration().getAuthorizationGrantType())
.isEqualTo(expected.getClientRegistration().getAuthorizationGrantType());
assertThat(actual.getClientRegistration().getClientAuthenticationMethod())
.isEqualTo(expected.getClientRegistration().getClientAuthenticationMethod());
assertThat(actual.getClientRegistration().getClientId())
.isEqualTo(expected.getClientRegistration().getClientId());
assertThat(actual.getClientRegistration().getClientSecret())
.isEqualTo(expected.getClientRegistration().getClientSecret());
assertThat(actual.getPrincipalName()).isEqualTo(expected.getPrincipalName());
assertThat(actual.getAccessToken().getTokenType()).isEqualTo(expected.getAccessToken().getTokenType());
assertThat(actual.getAccessToken().getTokenValue()).isEqualTo(expected.getAccessToken().getTokenValue());
assertThat(actual.getAccessToken().getIssuedAt()).isEqualTo(expected.getAccessToken().getIssuedAt());
assertThat(actual.getAccessToken().getExpiresAt()).isEqualTo(expected.getAccessToken().getExpiresAt());
assertThat(actual.getAccessToken().getScopes()).isEqualTo(expected.getAccessToken().getScopes());
if (expected.getRefreshToken() != null) {
assertThat(actual.getRefreshToken()).isNotNull();
assertThat(actual.getRefreshToken().getTokenValue())
.isEqualTo(expected.getRefreshToken().getTokenValue());
assertThat(actual.getRefreshToken().getIssuedAt()).isEqualTo(expected.getRefreshToken().getIssuedAt());
assertThat(actual.getRefreshToken().getExpiresAt())
.isEqualTo(expected.getRefreshToken().getExpiresAt());
}
};
}
}
@@ -17,4 +17,6 @@ dependencies {
testImplementation "org.mockito:mockito-core"
testImplementation "org.mockito:mockito-junit-jupiter"
testImplementation "org.springframework:spring-test"
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
@@ -23,4 +23,6 @@ dependencies {
testImplementation "org.mockito:mockito-core"
testImplementation "org.mockito:mockito-junit-jupiter"
testImplementation "org.springframework:spring-test"
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
@@ -26,4 +26,6 @@ dependencies {
testImplementation "org.mockito:mockito-core"
testImplementation "org.mockito:mockito-junit-jupiter"
testImplementation "org.springframework:spring-test"
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
+2
View File
@@ -14,4 +14,6 @@ dependencies {
testImplementation "org.mockito:mockito-core"
testImplementation "org.mockito:mockito-junit-jupiter"
testImplementation "org.springframework:spring-test"
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
@@ -24,4 +24,6 @@ dependencies {
testImplementation "org.mockito:mockito-core"
testImplementation "org.mockito:mockito-junit-jupiter"
testImplementation "org.springframework:spring-test"
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
+2
View File
@@ -24,6 +24,8 @@ dependencies {
testImplementation "org.mockito:mockito-core"
testImplementation "org.mockito:mockito-junit-jupiter"
testImplementation "org.springframework:spring-test"
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
configure(project.tasks.withType(Test)) {
+2
View File
@@ -32,4 +32,6 @@ dependencies {
testImplementation 'org.skyscreamer:jsonassert'
testImplementation 'org.springframework:spring-webmvc'
testImplementation 'org.springframework:spring-tx'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
+1
View File
@@ -39,4 +39,5 @@ dependencies {
testImplementation 'com.squareup.okhttp3:mockwebserver'
testRuntimeOnly 'org.hsqldb:hsqldb'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-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 jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseCookie;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -97,8 +98,18 @@ public final class CookieCsrfTokenRepository implements CsrfTokenRepository {
this.cookieCustomizer.accept(cookieBuilder);
Cookie cookie = mapToCookie(cookieBuilder.build());
response.addCookie(cookie);
ResponseCookie responseCookie = cookieBuilder.build();
if (!StringUtils.hasLength(responseCookie.getSameSite())) {
Cookie cookie = mapToCookie(responseCookie);
response.addCookie(cookie);
}
else if (request.getServletContext().getMajorVersion() > 5) {
Cookie cookie = mapToCookie(responseCookie);
response.addCookie(cookie);
}
else {
response.addHeader(HttpHeaders.SET_COOKIE, responseCookie.toString());
}
// Set request attribute to signal that response has blank cookie value,
// which allows loadToken to return null when token has been removed
@@ -16,6 +16,8 @@
package org.springframework.security.web.server.firewall;
import java.net.InetSocketAddress;
import java.net.URI;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@@ -23,6 +25,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.regex.Pattern;
@@ -33,6 +36,7 @@ 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.http.server.reactive.SslInfo;
import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
@@ -743,6 +747,11 @@ public class StrictServerWebExchangeFirewall implements ServerWebExchangeFirewal
return queryParams;
}
@Override
public Builder mutate() {
return new StrictFirewallBuilder(super.mutate());
}
private final class StrictFirewallHttpHeaders extends HttpHeaders {
private StrictFirewallHttpHeaders(HttpHeaders delegate) {
@@ -783,6 +792,61 @@ public class StrictServerWebExchangeFirewall implements ServerWebExchangeFirewal
}
private final class StrictFirewallBuilder implements Builder {
private final Builder delegate;
private StrictFirewallBuilder(Builder delegate) {
this.delegate = delegate;
}
@Override
public Builder method(HttpMethod httpMethod) {
return this.delegate.method(httpMethod);
}
@Override
public Builder uri(URI uri) {
return this.delegate.uri(uri);
}
@Override
public Builder path(String path) {
return this.delegate.path(path);
}
@Override
public Builder contextPath(String contextPath) {
return this.delegate.contextPath(contextPath);
}
@Override
public Builder header(String headerName, String... headerValues) {
return this.delegate.header(headerName, headerValues);
}
@Override
public Builder headers(Consumer<HttpHeaders> headersConsumer) {
return this.delegate.headers(headersConsumer);
}
@Override
public Builder sslInfo(SslInfo sslInfo) {
return this.delegate.sslInfo(sslInfo);
}
@Override
public Builder remoteAddress(InetSocketAddress remoteAddress) {
return this.delegate.remoteAddress(remoteAddress);
}
@Override
public ServerHttpRequest build() {
return new StrictFirewallHttpRequest(this.delegate.build());
}
}
}
}
@@ -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.
@@ -17,16 +17,20 @@
package org.springframework.security.web.csrf;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockServletContext;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.springframework.security.web.csrf.CsrfTokenAssert.assertThatCsrfToken;
@@ -447,6 +451,44 @@ class CookieCsrfTokenRepositoryTests {
assertThat(tokenCookie.isHttpOnly()).isEqualTo(Boolean.FALSE);
}
// gh-16173
@Test
void saveTokenWhenSameSiteAndServletVersion5ThenUsesAddHeader() {
HttpServletResponse response = mock(HttpServletResponse.class);
((MockServletContext) this.request.getServletContext()).setMajorVersion(5);
this.repository.setCookieCustomizer((builder) -> builder.sameSite("Strict"));
CsrfToken token = this.repository.generateToken(this.request);
this.repository.saveToken(token, this.request, response);
verify(response, never()).addCookie(any(Cookie.class));
verify(response).addHeader(any(), any());
}
// gh-16173
@Test
void saveTokenWhenSameSiteAndServletVersion6OrHigherThenUsesAddCookie() {
HttpServletResponse response = mock(HttpServletResponse.class);
this.repository.setCookieCustomizer((builder) -> builder.sameSite("Strict"));
CsrfToken token = this.repository.generateToken(this.request);
this.repository.saveToken(token, this.request, response);
verify(response).addCookie(any(Cookie.class));
verify(response, never()).addHeader(any(), any());
}
// gh-16173
@Test
void saveTokenWhenNoSameSiteThenUsesAddCookie() {
HttpServletResponse response = mock(HttpServletResponse.class);
CsrfToken token = this.repository.generateToken(this.request);
this.repository.saveToken(token, this.request, response);
verify(response).addCookie(any(Cookie.class));
verify(response, never()).addHeader(any(), any());
((MockServletContext) this.request.getServletContext()).setMajorVersion(5);
response = mock(HttpServletResponse.class);
this.repository.saveToken(token, this.request, response);
verify(response).addCookie(any(Cookie.class));
verify(response, never()).addHeader(any(), any());
}
@Test
void setCookieNameNullIllegalArgumentException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.repository.setCookieName(null));
@@ -513,4 +513,25 @@ class StrictServerWebExchangeFirewallTests {
assertThat(exchange.getRequest().getHeaders().get(null)).isNull();
}
@Test
void getFirewalledExchangeWhenMutateThenHeadersStillFirewalled() {
String invalidHeaderName = "bad name";
this.firewall.setAllowedHeaderNames((name) -> !name.equals(invalidHeaderName));
ServerWebExchange exchange = getFirewalledExchange();
ServerWebExchange mutatedExchange = exchange.mutate().request(exchange.getRequest().mutate().build()).build();
HttpHeaders headers = mutatedExchange.getRequest().getHeaders();
assertThatExceptionOfType(ServerExchangeRejectedException.class)
.isThrownBy(() -> headers.get(invalidHeaderName));
}
@Test
void getMutatedFirewalledExchangeGetHeaderWhenNotAllowedHeaderNameThenException() {
String invalidHeaderName = "bad name";
this.firewall.setAllowedHeaderNames((name) -> !name.equals(invalidHeaderName));
ServerWebExchange exchange = getFirewalledExchange();
HttpHeaders headers = exchange.getRequest().mutate().build().getHeaders();
assertThatExceptionOfType(ServerExchangeRejectedException.class)
.isThrownBy(() -> headers.get(invalidHeaderName));
}
}