1
0
mirror of synced 2026-07-12 06:10:02 +00:00

Compare commits

..

116 Commits

Author SHA1 Message Date
github-actions[bot] 20332fedb0 Release 5.8.13 2024-06-17 15:22:54 +00:00
dependabot[bot] 2537a033f5 Bump org.springframework:spring-framework-bom from 5.3.36 to 5.3.37
Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 5.3.36 to 5.3.37.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v5.3.36...v5.3.37)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-13 21:19:30 -07:00
Steve Riesenberg f622d8e2e2 Polish gh-15235 2024-06-13 16:00:03 -05:00
Harsh4902 92cab2b678 Add 'Required Dependencies' section in ldap.adoc file
Closes gh-14699
2024-06-13 16:00:02 -05:00
dependabot[bot] 254709ce0b Bump com.gradle.develocity from 3.17.4 to 3.17.5
Bumps com.gradle.develocity from 3.17.4 to 3.17.5.

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-12 20:46:14 -07:00
dependabot[bot] e9c1b7b0ae Bump io.projectreactor:reactor-bom from 2020.0.44 to 2020.0.45
Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2020.0.44 to 2020.0.45.
- [Release notes](https://github.com/reactor/reactor/releases)
- [Commits](https://github.com/reactor/reactor/compare/2020.0.44...2020.0.45)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-11 21:30:14 -07:00
dependabot[bot] 2b322da9ac Bump io.projectreactor.netty:reactor-netty from 1.0.45 to 1.0.46
Bumps [io.projectreactor.netty:reactor-netty](https://github.com/reactor/reactor-netty) from 1.0.45 to 1.0.46.
- [Release notes](https://github.com/reactor/reactor-netty/releases)
- [Commits](https://github.com/reactor/reactor-netty/compare/v1.0.45...v1.0.46)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-11 21:13:46 -07:00
Josh Cummings 6aabd768a8 Pick MvcRequestMatcher for MockMvc requests
Closes gh-13849
2024-06-06 13:17:43 -06:00
Josh Cummings cdd626644e Use Request-Level Servlet Context
Spring Security cannot use the ServletContext attached
to the ApplicationContext since there may be child
ApplicationContext's with their own ServletContext.

Because of that, it is necessary to always use the
ServletContext attached to the request.

Closes gh-14418
2024-06-03 17:41:51 -06:00
Josh Cummings 5a798e93f1 Polish MVC Tests
Issue gh-14418
2024-06-03 17:41:51 -06:00
dependabot[bot] aa88404d69 Bump org.hsqldb:hsqldb from 2.7.2 to 2.7.3
Bumps org.hsqldb:hsqldb from 2.7.2 to 2.7.3.

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-02 20:34:36 -07:00
Steve Riesenberg dcb8c563e8 Fix ArrayIndexOutOfBoundsException
Issue gh-13310
Closes gh-15184
2024-05-31 18:12:21 -05:00
Steve Riesenberg e34621ec2c Polish gh-14977 2024-05-29 16:23:00 -05:00
JANG 1695d03b72 Assert WebSession is not null
Issue gh-14975
2024-05-29 14:55:37 -05:00
Josh Cummings e7ea4091a0 Migrate SampleLDIF to UnboundID
Closes gh-15089
2024-05-24 15:03:53 -06:00
dependabot[bot] 235dfc4dfd Bump @antora/collector-extension in /docs
---
updated-dependencies:
- dependency-name: "@antora/collector-extension"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-24 07:57:40 -07:00
Josh Cummings 092a63d1e0 Conditionally Add Conventions Plugin 2024-05-23 16:56:35 -06:00
Caio Henrique 896dd93313 fix: add correction to java example in multitenancy.adoc
Closes gh-15146
2024-05-23 14:16:59 -03:00
Marcus Hert Da Coregio c7b739eb3f Fix broken link to jaspan article
Closes gh-14358
2024-05-23 14:04:10 -03:00
dependabot[bot] 32e860523c Bump @springio/antora-extensions from 1.10.0 to 1.11.1 in /docs
Bumps [@springio/antora-extensions](https://github.com/spring-io/antora-extensions) from 1.10.0 to 1.11.1.
- [Changelog](https://github.com/spring-io/antora-extensions/blob/main/CHANGELOG.adoc)
- [Commits](https://github.com/spring-io/antora-extensions/compare/v1.10.0...v1.11.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-23 04:10:21 -07:00
dependabot[bot] 58232b1172 Bump org.springframework:spring-framework-bom from 5.3.35 to 5.3.36
Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 5.3.35 to 5.3.36.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v5.3.35...v5.3.36)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-22 21:09:25 -07:00
dependabot[bot] 0e8fd1cd6c Bump com.github.spullara.mustache.java:compiler from 0.9.11 to 0.9.13
Bumps [com.github.spullara.mustache.java:compiler](https://github.com/spullara/mustache.java) from 0.9.11 to 0.9.13.
- [Commits](https://github.com/spullara/mustache.java/compare/mustache.java-0.9.11...mustache.java-0.9.13)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-21 10:38:07 -07:00
dependabot[bot] 1f001f8a6d Bump com.gradle.develocity from 3.17.2 to 3.17.4
Bumps com.gradle.develocity from 3.17.2 to 3.17.4.

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-21 08:36:59 -07:00
dependabot[bot] 3fdfa98172 Bump org.springframework:spring-framework-bom from 5.3.34 to 5.3.35
Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 5.3.34 to 5.3.35.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v5.3.34...v5.3.35)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-20 18:05:26 -07:00
dependabot[bot] 90f5141880 Bump io.projectreactor.netty:reactor-netty from 1.0.44 to 1.0.45
Bumps [io.projectreactor.netty:reactor-netty](https://github.com/reactor/reactor-netty) from 1.0.44 to 1.0.45.
- [Release notes](https://github.com/reactor/reactor-netty/releases)
- [Commits](https://github.com/reactor/reactor-netty/compare/v1.0.44...v1.0.45)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-20 15:48:30 -07:00
dependabot[bot] ab6667eb5c Bump io.projectreactor:reactor-bom from 2020.0.43 to 2020.0.44
Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2020.0.43 to 2020.0.44.
- [Release notes](https://github.com/reactor/reactor/releases)
- [Commits](https://github.com/reactor/reactor/compare/2020.0.43...2020.0.44)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-20 15:35:59 -07:00
Josh Cummings 4b10c2daac Turn Off Build Scans
Currently, --scan is preventing PR builds from completing.

Issue gh-15120
2024-05-20 16:20:43 -06:00
Josh Cummings 63e24a3026 Adjust JavaDoc Classpath
Issue gh-14931
2024-05-20 12:42:01 -06:00
github-actions[bot] 9027dceb71 Update Antora Spring UI to v0.4.15 2024-05-20 13:51:18 -03:00
Patrick Schmidt 1ebd663253 doc: added hint to declare GrantedAuthorityDefaults as infrastructure bean
Closes gh-14779
2024-05-13 11:19:41 -03:00
Marcus Hert Da Coregio 09198447af Attach Antora Docs to Pull Requests
Closes gh-14992
2024-05-13 08:50:24 -03:00
Steve Riesenberg e1ec77a7a1 Add Build Updates section to generated release notes
Closes gh-14485
2024-05-09 13:40:25 -05:00
dependabot[bot] d686037419 Bump io.spring.ge.conventions from 0.0.16 to 0.0.17
Bumps [io.spring.ge.conventions](https://github.com/spring-io/gradle-enterprise-conventions) from 0.0.16 to 0.0.17.
- [Release notes](https://github.com/spring-io/gradle-enterprise-conventions/releases)
- [Commits](https://github.com/spring-io/gradle-enterprise-conventions/compare/v0.0.16...v0.0.17)

---
updated-dependencies:
- dependency-name: io.spring.ge.conventions
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-08 10:03:34 -05:00
Steve Riesenberg 5c8311b7d3 Migrate to com.gradle.develocity plugin
Issue gh-15021
2024-05-08 10:03:34 -05:00
Steve Riesenberg d86545dd41 Update Gradle Enterprise plugin to 3.17.2
Issue gh-15020
2024-05-08 09:56:05 -05:00
Angel Gruevski ce995a0178 Correct a mistake in the documentation related to RequestCacheAwareFilter
Corrected a mistake in the documentation for the Spring Architecture where it was mentioned that RequestCacheAwareFilter is used to save the HttpServletRequest instead of the ExceptionTranslationFilter

Closes gh-14855
2024-04-30 08:58:25 -03:00
Josh Cummings e5ee45d568 Fix Import Error
Issue gh-14880
2024-04-26 17:20:53 -06:00
Abimael Sergio 3b9991fc89 Improve PasswordEncoder Error Messaging
Closes gh-14880
2024-04-26 17:13:17 -06:00
sheheryarumair 0e211382ee Remove useBase64 parameter 2024-04-26 17:05:49 -06:00
Josh Cummings c35e107744 Add ProxyRestriction Validator
Closes gh-14931
2024-04-25 16:04:37 -06:00
dependabot[bot] 8271258618 Bump slackapi/slack-github-action from 1.25.0 to 1.26.0
Bumps [slackapi/slack-github-action](https://github.com/slackapi/slack-github-action) from 1.25.0 to 1.26.0.
- [Release notes](https://github.com/slackapi/slack-github-action/releases)
- [Commits](https://github.com/slackapi/slack-github-action/compare/v1.25.0...v1.26.0)

---
updated-dependencies:
- dependency-name: slackapi/slack-github-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-21 18:12:20 -07:00
Rob Winch 1010d253a4 Merge branch '5.7.x' into 5.8.x 2024-04-19 14:20:16 -05:00
Rob Winch c8342fcfae Use antora-extensions 1.10.0 2024-04-19 14:16:02 -05:00
dependabot[bot] 2287d67106 Bump io.projectreactor.tools:blockhound
Bumps [io.projectreactor.tools:blockhound](https://github.com/reactor/BlockHound) from 1.0.8.RELEASE to 1.0.9.RELEASE.
- [Release notes](https://github.com/reactor/BlockHound/releases)
- [Commits](https://github.com/reactor/BlockHound/compare/1.0.8.RELEASE...1.0.9.RELEASE)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-17 17:01:58 -07:00
Josh Cummings 470e2c5c97 Address Build Issues
Issue gh-14837
2024-04-17 16:59:40 -06:00
Josh Cummings 657760af5b Improve Logging
Closes gh-14837
2024-04-17 16:43:29 -06:00
github-actions[bot] 71a51db3a1 Next development version 2024-04-15 15:58:06 +00:00
github-actions[bot] 17873adf1f Release 5.8.12 2024-04-15 15:21:00 +00:00
dependabot[bot] a608352689 Bump org.springframework:spring-framework-bom from 5.3.33 to 5.3.34
Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 5.3.33 to 5.3.34.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v5.3.33...v5.3.34)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-11 20:26:39 -07:00
dependabot[bot] c3dbbb2d01 Bump io.projectreactor:reactor-bom from 2020.0.42 to 2020.0.43
Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2020.0.42 to 2020.0.43.
- [Release notes](https://github.com/reactor/reactor/releases)
- [Commits](https://github.com/reactor/reactor/compare/2020.0.42...2020.0.43)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-09 21:20:55 -07:00
dependabot[bot] 9a5319d5a2 Bump io.projectreactor.netty:reactor-netty from 1.0.43 to 1.0.44
Bumps [io.projectreactor.netty:reactor-netty](https://github.com/reactor/reactor-netty) from 1.0.43 to 1.0.44.
- [Release notes](https://github.com/reactor/reactor-netty/releases)
- [Commits](https://github.com/reactor/reactor-netty/compare/v1.0.43...v1.0.44)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-09 21:05:03 -07:00
Josh Cummings 0af0751cfd Treat Map Method Parameter as Immutable
Closes gh-14802
2024-04-04 16:44:14 -06:00
Steve Riesenberg 39dbd24dcb Polish gh-14742 2024-04-04 14:51:19 -05:00
sheheryarumair 33ebd5405a Removed dataSource null validation
Fixed data source validation
2024-04-04 14:21:18 -05:00
dependabot[bot] 2d2c2d31fa Bump io.spring.ge.conventions from 0.0.15 to 0.0.16
Bumps [io.spring.ge.conventions](https://github.com/spring-io/gradle-enterprise-conventions) from 0.0.15 to 0.0.16.
- [Release notes](https://github.com/spring-io/gradle-enterprise-conventions/releases)
- [Commits](https://github.com/spring-io/gradle-enterprise-conventions/compare/v0.0.15...v0.0.16)

---
updated-dependencies:
- dependency-name: io.spring.ge.conventions
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-28 20:43:52 -07:00
Marcus Hert Da Coregio 6c9efc2f09 Merge branch '5.7.x' into 5.8.x 2024-03-18 09:27:41 -03:00
github-actions[bot] 1a163dca37 Next development version 2024-03-18 11:47:59 +00:00
github-actions[bot] 1b0c4d68da Next development version 2024-03-18 11:38:53 +00:00
github-actions[bot] b38b495630 Release 5.7.12 2024-03-18 11:10:08 +00:00
github-actions[bot] c8a0601e6a Release 5.8.11 2024-03-18 11:10:03 +00:00
Marcus Hert Da Coregio 2c9dc08e43 Merge branch '5.7.x' into 5.8.x
Closes gh-14664
2024-03-18 06:40:34 -03:00
Marcus Hert Da Coregio 5a7f12f1a9 Check for null Authentication
Closes gh-14715
2024-03-18 06:39:08 -03:00
dependabot[bot] 3b355a0278 Bump org.springframework:spring-framework-bom from 5.3.32 to 5.3.33
Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 5.3.32 to 5.3.33.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v5.3.32...v5.3.33)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-14 21:07:46 -07:00
Marcus Hert Da Coregio a6d362fa18 Make trigger-dependabot-auto-merge-forward.yml run on push
Issue gh-14721
2024-03-14 15:12:14 -03:00
Marcus Hert Da Coregio 37984c2e11 Add GH_TOKEN to set-milestone
Issue gh-14721
2024-03-14 14:45:36 -03:00
Marcus Hert Da Coregio b3a43d6154 Use token only for auto merge
Issue gh-14721
2024-03-14 14:42:33 -03:00
Marcus Hert Da Coregio a87fc4ea8a Use pull_request_target for merge-dependabot-pr.yml
Issue gh-14721
2024-03-14 14:33:00 -03:00
Marcus Hert Da Coregio e7bff4240a Use GH_ACTIONS_REPO_TOKEN for merge-dependabot-pr.yml
Issue gh-14721
2024-03-14 09:23:31 -03:00
dependabot[bot] 4e6b8e4d29 Bump io.projectreactor:reactor-bom from 2020.0.41 to 2020.0.42
Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2020.0.41 to 2020.0.42.
- [Release notes](https://github.com/reactor/reactor/releases)
- [Commits](https://github.com/reactor/reactor/compare/2020.0.41...2020.0.42)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-13 04:24:14 +00:00
dependabot[bot] e550c48180 Bump io.projectreactor.netty:reactor-netty from 1.0.41 to 1.0.43
Bumps [io.projectreactor.netty:reactor-netty](https://github.com/reactor/reactor-netty) from 1.0.41 to 1.0.43.
- [Release notes](https://github.com/reactor/reactor-netty/releases)
- [Commits](https://github.com/reactor/reactor-netty/compare/v1.0.41...v1.0.43)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-13 04:11:01 +00:00
Marcus Hert Da Coregio a3c06d98ca Trigger Dependabot Workflow when PR is merged
Issue gh-14721
2024-03-12 10:26:17 -03:00
Marcus Hert Da Coregio 8f42c86a57 Use AuthorizationInterceptorsOrder for Post Authorize Method Interceptors
Closes gh-14720
2024-03-12 10:17:45 -03:00
Marcus Hert Da Coregio 5dae6da15a Remove branch filter
Issue gh-14535
2024-03-11 09:25:58 -03:00
Josh Cummings e1c4177cd8 Fix ServerLogoutHandler Order in Docs
Closes gh-14379
2024-03-04 15:42:09 -07:00
Marcus Hert Da Coregio c2e97d7661 Ignore dependabot branches on trigger auto merge 2024-02-29 10:52:44 -03:00
Josh Cummings 94a66f7a26 Change Structure101 Repository URL
Issug gh-14639
2024-02-26 10:03:09 -07:00
Christian Becker 5f80468de3 Updated copyright date 2024-02-22 13:19:05 -07:00
Christian Becker 2f762fefe1 Allow tab in HTTP header values.
Closes gh-14573
2024-02-22 13:19:05 -07:00
DingHao 45c37c4454 Remove duplicate setSecurityContextHolderStrategy
Closes gh-14592
2024-02-22 12:14:35 -07:00
Josh Cummings 0c70f358d5 Update to Structure101 7.0.24375
Closes gh-14629
2024-02-20 15:43:58 -07:00
github-actions[bot] 7d38686e09 Next development version 2024-02-16 19:29:53 +00:00
github-actions[bot] 8b9beb0e1f Release 5.8.10 2024-02-16 18:47:27 +00:00
Marcus Hert Da Coregio 6230806d56 Change branch pattern
Issue gh-14535
2024-02-16 11:51:53 -03:00
Marcus Hert Da Coregio 2159f3a93d Fix branch pattern
Issue gh-14535
2024-02-16 09:50:42 -03:00
dependabot[bot] dc366cf8da Bump org.springframework:spring-framework-bom from 5.3.31 to 5.3.32
Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 5.3.31 to 5.3.32.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v5.3.31...v5.3.32)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-16 04:04:11 +00:00
Marcus Hert Da Coregio d32202bce3 Revert "Bump org-aspectj from 1.9.20.1 to 1.9.21.1"
This reverts commit a0b1d6317c.
2024-02-15 09:45:49 -03:00
dependabot[bot] 349ac07c10 Bump io.projectreactor:reactor-bom from 2020.0.40 to 2020.0.41
Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2020.0.40 to 2020.0.41.
- [Release notes](https://github.com/reactor/reactor/releases)
- [Commits](https://github.com/reactor/reactor/compare/2020.0.40...2020.0.41)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-15 06:42:20 -06:00
Marcus Hert Da Coregio 59be5553a1 Fix workflow syntax
Issue gh-14535
2024-02-15 09:23:35 -03:00
Marcus Hert Da Coregio 2bfdb0c6cd Only run workflow for branches that matches [5-9]+.[5-9]+.x
Issue gh-14535
2024-02-14 01:15:16 -03:00
dependabot[bot] 2ed7b857c3 Bump org-eclipse-jetty from 9.4.53.v20231009 to 9.4.54.v20240208
Bumps `org-eclipse-jetty` from 9.4.53.v20231009 to 9.4.54.v20240208.

Updates `org.eclipse.jetty:jetty-server` from 9.4.53.v20231009 to 9.4.54.v20240208

Updates `org.eclipse.jetty:jetty-servlet` from 9.4.53.v20231009 to 9.4.54.v20240208

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-14 03:57:50 +00:00
dependabot[bot] a0b1d6317c Bump org-aspectj from 1.9.20.1 to 1.9.21.1
Bumps `org-aspectj` from 1.9.20.1 to 1.9.21.1.

Updates `org.aspectj:aspectjrt` from 1.9.20.1 to 1.9.21.1
- [Release notes](https://github.com/eclipse/org.aspectj/releases)
- [Commits](https://github.com/eclipse/org.aspectj/commits)

Updates `org.aspectj:aspectjweaver` from 1.9.20.1 to 1.9.21.1
- [Release notes](https://github.com/eclipse/org.aspectj/releases)
- [Commits](https://github.com/eclipse/org.aspectj/commits)

---
updated-dependencies:
- dependency-name: org.aspectj:aspectjrt
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.aspectj:aspectjweaver
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-14 03:57:41 +00:00
Marcus Hert Da Coregio 731cd6131b Do not try to run trigger dependabot automerge workflow in PRs
Issue gh-14535
2024-02-13 10:45:08 -03:00
dependabot[bot] c2a589734c Bump spring-io/spring-github-workflows
Bumps [spring-io/spring-github-workflows](https://github.com/spring-io/spring-github-workflows) from eaf17a1890b1ef1b337f015d6eb263baaf8c6dab to 1e8b0587a1f4f01697f9753fa3339c3e0d30f396.
- [Release notes](https://github.com/spring-io/spring-github-workflows/releases)
- [Commits](https://github.com/spring-io/spring-github-workflows/compare/eaf17a1890b1ef1b337f015d6eb263baaf8c6dab...1e8b0587a1f4f01697f9753fa3339c3e0d30f396)

---
updated-dependencies:
- dependency-name: spring-io/spring-github-workflows
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-12 00:57:15 +00:00
dependabot[bot] e5e67f91e9 Bump Gamesight/slack-workflow-status from 1.2.0 to 1.3.0
Bumps [Gamesight/slack-workflow-status](https://github.com/gamesight/slack-workflow-status) from 1.2.0 to 1.3.0.
- [Release notes](https://github.com/gamesight/slack-workflow-status/releases)
- [Commits](https://github.com/gamesight/slack-workflow-status/compare/v1.2.0...v1.3.0)

---
updated-dependencies:
- dependency-name: Gamesight/slack-workflow-status
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-12 00:57:09 +00:00
Marcus Hert Da Coregio ccb2f06d0d Partially revert fc658d10
OpenIDAuthenticationFilter exists in versions < 6.0

Issue gh-14531
2024-02-07 10:13:34 -03:00
DingHao fc658d10d3 fix security filter sort in javadoc
Closes gh-14531
2024-02-07 09:37:01 -03:00
Marcus Hert Da Coregio e0fc8f37b0 Use latest version of spring-merge-dependabot-pr workflow
Issue gh-14535
2024-02-06 11:05:56 -03:00
Marcus Hert Da Coregio 43ced5291b Only run workflow for spring-projects repositories
Issue gh-14535
2024-02-06 08:18:31 -03:00
Marcus Hert Da Coregio 56c5fc281e Automatically trigger auto merge for Dependabot PRs
Issue gh-14535
2024-02-06 08:06:27 -03:00
Josh Cummings 7c3a6a567e Fix Compilation Errors
Issue gh-14525
2024-02-05 15:18:31 -07:00
Andreas Asplund 07e0b1dc37 Saml2 LogoutFilter Is Placed Before Common LogoutFilter
Closes gh-14525
2024-02-05 15:18:31 -07:00
Marcus Hert Da Coregio 05d3c4b695 Add permission to Edit Dependabot PR workflow
Issue gh-14486
2024-02-02 09:30:50 -03:00
Marcus Hert Da Coregio 5f80cfc705 Automatically assign milestone to Dependabot PR
Issue gh-14486
2024-02-02 09:30:39 -03:00
dependabot[bot] 7011930305 Bump gradle/gradle-build-action from 2 to 3
Bumps [gradle/gradle-build-action](https://github.com/gradle/gradle-build-action) from 2 to 3.
- [Release notes](https://github.com/gradle/gradle-build-action/releases)
- [Commits](https://github.com/gradle/gradle-build-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: gradle/gradle-build-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-29 17:44:51 -07:00
dependabot[bot] 8a75382b2d Bump slackapi/slack-github-action from 1.24.0 to 1.25.0
Bumps [slackapi/slack-github-action](https://github.com/slackapi/slack-github-action) from 1.24.0 to 1.25.0.
- [Release notes](https://github.com/slackapi/slack-github-action/releases)
- [Commits](https://github.com/slackapi/slack-github-action/compare/v1.24.0...v1.25.0)

---
updated-dependencies:
- dependency-name: slackapi/slack-github-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-29 17:41:43 -07:00
Hans Lindner ca10187fd1 Enhance JWT decoding error handling
Previously, the `decode` method threw a `JwtException` directly when encountering an unsupported algorithm or any exception during parsing. This commit introduces a more robust error handling mechanism. Now, instead of throwing exceptions directly, it returns a `Mono.error()` with a `BadJwtException` containing detailed error information. This approach provides more flexibility and allows the caller to handle errors in a more granular way, by being able to use project reactors onError functionality.

Closes gh-14467
2024-01-25 17:32:10 -07:00
dependabot[bot] 56f486588f Bump io.spring.ge.conventions from 0.0.14 to 0.0.15
Bumps [io.spring.ge.conventions](https://github.com/spring-io/gradle-enterprise-conventions) from 0.0.14 to 0.0.15.
- [Release notes](https://github.com/spring-io/gradle-enterprise-conventions/releases)
- [Commits](https://github.com/spring-io/gradle-enterprise-conventions/compare/v0.0.14...v0.0.15)

---
updated-dependencies:
- dependency-name: io.spring.ge.conventions
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-17 14:31:01 -03:00
dependabot[bot] 3f5f79d835 Bump io.projectreactor.netty:reactor-netty from 1.0.40 to 1.0.41
Bumps [io.projectreactor.netty:reactor-netty](https://github.com/reactor/reactor-netty) from 1.0.40 to 1.0.41.
- [Release notes](https://github.com/reactor/reactor-netty/releases)
- [Commits](https://github.com/reactor/reactor-netty/compare/v1.0.40...v1.0.41)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-12 09:52:45 -06:00
dependabot[bot] 9985580534 Bump io.projectreactor:reactor-bom from 2020.0.39 to 2020.0.40
Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2020.0.39 to 2020.0.40.
- [Release notes](https://github.com/reactor/reactor/releases)
- [Commits](https://github.com/reactor/reactor/compare/2020.0.39...2020.0.40)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-12 09:42:04 -06:00
dependabot[bot] 24d4abe5fd Bump io-spring-javaformat from 0.0.40 to 0.0.41
Bumps `io-spring-javaformat` from 0.0.40 to 0.0.41.

Updates `io.spring.javaformat:spring-javaformat-checkstyle` from 0.0.40 to 0.0.41
- [Release notes](https://github.com/spring-io/spring-javaformat/releases)
- [Commits](https://github.com/spring-io/spring-javaformat/compare/v0.0.40...v0.0.41)

Updates `io.spring.javaformat:spring-javaformat-gradle-plugin` from 0.0.40 to 0.0.41
- [Release notes](https://github.com/spring-io/spring-javaformat/releases)
- [Commits](https://github.com/spring-io/spring-javaformat/compare/v0.0.40...v0.0.41)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-12 09:37:19 -06:00
Steve Riesenberg 16dc6be3c8 Update copyright year
Issue gh-14329
2023-12-28 12:54:29 -06:00
Geir Hedemark c88aaedb48 Updated broken documentation link in javadocs 2023-12-28 12:54:29 -06:00
daniKir 9203567a20 improve Multitenancy Issuer Validator example Closes gh-14229 2023-12-19 18:04:50 -07:00
Josh Cummings e058b559b8 Polish Method Security Eager-Loading
Issue gh-11596
2023-12-18 15:18:09 -07:00
Josh Cummings 33800c0124 Address eager-loading of infrastructure beans
Closes gh-11596
2023-12-18 14:25:48 -07:00
github-actions[bot] b71962e87d Next development version 2023-12-18 19:49:40 +00:00
65 changed files with 930 additions and 369 deletions
@@ -10,9 +10,7 @@ on:
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
GRADLE_ENTERPRISE_CACHE_USER: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }}
GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }}
GRADLE_ENTERPRISE_SECRET_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
DEVELOCITY_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
COMMIT_OWNER: ${{ github.event.pusher.name }}
COMMIT_SHA: ${{ github.sha }}
STRUCTURE101_LICENSEID: ${{ secrets.STRUCTURE101_LICENSEID }}
@@ -57,14 +55,10 @@ jobs:
distribution: 'adopt'
cache: 'gradle'
- name: Set up Gradle
uses: gradle/gradle-build-action@v2
uses: gradle/gradle-build-action@v3
- name: Set up gradle user name
run: echo 'systemProp.user.name=spring-builds+github' >> gradle.properties
- name: Build with Gradle
env:
GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }}
GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }}
GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
run: ./gradlew clean build --continue -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD"
snapshot_tests:
name: Test against snapshots
@@ -79,11 +73,7 @@ jobs:
java-version: '11'
distribution: 'adopt'
- name: Snapshot Tests
run: |
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD"
export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY"
./gradlew test --refresh-dependencies -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" -PforceMavenRepositories=snapshot -PisOverrideVersionCatalog -PspringFrameworkVersion='5.+' -PreactorVersion='2020.0.+' -PspringDataVersion='2021.2.+' -PlocksDisabled --stacktrace
run: ./gradlew test --refresh-dependencies -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" -PforceMavenRepositories=snapshot -PisOverrideVersionCatalog -PspringFrameworkVersion='5.+' -PreactorVersion='2020.0.+' -PspringDataVersion='2021.2.+' -PlocksDisabled --stacktrace
check_samples:
name: Check Samples project
needs: [prerequisites]
@@ -102,9 +92,6 @@ jobs:
SAMPLES_DIR: ../spring-security-samples
VERSION: ${{ needs.prerequisites.outputs.project_version }}
run: |
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD"
export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY"
./gradlew publishMavenJavaPublicationToLocalRepository
./gradlew cloneSamples -PcloneOutputDirectory="$SAMPLES_DIR"
./gradlew --project-dir "$SAMPLES_DIR" --init-script spring-security-ci.gradle -PlocalRepositoryPath="$LOCAL_REPOSITORY_PATH" -PspringSecurityVersion="$VERSION" :runAllTests
@@ -121,11 +108,7 @@ jobs:
java-version: '11'
distribution: 'adopt'
- name: Check for package tangles
run: |
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD"
export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY"
./gradlew check s101 -Ps101.licenseId="$STRUCTURE101_LICENSEID" --stacktrace
run: ./gradlew check s101 -Ps101.licenseId="$STRUCTURE101_LICENSEID" --stacktrace
deploy_artifacts:
name: Deploy Artifacts
needs: [build_jdk_11, snapshot_tests, check_samples, check_tangles]
@@ -138,11 +121,7 @@ jobs:
java-version: '11'
distribution: 'adopt'
- name: Deploy artifacts
run: |
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD"
export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY"
./gradlew publishArtifacts finalizeDeployArtifacts -PossrhUsername="$OSSRH_TOKEN_USERNAME" -PossrhPassword="$OSSRH_TOKEN_PASSWORD" -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" --stacktrace
run: ./gradlew publishArtifacts finalizeDeployArtifacts -PossrhUsername="$OSSRH_TOKEN_USERNAME" -PossrhPassword="$OSSRH_TOKEN_PASSWORD" -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" --stacktrace
env:
ORG_GRADLE_PROJECT_signingKey: ${{ secrets.GPG_PRIVATE_KEY }}
ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.GPG_PASSPHRASE }}
@@ -162,11 +141,7 @@ jobs:
java-version: '11'
distribution: 'adopt'
- name: Deploy Docs
run: |
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD"
export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY"
./gradlew deployDocs -PdeployDocsSshKey="$DOCS_SSH_KEY" -PdeployDocsSshUsername="$DOCS_USERNAME" -PdeployDocsHost="$DOCS_HOST" --stacktrace
run: ./gradlew deployDocs -PdeployDocsSshKey="$DOCS_SSH_KEY" -PdeployDocsSshUsername="$DOCS_USERNAME" -PdeployDocsHost="$DOCS_HOST" --stacktrace
env:
DOCS_USERNAME: ${{ secrets.DOCS_USERNAME }}
DOCS_SSH_KEY: ${{ secrets.DOCS_SSH_KEY }}
@@ -183,11 +158,7 @@ jobs:
java-version: '11'
distribution: 'adopt'
- name: Deploy Schema
run: |
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD"
export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY"
./gradlew deploySchema -PdeployDocsSshKey="$DOCS_SSH_KEY" -PdeployDocsSshUsername="$DOCS_USERNAME" -PdeployDocsHost="$DOCS_HOST" --stacktrace --info
run: ./gradlew deploySchema -PdeployDocsSshKey="$DOCS_SSH_KEY" -PdeployDocsSshUsername="$DOCS_USERNAME" -PdeployDocsHost="$DOCS_HOST" --stacktrace --info
env:
DOCS_USERNAME: ${{ secrets.DOCS_USERNAME }}
DOCS_SSH_KEY: ${{ secrets.DOCS_SSH_KEY }}
@@ -236,14 +207,11 @@ jobs:
echo "Artifacts for $REPO@$VERSION have been released to Maven Central."
- name: Create GitHub Release
run: |
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD"
export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY"
echo "Tagging and publishing $REPO@$VERSION release on GitHub."
./gradlew createGitHubRelease -PnextVersion=$VERSION -Pbranch=$BRANCH -PcreateRelease=true -PgitHubAccessToken=$TOKEN
- name: Announce Release on Slack
id: spring-security-announcing
uses: slackapi/slack-github-action@v1.24.0
uses: slackapi/slack-github-action@v1.26.0
with:
payload: |
{
@@ -267,9 +235,6 @@ jobs:
git config user.email 'github-actions[bot]@users.noreply.github.com'
- name: Update to next Snapshot Version
run: |
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD"
export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY"
echo "Updating $REPO@$VERSION to next snapshot version."
./gradlew :updateToSnapshotVersion
git commit -am "Next development version"
+52
View File
@@ -0,0 +1,52 @@
name: Merge Dependabot PR
on: pull_request_target
run-name: Merge Dependabot PR ${{ github.ref_name }}
permissions: write-all
jobs:
merge-dependabot-pr:
runs-on: ubuntu-latest
if: github.actor == 'dependabot[bot]'
steps:
- uses: actions/checkout@v4
with:
show-progress: false
ref: ${{ github.event.pull_request.head.sha }}
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 17
- name: Set Milestone to Dependabot Pull Request
id: set-milestone
run: |
if test -f pom.xml
then
CURRENT_VERSION=$(mvn help:evaluate -Dexpression="project.version" -q -DforceStdout)
else
CURRENT_VERSION=$(cat gradle.properties | sed -n '/^version=/ { s/^version=//;p }')
fi
export CANDIDATE_VERSION=${CURRENT_VERSION/-SNAPSHOT}
MILESTONE=$(gh api repos/$GITHUB_REPOSITORY/milestones --jq 'map(select(.due_on != null and (.title | startswith(env.CANDIDATE_VERSION)))) | .[0] | .title')
if [ -z $MILESTONE ]
then
gh run cancel ${{ github.run_id }}
echo "::warning title=Cannot merge::No scheduled milestone for $CURRENT_VERSION version"
else
gh pr edit ${{ github.event.pull_request.number }} --milestone $MILESTONE
echo mergeEnabled=true >> $GITHUB_OUTPUT
fi
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Merge Dependabot pull request
if: steps.set-milestone.outputs.mergeEnabled
run: gh pr merge ${{ github.event.pull_request.number }} --auto --rebase
env:
GH_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
@@ -32,7 +32,7 @@ jobs:
actions: read
steps:
- name: Send Slack message
uses: Gamesight/slack-workflow-status@v1.2.0
uses: Gamesight/slack-workflow-status@v1.3.0
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
slack_webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }}
+22 -4
View File
@@ -3,9 +3,7 @@ name: PR Build
on: pull_request
env:
GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }}
GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }}
GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
DEVELOCITY_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
permissions:
contents: read
@@ -23,4 +21,24 @@ jobs:
java-version: '11'
distribution: 'adopt'
- name: Build with Gradle
run: ./gradlew clean build --continue --scan
run: ./gradlew clean build --continue
generate-docs:
name: Generate Docs
runs-on: ubuntu-latest
if: ${{ github.repository == 'spring-projects/spring-security' }}
steps:
- uses: actions/checkout@v4
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v2
with:
java-version: '11'
distribution: 'adopt'
- name: Run Antora
run: ./gradlew -PbuildSrc.skipTests=true :spring-security-docs:antora
- name: Upload Docs
id: upload
uses: actions/upload-artifact@v4
with:
name: docs
path: docs/build/site
overwrite: true
@@ -0,0 +1,22 @@
name: Trigger Dependabot Auto Merge Forward
on:
push:
branches:
- '*.x'
permissions: read-all
jobs:
trigger-worflow:
name: Trigger Workflow
runs-on: ubuntu-latest
if: ${{ github.event.commits[0].author.username == 'dependabot[bot]' && github.repository == 'spring-projects/spring-security' }}
steps:
- name: Checkout
id: checkout
uses: actions/checkout@v4
- id: trigger
env:
GH_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
run: gh workflow run dependabot-auto-merge-forward.yml -r main
@@ -5,9 +5,7 @@ on:
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
GRADLE_ENTERPRISE_CACHE_USER: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }}
GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }}
GRADLE_ENTERPRISE_SECRET_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
DEVELOCITY_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
permissions:
contents: read
@@ -34,18 +32,12 @@ jobs:
- id: check-release-due
name: Check Release Due
run: |
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD"
export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY"
./gradlew gitHubCheckNextVersionDueToday
echo "is_due_today=$(cat build/github/milestones/is-due-today)" >>$GITHUB_OUTPUT
- id: check-open-issues
name: Check for open issues
if: steps.check-release-due.outputs.is_due_today == 'true'
run: |
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD"
export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY"
./gradlew gitHubCheckMilestoneHasNoOpenIssues
echo "is_open_issues=$(cat build/github/milestones/is-open-issues)" >>$GITHUB_OUTPUT
- id: validate-release-state
@@ -58,9 +50,6 @@ jobs:
name: Update version and push
if: steps.check-release-due.outputs.is_due_today == 'true' && steps.check-open-issues.outputs.is_open_issues == 'false'
run: |
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD"
export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY"
git config user.name 'github-actions[bot]'
git config user.email 'github-actions[bot]@users.noreply.github.com'
./gradlew :updateProjectVersion
@@ -72,7 +61,7 @@ jobs:
- id: send-slack-notification
name: Send Slack message
if: failure()
uses: Gamesight/slack-workflow-status@v1.2.0
uses: Gamesight/slack-workflow-status@v1.3.0
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
slack_webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }}
+2
View File
@@ -28,3 +28,5 @@ s101plugin.state
!.idea/checkstyle-idea.xml
!.idea/externalDependencies.xml
node_modules
+4 -3
View File
@@ -129,10 +129,10 @@ allprojects {
}
}
if (hasProperty('buildScan')) {
develocity {
buildScan {
termsOfServiceUrl = 'https://gradle.com/terms-of-service'
termsOfServiceAgree = 'yes'
termsOfUseUrl = 'https://gradle.com/help/legal-terms-of-use'
termsOfUseAgree = 'yes'
}
}
@@ -148,5 +148,6 @@ tasks.register('cloneSamples', IncludeRepoTask) {
}
s101 {
repository = 'https://structure101.com/binaries/latest'
configurationDirectory = project.file("etc/s101")
}
+11 -46
View File
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,6 @@
package s101;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -34,18 +33,11 @@ import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.HtmlAnchor;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.github.mustachejava.DefaultMustacheFactory;
import com.github.mustachejava.Mustache;
import com.github.mustachejava.MustacheFactory;
@@ -71,6 +63,10 @@ public class S101Configurer {
private final Path licenseDirectory;
private final String repository;
private final String version;
private final Project project;
private final Logger logger;
@@ -90,6 +86,9 @@ public class S101Configurer {
throw new UncheckedIOException(ex);
}
this.licenseDirectory = new File(System.getProperty("user.home") + "/.Structure101/java").toPath();
S101PluginExtension extension = project.getExtensions().getByType(S101PluginExtension.class);
this.repository = extension.getRepository().get();
this.version = extension.getVersion().get();
}
public void license(String licenseId) {
@@ -129,25 +128,7 @@ public class S101Configurer {
public void configure(File installationDirectory, File configurationDirectory) {
deleteDirectory(configurationDirectory);
String version = computeVersionFromInstallation(installationDirectory);
configureProject(version, configurationDirectory);
}
private String computeVersionFromInstallation(File installationDirectory) {
File buildJar = new File(installationDirectory, "structure101-java-build.jar");
try (JarInputStream input = new JarInputStream(new FileInputStream(buildJar))) {
JarEntry entry;
while ((entry = input.getNextJarEntry()) != null) {
if (entry.getName().contains("structure101-build.properties")) {
Properties properties = new Properties();
properties.load(input);
return properties.getProperty("s101-build");
}
}
} catch (Exception ex) {
throw new RuntimeException(ex);
}
throw new IllegalStateException("Unable to determine Structure101 version");
configureProject(this.version, configurationDirectory);
}
private boolean deleteDirectory(File directoryToBeDeleted) {
@@ -161,24 +142,8 @@ public class S101Configurer {
}
private String installBuildTool(File installationDirectory, File configurationDirectory) {
String source = "https://structure101.com/binaries/v6";
try (final WebClient webClient = new WebClient()) {
HtmlPage page = webClient.getPage(source);
Matcher matcher = null;
for (HtmlAnchor anchor : page.getAnchors()) {
Matcher candidate = Pattern.compile("(structure101-build-java-all-)(.*).zip").matcher(anchor.getHrefAttribute());
if (candidate.find()) {
matcher = candidate;
}
}
if (matcher == null) {
return null;
}
copyZipToFilesystem(source, installationDirectory, matcher.group(1) + matcher.group(2));
return matcher.group(2);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
copyZipToFilesystem(this.repository, installationDirectory, "structure101-build-java-all-" + this.version);
return this.version;
}
private void copyZipToFilesystem(String source, File destination, String name) {
+1 -1
View File
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,12 @@
package s101;
import java.io.File;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.HtmlAnchor;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import org.gradle.api.Project;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;
@@ -25,6 +30,11 @@ import org.gradle.api.tasks.InputDirectory;
public class S101PluginExtension {
private final Property<String> licenseId;
private final Property<String> repository;
private final Property<String> version;
private final Property<File> installationDirectory;
private final Property<File> configurationDirectory;
private final Property<String> label;
@@ -65,6 +75,24 @@ public class S101PluginExtension {
this.label.set(label);
}
@Input
public Property<String> getRepository() {
return repository;
}
public void setRepository(String repository) {
this.repository.set(repository);
}
@Input
public Property<String> getVersion() {
return this.version;
}
public void setVersion(String version) {
this.version.set(version);
}
public S101PluginExtension(Project project) {
this.licenseId = project.getObjects().property(String.class);
if (project.hasProperty("s101.licenseId")) {
@@ -78,5 +106,31 @@ public class S101PluginExtension {
if (project.hasProperty("s101.label")) {
setLabel((String) project.findProperty("s101.label"));
}
this.repository = project.getObjects().property(String.class);
if (project.hasProperty("s101.repository")) {
setRepository((String) project.findProperty("s101.repository"));
} else {
setRepository("https://structure101.com/binaries/v6");
}
this.version = project.getObjects().property(String.class);
if (project.hasProperty("s101.version")) {
setVersion((String) project.findProperty("s101.version"));
} else {
try (final WebClient webClient = new WebClient()) {
HtmlPage page = webClient.getPage(getRepository().get());
Matcher matcher = null;
for (HtmlAnchor anchor : page.getAnchors()) {
Matcher candidate = Pattern.compile("(structure101-build-java-all-)(.*).zip").matcher(anchor.getHrefAttribute());
if (candidate.find()) {
matcher = candidate;
}
}
if (matcher != null) {
setVersion(matcher.group(2));
}
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -149,7 +149,7 @@ public class AuthenticationManagerBuilder
* {@link #getDefaultUserDetailsService()} method. Note that additional
* {@link UserDetailsService}'s may override this {@link UserDetailsService} as the
* default. See the <a href=
* "https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#user-schema"
* "https://docs.spring.io/spring-security/reference/servlet/appendix/database-schema.html"
* >User Schema</a> section of the reference for the default schema.
* </p>
* @return a {@link JdbcUserDetailsManagerConfigurer} to allow customization of the
@@ -43,6 +43,7 @@ import org.springframework.security.config.core.GrantedAuthorityDefaults;
* @since 5.0
*/
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
class ReactiveMethodSecurityConfiguration implements ImportAware {
private int advisorOrder;
@@ -51,16 +52,17 @@ class ReactiveMethodSecurityConfiguration implements ImportAware {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
MethodSecurityMetadataSourceAdvisor methodSecurityInterceptor(AbstractMethodSecurityMetadataSource source) {
static MethodSecurityMetadataSourceAdvisor methodSecurityInterceptor(AbstractMethodSecurityMetadataSource source,
ReactiveMethodSecurityConfiguration configuration) {
MethodSecurityMetadataSourceAdvisor advisor = new MethodSecurityMetadataSourceAdvisor(
"securityMethodInterceptor", source, "methodMetadataSource");
advisor.setOrder(this.advisorOrder);
advisor.setOrder(configuration.advisorOrder);
return advisor;
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
DelegatingMethodSecurityMetadataSource methodMetadataSource(
static DelegatingMethodSecurityMetadataSource methodMetadataSource(
MethodSecurityExpressionHandler methodSecurityExpressionHandler) {
ExpressionBasedAnnotationAttributeFactory attributeFactory = new ExpressionBasedAnnotationAttributeFactory(
methodSecurityExpressionHandler);
@@ -70,7 +72,7 @@ class ReactiveMethodSecurityConfiguration implements ImportAware {
}
@Bean
PrePostAdviceReactiveMethodInterceptor securityMethodInterceptor(AbstractMethodSecurityMetadataSource source,
static PrePostAdviceReactiveMethodInterceptor securityMethodInterceptor(AbstractMethodSecurityMetadataSource source,
MethodSecurityExpressionHandler handler) {
ExpressionBasedPostInvocationAdvice postAdvice = new ExpressionBasedPostInvocationAdvice(handler);
ExpressionBasedPreInvocationAdvice preAdvice = new ExpressionBasedPreInvocationAdvice();
@@ -80,10 +82,11 @@ class ReactiveMethodSecurityConfiguration implements ImportAware {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler() {
static DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler(
ReactiveMethodSecurityConfiguration configuration) {
DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
if (this.grantedAuthorityDefaults != null) {
handler.setDefaultRolePrefix(this.grantedAuthorityDefaults.getRolePrefix());
if (configuration.grantedAuthorityDefaults != null) {
handler.setDefaultRolePrefix(configuration.grantedAuthorityDefaults.getRolePrefix());
}
return handler;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@ import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
@@ -43,11 +44,13 @@ import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
import org.springframework.security.web.util.matcher.DispatcherTypeRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RegexRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
/**
@@ -322,43 +325,22 @@ public abstract class AbstractRequestMatcherRegistry<C> {
if (servletContext == null) {
return requestMatchers(RequestMatchers.antMatchersAsArray(method, patterns));
}
boolean isProgrammaticApiAvailable = isProgrammaticApiAvailable(servletContext);
List<RequestMatcher> matchers = new ArrayList<>();
for (String pattern : patterns) {
AntPathRequestMatcher ant = new AntPathRequestMatcher(pattern, (method != null) ? method.name() : null);
MvcRequestMatcher mvc = createMvcMatchers(method, pattern).get(0);
if (isProgrammaticApiAvailable) {
matchers.add(resolve(ant, mvc, servletContext));
}
else {
this.logger
.warn("The ServletRegistration API was not available at startup time. This may be due to a misconfiguration; "
+ "if you are using AbstractSecurityWebApplicationInitializer, please double-check the recommendations outlined in "
+ "https://docs.spring.io/spring-security/reference/servlet/configuration/java.html#abstractsecuritywebapplicationinitializer-with-spring-mvc");
matchers.add(new DeferredRequestMatcher((request) -> resolve(ant, mvc, request.getServletContext()),
mvc, ant));
}
matchers.add(new DeferredRequestMatcher((c) -> resolve(ant, mvc, c), mvc, ant));
}
return requestMatchers(matchers.toArray(new RequestMatcher[0]));
}
private static boolean isProgrammaticApiAvailable(ServletContext servletContext) {
try {
servletContext.getServletRegistrations();
return true;
}
catch (UnsupportedOperationException ex) {
return false;
}
}
private RequestMatcher resolve(AntPathRequestMatcher ant, MvcRequestMatcher mvc, ServletContext servletContext) {
Map<String, ? extends ServletRegistration> registrations = mappableServletRegistrations(servletContext);
if (registrations.isEmpty()) {
return ant;
return new DispatcherServletDelegatingRequestMatcher(ant, mvc, new MockMvcRequestMatcher());
}
if (!hasDispatcherServlet(registrations)) {
return ant;
return new DispatcherServletDelegatingRequestMatcher(ant, mvc, new MockMvcRequestMatcher());
}
ServletRegistration dispatcherServlet = requireOneRootDispatcherServlet(registrations);
if (dispatcherServlet != null) {
@@ -593,34 +575,29 @@ public abstract class AbstractRequestMatcherRegistry<C> {
static class DeferredRequestMatcher implements RequestMatcher {
final Function<HttpServletRequest, RequestMatcher> requestMatcherFactory;
final Function<ServletContext, RequestMatcher> requestMatcherFactory;
final AtomicReference<String> description = new AtomicReference<>();
volatile RequestMatcher requestMatcher;
final Map<ServletContext, RequestMatcher> requestMatchers = new ConcurrentHashMap<>();
DeferredRequestMatcher(Function<HttpServletRequest, RequestMatcher> resolver, RequestMatcher... candidates) {
this.requestMatcherFactory = (request) -> {
if (this.requestMatcher == null) {
synchronized (this) {
if (this.requestMatcher == null) {
this.requestMatcher = resolver.apply(request);
}
}
}
return this.requestMatcher;
};
DeferredRequestMatcher(Function<ServletContext, RequestMatcher> resolver, RequestMatcher... candidates) {
this.requestMatcherFactory = (sc) -> this.requestMatchers.computeIfAbsent(sc, resolver);
this.description.set("Deferred " + Arrays.toString(candidates));
}
RequestMatcher requestMatcher(ServletContext servletContext) {
return this.requestMatcherFactory.apply(servletContext);
}
@Override
public boolean matches(HttpServletRequest request) {
return this.requestMatcherFactory.apply(request).matches(request);
return this.requestMatcherFactory.apply(request.getServletContext()).matches(request);
}
@Override
public MatchResult matcher(HttpServletRequest request) {
return this.requestMatcherFactory.apply(request).matcher(request);
return this.requestMatcherFactory.apply(request.getServletContext()).matcher(request);
}
@Override
@@ -630,27 +607,70 @@ public abstract class AbstractRequestMatcherRegistry<C> {
}
static class MockMvcRequestMatcher implements RequestMatcher {
@Override
public boolean matches(HttpServletRequest request) {
return request.getAttribute("org.springframework.test.web.servlet.MockMvc.MVC_RESULT_ATTRIBUTE") != null;
}
}
static class DispatcherServletRequestMatcher implements RequestMatcher {
private final ServletContext servletContext;
DispatcherServletRequestMatcher(ServletContext servletContext) {
this.servletContext = servletContext;
}
@Override
public boolean matches(HttpServletRequest request) {
String name = request.getHttpServletMapping().getServletName();
ServletRegistration registration = this.servletContext.getServletRegistration(name);
Assert.notNull(name, "Failed to find servlet [" + name + "] in the servlet context");
try {
Class<?> clazz = Class.forName(registration.getClassName());
return DispatcherServlet.class.isAssignableFrom(clazz);
}
catch (ClassNotFoundException ex) {
return false;
}
}
}
static class DispatcherServletDelegatingRequestMatcher implements RequestMatcher {
private final AntPathRequestMatcher ant;
private final MvcRequestMatcher mvc;
private final ServletContext servletContext;
private final RequestMatcher dispatcherServlet;
DispatcherServletDelegatingRequestMatcher(AntPathRequestMatcher ant, MvcRequestMatcher mvc,
ServletContext servletContext) {
this(ant, mvc, new OrRequestMatcher(new MockMvcRequestMatcher(),
new DispatcherServletRequestMatcher(servletContext)));
}
DispatcherServletDelegatingRequestMatcher(AntPathRequestMatcher ant, MvcRequestMatcher mvc,
RequestMatcher dispatcherServlet) {
this.ant = ant;
this.mvc = mvc;
this.servletContext = servletContext;
this.dispatcherServlet = dispatcherServlet;
}
RequestMatcher requestMatcher(HttpServletRequest request) {
if (this.dispatcherServlet.matches(request)) {
return this.mvc;
}
return this.ant;
}
@Override
public boolean matches(HttpServletRequest request) {
String name = request.getHttpServletMapping().getServletName();
ServletRegistration registration = this.servletContext.getServletRegistration(name);
Assert.notNull(registration, "Failed to find servlet [" + name + "] in the servlet context");
if (isDispatcherServlet(registration)) {
if (this.dispatcherServlet.matches(request)) {
return this.mvc.matches(request);
}
return this.ant.matches(request);
@@ -658,27 +678,12 @@ public abstract class AbstractRequestMatcherRegistry<C> {
@Override
public MatchResult matcher(HttpServletRequest request) {
String name = request.getHttpServletMapping().getServletName();
ServletRegistration registration = this.servletContext.getServletRegistration(name);
Assert.notNull(registration, "Failed to find servlet [" + name + "] in the servlet context");
if (isDispatcherServlet(registration)) {
if (this.dispatcherServlet.matches(request)) {
return this.mvc.matcher(request);
}
return this.ant.matcher(request);
}
private boolean isDispatcherServlet(ServletRegistration registration) {
Class<?> dispatcherServlet = ClassUtils
.resolveClassName("org.springframework.web.servlet.DispatcherServlet", null);
try {
Class<?> clazz = Class.forName(registration.getClassName());
return dispatcherServlet.isAssignableFrom(clazz);
}
catch (ClassNotFoundException ex) {
return false;
}
}
@Override
public String toString() {
return "DispatcherServletDelegating [" + "ant = " + this.ant + ", mvc = " + this.mvc + "]";
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,6 +28,7 @@ import org.springframework.security.openid.OpenIDAuthenticationFilter;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.access.ExceptionTranslationFilter;
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
import org.springframework.security.web.access.intercept.AuthorizationFilter;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@@ -38,7 +39,11 @@ import org.springframework.security.web.authentication.rememberme.RememberMeAuth
import org.springframework.security.web.authentication.switchuser.SwitchUserFilter;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.authentication.www.DigestAuthenticationFilter;
import org.springframework.security.web.context.SecurityContextHolderFilter;
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.header.HeaderWriterFilter;
import org.springframework.security.web.jaasapi.JaasApiIntegrationFilter;
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter;
@@ -46,6 +51,7 @@ import org.springframework.security.web.session.ConcurrentSessionFilter;
import org.springframework.security.web.session.DisableEncodeUrlFilter;
import org.springframework.security.web.session.ForceEagerSessionCreationFilter;
import org.springframework.security.web.session.SessionManagementFilter;
import org.springframework.web.filter.CorsFilter;
/**
* @param <H>
@@ -127,15 +133,24 @@ public interface HttpSecurityBuilder<H extends HttpSecurityBuilder<H>>
* The ordering of the Filters is:
*
* <ul>
* <li>{@link ForceEagerSessionCreationFilter}</li>
* <li>{@link DisableEncodeUrlFilter}</li>
* <li>{@link ForceEagerSessionCreationFilter}</li>
* <li>{@link ChannelProcessingFilter}</li>
* <li>{@link WebAsyncManagerIntegrationFilter}</li>
* <li>{@link SecurityContextHolderFilter}</li>
* <li>{@link SecurityContextPersistenceFilter}</li>
* <li>{@link HeaderWriterFilter}</li>
* <li>{@link CorsFilter}</li>
* <li>{@link CsrfFilter}</li>
* <li>{@link LogoutFilter}</li>
* <li>{@link org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter}</li>
* <li>{@link org.springframework.security.saml2.provider.service.web.Saml2WebSsoAuthenticationRequestFilter}</li>
* <li>{@link X509AuthenticationFilter}</li>
* <li>{@link AbstractPreAuthenticatedProcessingFilter}</li>
* <li><a href="
* {@docRoot}/org/springframework/security/cas/web/CasAuthenticationFilter.html">CasAuthenticationFilter</a></li>
* <li>{@link org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter}</li>
* <li>{@link org.springframework.security.saml2.provider.service.web.authentication.Saml2WebSsoAuthenticationFilter}</li>
* <li>{@link UsernamePasswordAuthenticationFilter}</li>
* <li>{@link OpenIDAuthenticationFilter}</li>
* <li>{@link org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter}</li>
@@ -149,9 +164,11 @@ public interface HttpSecurityBuilder<H extends HttpSecurityBuilder<H>>
* <li>{@link JaasApiIntegrationFilter}</li>
* <li>{@link RememberMeAuthenticationFilter}</li>
* <li>{@link AnonymousAuthenticationFilter}</li>
* <li>{@link org.springframework.security.oauth2.client.web.OAuth2AuthorizationCodeGrantFilter}</li>
* <li>{@link SessionManagementFilter}</li>
* <li>{@link ExceptionTranslationFilter}</li>
* <li>{@link FilterSecurityInterceptor}</li>
* <li>{@link AuthorizationFilter}</li>
* <li>{@link SwitchUserFilter}</li>
* </ul>
* @param filter the {@link Filter} to add
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -335,7 +335,6 @@ public final class LogoutConfigurer<H extends HttpSecurityBuilder<H>>
LogoutFilter result = new LogoutFilter(getLogoutSuccessHandler(), handlers);
result.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
result.setLogoutRequestMatcher(getLogoutRequestMatcher(http));
result.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
result = postProcess(result);
return result;
}
@@ -268,12 +268,14 @@ public final class Saml2LogoutConfigurer<H extends HttpSecurityBuilder<H>>
return postProcess(logoutResponseFilter);
}
private LogoutFilter createRelyingPartyLogoutFilter(RelyingPartyRegistrationResolver registrations) {
private Saml2RelyingPartyInitiatedLogoutFilter createRelyingPartyLogoutFilter(
RelyingPartyRegistrationResolver registrations) {
LogoutHandler[] logoutHandlers = this.logoutHandlers.toArray(new LogoutHandler[0]);
Saml2RelyingPartyInitiatedLogoutSuccessHandler logoutRequestSuccessHandler = createSaml2LogoutRequestSuccessHandler(
registrations);
logoutRequestSuccessHandler.setLogoutRequestRepository(this.logoutRequestConfigurer.logoutRequestRepository);
LogoutFilter logoutFilter = new LogoutFilter(logoutRequestSuccessHandler, logoutHandlers);
Saml2RelyingPartyInitiatedLogoutFilter logoutFilter = new Saml2RelyingPartyInitiatedLogoutFilter(
logoutRequestSuccessHandler, logoutHandlers);
logoutFilter.setLogoutRequestMatcher(createLogoutMatcher());
return postProcess(logoutFilter);
}
@@ -568,4 +570,12 @@ public final class Saml2LogoutConfigurer<H extends HttpSecurityBuilder<H>>
}
private static class Saml2RelyingPartyInitiatedLogoutFilter extends LogoutFilter {
Saml2RelyingPartyInitiatedLogoutFilter(LogoutSuccessHandler logoutSuccessHandler, LogoutHandler... handlers) {
super(logoutSuccessHandler, handlers);
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -42,7 +42,7 @@ public class JdbcUserServiceBeanDefinitionParser extends AbstractUserDetailsServ
@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
String dataSource = element.getAttribute(ATT_DATA_SOURCE);
if (dataSource != null) {
if (StringUtils.hasText(dataSource)) {
builder.addPropertyReference("dataSource", dataSource);
}
else {
@@ -41,8 +41,6 @@ public class PasswordEncoderParser {
public static final String ATT_HASH = "hash";
static final String ATT_BASE_64 = "base64";
static final String OPT_HASH_BCRYPT = "bcrypt";
private static final Map<String, Class<?>> ENCODER_CLASSES = Collections.singletonMap(OPT_HASH_BCRYPT,
@@ -62,19 +60,17 @@ public class PasswordEncoderParser {
return;
}
String hash = element.getAttribute(ATT_HASH);
boolean useBase64 = StringUtils.hasText(element.getAttribute(ATT_BASE_64))
&& Boolean.parseBoolean(element.getAttribute(ATT_BASE_64));
String ref = element.getAttribute(ATT_REF);
if (StringUtils.hasText(ref)) {
this.passwordEncoder = new RuntimeBeanReference(ref);
}
else {
this.passwordEncoder = createPasswordEncoderBeanDefinition(hash, useBase64);
this.passwordEncoder = createPasswordEncoderBeanDefinition(hash);
((RootBeanDefinition) this.passwordEncoder).setSource(parserContext.extractSource(element));
}
}
public static BeanDefinition createPasswordEncoderBeanDefinition(String hash, boolean useBase64) {
public static BeanDefinition createPasswordEncoderBeanDefinition(String hash) {
Class<?> beanClass = ENCODER_CLASSES.get(hash);
BeanDefinitionBuilder beanBldr = BeanDefinitionBuilder.rootBeanDefinition(beanClass);
return beanBldr.getBeanDefinition();
@@ -98,7 +98,7 @@ public class LdapProviderBeanDefinitionParser implements BeanDefinitionParser {
}
else if (StringUtils.hasText(hash)) {
authenticatorBuilder.addPropertyValue("passwordEncoder",
PasswordEncoderParser.createPasswordEncoderBeanDefinition(hash, false));
PasswordEncoderParser.createPasswordEncoderBeanDefinition(hash));
}
}
authenticatorBuilder.addConstructorArgValue(contextSource);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -30,27 +30,35 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.security.config.MockServletContext;
import org.springframework.security.config.TestMockHttpServletMappings;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry.DispatcherServletDelegatingRequestMatcher;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.DispatcherTypeRequestMatcher;
import org.springframework.security.web.util.matcher.RegexRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.InstanceOfAssertFactories.type;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
/**
* Tests for {@link AbstractRequestMatcherRegistry}.
@@ -206,18 +214,65 @@ public class AbstractRequestMatcherRegistryTests {
mockMvcIntrospector(true);
MockServletContext servletContext = new MockServletContext();
given(this.context.getServletContext()).willReturn(servletContext);
servletContext.addServlet("servletOne", Servlet.class).addMapping("/one");
servletContext.addServlet("servletTwo", Servlet.class).addMapping("/two");
MockHttpServletRequest request = new MockHttpServletRequest();
List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers("/**");
assertThat(requestMatchers).isNotEmpty();
assertThat(requestMatchers).hasSize(1);
assertThat(requestMatchers.get(0)).isExactlyInstanceOf(AntPathRequestMatcher.class);
assertThat(requestMatchers.get(0)).asInstanceOf(type(DispatcherServletDelegatingRequestMatcher.class))
.extracting((matcher) -> matcher.requestMatcher(request))
.isInstanceOf(AntPathRequestMatcher.class);
servletContext.addServlet("servletOne", Servlet.class).addMapping("/one");
servletContext.addServlet("servletTwo", Servlet.class).addMapping("/two");
requestMatchers = this.matcherRegistry.requestMatchers("/**");
assertThat(requestMatchers).isNotEmpty();
assertThat(requestMatchers).hasSize(1);
assertThat(requestMatchers.get(0)).asInstanceOf(type(DispatcherServletDelegatingRequestMatcher.class))
.extracting((matcher) -> matcher.requestMatcher(request))
.isInstanceOf(AntPathRequestMatcher.class);
servletContext.addServlet("servletOne", Servlet.class);
servletContext.addServlet("servletTwo", Servlet.class);
requestMatchers = this.matcherRegistry.requestMatchers("/**");
assertThat(requestMatchers).isNotEmpty();
assertThat(requestMatchers).hasSize(1);
assertThat(requestMatchers.get(0)).isExactlyInstanceOf(AntPathRequestMatcher.class);
assertThat(requestMatchers.get(0)).asInstanceOf(type(DispatcherServletDelegatingRequestMatcher.class))
.extracting((matcher) -> matcher.requestMatcher(request))
.isInstanceOf(AntPathRequestMatcher.class);
}
// gh-14418
@Test
public void requestMatchersWhenNoDispatcherServletMockMvcThenMvcRequestMatcherType() throws Exception {
MockServletContext servletContext = new MockServletContext();
try (SpringTestContext spring = new SpringTestContext(this)) {
spring.register(MockMvcConfiguration.class)
.postProcessor((context) -> context.setServletContext(servletContext))
.autowire();
this.matcherRegistry.setApplicationContext(spring.getContext());
MockMvc mvc = MockMvcBuilders.webAppContextSetup(spring.getContext()).build();
MockHttpServletRequest request = mvc.perform(get("/")).andReturn().getRequest();
List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers("/**");
assertThat(requestMatchers).isNotEmpty();
assertThat(requestMatchers).hasSize(1);
assertThat(requestMatchers.get(0)).asInstanceOf(type(DispatcherServletDelegatingRequestMatcher.class))
.extracting((matcher) -> matcher.requestMatcher(request))
.isInstanceOf(MvcRequestMatcher.class);
servletContext.addServlet("servletOne", Servlet.class).addMapping("/one");
servletContext.addServlet("servletTwo", Servlet.class).addMapping("/two");
requestMatchers = this.matcherRegistry.requestMatchers("/**");
assertThat(requestMatchers).isNotEmpty();
assertThat(requestMatchers).hasSize(1);
assertThat(requestMatchers.get(0)).asInstanceOf(type(DispatcherServletDelegatingRequestMatcher.class))
.extracting((matcher) -> matcher.requestMatcher(request))
.isInstanceOf(MvcRequestMatcher.class);
servletContext.addServlet("servletOne", Servlet.class);
servletContext.addServlet("servletTwo", Servlet.class);
requestMatchers = this.matcherRegistry.requestMatchers("/**");
assertThat(requestMatchers).isNotEmpty();
assertThat(requestMatchers).hasSize(1);
assertThat(requestMatchers.get(0)).asInstanceOf(type(DispatcherServletDelegatingRequestMatcher.class))
.extracting((matcher) -> matcher.requestMatcher(request))
.isInstanceOf(MvcRequestMatcher.class);
}
}
@Test
@@ -381,11 +436,13 @@ public class AbstractRequestMatcherRegistryTests {
return requestMatchers;
}
private static List<RequestMatcher> unwrap(List<RequestMatcher> wrappedMatchers) {
private List<RequestMatcher> unwrap(List<RequestMatcher> wrappedMatchers) {
List<RequestMatcher> requestMatchers = new ArrayList<>();
for (RequestMatcher requestMatcher : wrappedMatchers) {
if (requestMatcher instanceof AbstractRequestMatcherRegistry.DeferredRequestMatcher) {
requestMatchers.add(((DeferredRequestMatcher) requestMatcher).requestMatcher);
if (requestMatcher instanceof DeferredRequestMatcher) {
DeferredRequestMatcher deferred = (DeferredRequestMatcher) requestMatcher;
WebApplicationContext web = (WebApplicationContext) getApplicationContext();
requestMatchers.add(deferred.requestMatcher(web.getServletContext()));
}
else {
requestMatchers.add(requestMatcher);
@@ -396,4 +453,11 @@ public class AbstractRequestMatcherRegistryTests {
}
@Configuration
@EnableWebSecurity
@EnableWebMvc
static class MockMvcConfiguration {
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -76,7 +76,7 @@ public class AuthorizeRequestsTests {
@BeforeEach
public void setup() {
this.servletContext = spy(MockServletContext.mvc());
this.request = new MockHttpServletRequest("GET", "");
this.request = new MockHttpServletRequest(this.servletContext, "GET", "");
this.request.setMethod("GET");
this.response = new MockHttpServletResponse();
this.chain = new MockFilterChain();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -75,7 +75,7 @@ public class HttpSecuritySecurityMatchersTests {
@BeforeEach
public void setup() throws Exception {
this.request = new MockHttpServletRequest("GET", "");
this.request = new MockHttpServletRequest(MockServletContext.mvc(), "GET", "");
this.request.setMethod("GET");
this.response = new MockHttpServletResponse();
this.chain = new MockFilterChain();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -72,7 +72,7 @@ public class UrlAuthorizationConfigurerTests {
@BeforeEach
public void setup() {
this.request = new MockHttpServletRequest("GET", "");
this.request = new MockHttpServletRequest(MockServletContext.mvc(), "GET", "");
this.request.setMethod("GET");
this.response = new MockHttpServletResponse();
this.chain = new MockFilterChain();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,7 +19,10 @@ package org.springframework.security.config.authentication;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.w3c.dom.Element;
import org.xml.sax.SAXParseException;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.CachingUserDetailsService;
import org.springframework.security.authentication.ProviderManager;
@@ -33,6 +36,7 @@ import org.springframework.security.provisioning.JdbcUserDetailsManager;
import org.springframework.security.util.FieldUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.Mockito.mock;
/**
@@ -160,6 +164,38 @@ public class JdbcUserServiceBeanDefinitionParserTests {
assertThat(AuthorityUtils.authorityListToSet(rod.getAuthorities())).contains("PREFIX_ROLE_SUPERVISOR");
}
@Test
public void testEmptyDataSourceRef() {
// @formatter:off
String xml = "<authentication-manager>"
+ " <authentication-provider>"
+ " <jdbc-user-service data-source-ref=''/>"
+ " </authentication-provider>"
+ "</authentication-manager>";
assertThatExceptionOfType(BeanDefinitionParsingException.class)
.isThrownBy(() -> setContext(xml))
.withFailMessage("Expected exception due to empty data-source-ref")
.withMessageContaining("data-source-ref is required for jdbc-user-service");
// @formatter:on
}
@Test
public void testMissingDataSourceRef() {
// @formatter:off
String xml = "<authentication-manager>"
+ " <authentication-provider>"
+ " <jdbc-user-service/>"
+ " </authentication-provider>"
+ "</authentication-manager>";
assertThatExceptionOfType(XmlBeanDefinitionStoreException.class)
.isThrownBy(() -> setContext(xml))
.withFailMessage("Expected exception due to missing data-source-ref")
.havingRootCause()
.isInstanceOf(SAXParseException.class)
.withMessageContaining("Attribute 'data-source-ref' must appear on element 'jdbc-user-service'");
// @formatter:on
}
private void setContext(String context) {
this.appContext = new InMemoryXmlApplicationContext(context);
}
@@ -20,10 +20,13 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.test.web.servlet.MockMvc;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -65,4 +68,15 @@ public class PasswordEncoderParserTests {
// @formatter:on
}
@Test
void testCreatePasswordEncoderBeanDefinition() throws Exception {
String hash = "bcrypt";
Class<?> expectedBeanClass = BCryptPasswordEncoder.class;
BeanDefinition beanDefinition = PasswordEncoderParser.createPasswordEncoderBeanDefinition(hash);
Class<?> actualBeanClass = Class.forName(beanDefinition.getBeanClassName());
assertThat(actualBeanClass).isEqualTo(expectedBeanClass);
}
}
@@ -61,7 +61,7 @@ public class AuthenticatedVoter implements AccessDecisionVoter<Object> {
private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();
private boolean isFullyAuthenticated(Authentication authentication) {
return (!this.authenticationTrustResolver.isAnonymous(authentication)
return authentication != null && (!this.authenticationTrustResolver.isAnonymous(authentication)
&& !this.authenticationTrustResolver.isRememberMe(authentication));
}
@@ -93,7 +93,7 @@ public final class AuthorizationManagerAfterMethodInterceptor
PostAuthorizeAuthorizationManager authorizationManager) {
AuthorizationManagerAfterMethodInterceptor interceptor = new AuthorizationManagerAfterMethodInterceptor(
AuthorizationMethodPointcuts.forAnnotations(PostAuthorize.class), authorizationManager);
interceptor.setOrder(500);
interceptor.setOrder(AuthorizationInterceptorsOrder.POST_AUTHORIZE.getOrder());
return interceptor;
}
@@ -59,6 +59,7 @@ public class AuthenticatedVoterTests {
assertThat(AccessDecisionVoter.ACCESS_GRANTED).isEqualTo(voter.vote(createAnonymous(), null, def));
assertThat(AccessDecisionVoter.ACCESS_GRANTED).isEqualTo(voter.vote(createRememberMe(), null, def));
assertThat(AccessDecisionVoter.ACCESS_GRANTED).isEqualTo(voter.vote(createFullyAuthenticated(), null, def));
assertThat(AccessDecisionVoter.ACCESS_DENIED).isEqualTo(voter.vote(null, null, def));
}
@Test
@@ -68,6 +69,7 @@ public class AuthenticatedVoterTests {
assertThat(AccessDecisionVoter.ACCESS_DENIED).isEqualTo(voter.vote(createAnonymous(), null, def));
assertThat(AccessDecisionVoter.ACCESS_DENIED).isEqualTo(voter.vote(createRememberMe(), null, def));
assertThat(AccessDecisionVoter.ACCESS_GRANTED).isEqualTo(voter.vote(createFullyAuthenticated(), null, def));
assertThat(AccessDecisionVoter.ACCESS_DENIED).isEqualTo(voter.vote(null, null, def));
}
@Test
@@ -77,6 +79,7 @@ public class AuthenticatedVoterTests {
assertThat(AccessDecisionVoter.ACCESS_DENIED).isEqualTo(voter.vote(createAnonymous(), null, def));
assertThat(AccessDecisionVoter.ACCESS_GRANTED).isEqualTo(voter.vote(createRememberMe(), null, def));
assertThat(AccessDecisionVoter.ACCESS_GRANTED).isEqualTo(voter.vote(createFullyAuthenticated(), null, def));
assertThat(AccessDecisionVoter.ACCESS_DENIED).isEqualTo(voter.vote(null, null, def));
}
@Test
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -129,6 +129,10 @@ public class DelegatingPasswordEncoder implements PasswordEncoder {
private static final String DEFAULT_ID_SUFFIX = "}";
public static final String NO_PASSWORD_ENCODER_MAPPED = "There is no PasswordEncoder mapped for the id \"%s\"";
public static final String NO_PASSWORD_ENCODER_PREFIX = "You have entered a password with no PasswordEncoder. If that is your intent, it should be prefixed with `{noop}`.";
private final String idPrefix;
private final String idSuffix;
@@ -286,7 +290,10 @@ public class DelegatingPasswordEncoder implements PasswordEncoder {
@Override
public boolean matches(CharSequence rawPassword, String prefixEncodedPassword) {
String id = extractId(prefixEncodedPassword);
throw new IllegalArgumentException("There is no PasswordEncoder mapped for the id \"" + id + "\"");
if (id != null && !id.isEmpty()) {
throw new IllegalArgumentException(String.format(NO_PASSWORD_ENCODER_MAPPED, id));
}
throw new IllegalArgumentException(NO_PASSWORD_ENCODER_PREFIX);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -43,6 +43,8 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
@ExtendWith(MockitoExtension.class)
public class DelegatingPasswordEncoderTests {
public static final String NO_PASSWORD_ENCODER = "You have entered a password with no PasswordEncoder. If that is your intent, it should be prefixed with `{noop}`.";
@Mock
private PasswordEncoder bcrypt;
@@ -201,7 +203,7 @@ public class DelegatingPasswordEncoderTests {
public void matchesWhenNoClosingPrefixStringThenIllegalArgumentException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> this.passwordEncoder.matches(this.rawPassword, "{bcrypt" + this.rawPassword))
.withMessage("There is no PasswordEncoder mapped for the id \"null\"");
.withMessage(NO_PASSWORD_ENCODER);
verifyNoMoreInteractions(this.bcrypt, this.noop);
}
@@ -209,7 +211,7 @@ public class DelegatingPasswordEncoderTests {
public void matchesWhenNoStartingPrefixStringThenFalse() {
assertThatIllegalArgumentException()
.isThrownBy(() -> this.passwordEncoder.matches(this.rawPassword, "bcrypt}" + this.rawPassword))
.withMessage("There is no PasswordEncoder mapped for the id \"null\"");
.withMessage(NO_PASSWORD_ENCODER);
verifyNoMoreInteractions(this.bcrypt, this.noop);
}
@@ -217,7 +219,7 @@ public class DelegatingPasswordEncoderTests {
public void matchesWhenNoIdStringThenFalse() {
assertThatIllegalArgumentException()
.isThrownBy(() -> this.passwordEncoder.matches(this.rawPassword, "{}" + this.rawPassword))
.withMessage("There is no PasswordEncoder mapped for the id \"\"");
.withMessage(NO_PASSWORD_ENCODER);
verifyNoMoreInteractions(this.bcrypt, this.noop);
}
@@ -226,7 +228,7 @@ public class DelegatingPasswordEncoderTests {
assertThatIllegalArgumentException()
.isThrownBy(() -> this.passwordEncoder.matches(this.rawPassword, "invalid" + this.bcryptEncodedPassword))
.isInstanceOf(IllegalArgumentException.class)
.withMessage("There is no PasswordEncoder mapped for the id \"null\"");
.withMessage(NO_PASSWORD_ENCODER);
verifyNoMoreInteractions(this.bcrypt, this.noop);
}
@@ -236,7 +238,7 @@ public class DelegatingPasswordEncoderTests {
DelegatingPasswordEncoder passwordEncoder = new DelegatingPasswordEncoder(this.bcryptId, this.delegates);
assertThatIllegalArgumentException()
.isThrownBy(() -> passwordEncoder.matches(this.rawPassword, this.rawPassword))
.withMessage("There is no PasswordEncoder mapped for the id \"null\"");
.withMessage(NO_PASSWORD_ENCODER);
verifyNoMoreInteractions(this.bcrypt, this.noop);
}
@@ -289,4 +291,14 @@ public class DelegatingPasswordEncoderTests {
verifyNoMoreInteractions(this.bcrypt);
}
@Test
void matchesShouldThrowIllegalArgumentExceptionWhenNoPasswordEncoderIsMappedForTheId() {
assertThatIllegalArgumentException()
.isThrownBy(() -> this.passwordEncoder.matches("rawPassword", "prefixEncodedPassword"))
.isInstanceOf(IllegalArgumentException.class)
.withMessage(NO_PASSWORD_ENCODER);
verifyNoMoreInteractions(this.bcrypt, this.noop);
}
}
+37
View File
@@ -0,0 +1,37 @@
antora:
extensions:
- require: '@springio/antora-extensions'
root_component_name: 'security'
site:
title: Spring Security
url: https://docs.spring.io/spring-security/reference
robots: allow
git:
ensure_git_suffix: false
content:
sources:
- url: https://github.com/spring-projects/spring-security
branches: [main, '5.{{6..9},{1..9}+({0..9})}.x', '6.+({0..9}).x']
tags: ['5.{{6..9},{1..9}+({0..9})}.{0..99}?(-RC+({0..9}))', '6.+({0..9}).+({0..9})?(-{RC,M}*)','!(5.6.{0..10}*)', '!(5.7.{0..8}*)', '!(5.8.{0..3}?({-RC,-M}+({0..9})))','!(6.0.{0..3}*)','!(6.1.0*)']
start_path: docs
asciidoc:
attributes:
page-stackoverflow-url: https://stackoverflow.com/tags/spring-security
page-related-doc-categories: security
page-related-doc-projects: framework,graphql
hide-uri-scheme: '@'
tabs-sync-option: '@'
extensions:
- '@asciidoctor/tabs'
- '@springio/asciidoctor-extensions'
urls:
latest_version_segment_strategy: redirect:to
latest_version_segment: ''
redirect_facility: httpd
ui:
bundle:
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.15/ui-bundle.zip
snapshot: true
runtime:
log:
failure_level: warn
@@ -20,7 +20,7 @@ Java::
@Bean
SecurityWebFilterChain http(ServerHttpSecurity http) throws Exception {
DelegatingServerLogoutHandler logoutHandler = new DelegatingServerLogoutHandler(
new WebSessionServerLogoutHandler(), new SecurityContextServerLogoutHandler()
new SecurityContextServerLogoutHandler(), new WebSessionServerLogoutHandler()
);
http
@@ -38,7 +38,7 @@ Kotlin::
@Bean
fun http(http: ServerHttpSecurity): SecurityWebFilterChain {
val customLogoutHandler = DelegatingServerLogoutHandler(
WebSessionServerLogoutHandler(), SecurityContextServerLogoutHandler()
SecurityContextServerLogoutHandler(), WebSessionServerLogoutHandler()
)
return http {
@@ -105,6 +105,7 @@ Java::
[source,java,role="primary"]
----
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static GrantedAuthorityDefaults grantedAuthorityDefaults() {
return new GrantedAuthorityDefaults("MYPREFIX_");
}
@@ -113,7 +114,8 @@ static GrantedAuthorityDefaults grantedAuthorityDefaults() {
[TIP]
====
We expose `GrantedAuthorityDefaults` using a `static` method to ensure that Spring publishes it before it initializes Spring Security's method security `@Configuration` classes
We expose `GrantedAuthorityDefaults` using a `static` method to ensure that Spring publishes it before it initializes Spring Security's method security `@Configuration` classes.
Since the `GrantedAuthorityDefaults` bean is part of internal workings of Spring Security, we should also expose it as an infrastructural bean effectively avoiding some warnings related to bean post-processing (see https://github.com/spring-projects/spring-security/issues/14751[gh-14751]).
====
[[jc-reactive-method-security-custom-authorization-manager]]
@@ -442,7 +442,7 @@ In Spring Security this is done by saving the `HttpServletRequest` using a <<req
The `HttpServletRequest` is saved in the {security-api-url}org/springframework/security/web/savedrequest/RequestCache.html[`RequestCache`].
When the user successfully authenticates, the `RequestCache` is used to replay the original request.
The <<requestcacheawarefilter,`RequestCacheAwareFilter`>> is what uses the `RequestCache` to save the `HttpServletRequest`.
The <<requestcacheawarefilter,`RequestCacheAwareFilter`>> uses the `RequestCache` to get the saved `HttpServletRequest` after the user authenticates, while the `ExceptionTranslationFilter` uses the `RequestCache` to save the `HttpServletRequest` after it detects `AuthenticationException`, before redirecting the user to the login endpoint.
By default, an `HttpSessionRequestCache` is used.
The code below demonstrates how to customize the `RequestCache` implementation that is used to check the `HttpSession` for a saved request if the parameter named `continue` is present.
@@ -510,7 +510,7 @@ XML::
[[requestcacheawarefilter]]
=== RequestCacheAwareFilter
The {security-api-url}org/springframework/security/web/savedrequest/RequestCacheAwareFilter.html[`RequestCacheAwareFilter`] uses the <<requestcache,`RequestCache`>> to save the `HttpServletRequest`.
The {security-api-url}org/springframework/security/web/savedrequest/RequestCacheAwareFilter.html[`RequestCacheAwareFilter`] uses the <<requestcache,`RequestCache`>> to replay the original request.
[[servlet-logging]]
== Logging
@@ -10,6 +10,41 @@ However, despite leveraging a username/password for authentication it does not i
There are many different scenarios for how an LDAP server may be configured so Spring Security's LDAP provider is fully configurable.
It uses separate strategy interfaces for authentication and role retrieval and provides default implementations which can be configured to handle a wide range of situations.
[[servlet-authentication-ldap-required-dependencies]]
== Required Dependencies
To get started, add the `spring-security-ldap` dependency to your project.
When using Spring Boot, add the following dependencies:
.Spring Security LDAP Dependencies
[tabs]
======
Maven::
+
[source,xml,role="primary"]
----
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-ldap</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-ldap</artifactId>
</dependency>
----
Gradle::
+
[source,groovy,role="secondary"]
----
depenendencies {
implementation "org.springframework.boot:spring-boot-starter-data-ldap"
implementation "org.springframework.security:spring-security-ldap"
}
----
======
[[servlet-authentication-ldap-prerequisites]]
== Prerequisites
@@ -81,14 +116,14 @@ dn: cn=user,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: groupOfNames
cn: user
uniqueMember: uid=admin,ou=people,dc=springframework,dc=org
uniqueMember: uid=user,ou=people,dc=springframework,dc=org
member: uid=admin,ou=people,dc=springframework,dc=org
member: uid=user,ou=people,dc=springframework,dc=org
dn: cn=admin,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: groupOfNames
cn: admin
uniqueMember: uid=admin,ou=people,dc=springframework,dc=org
member: uid=admin,ou=people,dc=springframework,dc=org
----
[[servlet-authentication-ldap-unboundid]]
@@ -52,9 +52,9 @@ If you have more than one in your application context, you need to specify which
[[remember-me-persistent-token]]
== Persistent Token Approach
This approach is based on the article https://web.archive.org/web/20180819014446/http://jaspan.com/improved_persistent_login_cookie_best_practice[http://jaspan.com/improved_persistent_login_cookie_best_practice] with some minor modifications footnote:[Essentially, the username is not included in the cookie, to prevent exposing a valid login name unecessarily.
This approach is based on the article https://web.archive.org/web/20180819014446/http://jaspan.com/improved_persistent_login_cookie_best_practice[Improved Persistent Login Cookie Best Practice] with some minor modifications footnote:[Essentially, the username is not included in the cookie, to prevent exposing a valid login name unecessarily.
There is a discussion on this in the comments section of this article.].
To use the this approach with namespace configuration, you would supply a datasource reference:
To use this approach with namespace configuration, you would supply a datasource reference:
[source,xml]
----
@@ -234,6 +234,7 @@ Java::
[source,java,role="primary"]
----
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static GrantedAuthorityDefaults grantedAuthorityDefaults() {
return new GrantedAuthorityDefaults("MYPREFIX_");
}
@@ -245,6 +246,7 @@ Kotlin::
----
companion object {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
fun grantedAuthorityDefaults() : GrantedAuthorityDefaults {
return GrantedAuthorityDefaults("MYPREFIX_");
}
@@ -265,7 +267,8 @@ Xml::
[TIP]
====
We expose `GrantedAuthorityDefaults` using a `static` method to ensure that Spring publishes it before it initializes Spring Security's method security `@Configuration` classes
We expose `GrantedAuthorityDefaults` using a `static` method to ensure that Spring publishes it before it initializes Spring Security's method security `@Configuration` classes.
Since the `GrantedAuthorityDefaults` bean is part of internal workings of Spring Security, we should also expose it as an infrastructural bean effectively avoiding some warnings related to bean post-processing (see https://github.com/spring-projects/spring-security/issues/14751[gh-14751]).
====
[[jc-method-security-custom-authorization-manager]]
@@ -339,7 +339,7 @@ Java::
[source,java,role="primary"]
----
@Bean
JWTProcessor jwtProcessor(JWTClaimSetJWSKeySelector keySelector) {
JWTProcessor jwtProcessor(JWTClaimsSetAwareJWSKeySelector keySelector) {
ConfigurableJWTProcessor<SecurityContext> jwtProcessor =
new DefaultJWTProcessor();
jwtProcessor.setJWTClaimsSetAwareJWSKeySelector(keySelector);
@@ -374,29 +374,22 @@ Java::
----
@Component
public class TenantJwtIssuerValidator implements OAuth2TokenValidator<Jwt> {
private final TenantRepository tenants;
private final Map<String, JwtIssuerValidator> validators = new ConcurrentHashMap<>();
private final TenantRepository tenants;
public TenantJwtIssuerValidator(TenantRepository tenants) {
this.tenants = tenants;
}
private final OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_TOKEN, "The iss claim is not valid",
"https://tools.ietf.org/html/rfc6750#section-3.1");
@Override
public OAuth2TokenValidatorResult validate(Jwt token) {
return this.validators.computeIfAbsent(toTenant(token), this::fromTenant)
.validate(token);
}
public TenantJwtIssuerValidator(TenantRepository tenants) {
this.tenants = tenants;
}
private String toTenant(Jwt jwt) {
return jwt.getIssuer();
}
private JwtIssuerValidator fromTenant(String tenant) {
return Optional.ofNullable(this.tenants.findById(tenant))
.map(t -> t.getAttribute("issuer"))
.map(JwtIssuerValidator::new)
.orElseThrow(() -> new IllegalArgumentException("unknown tenant"));
}
@Override
public OAuth2TokenValidatorResult validate(Jwt token) {
if(this.tenants.findById(token.getIssuer()) != null) {
return OAuth2TokenValidatorResult.success();
}
return OAuth2TokenValidatorResult.failure(this.error);
}
}
----
@@ -405,32 +398,17 @@ Kotlin::
[source,kotlin,role="secondary"]
----
@Component
class TenantJwtIssuerValidator(tenants: TenantRepository) : OAuth2TokenValidator<Jwt> {
private val tenants: TenantRepository
private val validators: MutableMap<String, JwtIssuerValidator> = ConcurrentHashMap()
class TenantJwtIssuerValidator(private val tenants: TenantRepository) : OAuth2TokenValidator<Jwt> {
private val error: OAuth2Error = OAuth2Error(OAuth2ErrorCodes.INVALID_TOKEN, "The iss claim is not valid",
"https://tools.ietf.org/html/rfc6750#section-3.1")
override fun validate(token: Jwt): OAuth2TokenValidatorResult {
return validators.computeIfAbsent(toTenant(token)) { tenant: String -> fromTenant(tenant) }
.validate(token)
}
private fun toTenant(jwt: Jwt): String {
return jwt.issuer.toString()
}
private fun fromTenant(tenant: String): JwtIssuerValidator {
return Optional.ofNullable(tenants.findById(tenant))
.map({ t -> t.getAttribute("issuer") })
.map({ JwtIssuerValidator() })
.orElseThrow({ IllegalArgumentException("unknown tenant") })
}
init {
this.tenants = tenants
return if (tenants.findById(token.issuer) != null)
OAuth2TokenValidatorResult.success() else OAuth2TokenValidatorResult.failure(error)
}
}
----
======
Now that we have a tenant-aware processor and a tenant-aware validator, we can proceed with creating our xref:servlet/oauth2/resource-server/jwt.adoc#oauth2resourceserver-jwt-architecture-jwtdecoder[`JwtDecoder`]:
[tabs]
+10
View File
@@ -0,0 +1,10 @@
{
"dependencies": {
"antora": "3.2.0-alpha.4",
"@antora/atlas-extension": "1.0.0-alpha.2",
"@antora/collector-extension": "1.0.0-alpha.4",
"@asciidoctor/tabs": "1.0.0-beta.6",
"@springio/antora-extensions": "1.11.1",
"@springio/asciidoctor-extensions": "1.0.0-alpha.10"
}
}
+4 -7
View File
@@ -8,14 +8,11 @@ apply plugin: 'io.spring.convention.docs'
apply plugin: 'java'
antora {
playbook = 'cached-antora-playbook.yml'
playbookProvider {
repository = 'spring-projects/spring-security'
branch = 'docs-build'
path = 'lib/antora/templates/per-branch-antora-playbook.yml'
checkLocalBranch = true
}
options = [clean: true, fetch: !project.gradle.startParameter.offline, stacktrace: true]
environment = [
'BUILD_REFNAME': 'HEAD',
'BUILD_VERSION': project.version,
]
}
tasks.named("generateAntoraYml") {
+1
View File
@@ -20,6 +20,7 @@
<property name="avoidStaticImportExcludes" value="org.springframework.security.web.csrf.CsrfTokenAssert.*" />
<property name="avoidStaticImportExcludes" value="org.springframework.security.web.util.matcher.AntPathRequestMatcher.*" />
<property name="avoidStaticImportExcludes" value="org.springframework.security.web.util.matcher.RegexRequestMatcher.*" />
<property name="avoidStaticImportExcludes" value="org.assertj.core.api.InstanceOfAssertFactories.*"/>
</module>
<module name="com.puppycrawl.tools.checkstyle.TreeWalker">
<module name="com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineJavaCheck">
+1 -1
View File
@@ -1,5 +1,5 @@
springBootVersion=2.7.12
version=5.8.9
version=5.8.13
samplesBranch=5.8.x
org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError
org.gradle.parallel=true
+8 -8
View File
@@ -2,12 +2,12 @@
com-squareup-okhttp3 = "3.14.9"
io-r2dbc = "0.9.1.RELEASE"
io-rsocket = "1.1.4"
io-spring-javaformat = "0.0.40"
io-spring-javaformat = "0.0.41"
io-spring-nohttp = "0.0.11"
org-apache-directory-server = "1.5.5"
org-aspectj = "1.9.20.1"
org-bouncycastle = "1.70"
org-eclipse-jetty = "9.4.53.v20231009"
org-eclipse-jetty = "9.4.54.v20240208"
org-jetbrains-kotlin = "1.7.22"
org-jetbrains-kotlinx = "1.6.4"
org-junit-jupiter = "5.9.3"
@@ -15,7 +15,7 @@ org-mockito = "4.8.1"
org-opensaml4 = "4.1.0"
org-opensaml3 = "3.4.6"
org-slf4j = "1.7.36"
org-springframework = "5.3.31"
org-springframework = "5.3.37"
[libraries]
ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.2.13"
@@ -30,9 +30,9 @@ com-unboundid-unboundid-ldapsdk = "com.unboundid:unboundid-ldapsdk:4.0.14"
commons-collections = "commons-collections:commons-collections:3.2.2"
io-freefair-gradle-aspectj-plugin = "io.freefair.gradle:aspectj-plugin:6.5.1"
io-mockk = "io.mockk:mockk:1.13.3"
io-projectreactor-netty-reactor-netty = "io.projectreactor.netty:reactor-netty:1.0.40"
io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2020.0.39"
io-projectreactor-tools-blockhound = "io.projectreactor.tools:blockhound:1.0.8.RELEASE"
io-projectreactor-netty-reactor-netty = "io.projectreactor.netty:reactor-netty:1.0.46"
io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2020.0.45"
io-projectreactor-tools-blockhound = "io.projectreactor.tools:blockhound:1.0.9.RELEASE"
io-r2dbc-r2dbc-h2 = { module = "io.r2dbc:r2dbc-h2", version.ref = "io-r2dbc" }
io-r2dbc-r2dbc-spi-test = { module = "io.r2dbc:r2dbc-spi-test", version.ref = "io-r2dbc" }
io-rsocket-rsocket-bom = { module = "io.rsocket:rsocket-bom", version.ref = "io-rsocket" }
@@ -69,7 +69,7 @@ org-eclipse-jetty-jetty-servlet = { module = "org.eclipse.jetty:jetty-servlet",
org-eclipse-persistence-javax-persistence = "org.eclipse.persistence:javax.persistence:2.2.1"
org-hamcrest = "org.hamcrest:hamcrest:2.2"
org-hibernate-hibernate-entitymanager = "org.hibernate:hibernate-entitymanager:5.6.15.Final"
org-hsqldb = "org.hsqldb:hsqldb:2.7.2"
org-hsqldb = "org.hsqldb:hsqldb:2.7.3"
org-jasig-cas-client-cas-client-core = "org.jasig.cas.client:cas-client-core:3.6.4"
org-jetbrains-kotlin-kotlin-bom = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "org-jetbrains-kotlin" }
org-jetbrains-kotlin-kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "org-jetbrains-kotlin" }
@@ -103,7 +103,7 @@ org-apache-commons-commons-io = "org.apache.commons:commons-io:1.3.2"
io-github-gradle-nexus-publish-plugin = "io.github.gradle-nexus:publish-plugin:1.1.0"
org-gretty-gretty = "org.gretty:gretty:3.0.9"
com-github-ben-manes-gradle-versions-plugin = "com.github.ben-manes:gradle-versions-plugin:0.38.0"
com-github-spullara-mustache-java-compiler = "com.github.spullara.mustache.java:compiler:0.9.11"
com-github-spullara-mustache-java-compiler = "com.github.spullara.mustache.java:compiler:0.9.13"
org-hidetake-gradle-ssh-plugin = "org.hidetake:gradle-ssh-plugin:2.10.1"
org-jfrog-buildinfo-build-info-extractor-gradle = "org.jfrog.buildinfo:build-info-extractor-gradle:4.29.4"
org-sonarsource-scanner-gradle-sonarqube-gradle-plugin = "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.7.1"
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@ package org.springframework.security.messaging.web.csrf;
import java.util.Base64;
import org.springframework.security.crypto.codec.Utf8;
import org.springframework.util.Assert;
/**
* Copied from
@@ -43,26 +44,26 @@ final class XorCsrfTokenUtils {
byte[] tokenBytes = Utf8.encode(token);
int tokenSize = tokenBytes.length;
if (actualBytes.length < tokenSize) {
if (actualBytes.length != tokenSize * 2) {
return null;
}
// extract token and random bytes
int randomBytesSize = actualBytes.length - tokenSize;
byte[] xoredCsrf = new byte[tokenSize];
byte[] randomBytes = new byte[randomBytesSize];
byte[] randomBytes = new byte[tokenSize];
System.arraycopy(actualBytes, 0, randomBytes, 0, randomBytesSize);
System.arraycopy(actualBytes, randomBytesSize, xoredCsrf, 0, tokenSize);
System.arraycopy(actualBytes, 0, randomBytes, 0, tokenSize);
System.arraycopy(actualBytes, tokenSize, xoredCsrf, 0, tokenSize);
byte[] csrfBytes = xorCsrf(randomBytes, xoredCsrf);
return Utf8.decode(csrfBytes);
}
private static byte[] xorCsrf(byte[] randomBytes, byte[] csrfBytes) {
int len = Math.min(randomBytes.length, csrfBytes.length);
Assert.isTrue(randomBytes.length == csrfBytes.length, "arrays must be equal length");
int len = csrfBytes.length;
byte[] xoredCsrf = new byte[len];
System.arraycopy(csrfBytes, 0, xoredCsrf, 0, csrfBytes.length);
System.arraycopy(csrfBytes, 0, xoredCsrf, 0, len);
for (int i = 0; i < len; i++) {
xoredCsrf[i] ^= randomBytes[i];
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
package org.springframework.security.messaging.web.csrf;
import java.util.Base64;
import java.util.HashMap;
import org.junit.jupiter.api.BeforeEach;
@@ -141,6 +142,73 @@ public class XorCsrfChannelInterceptorTests {
this.interceptor.preSend(message(), this.channel);
}
// gh-13310, gh-15184
@Test
public void preSendWhenCsrfBytesIsShorterThanRandomBytesThenThrowsInvalidCsrfTokenException() {
/*
* Token format: 3 random pad bytes + 2 padded bytes.
*/
byte[] actualBytes = { 1, 1, 1, 96, 99 };
String actualToken = Base64.getEncoder().encodeToString(actualBytes);
this.messageHeaders.setNativeHeader(this.token.getHeaderName(), actualToken);
this.messageHeaders.getSessionAttributes().put(CsrfToken.class.getName(), this.token);
// @formatter:off
assertThatExceptionOfType(InvalidCsrfTokenException.class)
.isThrownBy(() -> this.interceptor.preSend(message(), mock(MessageChannel.class)));
// @formatter:on
}
// gh-13310, gh-15184
@Test
public void preSendWhenCsrfBytesIsLongerThanRandomBytesThenThrowsInvalidCsrfTokenException() {
/*
* Token format: 3 random pad bytes + 4 padded bytes.
*/
byte[] actualBytes = { 1, 1, 1, 96, 99, 98, 97 };
String actualToken = Base64.getEncoder().encodeToString(actualBytes);
this.messageHeaders.setNativeHeader(this.token.getHeaderName(), actualToken);
this.messageHeaders.getSessionAttributes().put(CsrfToken.class.getName(), this.token);
// @formatter:off
assertThatExceptionOfType(InvalidCsrfTokenException.class)
.isThrownBy(() -> this.interceptor.preSend(message(), mock(MessageChannel.class)));
// @formatter:on
}
// gh-13310, gh-15184
@Test
public void preSendWhenTokenBytesIsShorterThanActualBytesThenThrowsInvalidCsrfTokenException() {
this.messageHeaders.setNativeHeader(this.token.getHeaderName(), XOR_CSRF_TOKEN_VALUE);
CsrfToken csrfToken = new DefaultCsrfToken("header", "param", "a");
this.messageHeaders.getSessionAttributes().put(CsrfToken.class.getName(), csrfToken);
// @formatter:off
assertThatExceptionOfType(InvalidCsrfTokenException.class)
.isThrownBy(() -> this.interceptor.preSend(message(), mock(MessageChannel.class)));
// @formatter:on
}
// gh-13310, gh-15184
@Test
public void preSendWhenTokenBytesIsLongerThanActualBytesThenThrowsInvalidCsrfTokenException() {
this.messageHeaders.setNativeHeader(this.token.getHeaderName(), XOR_CSRF_TOKEN_VALUE);
CsrfToken csrfToken = new DefaultCsrfToken("header", "param", "abcde");
this.messageHeaders.getSessionAttributes().put(CsrfToken.class.getName(), csrfToken);
// @formatter:off
assertThatExceptionOfType(InvalidCsrfTokenException.class)
.isThrownBy(() -> this.interceptor.preSend(message(), mock(MessageChannel.class)));
// @formatter:on
}
// gh-13310, gh-15184
@Test
public void preSendWhenActualBytesIsEmptyThenThrowsInvalidCsrfTokenException() {
this.messageHeaders.setNativeHeader(this.token.getHeaderName(), "");
this.messageHeaders.getSessionAttributes().put(CsrfToken.class.getName(), this.token);
// @formatter:off
assertThatExceptionOfType(InvalidCsrfTokenException.class)
.isThrownBy(() -> this.interceptor.preSend(message(), mock(MessageChannel.class)));
// @formatter:on
}
private Message<String> message() {
return MessageBuilder.withPayload("message").copyHeaders(this.messageHeaders.toMap()).build();
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -94,14 +94,10 @@ public final class WebSessionServerOAuth2AuthorizedClientRepository implements S
// @formatter:on
}
@SuppressWarnings("unchecked")
private Map<String, OAuth2AuthorizedClient> getAuthorizedClients(WebSession session) {
Map<String, OAuth2AuthorizedClient> authorizedClients = (session != null)
? (Map<String, OAuth2AuthorizedClient>) session.getAttribute(this.sessionAttributeName) : null;
if (authorizedClients == null) {
authorizedClients = new HashMap<>();
}
return authorizedClients;
Assert.notNull(session, "session cannot be null");
Map<String, OAuth2AuthorizedClient> authorizedClients = session.getAttribute(this.sessionAttributeName);
return (authorizedClients != null) ? authorizedClients : new HashMap<>();
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
package org.springframework.security.oauth2.client.web.server;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
@@ -24,10 +25,12 @@ import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebSession;
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;
/**
@@ -202,4 +205,28 @@ public class WebSessionServerOAuth2AuthorizedClientRepositoryTests {
assertThat(loadedAuthorizedClient2).isSameAs(authorizedClient2);
}
@Test
public void saveAuthorizedClientWhenSessionIsNullThenThrowIllegalArgumentException() {
ServerWebExchange exchange = mock(ServerWebExchange.class);
given(exchange.getSession()).willReturn(Mono.empty());
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(this.registration1, this.principalName1,
mock(OAuth2AccessToken.class));
// @formatter:off
assertThatIllegalArgumentException()
.isThrownBy(() -> this.authorizedClientRepository.saveAuthorizedClient(authorizedClient, null, exchange).block())
.withMessage("session cannot be null");
// @formatter:on
}
@Test
public void removeAuthorizedClientWhenSessionIsNullThenThrowIllegalArgumentException() {
ServerWebExchange exchange = mock(ServerWebExchange.class);
given(exchange.getSession()).willReturn(Mono.empty());
// @formatter:off
assertThatIllegalArgumentException()
.isThrownBy(() -> this.authorizedClientRepository.removeAuthorizedClient(this.registrationId1, null, exchange).block())
.withMessage("session cannot be null");
// @formatter:on
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -145,20 +145,17 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
}
@Override
public Mono<Jwt> decode(String token) throws JwtException {
JWT jwt = parse(token);
if (jwt instanceof PlainJWT) {
throw new BadJwtException("Unsupported algorithm of " + jwt.getHeader().getAlgorithm());
}
return this.decode(jwt);
}
private JWT parse(String token) {
public Mono<Jwt> decode(String token) {
try {
return JWTParser.parse(token);
JWT jwt = JWTParser.parse(token);
if (jwt instanceof PlainJWT) {
return Mono.error(new BadJwtException("Unsupported algorithm of " + jwt.getHeader().getAlgorithm()));
}
return this.decode(jwt);
}
catch (Exception ex) {
throw new BadJwtException("An error occurred while attempting to decode the Jwt: " + ex.getMessage(), ex);
return Mono.error(new BadJwtException(
"An error occurred while attempting to decode the Jwt: " + ex.getMessage(), ex));
}
}
@@ -22,6 +22,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.commons.logging.Log;
@@ -179,16 +180,17 @@ public class SpringOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
}
private OAuth2AuthenticatedPrincipal convertClaimsSet(Map<String, Object> claims) {
claims.computeIfPresent(OAuth2TokenIntrospectionClaimNames.AUD, (k, v) -> {
Map<String, Object> converted = new LinkedHashMap<>(claims);
converted.computeIfPresent(OAuth2TokenIntrospectionClaimNames.AUD, (k, v) -> {
if (v instanceof String) {
return Collections.singletonList(v);
}
return v;
});
claims.computeIfPresent(OAuth2TokenIntrospectionClaimNames.CLIENT_ID, (k, v) -> v.toString());
claims.computeIfPresent(OAuth2TokenIntrospectionClaimNames.EXP,
converted.computeIfPresent(OAuth2TokenIntrospectionClaimNames.CLIENT_ID, (k, v) -> v.toString());
converted.computeIfPresent(OAuth2TokenIntrospectionClaimNames.EXP,
(k, v) -> Instant.ofEpochSecond(((Number) v).longValue()));
claims.computeIfPresent(OAuth2TokenIntrospectionClaimNames.IAT,
converted.computeIfPresent(OAuth2TokenIntrospectionClaimNames.IAT,
(k, v) -> Instant.ofEpochSecond(((Number) v).longValue()));
// RFC-7662 page 7 directs users to RFC-7519 for defining the values of these
// issuer fields.
@@ -208,11 +210,11 @@ public class SpringOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
// may be awkward to debug, we do not want to manipulate this value. Previous
// versions of Spring Security
// would *only* allow valid URLs, which is not what we wish to achieve here.
claims.computeIfPresent(OAuth2TokenIntrospectionClaimNames.ISS, (k, v) -> v.toString());
claims.computeIfPresent(OAuth2TokenIntrospectionClaimNames.NBF,
converted.computeIfPresent(OAuth2TokenIntrospectionClaimNames.ISS, (k, v) -> v.toString());
converted.computeIfPresent(OAuth2TokenIntrospectionClaimNames.NBF,
(k, v) -> Instant.ofEpochSecond(((Number) v).longValue()));
Collection<GrantedAuthority> authorities = new ArrayList<>();
claims.computeIfPresent(OAuth2TokenIntrospectionClaimNames.SCOPE, (k, v) -> {
converted.computeIfPresent(OAuth2TokenIntrospectionClaimNames.SCOPE, (k, v) -> {
if (v instanceof String) {
Collection<String> scopes = Arrays.asList(((String) v).split(" "));
for (String scope : scopes) {
@@ -222,7 +224,7 @@ public class SpringOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
}
return v;
});
return new OAuth2IntrospectionAuthenticatedPrincipal(claims, authorities);
return new OAuth2IntrospectionAuthenticatedPrincipal(converted, authorities);
}
}
@@ -22,6 +22,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import reactor.core.publisher.Mono;
@@ -136,16 +137,17 @@ public class SpringReactiveOpaqueTokenIntrospector implements ReactiveOpaqueToke
}
private OAuth2AuthenticatedPrincipal convertClaimsSet(Map<String, Object> claims) {
claims.computeIfPresent(OAuth2TokenIntrospectionClaimNames.AUD, (k, v) -> {
Map<String, Object> converted = new LinkedHashMap<>(claims);
converted.computeIfPresent(OAuth2TokenIntrospectionClaimNames.AUD, (k, v) -> {
if (v instanceof String) {
return Collections.singletonList(v);
}
return v;
});
claims.computeIfPresent(OAuth2TokenIntrospectionClaimNames.CLIENT_ID, (k, v) -> v.toString());
claims.computeIfPresent(OAuth2TokenIntrospectionClaimNames.EXP,
converted.computeIfPresent(OAuth2TokenIntrospectionClaimNames.CLIENT_ID, (k, v) -> v.toString());
converted.computeIfPresent(OAuth2TokenIntrospectionClaimNames.EXP,
(k, v) -> Instant.ofEpochSecond(((Number) v).longValue()));
claims.computeIfPresent(OAuth2TokenIntrospectionClaimNames.IAT,
converted.computeIfPresent(OAuth2TokenIntrospectionClaimNames.IAT,
(k, v) -> Instant.ofEpochSecond(((Number) v).longValue()));
// RFC-7662 page 7 directs users to RFC-7519 for defining the values of these
// issuer fields.
@@ -165,11 +167,11 @@ public class SpringReactiveOpaqueTokenIntrospector implements ReactiveOpaqueToke
// may be awkward to debug, we do not want to manipulate this value. Previous
// versions of Spring Security
// would *only* allow valid URLs, which is not what we wish to achieve here.
claims.computeIfPresent(OAuth2TokenIntrospectionClaimNames.ISS, (k, v) -> v.toString());
claims.computeIfPresent(OAuth2TokenIntrospectionClaimNames.NBF,
converted.computeIfPresent(OAuth2TokenIntrospectionClaimNames.ISS, (k, v) -> v.toString());
converted.computeIfPresent(OAuth2TokenIntrospectionClaimNames.NBF,
(k, v) -> Instant.ofEpochSecond(((Number) v).longValue()));
Collection<GrantedAuthority> authorities = new ArrayList<>();
claims.computeIfPresent(OAuth2TokenIntrospectionClaimNames.SCOPE, (k, v) -> {
converted.computeIfPresent(OAuth2TokenIntrospectionClaimNames.SCOPE, (k, v) -> {
if (v instanceof String) {
Collection<String> scopes = Arrays.asList(((String) v).split(" "));
for (String scope : scopes) {
@@ -179,7 +181,7 @@ public class SpringReactiveOpaqueTokenIntrospector implements ReactiveOpaqueToke
}
return v;
});
return new OAuth2IntrospectionAuthenticatedPrincipal(claims, authorities);
return new OAuth2IntrospectionAuthenticatedPrincipal(converted, authorities);
}
private OAuth2IntrospectionException onError(Throwable ex) {
@@ -96,6 +96,7 @@ project.tasks.matching { t -> t.name == "sourcesJar"}.configureEach {
javadoc {
classpath += sourceSets.opensaml3Main.runtimeClasspath + sourceSets.opensaml4Main.runtimeClasspath
source += sourceSets.opensaml3Main.allJava + sourceSets.opensaml4Main.allJava
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -55,6 +55,7 @@ import org.opensaml.saml.saml2.assertion.SubjectConfirmationValidator;
import org.opensaml.saml.saml2.assertion.impl.AudienceRestrictionConditionValidator;
import org.opensaml.saml.saml2.assertion.impl.BearerSubjectConfirmationValidator;
import org.opensaml.saml.saml2.assertion.impl.DelegationRestrictionConditionValidator;
import org.opensaml.saml.saml2.assertion.impl.ProxyRestrictionConditionValidator;
import org.opensaml.saml.saml2.core.Assertion;
import org.opensaml.saml.saml2.core.Attribute;
import org.opensaml.saml.saml2.core.AttributeStatement;
@@ -837,6 +838,7 @@ public final class OpenSaml4AuthenticationProvider implements AuthenticationProv
return ValidationResult.VALID;
}
});
conditions.add(new ProxyRestrictionConditionValidator());
subjects.add(new BearerSubjectConfirmationValidator() {
@Override
protected ValidationResult validateAddress(SubjectConfirmation confirmation, Assertion assertion,
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -55,6 +55,7 @@ import org.opensaml.saml.saml2.core.EncryptedAttribute;
import org.opensaml.saml.saml2.core.EncryptedID;
import org.opensaml.saml.saml2.core.NameID;
import org.opensaml.saml.saml2.core.OneTimeUse;
import org.opensaml.saml.saml2.core.ProxyRestriction;
import org.opensaml.saml.saml2.core.Response;
import org.opensaml.saml.saml2.core.StatusCode;
import org.opensaml.saml.saml2.core.SubjectConfirmation;
@@ -63,6 +64,7 @@ import org.opensaml.saml.saml2.core.impl.AttributeBuilder;
import org.opensaml.saml.saml2.core.impl.EncryptedAssertionBuilder;
import org.opensaml.saml.saml2.core.impl.EncryptedIDBuilder;
import org.opensaml.saml.saml2.core.impl.NameIDBuilder;
import org.opensaml.saml.saml2.core.impl.ProxyRestrictionBuilder;
import org.opensaml.xmlsec.encryption.impl.EncryptedDataBuilder;
import org.opensaml.xmlsec.signature.support.SignatureConstants;
import org.w3c.dom.Element;
@@ -777,6 +779,19 @@ public class OpenSaml4AuthenticationProviderTests {
.withMessageContaining("did not match any valid issuers");
}
// gh-14931
@Test
public void authenticateWhenAssertionHasProxyRestrictionThenParses() {
OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider();
Response response = response();
Assertion assertion = assertion();
ProxyRestriction condition = new ProxyRestrictionBuilder().buildObject();
assertion.getConditions().getConditions().add(condition);
response.getAssertions().add(assertion);
Saml2AuthenticationToken token = token(signed(response), verifying(registration()));
provider.authenticate(token);
}
private <T extends XMLObject> T build(QName qName) {
return (T) XMLObjectProviderRegistrySupport.getBuilderFactory().getBuilder(qName).buildObject(qName);
}
+3 -1
View File
@@ -11,9 +11,11 @@ changelog:
labels: ["type: bug"]
sort: "title"
- title: ":hammer: Dependency Upgrades"
emoji: ":hammer:"
labels: ["type: dependency-upgrade"]
sort: "title"
- title: ":nut_and_bolt: Build Updates"
labels: ["type: task", "in: build"]
sort: "title"
issues:
exclude:
labels: ["status: duplicate"]
+6 -2
View File
@@ -5,8 +5,12 @@ pluginManagement {
}
plugins {
id "com.gradle.enterprise" version "3.11.4"
id "io.spring.ge.conventions" version "0.0.14"
id "com.gradle.develocity" version "3.17.5"
id "io.spring.ge.conventions" version "0.0.17" apply false
}
if (System.getenv("DEVELOCITY_ACCESS_KEY")) {
apply plugin: "io.spring.ge.conventions"
}
dependencyResolutionManagement {
@@ -30,6 +30,7 @@ import org.springframework.core.log.LogMessage;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.util.UrlUtils;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher.MatchResult;
import org.springframework.security.web.util.matcher.RequestMatcherEntry;
@@ -67,7 +68,7 @@ public final class RequestMatcherDelegatingAuthorizationManager implements Autho
@Override
public AuthorizationDecision check(Supplier<Authentication> authentication, HttpServletRequest request) {
if (this.logger.isTraceEnabled()) {
this.logger.trace(LogMessage.format("Authorizing %s", request));
this.logger.trace(LogMessage.format("Authorizing %s", requestLine(request)));
}
for (RequestMatcherEntry<AuthorizationManager<RequestAuthorizationContext>> mapping : this.mappings) {
@@ -76,7 +77,8 @@ public final class RequestMatcherDelegatingAuthorizationManager implements Autho
if (matchResult.isMatch()) {
AuthorizationManager<RequestAuthorizationContext> manager = mapping.getEntry();
if (this.logger.isTraceEnabled()) {
this.logger.trace(LogMessage.format("Checking authorization on %s using %s", request, manager));
this.logger.trace(
LogMessage.format("Checking authorization on %s using %s", requestLine(request), manager));
}
return manager.check(authentication,
new RequestAuthorizationContext(request, matchResult.getVariables()));
@@ -86,6 +88,10 @@ public final class RequestMatcherDelegatingAuthorizationManager implements Autho
return null;
}
private static String requestLine(HttpServletRequest request) {
return request.getMethod() + " " + UrlUtils.buildRequestUrl(request);
}
/**
* Creates a builder for {@link RequestMatcherDelegatingAuthorizationManager}.
* @return the new {@link Builder} instance
@@ -32,8 +32,8 @@ import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.util.Assert;
/**
* {@link RememberMeServices} implementation based on Barry Jaspan's
* <a href="http://jaspan.com/improved_persistent_login_cookie_best_practice">Improved
* {@link RememberMeServices} implementation based on Barry Jaspan's <a href=
* "https://web.archive.org/web/20180819014446/http://jaspan.com/improved_persistent_login_cookie_best_practice">Improved
* Persistent Login Cookie Best Practice</a>.
*
* There is a slight modification to the described approach, in that the username is not
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -84,17 +84,16 @@ public final class XorCsrfTokenRequestAttributeHandler extends CsrfTokenRequestA
byte[] tokenBytes = Utf8.encode(token);
int tokenSize = tokenBytes.length;
if (actualBytes.length < tokenSize) {
if (actualBytes.length != tokenSize * 2) {
return null;
}
// extract token and random bytes
int randomBytesSize = actualBytes.length - tokenSize;
byte[] xoredCsrf = new byte[tokenSize];
byte[] randomBytes = new byte[randomBytesSize];
byte[] randomBytes = new byte[tokenSize];
System.arraycopy(actualBytes, 0, randomBytes, 0, randomBytesSize);
System.arraycopy(actualBytes, randomBytesSize, xoredCsrf, 0, tokenSize);
System.arraycopy(actualBytes, 0, randomBytes, 0, tokenSize);
System.arraycopy(actualBytes, tokenSize, xoredCsrf, 0, tokenSize);
byte[] csrfBytes = xorCsrf(randomBytes, xoredCsrf);
return Utf8.decode(csrfBytes);
@@ -114,9 +113,10 @@ public final class XorCsrfTokenRequestAttributeHandler extends CsrfTokenRequestA
}
private static byte[] xorCsrf(byte[] randomBytes, byte[] csrfBytes) {
int len = Math.min(randomBytes.length, csrfBytes.length);
Assert.isTrue(randomBytes.length == csrfBytes.length, "arrays must be equal length");
int len = csrfBytes.length;
byte[] xoredCsrf = new byte[len];
System.arraycopy(csrfBytes, 0, xoredCsrf, 0, csrfBytes.length);
System.arraycopy(csrfBytes, 0, xoredCsrf, 0, len);
for (int i = 0; i < len; i++) {
xoredCsrf[i] ^= randomBytes[i];
}
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2021 the original author or authors.
* Copyright 2012-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -130,9 +130,13 @@ public class StrictHttpFirewall implements HttpFirewall {
private static final Predicate<String> ASSIGNED_AND_NOT_ISO_CONTROL_PREDICATE = (
s) -> ASSIGNED_AND_NOT_ISO_CONTROL_PATTERN.matcher(s).matches();
private static final Pattern HEADER_VALUE_PATTERN = Pattern.compile("[\\p{IsAssigned}&&[[^\\p{IsControl}]||\\t]]*");
private static final Predicate<String> HEADER_VALUE_PREDICATE = (s) -> HEADER_VALUE_PATTERN.matcher(s).matches();
private Predicate<String> allowedHeaderNames = ASSIGNED_AND_NOT_ISO_CONTROL_PREDICATE;
private Predicate<String> allowedHeaderValues = ASSIGNED_AND_NOT_ISO_CONTROL_PREDICATE;
private Predicate<String> allowedHeaderValues = HEADER_VALUE_PREDICATE;
private Predicate<String> allowedParameterNames = ASSIGNED_AND_NOT_ISO_CONTROL_PREDICATE;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -77,17 +77,16 @@ public final class XorServerCsrfTokenRequestAttributeHandler extends ServerCsrfT
byte[] tokenBytes = Utf8.encode(token);
int tokenSize = tokenBytes.length;
if (actualBytes.length < tokenSize) {
if (actualBytes.length != tokenSize * 2) {
return null;
}
// extract token and random bytes
int randomBytesSize = actualBytes.length - tokenSize;
byte[] xoredCsrf = new byte[tokenSize];
byte[] randomBytes = new byte[randomBytesSize];
byte[] randomBytes = new byte[tokenSize];
System.arraycopy(actualBytes, 0, randomBytes, 0, randomBytesSize);
System.arraycopy(actualBytes, randomBytesSize, xoredCsrf, 0, tokenSize);
System.arraycopy(actualBytes, 0, randomBytes, 0, tokenSize);
System.arraycopy(actualBytes, tokenSize, xoredCsrf, 0, tokenSize);
byte[] csrfBytes = xorCsrf(randomBytes, xoredCsrf);
return Utf8.decode(csrfBytes);
@@ -107,9 +106,10 @@ public final class XorServerCsrfTokenRequestAttributeHandler extends ServerCsrfT
}
private static byte[] xorCsrf(byte[] randomBytes, byte[] csrfBytes) {
int len = Math.min(randomBytes.length, csrfBytes.length);
Assert.isTrue(randomBytes.length == csrfBytes.length, "arrays must be equal length");
int len = csrfBytes.length;
byte[] xoredCsrf = new byte[len];
System.arraycopy(csrfBytes, 0, xoredCsrf, 0, csrfBytes.length);
System.arraycopy(csrfBytes, 0, xoredCsrf, 0, len);
for (int i = 0; i < len; i++) {
xoredCsrf[i] ^= randomBytes[i];
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -44,6 +44,9 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
*/
public class XorCsrfTokenRequestAttributeHandlerTests {
/*
* Token format: 3 random pad bytes + 3 padded bytes.
*/
private static final byte[] XOR_CSRF_TOKEN_BYTES = new byte[] { 1, 1, 1, 96, 99, 98 };
private static final String XOR_CSRF_TOKEN_VALUE = Base64.getEncoder().encodeToString(XOR_CSRF_TOKEN_BYTES);
@@ -203,6 +206,58 @@ public class XorCsrfTokenRequestAttributeHandlerTests {
assertThat(tokenValue).isEqualTo(this.token.getToken());
}
// gh-13310, gh-15184
@Test
public void resolveCsrfTokenValueWhenCsrfBytesIsShorterThanRandomBytesThenReturnsNull() {
/*
* Token format: 3 random pad bytes + 2 padded bytes.
*/
byte[] actualBytes = { 1, 1, 1, 96, 99 };
String actualToken = Base64.getEncoder().encodeToString(actualBytes);
this.request.setParameter(this.token.getParameterName(), actualToken);
String tokenValue = this.handler.resolveCsrfTokenValue(this.request, this.token);
assertThat(tokenValue).isNull();
}
// gh-13310, gh-15184
@Test
public void resolveCsrfTokenValueWhenCsrfBytesIsLongerThanRandomBytesThenReturnsNull() {
/*
* Token format: 3 random pad bytes + 4 padded bytes.
*/
byte[] actualBytes = { 1, 1, 1, 96, 99, 98, 97 };
String actualToken = Base64.getEncoder().encodeToString(actualBytes);
this.request.setParameter(this.token.getParameterName(), actualToken);
String tokenValue = this.handler.resolveCsrfTokenValue(this.request, this.token);
assertThat(tokenValue).isNull();
}
// gh-13310, gh-15184
@Test
public void resolveCsrfTokenValueWhenTokenBytesIsShorterThanActualBytesThenReturnsNull() {
this.request.setParameter(this.token.getParameterName(), XOR_CSRF_TOKEN_VALUE);
CsrfToken csrfToken = new DefaultCsrfToken("headerName", "paramName", "a");
String tokenValue = this.handler.resolveCsrfTokenValue(this.request, csrfToken);
assertThat(tokenValue).isNull();
}
// gh-13310, gh-15184
@Test
public void resolveCsrfTokenValueWhenTokenBytesIsLongerThanActualBytesThenReturnsNull() {
this.request.setParameter(this.token.getParameterName(), XOR_CSRF_TOKEN_VALUE);
CsrfToken csrfToken = new DefaultCsrfToken("headerName", "paramName", "abcde");
String tokenValue = this.handler.resolveCsrfTokenValue(this.request, csrfToken);
assertThat(tokenValue).isNull();
}
// gh-13310, gh-15184
@Test
public void resolveCsrfTokenValueWhenActualBytesIsEmptyThenReturnsNull() {
this.request.setParameter(this.token.getParameterName(), "");
String tokenValue = this.handler.resolveCsrfTokenValue(this.request, this.token);
assertThat(tokenValue).isNull();
}
private static Answer<Void> fillByteArray() {
return (invocation) -> {
byte[] bytes = invocation.getArgument(0);
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2021 the original author or authors.
* Copyright 2012-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -782,6 +782,13 @@ public class StrictHttpFirewallTests {
assertThatExceptionOfType(RequestRejectedException.class).isThrownBy(() -> request.getHeader("Something"));
}
@Test
public void getFirewalledRequestGetHeaderWhenHorizontalTabInHeaderValueThenNoException() {
this.request.addHeader("Something", "tab\tvalue");
HttpServletRequest request = this.firewall.getFirewalledRequest(this.request);
assertThat(request.getHeader("Something")).isEqualTo("tab\tvalue");
}
@Test
public void getFirewalledRequestGetHeaderWhenUndefinedCharacterInHeaderValueThenException() {
this.request.addHeader("Something", "bad\uFFFEvalue");
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -46,6 +46,9 @@ import static org.mockito.Mockito.verify;
*/
public class XorServerCsrfTokenRequestAttributeHandlerTests {
/*
* Token format: 3 random pad bytes + 3 padded bytes.
*/
private static final byte[] XOR_CSRF_TOKEN_BYTES = new byte[] { 1, 1, 1, 96, 99, 98 };
private static final String XOR_CSRF_TOKEN_VALUE = Base64.getEncoder().encodeToString(XOR_CSRF_TOKEN_BYTES);
@@ -188,6 +191,78 @@ public class XorServerCsrfTokenRequestAttributeHandlerTests {
StepVerifier.create(csrfToken).expectNext(this.token.getToken()).verifyComplete();
}
// gh-13310, gh-15184
@Test
public void resolveCsrfTokenValueWhenCsrfBytesIsShorterThanRandomBytesThenReturnsNull() {
/*
* Token format: 3 random pad bytes + 2 padded bytes.
*/
byte[] actualBytes = { 1, 1, 1, 96, 99 };
String actualToken = Base64.getEncoder().encodeToString(actualBytes);
this.exchange = MockServerWebExchange
.builder(MockServerHttpRequest.post("/")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.header(this.token.getHeaderName(), actualToken))
.build();
String tokenValue = this.handler.resolveCsrfTokenValue(this.exchange, this.token).block();
assertThat(tokenValue).isNull();
}
// gh-13310, gh-15184
@Test
public void resolveCsrfTokenValueWhenCsrfBytesIsLongerThanRandomBytesThenReturnsNull() {
/*
* Token format: 3 random pad bytes + 4 padded bytes.
*/
byte[] actualBytes = { 1, 1, 1, 96, 99, 98, 97 };
String actualToken = Base64.getEncoder().encodeToString(actualBytes);
this.exchange = MockServerWebExchange
.builder(MockServerHttpRequest.post("/")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.header(this.token.getHeaderName(), actualToken))
.build();
String tokenValue = this.handler.resolveCsrfTokenValue(this.exchange, this.token).block();
assertThat(tokenValue).isNull();
}
// gh-13310, gh-15184
@Test
public void resolveCsrfTokenValueWhenTokenBytesIsShorterThanActualBytesThenReturnsNull() {
this.exchange = MockServerWebExchange
.builder(MockServerHttpRequest.post("/")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.header(this.token.getHeaderName(), XOR_CSRF_TOKEN_VALUE))
.build();
CsrfToken csrfToken = new DefaultCsrfToken("headerName", "paramName", "a");
String tokenValue = this.handler.resolveCsrfTokenValue(this.exchange, csrfToken).block();
assertThat(tokenValue).isNull();
}
// gh-13310, gh-15184
@Test
public void resolveCsrfTokenValueWhenTokenBytesIsLongerThanActualBytesThenReturnsNull() {
this.exchange = MockServerWebExchange
.builder(MockServerHttpRequest.post("/")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.header(this.token.getHeaderName(), XOR_CSRF_TOKEN_VALUE))
.build();
CsrfToken csrfToken = new DefaultCsrfToken("headerName", "paramName", "abcde");
String tokenValue = this.handler.resolveCsrfTokenValue(this.exchange, csrfToken).block();
assertThat(tokenValue).isNull();
}
// gh-13310, gh-15184
@Test
public void resolveCsrfTokenValueWhenActualBytesIsEmptyThenReturnsNull() {
this.exchange = MockServerWebExchange
.builder(MockServerHttpRequest.post("/")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.header(this.token.getHeaderName(), ""))
.build();
String tokenValue = this.handler.resolveCsrfTokenValue(this.exchange, this.token).block();
assertThat(tokenValue).isNull();
}
private static Answer<Void> fillByteArray() {
return (invocation) -> {
byte[] bytes = invocation.getArgument(0);