1
0
mirror of synced 2026-07-15 07:35:11 +00:00

Compare commits

..

120 Commits

Author SHA1 Message Date
github-actions[bot] 9bd793ffe6 Release 7.0.4 2026-03-16 18:16:34 +00:00
Josh Cummings a2c0ac112b Update to spring-security-release-tools 1.0.15
Closes gh-18909

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-03-16 11:43:19 -06:00
Josh Cummings ea6e7ab78f Merge branch '6.5.x' into 7.0.x 2026-03-16 10:49:23 -06:00
Josh Cummings 01ff3b086a Add Workflow for Deferring Issues
Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-03-16 10:49:07 -06:00
Rob Winch e8cb0ef541 Merge Fix Jackson Deserializer for AuthenticationExtensionsClientOutputs 2026-03-16 11:57:23 -04:00
Rob Winch 33e6f4bd3f Merge Fix Jackson Deserializer for AuthenticationExtensionsClientOutputs 2026-03-16 11:57:07 -04:00
Rob Winch 524ae92f6b Merge Add Jackson Mixin for WebAuthnAuthentication
Add Jackson Mixin for WebAuthnAuthentication
2026-03-16 11:50:23 -04:00
Toshiaki Maki 47146f375b Add Jackson Mixin for WebAuthnAuthentication
Closes gh-18034

Signed-off-by: Toshiaki Maki <makingx@gmail.com>
2026-03-16 10:33:00 -05:00
Robert Winch e7080e8c7c Update Antora UI Spring to v0.4.26 2026-03-16 08:50:29 -05:00
Robert Winch c348a7aa46 Bump io.projectreactor:reactor-bom from 2025.0.3 to 2025.0.4 2026-03-16 08:44:25 -05:00
Robert Winch f227934749 Bump org.apache.maven:maven-resolver-provider from 3.9.13 to 3.9.14 2026-03-16 08:44:19 -05:00
Robert Winch e645aef3be Bump org.springframework.data:spring-data-bom from 2025.1.3 to 2025.1.4 2026-03-16 08:44:13 -05:00
Robert Winch b238632fdc Bump org.springframework:spring-framework-bom from 7.0.5 to 7.0.6 2026-03-16 08:44:07 -05:00
Robert Winch 3f05f4d30c Merge branch '6.5.x' into 7.0.x 2026-03-16 08:42:55 -05:00
Robert Winch cdd4b36d37 Update Antora UI Spring to v0.4.26 2026-03-16 08:26:19 -05:00
Robert Winch 7672f76fde Bump io.projectreactor:reactor-bom from 2024.0.15 to 2024.0.16 2026-03-16 08:26:12 -05:00
Robert Winch 3db4999da4 Bump org.apache.maven:maven-resolver-provider from 3.9.13 to 3.9.14 2026-03-16 08:26:04 -05:00
dependabot[bot] 3813627a15 Bump org.springframework:spring-framework-bom from 7.0.5 to 7.0.6
Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 7.0.5 to 7.0.6.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v7.0.5...v7.0.6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-16 03:13:40 +00:00
dependabot[bot] 9616e6b640 Bump org.springframework.data:spring-data-bom from 2025.1.3 to 2025.1.4
Bumps [org.springframework.data:spring-data-bom](https://github.com/spring-projects/spring-data-bom) from 2025.1.3 to 2025.1.4.
- [Release notes](https://github.com/spring-projects/spring-data-bom/releases)
- [Commits](https://github.com/spring-projects/spring-data-bom/compare/2025.1.3...2025.1.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-16 03:13:21 +00:00
dependabot[bot] a708d2f61b Bump org.springframework:spring-framework-bom from 6.2.16 to 6.2.17
Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 6.2.16 to 6.2.17.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v6.2.16...v6.2.17)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-16 03:07:46 +00:00
Ziqin Wang ae827b6e1b Fix Jackson 3 deserializer for AuthenticationExtensionsClientOutputs
The deserializer is updated to properly ignore unknown extensions.

This fix addresses the WebAuthn authentication failure appeared when
using FIDO2 security keys on Safari.

Closes gh-18643

Signed-off-by: Ziqin Wang <ziqin@wangziqin.net>
2026-03-15 15:34:34 +08:00
Ziqin Wang 65bf54d842 Test Jackson 3 deserializer with unknown primitive WebAuthn ext
Signed-off-by: Ziqin Wang <ziqin@wangziqin.net>
2026-03-15 15:34:24 +08:00
Ziqin Wang 7f75fd611e Test Jackson 3 deserializer with unknown obj/arr WebAuthn ext
Signed-off-by: Ziqin Wang <ziqin@wangziqin.net>
2026-03-15 15:34:13 +08:00
Ziqin Wang a013bfaaec Merge branch 'gh-18643-6.5.x' into gh-18643-7.0.x 2026-03-15 15:25:04 +08:00
Ziqin Wang e726c05e76 Fix Jackson 2 deserializer for AuthenticationExtensionsClientOutputs
The deserializer is updated to properly ignore unknown extensions.

Closes gh-18643

Signed-off-by: Ziqin Wang <ziqin@wangziqin.net>
2026-03-15 15:04:14 +08:00
Ziqin Wang a7039fb3e6 Test Jackson 2 deserializer with unknown primitive WebAuthn ext
Signed-off-by: Ziqin Wang <ziqin@wangziqin.net>
2026-03-15 15:03:28 +08:00
Ziqin Wang 88ea668f47 Test Jackson 2 deserializer with unknown obj/arr WebAuthn ext
Signed-off-by: Ziqin Wang <ziqin@wangziqin.net>
2026-03-15 15:03:17 +08:00
github-actions[bot] 2c1c50ddca Update Antora Spring UI to v0.4.26 2026-03-13 17:45:06 +00:00
github-actions[bot] 03a5de1955 Update Antora Spring UI to v0.4.26 2026-03-13 17:45:05 +00:00
dependabot[bot] 91167adaa8 Bump org.apache.maven:maven-resolver-provider from 3.9.13 to 3.9.14
Bumps org.apache.maven:maven-resolver-provider from 3.9.13 to 3.9.14.

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-13 03:09:24 +00:00
dependabot[bot] 06cbea383e Bump org.apache.maven:maven-resolver-provider from 3.9.13 to 3.9.14
Bumps org.apache.maven:maven-resolver-provider from 3.9.13 to 3.9.14.

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-13 03:07:50 +00:00
Andrey Litvitski e250236279 Read relayState from authenticationRequest
Closes gh-18243

Signed-off-by: Andrey Litvitski <andrey1010102008@gmail.com>
2026-03-12 10:30:11 -06:00
Josh Cummings 5b4fc73878 Merge branch '6.5.x' into 7.0.x 2026-03-11 16:46:51 -06:00
Josh Cummings ef76ba040d Require non-null authenticationRequest
Closes gh-18880

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-03-11 16:45:23 -06:00
dependabot[bot] 763a8d4691 Bump io.projectreactor:reactor-bom from 2025.0.3 to 2025.0.4
Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2025.0.3 to 2025.0.4.
- [Release notes](https://github.com/reactor/reactor/releases)
- [Commits](https://github.com/reactor/reactor/compare/2025.0.3...2025.0.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-11 03:08:26 +00:00
dependabot[bot] d69af716c8 Bump io.projectreactor:reactor-bom from 2024.0.15 to 2024.0.16
Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2024.0.15 to 2024.0.16.
- [Release notes](https://github.com/reactor/reactor/releases)
- [Commits](https://github.com/reactor/reactor/compare/2024.0.15...2024.0.16)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-11 03:06:57 +00:00
Joe Grandja 1906075b0c OAuth2DeviceVerificationEndpointFilter is applied after AuthorizationFilter
Closes gh-18873
2026-03-10 15:32:24 -04:00
Ronny Perinke e8e0da1ec6 Add Null Guard for Setting ReactiveUserDetailsPasswordService
This use case specifically arises when using `ReactiveUserDetailsService`
without `ReactiveUserDetailsPasswordService`.

Closes gh-17986

Signed-off-by: Ronny Perinke <23166289+sephiroth-j@users.noreply.github.com>
2026-03-09 17:12:59 -06:00
Rob Winch 2f81d2d99e Merge Fix spring-security-webauthn dependency in passkeys documentation 2026-03-09 15:39:54 -04:00
Rob Winch 6cf4a5eed9 Merge Fix CookieRequestCache parameters 2026-03-09 15:30:46 -04:00
Robert Winch 26937bf06c Remove unnecessary webauthn4j dependency 2026-03-09 14:25:08 -05:00
Rob Winch 7e37aa2b75 Merge Fix CookieRequestCache parameters 2026-03-09 15:25:05 -04:00
Tran Ngoc Nhan 8e8e1a80a9 Add Passkeys webauthn in example
Signed-off-by: Tran Ngoc Nhan <ngocnhan.tran1996@gmail.com>
2026-03-09 14:23:14 -05:00
Robert Winch 3110c9074f Merge Fix CookieRequestCache parameters 2026-03-09 14:11:27 -05:00
Vishnutheep B 07bfe371b4 Fix CookieRequestCache parameters
Previously the parameters were not restored.

This commit ensures the parameters are restored.

Closes gh-18204

Signed-off-by: Vishnutheep B <vishnutheep@gmail.com>
2026-03-09 14:10:30 -05:00
Robert Winch c29775a79e Bump @antora/collector-extension from 1.0.2 to 1.0.3 in /docs 2026-03-09 09:58:42 -05:00
Robert Winch bc96812461 Bump org.apache.maven:maven-resolver-provider from 3.9.12 to 3.9.13 2026-03-09 09:58:37 -05:00
Robert Winch 7d9c2ce9d7 Merge branch '6.5.x' into 7.0.x 2026-03-09 09:58:22 -05:00
Robert Winch e12edf43f2 Bump @antora/collector-extension from 1.0.2 to 1.0.3 in /docs 2026-03-09 09:58:04 -05:00
dependabot[bot] ca6dccf8d7 Bump org.apache.maven:maven-resolver-provider from 3.9.12 to 3.9.13
Bumps org.apache.maven:maven-resolver-provider from 3.9.12 to 3.9.13.

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-09 03:13:40 +00:00
dependabot[bot] a499e56b9b Bump org.apache.maven:maven-resolver-provider from 3.9.12 to 3.9.13
Bumps org.apache.maven:maven-resolver-provider from 3.9.12 to 3.9.13.

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-09 03:09:41 +00:00
dependabot[bot] 8c3f6ea0d4 Bump @antora/collector-extension from 1.0.2 to 1.0.3 in /docs
---
updated-dependencies:
- dependency-name: "@antora/collector-extension"
  dependency-version: 1.0.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-06 00:37:07 +00:00
dependabot[bot] 40682415ba Bump @antora/collector-extension from 1.0.2 to 1.0.3 in /docs
---
updated-dependencies:
- dependency-name: "@antora/collector-extension"
  dependency-version: 1.0.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-06 00:37:05 +00:00
Josh Cummings 9893048ec9 Merge branch '6.5.x' into 7.0.x 2026-03-03 18:51:53 -07:00
Josh Cummings e17d85e460 Add IDE Setup Documentation
Issue gh-17833

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-03-03 18:51:32 -07:00
Andrey Litvitski 4f97217f68 Refine upgradeEncoding condition in DaoAuthenticationProvider
After adding jspecify support in the module that contains the
DaoAuthenticationProvider class, we actually changed the contract logic,
which is a good thing, and this commit fixes it.

Closes: gh-18781

Signed-off-by: Andrey Litvitski <andrey1010102008@gmail.com>
2026-03-03 18:18:13 -07:00
Josh Cummings fdaa883fb7 Merge remote-tracking branch 'origin/6.5.x' into 7.0.x 2026-03-03 18:17:08 -07:00
dependabot[bot] f12036db05 Bump actions/upload-artifact from 6.0.0 to 7.0.0
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6.0.0 to 7.0.0.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/b7c566a772e6b6bfb58ed0dc250532a479d7789f...bbbca2ddaa5d8feaa63e36b76fdaad77386f024f)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-03 18:16:39 -07:00
dependabot[bot] fbd9880a33 Bump actions/upload-artifact from 6.0.0 to 7.0.0
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6.0.0 to 7.0.0.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/b7c566a772e6b6bfb58ed0dc250532a479d7789f...bbbca2ddaa5d8feaa63e36b76fdaad77386f024f)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-03 17:48:29 -07:00
Josh Cummings 5e38c2aa88 Merge remote-tracking branch 'origin/6.5.x' into 7.0.x 2026-03-03 17:47:40 -07:00
dependabot[bot] 7b5c502a97 Bump org.hibernate.orm:hibernate-core from 6.6.43.Final to 6.6.44.Final
Bumps [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm) from 6.6.43.Final to 6.6.44.Final.
- [Release notes](https://github.com/hibernate/hibernate-orm/releases)
- [Changelog](https://github.com/hibernate/hibernate-orm/blob/6.6.44/changelog.txt)
- [Commits](https://github.com/hibernate/hibernate-orm/compare/6.6.43...6.6.44)

---
updated-dependencies:
- dependency-name: org.hibernate.orm:hibernate-core
  dependency-version: 6.6.44.Final
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-03 17:47:07 -07:00
Andrey Litvitski 57434fc597 Update RestTemplateBuilder usage in opaque-token.adoc
We just now use a new form instead of the deprecate one.

Signed-off-by: Andrey Litvitski <andrey1010102008@gmail.com>
2026-03-03 16:48:22 -07:00
Josh Cummings 20a7f96062 Merge branch '6.5.x' into 7.0.x 2026-03-03 16:44:12 -07:00
HaiYan 706b059ea8 Update logout.adoc
Directives should be Directive

Signed-off-by: HaiYan <haiyan_qi@hotmail.com>
2026-03-03 16:43:18 -07:00
dependabot[bot] 7c49e0b457 Bump com.webauthn4j:webauthn4j-core
Bumps [com.webauthn4j:webauthn4j-core](https://github.com/webauthn4j/webauthn4j) from 0.31.0.RELEASE to 0.31.1.RELEASE.
- [Release notes](https://github.com/webauthn4j/webauthn4j/releases)
- [Commits](https://github.com/webauthn4j/webauthn4j/compare/0.31.0.RELEASE...0.31.1.RELEASE)

---
updated-dependencies:
- dependency-name: com.webauthn4j:webauthn4j-core
  dependency-version: 0.31.1.RELEASE
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-03 15:52:30 -07:00
Rob Winch 04b270a0a3 Merge Fix Flaky Crypto Tests
Forward merge gh-18841
2026-03-03 16:02:33 -06:00
Rob Winch ea3b112bea Fix Flaky Crypto Tests 2026-03-03 15:58:17 -06:00
Robert Winch 17776e4738 Merge Fix Flaky Crypto Tests 2026-03-03 15:26:53 -06:00
Robert Winch 1261c229a3 Fix Flaky Crypto Tests
Previously the RsaSecretEncryptorTests were flaky because the assumed that a BadPaddigException would be thrown
when using things like different salt. However, given that the tests had random inputs (e.g. keys) there is the
possibility that, despite the fact that it can never be properly decrypted, the final bytes look like a valid
encrypted value.

This updates the tests to ensure that decrypt either throws an Exception or is not equal to the original
plaintext.
2026-03-03 14:52:28 -06:00
Rob Winch 9ce2d76508 Merge HttpMessageConverterAuthenticationSuccessHandler Supports Jackson 3 2026-03-02 11:48:14 -06:00
Robert Winch fb84e24893 HttpMessageConverterAuthenticationSuccessHandler Supports Jackson 3
Closes gh-18804
2026-03-02 11:31:52 -06:00
Josh Cummings 1575610d49 Add Tests
Issue gh-18486

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-02-26 17:10:55 -07:00
Michael Lück 3a14745d92 Delegate calls of hasAuthority to AuthorizationManager#hasAuthority
Closes gh-18486

Signed-off-by: Michael Lück <michael@lueckonline.net>
2026-02-26 17:10:55 -07:00
Josh Cummings c29af014f4 Merge remote-tracking branch 'origin/6.5.x' into 7.0.x 2026-02-26 17:10:16 -07:00
Josh Cummings 4501ae7d1c Update Reactive Resource Server startup exceptations
Issue gh-16708

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-02-26 16:56:22 -07:00
Josh Cummings 48112d3d74 Polish Resource Server startup expectations
Issue gh-16708

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-02-26 16:56:22 -07:00
[CLOUD4] 한현 b8735abb63 Clarify Resource Server startup expectations
Clarify that Spring Boot defers OIDC discovery by default.

Closes gh-16708

Signed-off-by: [CLOUD4] 한현 <gusgus1467@naver.com>
2026-02-26 16:56:22 -07:00
Tran Ngoc Nhan 7c3c8bbdcb Update Remember-Me example
Closes gh-18639

Signed-off-by: Tran Ngoc Nhan <ngocnhan.tran1996@gmail.com>
2026-02-26 15:28:32 -07:00
Josh Cummings 731848d5d3 Merge branch '6.5.x' into 7.0.x 2026-02-26 15:09:45 -07:00
Guillaume Husta 68a02ff176 Update Link to CRSF Docs in FAQ
Signed-off-by: Guillaume Husta <guillaume.husta@gmail.com>
2026-02-26 14:47:21 -07:00
Menashe Eliezer ee97c83042 Update request-matcher schema and XML tests to use path
Closes gh-18641

Signed-off-by: Menashe Eliezer <menashe.eliezer@gmail.com>
2026-02-26 14:42:09 -07:00
dependabot[bot] ba12f5e6d0 Bump org-apache-maven-resolver from 1.9.26 to 1.9.27
Bumps `org-apache-maven-resolver` from 1.9.26 to 1.9.27.

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

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

Updates `org.apache.maven.resolver:maven-resolver-transport-http` from 1.9.26 to 1.9.27

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-26 14:38:51 -07:00
dependabot[bot] f37a706d62 Bump org-apache-maven-resolver from 1.9.26 to 1.9.27
Bumps `org-apache-maven-resolver` from 1.9.26 to 1.9.27.

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

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

Updates `org.apache.maven.resolver:maven-resolver-transport-http` from 1.9.26 to 1.9.27

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-26 14:38:30 -07:00
Rob Winch b48967eebc Merge Add Missing OnCommitedResponseWrapper Header Overrides
Add Missing OnCommitedResponseWrapper Header Overrides
2026-02-24 20:16:39 -06:00
Rob Winch 522c48b3b5 Merge Add Missing OnCommitedResponseWrapper Header Overrides
Add Missing OnCommitedResponseWrapper Header Overrides
2026-02-24 20:16:24 -06:00
Robert Winch 6898de8003 Merge Add Missing OnCommitedResponseWrapper Header Overrides 2026-02-24 19:49:38 -06:00
Robert Winch 1dae9aa459 Add Missing OnCommitedResponseWrapper Header Overrides
Spring Security's `OnCommitedResponseWrapper` does not override the `setHeader`, `setIntHeader`, `addIntHeader`
methods. This means that if the `Content-Length` response header is specified using any of those methods then
the response body length is not tracked and can be committed before the response headers are written.

Spring Security should override the missing methods and track `Content-Length` as is already done for `addHeader`.

This issue is the underlying problem for spring-projects/spring-framework#36381

Closes gh-18797
2026-02-24 19:46:29 -06:00
Josh Cummings 73ee893d98 Merge remote-tracking branch 'origin/6.5.x' into 7.0.x 2026-02-24 17:10:14 -07:00
Josh Cummings bec25edeb0 Merge pull request #18566 from Hann244/docs/gh-16530-jsp-method-attribute
Clarify need for method attribute in JSP authorize tag
2026-02-24 17:08:14 -07:00
Josh Cummings 4d43edfb20 Polish Documentation
- Combined explanation of method attribute with usage recommendations
- Used one sentence per line format

Issue gh-16530

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-02-24 14:24:11 -07:00
onhann 9f9699f8a5 Clarify need for method attribute in JSP authorize tag
Closes gh-16530

This aligns the JSP documentation with the changes made in gh-16529.
Added a NOTE to clarify that the method attribute is required when the underlying RequestMatcher is method-specific.

Signed-off-by: onhann <gusgus1467@naver.com>
2026-02-24 14:24:11 -07:00
Robert Winch 311235f39e Document Keberose Dependency Coordinates
Closes gh-18773
2026-02-23 11:32:37 -06:00
Robert Winch fec988c82d Add Kerberos Migration Section
This links to the updated dependency coordinates

Issue gh-18773

Signed-off-by: Robert Winch <362503+rwinch@users.noreply.github.com>
2026-02-23 11:29:50 -06:00
busoco-sjb 17b434c1c1 Document the change in dependency coordinates with Spring Security 7
Signed-off-by: busoco-sjb <169069865+busoco-sjb@users.noreply.github.com>
2026-02-23 11:21:59 -06:00
Rob Winch 0bb65411be Merge pull request Fix GrantedAuthority.authority null in AuthoritiesAuthorizationManager
Fix GrantedAuthority.authority null in AuthoritiesAuthorizationManager
2026-02-23 11:17:06 -06:00
Rob Winch d29c984881 Merge pull request #18544 from Khyojae/gh-18543
Fix GrantedAuthority.authority null in AuthoritiesAuthorizationManager
2026-02-23 11:16:42 -06:00
Robert Winch 151bcf3b0b Merge Fix: Handle null authority string in AuthoritiesAuthorizationManager into 7.0.x 2026-02-23 10:53:40 -06:00
Robert Winch 1116241ee3 Fix Checks for NullPointerException in AuthoritiesAuthorizationManager
- Fix checkstyle
- Fix the test to use Collection that throws NullPointerException on .contains(null) to replicate the reported issue

Closes gh-18544

Signed-off-by: Robert Winch <362503+rwinch@users.noreply.github.com>
2026-02-23 10:47:11 -06:00
Khyojae d87dc9ae57 Fix: Handle null authority string in AuthoritiesAuthorizationManager
This prevents NPE when GrantedAuthority.getAuthority() returns null. Closes gh-18543

Signed-off-by: Khyojae <khjae201@gmail.com>
2026-02-23 09:30:28 -06:00
Robert Winch 2eb948d9b5 Ensure tests clear AuthorizationServerContextHolder
Closes gh-18768
2026-02-23 08:17:02 -06:00
Robert Winch f2aef5168c Merge branch '6.5.x' into 7.0.x 2026-02-23 08:13:38 -06:00
dependabot[bot] ac556a45f9 Bump org.hibernate.orm:hibernate-core from 6.6.42.Final to 6.6.43.Final
Bumps [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm) from 6.6.42.Final to 6.6.43.Final.
- [Release notes](https://github.com/hibernate/hibernate-orm/releases)
- [Changelog](https://github.com/hibernate/hibernate-orm/blob/6.6.43/changelog.txt)
- [Commits](https://github.com/hibernate/hibernate-orm/compare/6.6.42...6.6.43)

---
updated-dependencies:
- dependency-name: org.hibernate.orm:hibernate-core
  dependency-version: 6.6.43.Final
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-23 08:12:23 -06:00
dependabot[bot] c8731a8dc0 Bump com.fasterxml.jackson:jackson-bom from 2.18.5 to 2.18.6
Bumps [com.fasterxml.jackson:jackson-bom](https://github.com/FasterXML/jackson-bom) from 2.18.5 to 2.18.6.
- [Commits](https://github.com/FasterXML/jackson-bom/compare/jackson-bom-2.18.5...jackson-bom-2.18.6)

---
updated-dependencies:
- dependency-name: com.fasterxml.jackson:jackson-bom
  dependency-version: 2.18.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-23 08:12:09 -06:00
Robert Winch a4a6e9124c Merge branch '6.5.x' into 7.0.x 2026-02-19 13:30:13 -06:00
Robert Winch b21159f453 Bump org.junit:junit-bom from 6.0.2 to 6.0.3 2026-02-19 13:29:42 -06:00
Robert Winch 6f7c8cb352 Bump org-apache-maven-resolver from 1.9.25 to 1.9.26 2026-02-19 13:29:36 -06:00
Robert Winch 5973a66bb1 Bump ch.qos.logback:logback-classic from 1.5.29 to 1.5.32 2026-02-19 13:29:30 -06:00
Robert Winch 3e3eeda560 Bump ch.qos.logback:logback-classic from 1.5.29 to 1.5.32 2026-02-19 13:28:49 -06:00
dependabot[bot] e2486a2590 Bump org.springframework:spring-framework-bom from 7.0.4 to 7.0.5
Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 7.0.4 to 7.0.5.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v7.0.4...v7.0.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-19 10:58:10 -06:00
dependabot[bot] 3c55f057b1 Bump ch.qos.logback:logback-classic from 1.5.29 to 1.5.32
Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.5.29 to 1.5.32.
- [Release notes](https://github.com/qos-ch/logback/releases)
- [Commits](https://github.com/qos-ch/logback/compare/v_1.5.29...v_1.5.32)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-17 03:10:25 +00:00
dependabot[bot] 6d2a414022 Bump org-apache-maven-resolver from 1.9.25 to 1.9.26
Bumps `org-apache-maven-resolver` from 1.9.25 to 1.9.26.

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

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

Updates `org.apache.maven.resolver:maven-resolver-transport-http` from 1.9.25 to 1.9.26

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-17 03:09:56 +00:00
dependabot[bot] 58df50c3a3 Bump org-apache-maven-resolver from 1.9.25 to 1.9.26
Bumps `org-apache-maven-resolver` from 1.9.25 to 1.9.26.

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

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

Updates `org.apache.maven.resolver:maven-resolver-transport-http` from 1.9.25 to 1.9.26

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-17 03:06:35 +00:00
dependabot[bot] 79156b2387 Bump ch.qos.logback:logback-classic from 1.5.29 to 1.5.32
Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.5.29 to 1.5.32.
- [Release notes](https://github.com/qos-ch/logback/releases)
- [Commits](https://github.com/qos-ch/logback/compare/v_1.5.29...v_1.5.32)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-17 03:06:15 +00:00
dependabot[bot] 3abb69d5a9 Bump org.junit:junit-bom from 6.0.2 to 6.0.3
Bumps [org.junit:junit-bom](https://github.com/junit-team/junit-framework) from 6.0.2 to 6.0.3.
- [Release notes](https://github.com/junit-team/junit-framework/releases)
- [Commits](https://github.com/junit-team/junit-framework/compare/r6.0.2...r6.0.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-16 03:15:44 +00:00
github-actions[bot] 6c2b2a7611 Next development version 2026-02-13 18:24:26 +00:00
github-actions[bot] 0fab34f359 Release 6.5.8 2026-02-13 17:54:05 +00:00
github-actions[bot] c0da8b390b Next development version 2026-02-13 15:57:31 +00:00
dependabot[bot] 08e5b375ac Bump io.projectreactor:reactor-bom from 2024.0.14 to 2024.0.15
Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2024.0.14 to 2024.0.15.
- [Release notes](https://github.com/reactor/reactor/releases)
- [Commits](https://github.com/reactor/reactor/compare/2024.0.14...2024.0.15)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-13 06:41:17 -05:00
dependabot[bot] f9c32afb6f Bump org.springframework:spring-framework-bom from 6.2.15 to 6.2.16
Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 6.2.15 to 6.2.16.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v6.2.15...v6.2.16)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-13 06:33:08 -05:00
dependabot[bot] 3d61276a1a Bump io.spring.gradle:spring-security-release-plugin
Bumps [io.spring.gradle:spring-security-release-plugin](https://github.com/spring-io/spring-security-release-tools) from 1.0.13 to 1.0.14.
- [Release notes](https://github.com/spring-io/spring-security-release-tools/releases)
- [Changelog](https://github.com/spring-io/spring-security-release-tools/blob/main/RELEASE.adoc)
- [Commits](https://github.com/spring-io/spring-security-release-tools/compare/v1.0.13...v1.0.14)

---
updated-dependencies:
- dependency-name: io.spring.gradle:spring-security-release-plugin
  dependency-version: 1.0.14
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-12 10:31:30 -07:00
69 changed files with 1520 additions and 230 deletions
@@ -17,7 +17,7 @@ permissions:
jobs:
build:
name: Build
uses: spring-io/spring-security-release-tools/.github/workflows/build.yml@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14
uses: spring-io/spring-security-release-tools/.github/workflows/build.yml@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15
strategy:
matrix:
os: [ ubuntu-latest, windows-latest ]
@@ -30,7 +30,7 @@ jobs:
deploy-artifacts:
name: Deploy Artifacts
needs: [ build]
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-artifacts.yml@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-artifacts.yml@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15
with:
should-deploy-artifacts: ${{ needs.build.outputs.should-deploy-artifacts }}
default-publish-milestones-central: true
@@ -38,14 +38,14 @@ jobs:
deploy-schema:
name: Deploy Schema
needs: [ build ]
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-schema.yml@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-schema.yml@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15
with:
should-deploy-schema: ${{ needs.build.outputs.should-deploy-artifacts }}
secrets: inherit
perform-release:
name: Perform Release
needs: [ deploy-artifacts, deploy-schema ]
uses: spring-io/spring-security-release-tools/.github/workflows/perform-release.yml@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14
uses: spring-io/spring-security-release-tools/.github/workflows/perform-release.yml@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15
with:
should-perform-release: ${{ needs.deploy-artifacts.outputs.artifacts-deployed }}
project-version: ${{ needs.deploy-artifacts.outputs.project-version }}
@@ -61,6 +61,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Send Notification
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15
with:
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
+76
View File
@@ -0,0 +1,76 @@
name: Defer Issues
on:
workflow_dispatch:
permissions:
contents: read
jobs:
defer-issues:
name: Defer Issues
runs-on: ubuntu-latest
if: github.repository_owner == 'spring-projects'
permissions:
issues: write
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Compute Version
id: compute-version
uses: spring-io/spring-release-actions/compute-version@0.0.3
- name: Get Today's Release Version
id: todays-release
uses: spring-io/spring-release-actions/get-todays-release-version@0.0.3
with:
snapshot-version: ${{ steps.compute-version.outputs.version }}
milestone-repository: ${{ github.repository }}
milestone-token: ${{ secrets.GITHUB_TOKEN }}
- name: Compute Next Version
id: next-version
uses: spring-io/spring-release-actions/compute-next-version@0.0.3
with:
version: ${{ steps.todays-release.outputs.release-version }}
- name: Schedule Next Milestone
uses: spring-io/spring-release-actions/schedule-milestone@0.0.3
with:
version: ${{ steps.next-version.outputs.version }}
version-date: ${{ steps.next-version.outputs.version-date }}
repository: ${{ github.repository }}
token: ${{ secrets.GITHUB_TOKEN }}
- name: Move Open Issues to Next Milestone
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CURRENT_MILESTONE: ${{ steps.todays-release.outputs.release-version }}
NEXT_MILESTONE: ${{ steps.next-version.outputs.version }}
run: |
current_milestone_number=$(gh api repos/${{ github.repository }}/milestones \
--jq ".[] | select(.title == \"$CURRENT_MILESTONE\") | .number")
if [ -z "$current_milestone_number" ]; then
echo "No milestone found for $CURRENT_MILESTONE"
exit 0
fi
next_milestone_number=$(gh api repos/${{ github.repository }}/milestones \
--jq ".[] | select(.title == \"$NEXT_MILESTONE\") | .number")
if [ -z "$next_milestone_number" ]; then
echo "No milestone found for $NEXT_MILESTONE"
exit 1
fi
echo "Moving open issues from milestone '$CURRENT_MILESTONE' (#$current_milestone_number) to '$NEXT_MILESTONE' (#$next_milestone_number)"
page=1
while true; do
issues=$(gh api "repos/${{ github.repository }}/issues?milestone=$current_milestone_number&state=open&per_page=100&page=$page" \
--jq '.[].number')
if [ -z "$issues" ]; then
break
fi
for issue in $issues; do
echo "Moving issue/PR #$issue to milestone $NEXT_MILESTONE"
gh api repos/${{ github.repository }}/issues/$issue \
--method PATCH \
--field milestone=$next_milestone_number \
--silent
done
page=$((page + 1))
done
echo "Done."
+1 -1
View File
@@ -34,7 +34,7 @@ jobs:
run: ./gradlew -PbuildSrc.skipTests=true :spring-security-docs:antora
- name: Upload Docs
id: upload
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: docs
path: docs/build/site
+21
View File
@@ -68,6 +68,27 @@ The https://github.com/spring-projects/spring-security/tree/docs-build[playbook
Discover more commands with `./gradlew tasks`.
=== IDE setup (IntelliJ)
No special steps are needed to open Spring Security in IntelliJ.
=== IDE setup (Eclipse and VS Code)
To work in Eclipse or VS Code, first generate Eclipse metadata so you can import the project into Eclipse or VS Code:
[indent=0]
----
./gradlew cleanEclipse eclipse
----
If you have not built the project yet, run `./gradlew publishToMavenLocal` first so dependencies are resolved.
*VS Code:* Open the repository root as a folder. The repository includes `.vscode/settings.json` which disables automatic Gradle import so that the generated Eclipse metadata (`.classpath`, `.project`) is used. Do not use the Gradle for Java extension to import the project.
*Eclipse:* File → Import → General → Existing Projects into Workspace, then select the repository root.
The build uses a custom Eclipse plugin to work around Gradle dependency cycles that confuse IDE metadata generation. You may see Eclipse warnings about `xml-apis` from some test dependencies; those are excluded in the build and can be ignored.
== Getting Support
Check out the https://stackoverflow.com/questions/tagged/spring-security[Spring Security tags on Stack Overflow].
https://spring.io/support[Commercial support] is available too.
@@ -40,11 +40,11 @@ import org.springframework.security.oauth2.server.authorization.settings.Authori
import org.springframework.security.oauth2.server.authorization.web.OAuth2DeviceVerificationEndpointFilter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2DeviceAuthorizationConsentAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2DeviceVerificationAuthenticationConverter;
import org.springframework.security.web.access.intercept.AuthorizationFilter;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.DelegatingAuthenticationConverter;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
@@ -279,8 +279,7 @@ public final class OAuth2DeviceVerificationEndpointConfigurer extends AbstractOA
if (StringUtils.hasText(this.consentPage)) {
deviceVerificationEndpointFilter.setConsentPage(this.consentPage);
}
builder.addFilterBefore(postProcess(deviceVerificationEndpointFilter),
AbstractPreAuthenticatedProcessingFilter.class);
builder.addFilterAfter(postProcess(deviceVerificationEndpointFilter), AuthorizationFilter.class);
}
@Override
@@ -255,7 +255,9 @@ class ServerHttpSecurityConfiguration {
if (this.passwordEncoder != null) {
manager.setPasswordEncoder(this.passwordEncoder);
}
manager.setUserDetailsPasswordService(this.userDetailsPasswordService);
if (this.userDetailsPasswordService != null) {
manager.setUserDetailsPasswordService(this.userDetailsPasswordService);
}
manager.setCompromisedPasswordChecker(this.compromisedPasswordChecker);
return this.postProcessor.postProcess(manager);
}
@@ -12,8 +12,8 @@ base64 =
## Whether a string should be base64 encoded
attribute base64 {xsd:boolean}
request-matcher =
## Defines the strategy use for matching incoming requests. Currently the options are 'mvc' (for Spring MVC matcher), 'ant' (for ant path patterns), 'regex' for regular expressions and 'ciRegex' for case-insensitive regular expressions.
attribute request-matcher {"mvc" | "ant" | "regex" | "ciRegex"}
## Defines the strategy use for matching incoming requests. Currently the options are 'path' (for PathPatternRequestMatcher), 'regex' for regular expressions and 'ciRegex' for case-insensitive regular expressions.
attribute request-matcher {"path" | "regex" | "ciRegex"}
port =
## Specifies an IP port number. Used to configure an embedded LDAP server, for example.
attribute port { xsd:nonNegativeInteger }
@@ -27,15 +27,14 @@
<xs:attributeGroup name="request-matcher">
<xs:attribute name="request-matcher" use="required">
<xs:annotation>
<xs:documentation>Defines the strategy use for matching incoming requests. Currently the options are 'mvc'
(for Spring MVC matcher), 'ant' (for ant path patterns), 'regex' for regular expressions
and 'ciRegex' for case-insensitive regular expressions.
<xs:documentation>Defines the strategy use for matching incoming requests. Currently the options are 'path'
(for PathPatternRequestMatcher), 'regex' for regular expressions and 'ciRegex' for
case-insensitive regular expressions.
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:enumeration value="mvc"/>
<xs:enumeration value="ant"/>
<xs:enumeration value="path"/>
<xs:enumeration value="regex"/>
<xs:enumeration value="ciRegex"/>
</xs:restriction>
@@ -1306,15 +1305,14 @@
</xs:attribute>
<xs:attribute name="request-matcher">
<xs:annotation>
<xs:documentation>Defines the strategy use for matching incoming requests. Currently the options are 'mvc'
(for Spring MVC matcher), 'ant' (for ant path patterns), 'regex' for regular expressions
and 'ciRegex' for case-insensitive regular expressions.
<xs:documentation>Defines the strategy use for matching incoming requests. Currently the options are 'path'
(for PathPatternRequestMatcher), 'regex' for regular expressions and 'ciRegex' for
case-insensitive regular expressions.
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:enumeration value="mvc"/>
<xs:enumeration value="ant"/>
<xs:enumeration value="path"/>
<xs:enumeration value="regex"/>
<xs:enumeration value="ciRegex"/>
</xs:restriction>
@@ -2474,15 +2472,14 @@
<xs:attributeGroup name="filter-chain-map.attlist">
<xs:attribute name="request-matcher">
<xs:annotation>
<xs:documentation>Defines the strategy use for matching incoming requests. Currently the options are 'mvc'
(for Spring MVC matcher), 'ant' (for ant path patterns), 'regex' for regular expressions
and 'ciRegex' for case-insensitive regular expressions.
<xs:documentation>Defines the strategy use for matching incoming requests. Currently the options are 'path'
(for PathPatternRequestMatcher), 'regex' for regular expressions and 'ciRegex' for
case-insensitive regular expressions.
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:enumeration value="mvc"/>
<xs:enumeration value="ant"/>
<xs:enumeration value="path"/>
<xs:enumeration value="regex"/>
<xs:enumeration value="ciRegex"/>
</xs:restriction>
@@ -2580,15 +2577,14 @@
</xs:attribute>
<xs:attribute name="request-matcher">
<xs:annotation>
<xs:documentation>Defines the strategy use for matching incoming requests. Currently the options are 'mvc'
(for Spring MVC matcher), 'ant' (for ant path patterns), 'regex' for regular expressions
and 'ciRegex' for case-insensitive regular expressions.
<xs:documentation>Defines the strategy use for matching incoming requests. Currently the options are 'path'
(for PathPatternRequestMatcher), 'regex' for regular expressions and 'ciRegex' for
case-insensitive regular expressions.
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:enumeration value="mvc"/>
<xs:enumeration value="ant"/>
<xs:enumeration value="path"/>
<xs:enumeration value="regex"/>
<xs:enumeration value="ciRegex"/>
</xs:restriction>
@@ -359,7 +359,7 @@ public class OAuth2DeviceCodeGrantTests {
}
@Test
public void requestWhenDeviceAuthorizationConsentRequestUnauthenticatedThenBadRequest() throws Exception {
public void requestWhenDeviceAuthorizationConsentRequestUnauthenticatedThenUnauthorized() throws Exception {
this.spring.register(AuthorizationServerConfiguration.class).autowire();
// @formatter:off
@@ -392,7 +392,7 @@ public class OAuth2DeviceCodeGrantTests {
// @formatter:off
this.mvc.perform(post(DEFAULT_DEVICE_VERIFICATION_ENDPOINT_URI)
.params(parameters))
.andExpect(status().isBadRequest());
.andExpect(status().isUnauthorized());
// @formatter:on
}
@@ -107,7 +107,7 @@ public class ServerHttpSecurityConfigurationTests {
}
@Test
public void loadConfigWhenReactiveUserDetailsServiceConfiguredThenServerHttpSecurityExists() {
public void loadConfigWhenReactiveUserAuthenticationServiceConfiguredThenServerHttpSecurityExists() {
this.spring
.register(ServerHttpSecurityConfiguration.class, ReactiveAuthenticationTestConfiguration.class,
WebFluxSecurityConfiguration.class)
@@ -116,6 +116,16 @@ public class ServerHttpSecurityConfigurationTests {
assertThat(serverHttpSecurity).isNotNull();
}
@Test
public void loadConfigWhenOnlyReactiveUserDetailsServiceConfiguredThenServerHttpSecurityExists() {
this.spring
.register(ServerHttpSecurityConfiguration.class, ReactiveUserDetailsServiceOnlyTestConfiguration.class,
WebFluxSecurityConfiguration.class)
.autowire();
ServerHttpSecurity serverHttpSecurity = this.spring.getContext().getBean(ServerHttpSecurity.class);
assertThat(serverHttpSecurity).isNotNull();
}
@Test
public void loadConfigWhenProxyingEnabledAndSubclassThenServerHttpSecurityExists() {
this.spring
@@ -581,4 +591,14 @@ public class ServerHttpSecurityConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
static class ReactiveUserDetailsServiceOnlyTestConfiguration {
@Bean
static ReactiveUserDetailsService userDetailsService() {
return (username) -> Mono.just(PasswordEncodedUser.user());
}
}
}
@@ -24,7 +24,7 @@
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<http request-matcher="ant" use-authorization-manager="false">
<http request-matcher="path" use-authorization-manager="false">
<intercept-url pattern="/path" access="denyAll" servlet-path="/spring"/>
<http-basic/>
</http>
@@ -24,7 +24,7 @@
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<http request-matcher="ant">
<http request-matcher="path">
<intercept-url pattern="/path" access="denyAll" servlet-path="/spring"/>
<http-basic/>
</http>
@@ -27,7 +27,7 @@
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<http auto-config="true" request-matcher="mvc" use-authorization-manager="false">
<http auto-config="true" request-matcher="path" use-authorization-manager="false">
<intercept-url pattern="/path" access="denyAll"/>
<http-basic/>
</http>
@@ -27,7 +27,7 @@
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<http auto-config="true" request-matcher="mvc">
<http auto-config="true" request-matcher="path">
<intercept-url pattern="/path" access="denyAll"/>
<http-basic/>
</http>
@@ -27,7 +27,7 @@
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<http auto-config="true" request-matcher="mvc" use-authorization-manager="false">
<http auto-config="true" request-matcher="path" use-authorization-manager="false">
<intercept-url pattern="/path" access="denyAll" servlet-path="/spring"/>
<http-basic/>
</http>
@@ -27,7 +27,7 @@
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<http auto-config="true" request-matcher="mvc">
<http auto-config="true" request-matcher="path">
<intercept-url pattern="/path" access="denyAll" servlet-path="/spring"/>
<http-basic/>
</http>
@@ -116,7 +116,7 @@ public abstract class SecurityExpressionRoot<T extends @Nullable Object> impleme
@Override
public final boolean hasAuthority(String authority) {
return isGranted(this.authorizationManagerFactory.hasAnyAuthority(authority));
return isGranted(this.authorizationManagerFactory.hasAuthority(authority));
}
@Override
@@ -16,6 +16,7 @@
package org.springframework.security.authentication.dao;
import java.util.Objects;
import java.util.function.Supplier;
import org.jspecify.annotations.Nullable;
@@ -43,6 +44,7 @@ import org.springframework.util.function.SingletonSupplier;
*
* @author Ben Alex
* @author Rob Winch
* @author Andrey Litvitski
*/
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
@@ -131,7 +133,8 @@ public class DaoAuthenticationProvider extends AbstractUserDetailsAuthentication
throw new CompromisedPasswordException("The provided password is compromised, please change your password");
}
String existingEncodedPassword = user.getPassword();
boolean upgradeEncoding = existingEncodedPassword != null && this.userDetailsPasswordService != null
boolean upgradeEncoding = existingEncodedPassword != null
&& !Objects.equals(this.userDetailsPasswordService, UserDetailsPasswordService.NOOP)
&& this.passwordEncoder.get().upgradeEncoding(existingEncodedPassword);
if (upgradeEncoding) {
String newPassword = this.passwordEncoder.get().encode(presentedPassword);
@@ -69,7 +69,11 @@ public final class AuthoritiesAuthorizationManager implements AuthorizationManag
private boolean isAuthorized(Authentication authentication, Collection<String> authorities) {
for (GrantedAuthority grantedAuthority : getGrantedAuthorities(authentication)) {
if (authorities.contains(grantedAuthority.getAuthority())) {
String authority = grantedAuthority.getAuthority();
if (authority == null) {
continue;
}
if (authorities.contains(authority)) {
return true;
}
}
@@ -21,6 +21,9 @@ import org.junit.jupiter.api.Test;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.AuthorizationManagerFactory;
import org.springframework.security.authorization.SingleResultAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
@@ -28,6 +31,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/**
* @author Luke Taylor
@@ -174,4 +178,148 @@ public class SecurityExpressionRootTests {
assertThat(this.root.isAuthenticated()).isTrue();
}
// gh-18486
@Test
@SuppressWarnings("unchecked")
public void hasAuthorityDelegatesToAuthorizationManagerFactoryHasAuthority() {
AuthorizationManagerFactory<Object> factory = mock(AuthorizationManagerFactory.class);
AuthorizationManager<Object> manager = SingleResultAuthorizationManager.denyAll();
given(factory.hasAuthority("CUSTOM_AUTHORITY")).willReturn(manager);
this.root.setAuthorizationManagerFactory(factory);
assertThat(this.root.hasAuthority("CUSTOM_AUTHORITY")).isFalse();
verify(factory).hasAuthority("CUSTOM_AUTHORITY");
}
// gh-18486
@Test
@SuppressWarnings("unchecked")
public void hasAnyAuthorityDelegatesToAuthorizationManagerFactoryHasAnyAuthority() {
AuthorizationManagerFactory<Object> factory = mock(AuthorizationManagerFactory.class);
AuthorizationManager<Object> manager = SingleResultAuthorizationManager.denyAll();
given(factory.hasAnyAuthority("CUSTOM_AUTHORITY")).willReturn(manager);
this.root.setAuthorizationManagerFactory(factory);
assertThat(this.root.hasAnyAuthority("CUSTOM_AUTHORITY")).isFalse();
verify(factory).hasAnyAuthority("CUSTOM_AUTHORITY");
}
// gh-18486
@Test
@SuppressWarnings("unchecked")
public void hasAllAuthoritiesDelegatesToAuthorizationManagerFactoryHasAllAuthorities() {
AuthorizationManagerFactory<Object> factory = mock(AuthorizationManagerFactory.class);
AuthorizationManager<Object> manager = SingleResultAuthorizationManager.denyAll();
given(factory.hasAllAuthorities("A", "B")).willReturn(manager);
this.root.setAuthorizationManagerFactory(factory);
assertThat(this.root.hasAllAuthorities("A", "B")).isFalse();
verify(factory).hasAllAuthorities("A", "B");
}
// gh-18486
@Test
@SuppressWarnings("unchecked")
public void hasRoleDelegatesToAuthorizationManagerFactoryHasRole() {
AuthorizationManagerFactory<Object> factory = mock(AuthorizationManagerFactory.class);
AuthorizationManager<Object> manager = SingleResultAuthorizationManager.denyAll();
given(factory.hasRole("CUSTOM_ROLE")).willReturn(manager);
this.root.setAuthorizationManagerFactory(factory);
assertThat(this.root.hasRole("CUSTOM_ROLE")).isFalse();
verify(factory).hasRole("CUSTOM_ROLE");
}
// gh-18486
@Test
@SuppressWarnings("unchecked")
public void hasAnyRoleDelegatesToAuthorizationManagerFactoryHasAnyRole() {
AuthorizationManagerFactory<Object> factory = mock(AuthorizationManagerFactory.class);
AuthorizationManager<Object> manager = SingleResultAuthorizationManager.denyAll();
given(factory.hasAnyRole("A", "B")).willReturn(manager);
this.root.setAuthorizationManagerFactory(factory);
assertThat(this.root.hasAnyRole("A", "B")).isFalse();
verify(factory).hasAnyRole("A", "B");
}
// gh-18486
@Test
@SuppressWarnings("unchecked")
public void hasAllRolesDelegatesToAuthorizationManagerFactoryHasAllRoles() {
AuthorizationManagerFactory<Object> factory = mock(AuthorizationManagerFactory.class);
AuthorizationManager<Object> manager = SingleResultAuthorizationManager.denyAll();
given(factory.hasAllRoles("A", "B")).willReturn(manager);
this.root.setAuthorizationManagerFactory(factory);
assertThat(this.root.hasAllRoles("A", "B")).isFalse();
verify(factory).hasAllRoles("A", "B");
}
// gh-18486
@Test
@SuppressWarnings("unchecked")
public void permitAllDelegatesToAuthorizationManagerFactoryPermitAll() {
AuthorizationManagerFactory<Object> factory = mock(AuthorizationManagerFactory.class);
AuthorizationManager<Object> manager = SingleResultAuthorizationManager.denyAll();
given(factory.permitAll()).willReturn(manager);
this.root.setAuthorizationManagerFactory(factory);
assertThat(this.root.permitAll()).isFalse();
verify(factory).permitAll();
}
// gh-18486
@Test
@SuppressWarnings("unchecked")
public void denyAllDelegatesToAuthorizationManagerFactoryDenyAll() {
AuthorizationManagerFactory<Object> factory = mock(AuthorizationManagerFactory.class);
AuthorizationManager<Object> manager = SingleResultAuthorizationManager.denyAll();
given(factory.denyAll()).willReturn(manager);
this.root.setAuthorizationManagerFactory(factory);
assertThat(this.root.denyAll()).isFalse();
verify(factory).denyAll();
}
// gh-18486
@Test
@SuppressWarnings("unchecked")
public void isAnonymousDelegatesToAuthorizationManagerFactoryAnonymous() {
AuthorizationManagerFactory<Object> factory = mock(AuthorizationManagerFactory.class);
AuthorizationManager<Object> manager = SingleResultAuthorizationManager.denyAll();
given(factory.anonymous()).willReturn(manager);
this.root.setAuthorizationManagerFactory(factory);
assertThat(this.root.isAnonymous()).isFalse();
verify(factory).anonymous();
}
// gh-18486
@Test
@SuppressWarnings("unchecked")
public void isAuthenticatedDelegatesToAuthorizationManagerFactoryAuthenticated() {
AuthorizationManagerFactory<Object> factory = mock(AuthorizationManagerFactory.class);
AuthorizationManager<Object> manager = SingleResultAuthorizationManager.denyAll();
given(factory.authenticated()).willReturn(manager);
this.root.setAuthorizationManagerFactory(factory);
assertThat(this.root.isAuthenticated()).isFalse();
verify(factory).authenticated();
}
// gh-18486
@Test
@SuppressWarnings("unchecked")
public void isRememberMeDelegatesToAuthorizationManagerFactoryRememberMe() {
AuthorizationManagerFactory<Object> factory = mock(AuthorizationManagerFactory.class);
AuthorizationManager<Object> manager = SingleResultAuthorizationManager.denyAll();
given(factory.rememberMe()).willReturn(manager);
this.root.setAuthorizationManagerFactory(factory);
assertThat(this.root.isRememberMe()).isFalse();
verify(factory).rememberMe();
}
// gh-18486
@Test
@SuppressWarnings("unchecked")
public void isFullyAuthenticatedDelegatesToAuthorizationManagerFactoryFullyAuthenticated() {
AuthorizationManagerFactory<Object> factory = mock(AuthorizationManagerFactory.class);
AuthorizationManager<Object> manager = SingleResultAuthorizationManager.denyAll();
given(factory.fullyAuthenticated()).willReturn(manager);
this.root.setAuthorizationManagerFactory(factory);
assertThat(this.root.isFullyAuthenticated()).isFalse();
verify(factory).fullyAuthenticated();
}
}
@@ -17,7 +17,9 @@
package org.springframework.security.authorization;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import java.util.function.Supplier;
import org.junit.jupiter.api.Test;
@@ -30,11 +32,13 @@ import org.springframework.security.core.Authentication;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatNullPointerException;
/**
* Tests for {@link AuthoritiesAuthorizationManager}.
*
* @author Evgeniy Cheban
* @author Khyojae
*/
class AuthoritiesAuthorizationManagerTests {
@@ -83,4 +87,20 @@ class AuthoritiesAuthorizationManagerTests {
assertThat(manager.authorize(authentication, Collections.singleton("ROLE_USER")).isGranted()).isTrue();
}
@Test
// gh-18543
void authorizeWhenAuthorityIsNullThenDoesNotThrowNullPointerException() {
AuthoritiesAuthorizationManager manager = new AuthoritiesAuthorizationManager();
Authentication authentication = new TestingAuthenticationToken("user", "password",
Collections.singletonList(() -> null));
Collection<String> authoritiesContainsThrowsNPE = Set.of("ROLE_USER");
// must be Collection that throws NPE when .contains(null) is invoked
// to replicate the issue in gh-18543
assertThatNullPointerException().isThrownBy(() -> authoritiesContainsThrowsNPE.contains(null));
assertThat(manager.authorize(() -> authentication, authoritiesContainsThrowsNPE).isGranted()).isFalse();
}
}
+3
View File
@@ -1,5 +1,6 @@
plugins {
id 'security-nullability'
id 'java-test-fixtures'
}
apply plugin: 'io.spring.convention.spring-module'
@@ -10,6 +11,8 @@ dependencies {
optional 'org.bouncycastle:bcpkix-jdk18on'
optional libs.com.password4j.password4j
testFixturesImplementation "org.assertj:assertj-core"
testImplementation "org.assertj:assertj-core"
testImplementation "org.junit.jupiter:junit-jupiter-api"
testImplementation "org.junit.jupiter:junit-jupiter-params"
@@ -0,0 +1,47 @@
/*
* Copyright 2004-present 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.crypto.assertions;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Tests for {@link CryptoAssertions} and {@link CryptoStringAssert}.
*/
class CryptoAssertionsTests {
@Test
void doesNotDecryptToPassesWhenSupplierThrows() {
CryptoAssertions.assertThat((() -> {
throw new RuntimeException("decrypt failed");
})).doesNotDecryptTo("any");
}
@Test
void doesNotDecryptToPassesWhenResultDiffersFromExpected() {
CryptoAssertions.assertThat(() -> "other").doesNotDecryptTo("plaintext");
}
@Test
void doesNotDecryptToFailsWhenResultEqualsExpected() {
assertThatExceptionOfType(AssertionError.class)
.isThrownBy(() -> CryptoAssertions.assertThat(() -> "plaintext").doesNotDecryptTo("plaintext"))
.withMessageContaining("Expected supplier not to return <plaintext> but it did");
}
}
@@ -22,8 +22,9 @@ import java.security.interfaces.RSAPublicKey;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.security.crypto.assertions.CryptoAssertions;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
/**
* @author Dave Syer
@@ -86,13 +87,15 @@ public class RsaSecretEncryptorTests {
@Test
public void roundTripWithMixedAlgorithm() {
RsaSecretEncryptor oaep = new RsaSecretEncryptor(RsaAlgorithm.OAEP);
assertThatIllegalStateException().isThrownBy(() -> oaep.decrypt(this.encryptor.encrypt("encryptor")));
CryptoAssertions.assertThat(() -> oaep.decrypt(this.encryptor.encrypt("encryptor")))
.doesNotDecryptTo("encryptor");
}
@Test
public void roundTripWithMixedSalt() {
RsaSecretEncryptor other = new RsaSecretEncryptor(this.encryptor.getPublicKey(), RsaAlgorithm.DEFAULT, "salt");
assertThatIllegalStateException().isThrownBy(() -> this.encryptor.decrypt(other.encrypt("encryptor")));
CryptoAssertions.assertThat(() -> this.encryptor.decrypt(other.encrypt("encryptor")))
.doesNotDecryptTo("encryptor");
}
@Test
@@ -106,7 +109,8 @@ public class RsaSecretEncryptorTests {
public void publicKeyCannotDecrypt() {
RsaSecretEncryptor encryptor = new RsaSecretEncryptor(this.encryptor.getPublicKey());
assertThat(encryptor.canDecrypt()).as("Encryptor schould not be able to decrypt").isFalse();
assertThatIllegalStateException().isThrownBy(() -> encryptor.decrypt(encryptor.encrypt("encryptor")));
CryptoAssertions.assertThat(() -> encryptor.decrypt(encryptor.encrypt("encryptor")))
.doesNotDecryptTo("encryptor");
}
@Test
@@ -0,0 +1,44 @@
/*
* Copyright 2004-present 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.crypto.assertions;
import java.util.function.Supplier;
/**
* AssertJ entry point for crypto-related assertions. Use {@link #assertThat(Supplier)} to
* assert on a {@link Supplier}&lt;{@link String}&gt; (e.g. a decryption lambda).
* <p>
* Example: <pre>
* assertThat(() -&gt; encryptor.decrypt(ciphertext)).doesNotDecryptTo("plaintext");
* </pre>
*/
public final class CryptoAssertions {
private CryptoAssertions() {
}
/**
* Create assertions for the given supplier (e.g. a decryption expression).
* @param actual the supplier to assert on
* @return assertion object with methods like
* {@link CryptoStringAssert#doesNotDecryptTo(String)}
*/
public static CryptoStringAssert assertThat(Supplier<String> actual) {
return new CryptoStringAssert(actual);
}
}
@@ -0,0 +1,56 @@
/*
* Copyright 2004-present 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.crypto.assertions;
import java.util.Objects;
import java.util.function.Supplier;
import org.assertj.core.api.AbstractObjectAssert;
/**
* AssertJ assertion for {@link Supplier}&lt;{@link String}&gt;, supporting
* decryption-related checks such as {@link #doesNotDecryptTo(String)}.
*/
public final class CryptoStringAssert extends AbstractObjectAssert<CryptoStringAssert, Supplier<String>> {
CryptoStringAssert(Supplier<String> actual) {
super(actual, CryptoStringAssert.class);
}
/**
* Asserts that either the supplier throws an exception when invoked, or the value it
* returns is not equal to the given string. Use this to assert that a decryption
* attempt does not yield a specific plaintext (e.g. wrong key or tampered
* ciphertext).
* @param expected the value that the supplier must not return
* @return this assertion for chaining
*/
public CryptoStringAssert doesNotDecryptTo(String expected) {
isNotNull();
try {
String result = this.actual.get();
if (Objects.equals(result, expected)) {
failWithMessage("Expected supplier not to return <%s> but it did", expected);
}
}
catch (Exception ex) {
// Exception thrown: supplier does not "decrypt to" the expected value
}
return this;
}
}
+1 -1
View File
@@ -31,7 +31,7 @@ urls:
redirect_facility: httpd
ui:
bundle:
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.25/ui-bundle.zip
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.26/ui-bundle.zip
snapshot: true
runtime:
log:
@@ -0,0 +1,4 @@
= Kerberos Migrations
For users leveraging Spring Security's Kerberos support, the Maven and Gradle Coordinates have been changed since the support was moved from an external module into Spring Security.
See the xref:servlet/authentication/kerberos/introduction.adoc[Keberos documentation] for the new Maven and Gradle coordinates.
@@ -39,17 +39,18 @@ This endpoint is referred to as a https://openid.net/specs/openid-connect-discov
When this property and these dependencies are used, Resource Server automatically configures itself to validate JWT-encoded Bearer Tokens.
It achieves this through a deterministic startup process:
It achieves this through a deterministic discovery process it launches at the first request containing a JWT:
. Hit the Provider Configuration or Authorization Server Metadata endpoint, processing the response for the `jwks_url` property.
. Configure the validation strategy to query `jwks_url` for valid public keys.
. Configure the validation strategy to validate each JWT's `iss` claim against `https://idp.example.com`.
A consequence of this process is that the authorization server must be receiving requests in order for Resource Server to successfully start up.
One benefit of deferring this process is that Resource Server startup is not coupled to the authorization server's availability.
[NOTE]
====
If the authorization server is down when Resource Server queries it (given appropriate timeouts), then startup fails.
This deferral is managed by javadoc:org.springframework.security.oauth2.jwt.SupplierReactiveJwtDecoder[`SupplierReactiveJwtDecoder`].
Consider wrapping any <<webflux-oauth2resourceserver-decoder-bean,`JwtDecoder` `@Bean`>> you declare in order to preserve this behavior.
====
=== Runtime Expectations
@@ -85,7 +86,7 @@ From here, consider jumping to:
[[webflux-oauth2resourceserver-jwt-jwkseturi]]
=== Specifying the Authorization Server JWK Set Uri Directly
If the authorization server does not support any configuration endpoints, or if Resource Server must be able to start up independently from the authorization server, you can supply `jwk-set-uri` as well:
If the authorization server does not support any configuration endpoints, or if Resource Server must be able to initialize independently from the authorization server, you can supply `jwk-set-uri` as well:
[source,yaml]
----
@@ -320,7 +320,7 @@ If you have trouble working out where a session is being created, you can add so
[[appendix-faq-forbidden-csrf]]
=== I get a 403 Forbidden when performing a POST. What is wrong?
If an HTTP 403 Forbidden error is returned for HTTP POST, but it works for HTTP GET, the issue is most likely related to https://docs.spring.io/spring-security/site/docs/3.2.x/reference/htmlsingle/#csrf[CSRF]. Either provide the CSRF Token or disable CSRF protection (the latter is not recommended).
If an HTTP 403 Forbidden error is returned for HTTP POST, but it works for HTTP GET, the issue is most likely related to xref:features/exploits/csrf.adoc#csrf[CSRF]. Either provide the CSRF Token or disable CSRF protection (the latter is not recommended).
[[appendix-faq-no-security-on-forward]]
=== I am forwarding a request to another URL by using the RequestDispatcher, but my security constraints are not being applied.
@@ -3,3 +3,38 @@
Spring Security Kerberos {spring-security-version} is built and tested with JDK 17,
Spring Security {spring-security-version} and Spring Framework {spring-core-version}.
The dependency coordinates changed with Spring Security 7:
[tabs]
======
Maven::
+
.pom.xml
[source,xml,subs="verbatim,attributes"]
----
<dependencies>
<!-- ... other dependency elements ... -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-kerberos-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-kerberos-web</artifactId>
</dependency>
</dependencies>
----
Gradle::
+
.build.gradle
[source,groovy]
[subs="verbatim,attributes"]
----
dependencies {
implementation "org.springframework.security:spring-security-kerberos-core"
implementation "org.springframework.security:spring-security-kerberos-web"
}
----
======
@@ -259,7 +259,7 @@ Java::
+
[source,java,role="primary"]
----
HeaderWriterLogoutHandler clearSiteData = new HeaderWriterLogoutHandler(new ClearSiteDataHeaderWriter(Directives.ALL));
HeaderWriterLogoutHandler clearSiteData = new HeaderWriterLogoutHandler(new ClearSiteDataHeaderWriter(Directive.ALL));
http
.logout((logout) -> logout.addLogoutHandler(clearSiteData))
----
@@ -268,7 +268,7 @@ Kotlin::
+
[source,kotlin,role="secondary"]
----
val clearSiteData = HeaderWriterLogoutHandler(ClearSiteDataHeaderWriter(Directives.ALL))
val clearSiteData = HeaderWriterLogoutHandler(ClearSiteDataHeaderWriter(Directive.ALL))
http {
logout {
addLogoutHandler(clearSiteData)
@@ -10,7 +10,7 @@ After the credential is registered, it can be used to authenticate by xref:servl
[[passkeys-dependencies]]
== Required Dependencies
To get started, add the `webauthn4j-core` dependency to your project.
To get started, add the `spring-security-webauthn` dependency to your project.
[NOTE]
====
@@ -26,12 +26,7 @@ Maven::
----
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>
<dependency>
<groupId>com.webauthn4j</groupId>
<artifactId>webauthn4j-core</artifactId>
<version>{webauthn4j-core-version}</version>
<artifactId>spring-security-webauthn</artifactId>
</dependency>
----
@@ -40,8 +35,7 @@ Gradle::
[source,groovy,role="secondary",subs="verbatim,attributes"]
----
dependencies {
implementation "org.springframework.security:spring-security-web"
implementation "com.webauthn4j:webauthn4j-core:{webauthn4j-core-version}"
implementation "org.springframework.security:spring-security-webauthn"
}
----
======
@@ -97,6 +97,7 @@ This design allows any number of remember-me implementation strategies.
We have seen earlier that Spring Security provides two implementations.
We look at each of these in turn.
[[token-based-remember-me-services]]
=== TokenBasedRememberMeServices
This implementation supports the simpler approach described in <<remember-me-hash-token>>.
`TokenBasedRememberMeServices` generates a `RememberMeAuthenticationToken`, which is processed by `RememberMeAuthenticationProvider`.
@@ -110,105 +111,11 @@ If no `algorithmName` is present, the default matching algorithm will be used, w
You can specify different algorithms for signature encoding and for signature matching, this allows users to safely upgrade to a different encoding algorithm while still able to verify old ones if there is no `algorithmName` present.
To do that you can specify your customized `TokenBasedRememberMeServices` as a Bean and use it in the configuration.
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http, RememberMeServices rememberMeServices) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.rememberMe((remember) -> remember
.rememberMeServices(rememberMeServices)
);
return http.build();
}
@Bean
RememberMeServices rememberMeServices(UserDetailsService userDetailsService) {
RememberMeTokenAlgorithm encodingAlgorithm = RememberMeTokenAlgorithm.SHA256;
TokenBasedRememberMeServices rememberMe = new TokenBasedRememberMeServices(myKey, userDetailsService, encodingAlgorithm);
rememberMe.setMatchingAlgorithm(RememberMeTokenAlgorithm.MD5);
return rememberMe;
}
----
XML::
+
[source,xml,role="secondary"]
----
<http>
<remember-me services-ref="rememberMeServices"/>
</http>
<bean id="rememberMeServices" class=
"org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices">
<property name="userDetailsService" ref="myUserDetailsService"/>
<property name="key" value="springRocks"/>
<property name="matchingAlgorithm" value="MD5"/>
<property name="encodingAlgorithm" value="SHA256"/>
</bean>
----
======
include-code::./CustomAlgorithmRememberMeServicesConfiguration[tag=snippet,indent=0]
The following beans are required in an application context to enable remember-me services:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
RememberMeAuthenticationFilter rememberMeFilter() {
RememberMeAuthenticationFilter rememberMeFilter = new RememberMeAuthenticationFilter();
rememberMeFilter.setRememberMeServices(rememberMeServices());
rememberMeFilter.setAuthenticationManager(theAuthenticationManager);
return rememberMeFilter;
}
@Bean
TokenBasedRememberMeServices rememberMeServices() {
TokenBasedRememberMeServices rememberMeServices = new TokenBasedRememberMeServices();
rememberMeServices.setUserDetailsService(myUserDetailsService);
rememberMeServices.setKey("springRocks");
return rememberMeServices;
}
@Bean
RememberMeAuthenticationProvider rememberMeAuthenticationProvider() {
RememberMeAuthenticationProvider rememberMeAuthenticationProvider = new RememberMeAuthenticationProvider();
rememberMeAuthenticationProvider.setKey("springRocks");
return rememberMeAuthenticationProvider;
}
----
XML::
+
[source,xml,role="secondary"]
----
<bean id="rememberMeFilter" class=
"org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter">
<property name="rememberMeServices" ref="rememberMeServices"/>
<property name="authenticationManager" ref="theAuthenticationManager" />
</bean>
<bean id="rememberMeServices" class=
"org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices">
<property name="userDetailsService" ref="myUserDetailsService"/>
<property name="key" value="springRocks"/>
</bean>
<bean id="rememberMeAuthenticationProvider" class=
"org.springframework.security.authentication.RememberMeAuthenticationProvider">
<property name="key" value="springRocks"/>
</bean>
----
======
include-code::./DefaultAlgorithmRememberMeServicesConfiguration[tag=snippet,indent=0]
Remember to add your `RememberMeServices` implementation to your `UsernamePasswordAuthenticationFilter.setRememberMeServices()` property, include the `RememberMeAuthenticationProvider` in your `AuthenticationManager.setProviders()` list, and add `RememberMeAuthenticationFilter` into your `FilterChainProxy` (typically immediately after your `UsernamePasswordAuthenticationFilter`).
@@ -63,7 +63,9 @@ To use this tag, you must also have an instance of `WebInvocationPrivilegeEvalua
If you are using the namespace, one is automatically registered.
This is an instance of `DefaultWebInvocationPrivilegeEvaluator`, which creates a dummy web request for the supplied URL and invokes the security interceptor to see whether the request would succeed or fail.
This lets you delegate to the access-control setup you defined by using `intercept-url` declarations within the `<http>` namespace configuration and saves having to duplicate the information (such as the required roles) within your JSPs.
You can also combine this approach with a `method` attribute (supplying the HTTP method, such as `POST`) for a more specific match.
If you have xref:servlet/authorization/authorize-http-requests.adoc#match-by-httpmethod[method-based authorization rules], you should combine this approach with the `method` attribute (supplying the HTTP method, such as `POST`) to activate the intended method-based rule.
For example, if you have a rule `.requestMatchers(POST, "/admin").hasRole("ADMIN")`, then you should do `<sec:authorize method="POST" url="/admin">` to match.
You can store the Boolean result of evaluating the tag (whether it grants or denies access) in a page context scope variable by setting the `var` attribute to the variable name, avoiding the need for duplicating and re-evaluating the condition at other points in the page.
@@ -40,17 +40,20 @@ And that's it!
When this property and these dependencies are used, Resource Server will automatically configure itself to validate JWT-encoded Bearer Tokens.
It achieves this through a deterministic startup process:
It achieves this through a deterministic discovery process it launches at the first request containing a JWT:
1. Query the Provider Configuration or Authorization Server Metadata endpoint for the `jwks_url` property
2. Query the `jwks_url` endpoint for supported algorithms
3. Configure the validation strategy to query `jwks_url` for valid public keys of the algorithms found
4. Configure the validation strategy to validate each JWTs `iss` claim against `https://idp.example.com`.
A consequence of this process is that the authorization server must be up and receiving requests in order for Resource Server to successfully start up.
One benefit of deferring this process is that Resource Server startup is not coupled to the authorization server's availability.
[NOTE]
If the authorization server is down when Resource Server queries it (given appropriate timeouts), then startup will fail.
====
This deferral is managed by javadoc:org.springframework.security.oauth2.jwt.SupplierJwtDecoder[`SupplierJwtDecoder`].
Consider wrapping any <<oauth2resourceserver-jwt-decoder,`JwtDecoder` `@Bean`>> you declare in order to preserve this behavior.
====
=== Runtime Expectations
@@ -66,7 +69,7 @@ So long as this scheme is indicated, Resource Server will attempt to process the
Given a well-formed JWT, Resource Server will:
1. Validate its signature against a public key obtained from the `jwks_url` endpoint during startup and matched against the JWT
1. Validate its signature against a public key obtained from the `jwks_url` endpoint during startup or on first request, depending on configuration, and matched against the JWT
2. Validate the JWT's `exp` and `nbf` timestamps and the JWT's `iss` claim, and
3. Map each scope to an authority with the prefix `SCOPE_`.
@@ -111,7 +114,7 @@ Ultimately, the returned `JwtAuthenticationToken` will be set on the xref:servle
[[oauth2resourceserver-jwt-jwkseturi]]
== Specifying the Authorization Server JWK Set Uri Directly
If the authorization server doesn't support any configuration endpoints, or if Resource Server must be able to start up independently from the authorization server, then the `jwk-set-uri` can be supplied as well:
If the authorization server doesn't support any configuration endpoints, or if Resource Server must be able to initialize independently from the authorization server, then the `jwk-set-uri` can be supplied as well:
[source,yaml]
----
@@ -751,8 +751,8 @@ Java::
public OpaqueTokenIntrospector introspector(RestTemplateBuilder builder, OAuth2ResourceServerProperties properties) {
RestOperations rest = builder
.basicAuthentication(properties.getOpaquetoken().getClientId(), properties.getOpaquetoken().getClientSecret())
.setConnectTimeout(Duration.ofSeconds(60))
.setReadTimeout(Duration.ofSeconds(60))
.connectTimeout(Duration.ofSeconds(60))
.readTimeout(Duration.ofSeconds(60))
.build();
return SpringOpaqueTokenIntrospector(introspectionUri, rest);
@@ -767,8 +767,8 @@ Kotlin::
fun introspector(builder: RestTemplateBuilder, properties: OAuth2ResourceServerProperties): OpaqueTokenIntrospector? {
val rest: RestOperations = builder
.basicAuthentication(properties.opaquetoken.clientId, properties.opaquetoken.clientSecret)
.setConnectTimeout(Duration.ofSeconds(60))
.setReadTimeout(Duration.ofSeconds(60))
.connectTimeout(Duration.ofSeconds(60))
.readTimeout(Duration.ofSeconds(60))
.build()
return SpringOpaqueTokenIntrospector(introspectionUri, rest)
}
+1 -1
View File
@@ -2,7 +2,7 @@
"dependencies": {
"antora": "3.2.0-alpha.11",
"@antora/atlas-extension": "1.0.0-alpha.5",
"@antora/collector-extension": "1.0.2",
"@antora/collector-extension": "1.0.3",
"@asciidoctor/tabs": "1.0.0-beta.6",
"@springio/antora-extensions": "1.14.7",
"@springio/asciidoctor-extensions": "1.0.0-alpha.17"
@@ -0,0 +1,65 @@
/*
* Copyright 2026-present 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.docs.servlet.authentication.tokenbasedremembermeservices;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices;
import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices.RememberMeTokenAlgorithm;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
/**
* Demonstrates custom algorithm for remember me configuration.
*
* @author Ngoc Nhan
*/
@EnableWebMvc
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
public class CustomAlgorithmRememberMeServicesConfiguration {
// tag::snippet[]
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http, RememberMeServices rememberMeServices) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.rememberMe((remember) -> remember
.rememberMeServices(rememberMeServices)
);
// @formatter:on
return http.build();
}
@Bean
RememberMeServices rememberMeServices(UserDetailsService userDetailsService) {
RememberMeTokenAlgorithm encodingAlgorithm = RememberMeTokenAlgorithm.SHA256;
TokenBasedRememberMeServices rememberMe = new TokenBasedRememberMeServices("myKey", userDetailsService,
encodingAlgorithm);
rememberMe.setMatchingAlgorithm(RememberMeTokenAlgorithm.MD5);
return rememberMe;
}
// end::snippet[]
}
@@ -0,0 +1,58 @@
/*
* Copyright 2026-present 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.docs.servlet.authentication.tokenbasedremembermeservices;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.RememberMeAuthenticationProvider;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
/**
* Demonstrates default algorithm for remember me configuration.
*
* @author Ngoc Nhan
*/
@EnableWebMvc
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
public class DefaultAlgorithmRememberMeServicesConfiguration {
// tag::snippet[]
@Bean
RememberMeServices rememberMeServices(UserDetailsService userDetailsService) {
return new TokenBasedRememberMeServices("myKey", userDetailsService);
}
@Bean
RememberMeAuthenticationFilter rememberMeFilter(AuthenticationManager authenticationManager,
TokenBasedRememberMeServices rememberMeServices) {
return new RememberMeAuthenticationFilter(authenticationManager, rememberMeServices);
}
@Bean
RememberMeAuthenticationProvider rememberMeAuthenticationProvider() {
return new RememberMeAuthenticationProvider("myKey");
}
// end::snippet[]
}
@@ -0,0 +1,61 @@
/*
* Copyright 2026-present 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.kt.docs.servlet.authentication.tokenbasedremembermeservices
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.web.SecurityFilterChain
import org.springframework.security.web.authentication.RememberMeServices
import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices
import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices.RememberMeTokenAlgorithm
import org.springframework.web.servlet.config.annotation.EnableWebMvc
/**
* Demonstrates custom algorithm for remember me configuration.
*
* @author Ngoc Nhan
*/
@EnableWebMvc
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
class CustomAlgorithmRememberMeServicesConfiguration {
// tag::snippet[]
@Bean
@Throws(Exception::class)
fun securityFilterChain(http: HttpSecurity, rememberMeServices: RememberMeServices): SecurityFilterChain {
// @formatter:off
http
.authorizeHttpRequests{ it.anyRequest().authenticated() }
.rememberMe { it.rememberMeServices(rememberMeServices) }
// @formatter:on
return http.build()
}
@Bean
fun rememberMeServices(userDetailsService: UserDetailsService): RememberMeServices {
val encodingAlgorithm = RememberMeTokenAlgorithm.SHA256
val rememberMe = TokenBasedRememberMeServices("myKey", userDetailsService, encodingAlgorithm)
rememberMe.setMatchingAlgorithm(RememberMeTokenAlgorithm.MD5)
return rememberMe
}
// end::snippet[]
}
@@ -0,0 +1,57 @@
/*
* Copyright 2026-present 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.kt.docs.servlet.authentication.tokenbasedremembermeservices
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.RememberMeAuthenticationProvider
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.web.authentication.RememberMeServices
import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter
import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices
import org.springframework.web.servlet.config.annotation.EnableWebMvc
/**
* Demonstrates default algorithm for remember me configuration.
*
* @author Ngoc Nhan
*/
@EnableWebMvc
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
class DefaultAlgorithmRememberMeServicesConfiguration {
// tag::snippet[]
@Bean
fun rememberMeServices(userDetailsService: UserDetailsService): RememberMeServices {
return TokenBasedRememberMeServices("myKey", userDetailsService)
}
@Bean
fun rememberMeFilter(authenticationManager: AuthenticationManager, rememberMeServices: TokenBasedRememberMeServices): RememberMeAuthenticationFilter {
return RememberMeAuthenticationFilter(authenticationManager, rememberMeServices)
}
@Bean
fun rememberMeAuthenticationProvider(): RememberMeAuthenticationProvider {
return RememberMeAuthenticationProvider("myKey")
}
// end::snippet[]
}
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2026-present 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.
-->
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/security"
xsi:schemaLocation="
http://www.springframework.org/schema/security
https://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<b:bean id="userDetailsService"
class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl"/>
<!-- tag::snippet[] -->
<http>
<intercept-url pattern="/**" access="authenticated"/>
<remember-me services-ref="rememberMeServices"/>
</http>
<b:bean id="rememberMeServices"
class="org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices">
<b:constructor-arg value="myKey"/>
<b:constructor-arg ref="userDetailsService"/>
<b:constructor-arg value="SHA256"
type="org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices$RememberMeTokenAlgorithm"/>
<b:property name="matchingAlgorithm" value="MD5"/>
</b:bean>
<!-- end::snippet[] -->
</b:beans>
@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2026-present 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.
-->
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/security"
xsi:schemaLocation="
http://www.springframework.org/schema/security
https://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<b:bean id="userDetailsService"
class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl"/>
<!-- tag::snippet[] -->
<b:bean id="rememberMeServices"
class="org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices">
<b:constructor-arg value="myKey"/>
<b:constructor-arg ref="userDetailsService"/>
</b:bean>
<b:bean id="rememberMeFilter"
class="org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter">
<b:constructor-arg ref="authenticationManager"/>
<b:constructor-arg ref="rememberMeServices"/>
</b:bean>
<b:bean id="rememberMeAuthenticationProvider"
class="org.springframework.security.authentication.RememberMeAuthenticationProvider">
<b:constructor-arg value="myKey"/>
</b:bean>
<!-- end::snippet[] -->
<authentication-manager alias="authenticationManager">
<authentication-provider ref="rememberMeAuthenticationProvider"/>
</authentication-manager>
</b:beans>
+1 -1
View File
@@ -14,7 +14,7 @@
# limitations under the License.
#
springBootVersion=4.0.0-SNAPSHOT
version=7.0.3
version=7.0.4
samplesBranch=main
org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError
org.gradle.parallel=true
+9 -9
View File
@@ -4,7 +4,7 @@ io-rsocket = "1.1.5"
io-spring-javaformat = "0.0.47"
io-spring-nohttp = "0.0.11"
jakarta-websocket = "2.2.0"
org-apache-maven-resolver = "1.9.25"
org-apache-maven-resolver = "1.9.27"
org-aspectj = "1.9.25.1"
org-bouncycastle = "1.80"
org-eclipse-jetty = "11.0.26"
@@ -12,11 +12,11 @@ org-jetbrains-kotlin = "2.2.21"
org-jetbrains-kotlinx = "1.10.2"
org-mockito = "5.17.0"
org-opensaml5 = "5.1.6"
org-springframework = "7.0.4"
org-springframework = "7.0.6"
com-password4j = "1.8.4"
[libraries]
ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.5.29"
ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.5.32"
com-fasterxml-jackson-jackson-bom = "com.fasterxml.jackson:jackson-bom:2.20.2"
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"
@@ -30,13 +30,13 @@ commons-collections = "commons-collections:commons-collections:3.2.2"
io-micrometer-context-propagation = "io.micrometer:context-propagation:1.1.4"
io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.14.14"
io-mockk = "io.mockk:mockk:1.14.9"
io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2025.0.3"
io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2025.0.4"
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" }
io-spring-nohttp-nohttp-checkstyle = { module = "io.spring.nohttp:nohttp-checkstyle", version.ref = "io-spring-nohttp" }
io-spring-nohttp-nohttp-gradle = { module = "io.spring.nohttp:nohttp-gradle", version.ref = "io-spring-nohttp" }
io-spring-security-release-plugin = "io.spring.gradle:spring-security-release-plugin:1.0.14"
io-spring-security-release-plugin = "io.spring.gradle:spring-security-release-plugin:1.0.15"
jakarta-annotation-jakarta-annotation-api = "jakarta.annotation:jakarta.annotation-api:3.0.0"
jakarta-inject-jakarta-inject-api = "jakarta.inject:jakarta.inject-api:2.0.1"
jakarta-persistence-jakarta-persistence-api = "jakarta.persistence:jakarta.persistence-api:3.2.0"
@@ -51,7 +51,7 @@ net-sourceforge-htmlunit = "net.sourceforge.htmlunit:htmlunit:2.70.0"
org-htmlunit-htmlunit = "org.htmlunit:htmlunit:4.11.1"
org-apache-httpcomponents-httpclient = "org.apache.httpcomponents.client5:httpclient5:5.5.2"
org-apache-kerby-simplekdc='org.apache.kerby:kerb-simplekdc:2.1.1'
org-apache-maven-maven-resolver-provider = "org.apache.maven:maven-resolver-provider:3.9.12"
org-apache-maven-maven-resolver-provider = "org.apache.maven:maven-resolver-provider:3.9.14"
org-apache-maven-resolver-maven-resolver-connector-basic = { module = "org.apache.maven.resolver:maven-resolver-connector-basic", version.ref = "org-apache-maven-resolver" }
org-apache-maven-resolver-maven-resolver-impl = { module = "org.apache.maven.resolver:maven-resolver-impl", version.ref = "org-apache-maven-resolver" }
org-apache-maven-resolver-maven-resolver-transport-http = { module = "org.apache.maven.resolver:maven-resolver-transport-http", version.ref = "org-apache-maven-resolver" }
@@ -70,7 +70,7 @@ org-hsqldb = "org.hsqldb:hsqldb:2.7.4"
org-jetbrains-kotlin-kotlin-bom = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "org-jetbrains-kotlin" }
org-jetbrains-kotlin-kotlin-gradle-plugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:2.2.21"
org-jetbrains-kotlinx-kotlinx-coroutines-bom = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-bom", version.ref = "org-jetbrains-kotlinx" }
org-junit-junit-bom = "org.junit:junit-bom:6.0.2"
org-junit-junit-bom = "org.junit:junit-bom:6.0.3"
org-mockito-mockito-bom = { module = "org.mockito:mockito-bom", version.ref = "org-mockito" }
org-opensaml-opensaml5-saml-api = { module = "org.opensaml:opensaml-saml-api", version.ref = "org-opensaml5" }
org-opensaml-opensaml5-saml-impl = { module = "org.opensaml:opensaml-saml-impl", version.ref = "org-opensaml5" }
@@ -81,7 +81,7 @@ org-seleniumhq-selenium-selenium-support = "org.seleniumhq.selenium:selenium-sup
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.17"
org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2025.1.3"
org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2025.1.4"
org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:4.0.2"
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"
@@ -102,7 +102,7 @@ org-sonarsource-scanner-gradle-sonarqube-gradle-plugin = "org.sonarsource.scanne
org-instancio-instancio-junit = "org.instancio:instancio-junit:3.7.1"
spring-nullability = 'io.spring.nullability:io.spring.nullability.gradle.plugin:0.0.6'
webauthn4j-core = 'com.webauthn4j:webauthn4j-core:0.31.0.RELEASE'
webauthn4j-core = 'com.webauthn4j:webauthn4j-core:0.31.1.RELEASE'
com-password4j-password4j = { module = "com.password4j:password4j", version.ref = "com-password4j" }
[plugins]
@@ -132,9 +132,7 @@ public final class OAuth2DeviceVerificationAuthenticationProvider implements Aut
if (this.logger.isTraceEnabled()) {
this.logger.trace("Did not authenticate device verification request since principal not authenticated");
}
// Return the device verification request as-is where isAuthenticated() is
// false
return deviceVerificationAuthentication;
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST);
}
RegisteredClient registeredClient = this.registeredClientRepository
@@ -161,15 +161,6 @@ public final class OAuth2DeviceVerificationEndpointFilter extends OncePerRequest
}
Authentication authenticationResult = this.authenticationManager.authenticate(authentication);
if (!authenticationResult.isAuthenticated()) {
// If the Principal (Resource Owner) is not authenticated then pass
// through the chain
// with the expectation that the authentication process will commence via
// AuthenticationEntryPoint
filterChain.doFilter(request, response);
return;
}
if (authenticationResult instanceof OAuth2DeviceAuthorizationConsentAuthenticationToken) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Device authorization consent is required");
@@ -30,6 +30,7 @@ import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.OctetSequenceKey;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -112,6 +113,11 @@ public class JwtClientAssertionAuthenticationProviderTests {
.setContext(new TestAuthorizationServerContext(this.authorizationServerSettings, null));
}
@AfterEach
public void tearDown() {
AuthorizationServerContextHolder.resetContext();
}
@Test
public void constructorWhenRegisteredClientRepositoryNullThenThrowIllegalArgumentException() {
assertThatExceptionOfType(IllegalArgumentException.class)
@@ -25,6 +25,7 @@ import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
@@ -111,6 +112,11 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
.setContext(new TestAuthorizationServerContext(authorizationServerSettings, null));
}
@AfterEach
public void tearDown() {
AuthorizationServerContextHolder.resetContext();
}
@Test
public void constructorWhenRegisteredClientRepositoryNullThenThrowIllegalArgumentException() {
assertThatExceptionOfType(IllegalArgumentException.class)
@@ -22,6 +22,7 @@ import java.util.HashSet;
import java.util.Set;
import java.util.function.Consumer;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
@@ -95,6 +96,11 @@ public class OAuth2AuthorizationConsentAuthenticationProviderTests {
.setContext(new TestAuthorizationServerContext(authorizationServerSettings, null));
}
@AfterEach
public void tearDown() {
AuthorizationServerContextHolder.resetContext();
}
@Test
public void constructorWhenRegisteredClientRepositoryNullThenThrowIllegalArgumentException() {
assertThatExceptionOfType(IllegalArgumentException.class)
@@ -25,6 +25,7 @@ import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
@@ -96,6 +97,11 @@ public class OAuth2DeviceVerificationAuthenticationProviderTests {
mockAuthorizationServerContext();
}
@AfterEach
public void tearDown() {
AuthorizationServerContextHolder.resetContext();
}
@Test
public void constructorWhenRegisteredClientRepositoryIsNullThenThrowIllegalArgumentException() {
// @formatter:off
@@ -221,7 +227,7 @@ public class OAuth2DeviceVerificationAuthenticationProviderTests {
}
@Test
public void authenticateWhenPrincipalNotAuthenticatedThenReturnUnauthenticated() {
public void authenticateWhenPrincipalNotAuthenticatedThenThrowOAuth2AuthenticationException() {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
// @formatter:off
OAuth2Authorization authorization = TestOAuth2Authorizations
@@ -231,15 +237,21 @@ public class OAuth2DeviceVerificationAuthenticationProviderTests {
.attribute(OAuth2ParameterNames.SCOPE, registeredClient.getScopes())
.build();
// @formatter:on
TestingAuthenticationToken principal = new TestingAuthenticationToken("user", null);
TestingAuthenticationToken principal = new TestingAuthenticationToken("anonymous", null);
principal.setAuthenticated(false);
Authentication authentication = new OAuth2DeviceVerificationAuthenticationToken(principal, USER_CODE,
Collections.emptyMap());
given(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).willReturn(authorization);
given(this.authorizationService.findByToken(eq(USER_CODE),
eq(OAuth2DeviceVerificationAuthenticationProvider.USER_CODE_TOKEN_TYPE)))
.willReturn(authorization);
OAuth2DeviceVerificationAuthenticationToken authenticationResult = (OAuth2DeviceVerificationAuthenticationToken) this.authenticationProvider
.authenticate(authentication);
assertThat(authenticationResult).isEqualTo(authentication);
assertThat(authenticationResult.isAuthenticated()).isFalse();
// @formatter:off
assertThatExceptionOfType(OAuth2AuthenticationException.class)
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.extracting(OAuth2AuthenticationException::getError)
.extracting(OAuth2Error::getErrorCode)
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST);
// @formatter:on
verify(this.authorizationService).findByToken(USER_CODE,
OAuth2DeviceVerificationAuthenticationProvider.USER_CODE_TOKEN_TYPE);
@@ -166,21 +166,6 @@ public class OAuth2DeviceVerificationEndpointFilterTests {
verifyNoInteractions(this.authenticationManager);
}
@Test
public void doFilterWhenUnauthenticatedThenPassThrough() throws Exception {
TestingAuthenticationToken unauthenticatedResult = new TestingAuthenticationToken("user", null);
given(this.authenticationManager.authenticate(any(Authentication.class))).willReturn(unauthenticatedResult);
MockHttpServletRequest request = createRequest();
request.addParameter(OAuth2ParameterNames.USER_CODE, USER_CODE);
updateQueryString(request);
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain filterChain = mock(FilterChain.class);
this.filter.doFilter(request, response, filterChain);
verify(this.authenticationManager).authenticate(any(Authentication.class));
verify(filterChain).doFilter(request, response);
}
@Test
public void doFilterWhenDeviceAuthorizationConsentRequestThenSuccess() throws Exception {
Authentication authenticationResult = createDeviceVerificationAuthentication();
@@ -52,6 +52,7 @@ public final class CacheSaml2AuthenticationRequestRepository
@Override
public void saveAuthenticationRequest(AbstractSaml2AuthenticationRequest authenticationRequest,
HttpServletRequest request, HttpServletResponse response) {
Assert.notNull(authenticationRequest, "authenticationRequest must not be null");
String relayState = request.getParameter(Saml2ParameterNames.RELAY_STATE);
Assert.notNull(relayState, "relayState must not be null");
this.cache.put(relayState, authenticationRequest);
@@ -0,0 +1,99 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.authentication;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.List;
import org.jspecify.annotations.Nullable;
import org.springframework.core.ResolvableType;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.GenericHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.http.converter.SmartHttpMessageConverter;
/**
* {@link GenericHttpMessageConverter} implementation that delegates to a
* {@link SmartHttpMessageConverter}.
*
* @param <T> the converted object type
* @author Sebastien Deleuze
* @since 7.0
*/
final class GenericHttpMessageConverterAdapter<T> implements GenericHttpMessageConverter<T> {
private final SmartHttpMessageConverter<T> smartConverter;
GenericHttpMessageConverterAdapter(SmartHttpMessageConverter<T> smartConverter) {
this.smartConverter = smartConverter;
}
@Override
public boolean canRead(Type type, @Nullable Class<?> contextClass, @Nullable MediaType mediaType) {
return this.smartConverter.canRead(ResolvableType.forType(type), mediaType);
}
@Override
public T read(Type type, @Nullable Class<?> contextClass, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
return this.smartConverter.read(ResolvableType.forType(type), inputMessage, null);
}
@Override
public boolean canWrite(@Nullable Type type, Class<?> clazz, @Nullable MediaType mediaType) {
return this.smartConverter.canWrite(ResolvableType.forType(type), clazz, mediaType);
}
@Override
public void write(T t, @Nullable Type type, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
this.smartConverter.write(t, ResolvableType.forType(type), contentType, outputMessage, null);
}
@Override
public boolean canRead(Class<?> clazz, @Nullable MediaType mediaType) {
return this.smartConverter.canRead(ResolvableType.forClass(clazz), mediaType);
}
@Override
public boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType) {
return this.smartConverter.canWrite(clazz, mediaType);
}
@Override
public List<MediaType> getSupportedMediaTypes() {
return this.smartConverter.getSupportedMediaTypes();
}
@Override
public T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
return this.smartConverter.read(clazz, inputMessage);
}
@Override
public void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
this.smartConverter.write(t, contentType, outputMessage);
}
}
@@ -49,7 +49,7 @@ import org.springframework.util.Assert;
*/
public final class HttpMessageConverterAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private HttpMessageConverter<Object> converter = new MappingJackson2HttpMessageConverter();
private HttpMessageConverter<Object> converter = HttpMessageConverters.getJsonMessageConverter();
private RequestCache requestCache = new HttpSessionRequestCache();
@@ -0,0 +1,88 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.authentication;
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.JacksonJsonHttpMessageConverter;
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 Rob Winch
* @since 7.0.4
*/
final class HttpMessageConverters {
private static final boolean jacksonPresent;
private static final boolean jackson2Present;
private static final boolean gsonPresent;
private static final boolean jsonbPresent;
private static final String JSON_MAPPER = "tools.jackson.databind.json.JsonMapper";
private static final String OBJECT_MAPPER = "com.fasterxml.jackson.databind.ObjectMapper";
private static final String JSON_GENERATOR = "com.fasterxml.jackson.core.JsonGenerator";
private static final String GSON = "com.google.gson.Gson";
private static final String JSONB = "jakarta.json.bind.Jsonb";
static {
ClassLoader classLoader = HttpMessageConverters.class.getClassLoader();
jacksonPresent = ClassUtils.isPresent(JSON_MAPPER, classLoader);
jackson2Present = ClassUtils.isPresent(OBJECT_MAPPER, classLoader)
&& ClassUtils.isPresent(JSON_GENERATOR, classLoader);
gsonPresent = ClassUtils.isPresent(GSON, classLoader);
jsonbPresent = ClassUtils.isPresent(JSONB, classLoader);
}
private HttpMessageConverters() {
}
/**
* Gets the {@link GenericHttpMessageConverterAdapter} to use for JSON.
* @return the {@link GenericHttpMessageConverterAdapter} to use.
*/
@SuppressWarnings("removal")
static GenericHttpMessageConverter<Object> getJsonMessageConverter() {
if (jacksonPresent) {
return new GenericHttpMessageConverterAdapter<>(new JacksonJsonHttpMessageConverter());
}
if (jackson2Present) {
return new MappingJackson2HttpMessageConverter();
}
if (gsonPresent) {
return new GsonHttpMessageConverter();
}
if (jsonbPresent) {
return new JsonbHttpMessageConverter();
}
throw new IllegalStateException(
"Cannot find JSON Converter on the classpath. Add one following classes to the classpath "
+ String.join(", ", JSON_MAPPER, OBJECT_MAPPER, JSON_MAPPER, GSON, JSONB));
}
}
@@ -93,6 +93,7 @@ public class CookieRequestCache implements RequestCache {
.setServerPort(port)
.setMethod(request.getMethod())
.setLocales(Collections.list(request.getLocales()))
.setParameters(request.getParameterMap())
.build();
}
@@ -58,10 +58,38 @@ public abstract class OnCommittedResponseWrapper extends HttpServletResponseWrap
@Override
public void addHeader(String name, String value) {
checkContentLengthHeader(name, value);
super.addHeader(name, value);
}
@Override
public void addIntHeader(String name, int value) {
checkContentLengthHeader(name, value);
super.addIntHeader(name, value);
}
@Override
public void setHeader(String name, String value) {
checkContentLengthHeader(name, value);
super.setHeader(name, value);
}
@Override
public void setIntHeader(String name, int value) {
checkContentLengthHeader(name, value);
super.setIntHeader(name, value);
}
private void checkContentLengthHeader(String name, int value) {
if ("Content-Length".equalsIgnoreCase(name)) {
setContentLength(value);
}
}
private void checkContentLengthHeader(String name, String value) {
if ("Content-Length".equalsIgnoreCase(name)) {
setContentLength(Long.parseLong(value));
}
super.addHeader(name, value);
}
@Override
@@ -108,6 +108,21 @@ public class CookieRequestCacheTests {
assertThat(savedRequest.getRedirectUrl()).isEqualTo(redirectUrl);
}
@Test
public void getRequestWhenRequestContainsSavedRequestCookieThenSavedRequestContainsRequestParameters() {
CookieRequestCache cookieRequestCache = new CookieRequestCache();
MockHttpServletRequest request = new MockHttpServletRequest();
request.setCookies(new Cookie(DEFAULT_COOKIE_NAME, encodeCookie("https://abc.com/destination")));
request.setParameter("single", "first");
request.addParameter("multi", "second");
request.addParameter("multi", "third");
SavedRequest savedRequest = cookieRequestCache.getRequest(request, new MockHttpServletResponse());
assertThat(savedRequest).isNotNull();
assertThat(savedRequest.getParameterValues("single")).containsExactly("first");
assertThat(savedRequest.getParameterValues("multi")).containsExactly("second", "third");
assertThat(savedRequest.getParameterMap()).containsKeys("single", "multi");
}
@Test
public void matchingRequestWhenRequestDoesNotContainSavedRequestCookieThenReturnsNull() {
CookieRequestCache cookieRequestCache = new CookieRequestCache();
@@ -1006,6 +1006,33 @@ public class OnCommittedResponseWrapperTests {
assertThat(this.committed).isTrue();
}
@Test
public void addIntHeaderContentLengthPrintWriterWriteStringCommits() throws Exception {
givenGetWriterThenReturn();
int expected = 1234;
this.response.addIntHeader("Content-Length", String.valueOf(expected).length());
this.response.getWriter().write(expected);
assertThat(this.committed).isTrue();
}
@Test
public void setHeaderContentLengthPrintWriterWriteStringCommits() throws Exception {
givenGetWriterThenReturn();
int expected = 1234;
this.response.setHeader("Content-Length", String.valueOf(String.valueOf(expected).length()));
this.response.getWriter().write(expected);
assertThat(this.committed).isTrue();
}
@Test
public void setIntHeaderContentLengthPrintWriterWriteStringCommits() throws Exception {
givenGetWriterThenReturn();
int expected = 1234;
this.response.setIntHeader("Content-Length", String.valueOf(expected).length());
this.response.getWriter().write(expected);
assertThat(this.committed).isTrue();
}
@Test
public void bufferSizePrintWriterWriteCommits() throws Exception {
givenGetWriterThenReturn();
@@ -55,11 +55,8 @@ class AuthenticationExtensionsClientOutputsDeserializer extends StdDeserializer<
throws JacksonException {
List<AuthenticationExtensionsClientOutput<?>> outputs = new ArrayList<>();
for (String key = parser.nextName(); key != null; key = parser.nextName()) {
JsonToken startObject = parser.nextValue();
if (startObject != JsonToken.START_OBJECT) {
break;
}
if (CredentialPropertiesOutput.EXTENSION_ID.equals(key)) {
JsonToken next = parser.nextToken();
if (next == JsonToken.START_OBJECT && CredentialPropertiesOutput.EXTENSION_ID.equals(key)) {
CredentialPropertiesOutput output = parser.readValueAs(CredentialPropertiesOutput.class);
outputs.add(output);
}
@@ -67,7 +64,9 @@ class AuthenticationExtensionsClientOutputsDeserializer extends StdDeserializer<
if (logger.isDebugEnabled()) {
logger.debug("Skipping unknown extension with id " + key);
}
parser.nextValue();
if (next.isStructStart()) {
parser.skipChildren();
}
}
}
@@ -62,11 +62,8 @@ class AuthenticationExtensionsClientOutputsJackson2Deserializer
throws IOException, JacksonException {
List<AuthenticationExtensionsClientOutput<?>> outputs = new ArrayList<>();
for (String key = parser.nextFieldName(); key != null; key = parser.nextFieldName()) {
JsonToken startObject = parser.nextValue();
if (startObject != JsonToken.START_OBJECT) {
break;
}
if (CredentialPropertiesOutput.EXTENSION_ID.equals(key)) {
JsonToken next = parser.nextToken();
if (next == JsonToken.START_OBJECT && CredentialPropertiesOutput.EXTENSION_ID.equals(key)) {
CredentialPropertiesOutput output = parser.readValueAs(CredentialPropertiesOutput.class);
outputs.add(output);
}
@@ -74,7 +71,9 @@ class AuthenticationExtensionsClientOutputsJackson2Deserializer
if (logger.isDebugEnabled()) {
logger.debug("Skipping unknown extension with id " + key);
}
parser.nextValue();
if (next.isStructStart()) {
parser.skipChildren();
}
}
}
@@ -0,0 +1,36 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.jackson;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.security.web.webauthn.api.Bytes;
import org.springframework.security.web.webauthn.api.ImmutablePublicKeyCredentialUserEntity;
/**
* Jackson mixin for {@link ImmutablePublicKeyCredentialUserEntity}
*
* @author Toshiaki Maki
* @since 7.0.4
*/
abstract class ImmutablePublicKeyCredentialUserEntityMixin {
ImmutablePublicKeyCredentialUserEntityMixin(@JsonProperty("name") String name, @JsonProperty("id") Bytes id,
@JsonProperty("displayName") String displayName) {
}
}
@@ -0,0 +1,41 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.jackson;
import java.util.Collection;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity;
import org.springframework.security.web.webauthn.authentication.WebAuthnAuthentication;
/**
* Jackson mixin for {@link WebAuthnAuthentication}
*
* @author Toshiaki Maki
* @since 7.0.4
*/
@JsonIgnoreProperties({ "authenticated" })
abstract class WebAuthnAuthenticationMixin {
WebAuthnAuthenticationMixin(@JsonProperty("principal") PublicKeyCredentialUserEntity principal,
@JsonProperty("authorities") Collection<? extends GrantedAuthority> authorities) {
}
}
@@ -33,12 +33,14 @@ import org.springframework.security.web.webauthn.api.Bytes;
import org.springframework.security.web.webauthn.api.COSEAlgorithmIdentifier;
import org.springframework.security.web.webauthn.api.CredProtectAuthenticationExtensionsClientInput;
import org.springframework.security.web.webauthn.api.CredentialPropertiesOutput;
import org.springframework.security.web.webauthn.api.ImmutablePublicKeyCredentialUserEntity;
import org.springframework.security.web.webauthn.api.PublicKeyCredential;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialRequestOptions;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialType;
import org.springframework.security.web.webauthn.api.ResidentKeyRequirement;
import org.springframework.security.web.webauthn.api.UserVerificationRequirement;
import org.springframework.security.web.webauthn.authentication.WebAuthnAuthentication;
import org.springframework.security.web.webauthn.management.RelyingPartyPublicKey;
/**
@@ -47,6 +49,7 @@ import org.springframework.security.web.webauthn.management.RelyingPartyPublicKe
*
* @author Sebastien Deleuze
* @author Rob Winch
* @author Toshiaki Maki
* @since 7.0
*/
@SuppressWarnings("serial")
@@ -61,6 +64,8 @@ public class WebauthnJacksonModule extends SecurityJacksonModule {
@Override
public void configurePolymorphicTypeValidator(BasicPolymorphicTypeValidator.Builder builder) {
builder.allowIfSubType(WebAuthnAuthentication.class)
.allowIfSubType(ImmutablePublicKeyCredentialUserEntity.class);
}
@Override
@@ -92,6 +97,9 @@ public class WebauthnJacksonModule extends SecurityJacksonModule {
context.setMixIn(RelyingPartyPublicKey.class, RelyingPartyPublicKeyMixin.class);
context.setMixIn(ResidentKeyRequirement.class, ResidentKeyRequirementMixin.class);
context.setMixIn(UserVerificationRequirement.class, UserVerificationRequirementMixin.class);
context.setMixIn(WebAuthnAuthentication.class, WebAuthnAuthenticationMixin.class);
context.setMixIn(ImmutablePublicKeyCredentialUserEntity.class,
ImmutablePublicKeyCredentialUserEntityMixin.class);
}
}
@@ -122,6 +122,47 @@ class Jackson2Tests {
assertThat(outputs).usingRecursiveComparison().isEqualTo(credProps);
}
@Test
void readAuthenticationExtensionsClientOutputsWhenAppId() throws Exception {
String json = """
{
"appid": false,
"credProps": {
"rk": false
}
}
""";
CredentialPropertiesOutput credProps = new CredentialPropertiesOutput(false);
AuthenticationExtensionsClientOutputs outputs = this.mapper.readValue(json,
AuthenticationExtensionsClientOutputs.class);
assertThat(outputs.getOutputs()).usingRecursiveFieldByFieldElementComparator().contains(credProps);
}
@Test
void readAuthenticationExtensionsClientOutputsWhenUnknownExtension() throws Exception {
String json = """
{
"unknownObject1": {
"key": "value"
},
"unknownArray": [
{ "key": "value1" },
{ "key": "value2" }
],
"credProps": {
"rk": false
},
"unknownObject2": {}
}
""";
CredentialPropertiesOutput credProps = new CredentialPropertiesOutput(false);
AuthenticationExtensionsClientOutputs outputs = this.mapper.readValue(json,
AuthenticationExtensionsClientOutputs.class);
assertThat(outputs.getOutputs()).usingRecursiveFieldByFieldElementComparator().contains(credProps);
}
@Test
void readAuthenticationExtensionsClientOutputsWhenFieldAfter() throws Exception {
String json = """
@@ -121,6 +121,47 @@ class JacksonTests {
assertThat(outputs).usingRecursiveComparison().isEqualTo(credProps);
}
@Test
void readAuthenticationExtensionsClientOutputsWhenAppId() {
String json = """
{
"appid": false,
"credProps": {
"rk": false
}
}
""";
CredentialPropertiesOutput credProps = new CredentialPropertiesOutput(false);
AuthenticationExtensionsClientOutputs outputs = this.mapper.readValue(json,
AuthenticationExtensionsClientOutputs.class);
assertThat(outputs.getOutputs()).usingRecursiveFieldByFieldElementComparator().contains(credProps);
}
@Test
void readAuthenticationExtensionsClientOutputsWhenUnknownExtension() {
String json = """
{
"unknownObject1": {
"key": "value"
},
"unknownArray": [
{ "key": "value1" },
{ "key": "value2" }
],
"credProps": {
"rk": false
},
"unknownObject2": {}
}
""";
CredentialPropertiesOutput credProps = new CredentialPropertiesOutput(false);
AuthenticationExtensionsClientOutputs outputs = this.mapper.readValue(json,
AuthenticationExtensionsClientOutputs.class);
assertThat(outputs.getOutputs()).usingRecursiveFieldByFieldElementComparator().contains(credProps);
}
@Test
void readAuthenticationExtensionsClientOutputsWhenFieldAfter() throws Exception {
String json = """
@@ -0,0 +1,133 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.jackson;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.skyscreamer.jsonassert.JSONAssert;
import tools.jackson.databind.JacksonModule;
import tools.jackson.databind.json.JsonMapper;
import tools.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.jackson.SecurityJacksonModules;
import org.springframework.security.web.webauthn.api.Bytes;
import org.springframework.security.web.webauthn.api.ImmutablePublicKeyCredentialUserEntity;
import org.springframework.security.web.webauthn.authentication.WebAuthnAuthentication;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link WebAuthnAuthenticationMixin} and
* {@link ImmutablePublicKeyCredentialUserEntityMixin} with polymorphic type handling.
*
* <p>
* This test class is separate from {@link JacksonTests} because it requires a
* {@link JsonMapper} configured with {@link SecurityJacksonModules} to enable polymorphic
* type information ({@code @class}). {@link JacksonTests} uses a {@link JsonMapper}
* configured only with {@link WebauthnJacksonModule}, and its existing custom serializers
* are not compatible with the automatic inclusion of type information enabled by
* {@link SecurityJacksonModules}.
*
* @author Toshiaki Maki
* @since 7.1
*/
class WebAuthnAuthenticationMixinTests {
private JsonMapper mapper;
@BeforeEach
void setup() {
ClassLoader classLoader = getClass().getClassLoader();
WebauthnJacksonModule webauthnJacksonModule = new WebauthnJacksonModule();
BasicPolymorphicTypeValidator.Builder typeValidatorBuilder = BasicPolymorphicTypeValidator.builder();
webauthnJacksonModule.configurePolymorphicTypeValidator(typeValidatorBuilder);
List<JacksonModule> modules = SecurityJacksonModules.getModules(classLoader, typeValidatorBuilder);
modules.add(webauthnJacksonModule);
this.mapper = JsonMapper.builder().addModules(modules).build();
}
@Test
void writeWebAuthnAuthentication() throws Exception {
ImmutablePublicKeyCredentialUserEntity principal = (ImmutablePublicKeyCredentialUserEntity) ImmutablePublicKeyCredentialUserEntity
.builder()
.name("user@example.localhost")
.id(Bytes.fromBase64("oWJtkJ6vJ_m5b84LB4_K7QKTCTEwLIjCh4tFMCGHO4w"))
.displayName("User")
.build();
WebAuthnAuthentication authentication = new WebAuthnAuthentication(principal,
List.of(new SimpleGrantedAuthority("ROLE_USER")));
String json = this.mapper.writeValueAsString(authentication);
String expected = """
{
"@class": "org.springframework.security.web.webauthn.authentication.WebAuthnAuthentication",
"principal": {
"@class": "org.springframework.security.web.webauthn.api.ImmutablePublicKeyCredentialUserEntity",
"name": "user@example.localhost",
"id": "oWJtkJ6vJ_m5b84LB4_K7QKTCTEwLIjCh4tFMCGHO4w",
"displayName": "User"
},
"authorities": ["java.util.Collections$UnmodifiableRandomAccessList", [
{
"@class": "org.springframework.security.core.authority.SimpleGrantedAuthority",
"authority": "ROLE_USER"
}
]]
}
""";
JSONAssert.assertEquals(expected, json, false);
assertThat(json).doesNotContain("\"authenticated\"");
}
@Test
void readWebAuthnAuthentication() throws Exception {
String json = """
{
"@class": "org.springframework.security.web.webauthn.authentication.WebAuthnAuthentication",
"principal": {
"@class": "org.springframework.security.web.webauthn.api.ImmutablePublicKeyCredentialUserEntity",
"name": "user@example.localhost",
"id": "oWJtkJ6vJ_m5b84LB4_K7QKTCTEwLIjCh4tFMCGHO4w",
"displayName": "User"
},
"authorities": ["java.util.Collections$UnmodifiableRandomAccessList", [
{
"@class": "org.springframework.security.core.authority.SimpleGrantedAuthority",
"authority": "ROLE_USER"
}
]]
}
""";
ImmutablePublicKeyCredentialUserEntity expectedPrincipal = (ImmutablePublicKeyCredentialUserEntity) ImmutablePublicKeyCredentialUserEntity
.builder()
.name("user@example.localhost")
.id(Bytes.fromBase64("oWJtkJ6vJ_m5b84LB4_K7QKTCTEwLIjCh4tFMCGHO4w"))
.displayName("User")
.build();
WebAuthnAuthentication expected = new WebAuthnAuthentication(expectedPrincipal,
List.of(new SimpleGrantedAuthority("ROLE_USER")));
WebAuthnAuthentication authentication = this.mapper.readValue(json, WebAuthnAuthentication.class);
assertThat(authentication).usingRecursiveComparison().isEqualTo(expected);
}
}