1
0
mirror of synced 2026-07-14 07:05:11 +00:00

Compare commits

..

78 Commits

Author SHA1 Message Date
github-actions[bot] 2966a7210b Release 6.2.5 2024-06-17 15:22:22 +00:00
dependabot[bot] 2a6f6ecdb4 Bump org.springframework.data:spring-data-bom from 2023.1.6 to 2023.1.7
Bumps [org.springframework.data:spring-data-bom](https://github.com/spring-projects/spring-data-bom) from 2023.1.6 to 2023.1.7.
- [Release notes](https://github.com/spring-projects/spring-data-bom/releases)
- [Commits](https://github.com/spring-projects/spring-data-bom/compare/2023.1.6...2023.1.7)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-16 21:04:14 -07:00
github-actions[bot] 73e2e751a1 Merge branch '5.8.x' into 6.2.x 2024-06-14 04:20:20 +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
dependabot[bot] 79c8eb3af4 Bump org.springframework:spring-framework-bom from 6.1.8 to 6.1.9
Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 6.1.8 to 6.1.9.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v6.1.8...v6.1.9)

---
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 20:26:45 -07:00
dependabot[bot] 3d9df377ef Bump org.springframework.ldap:spring-ldap-core from 3.2.3 to 3.2.4
Bumps [org.springframework.ldap:spring-ldap-core](https://github.com/spring-projects/spring-ldap) from 3.2.3 to 3.2.4.
- [Release notes](https://github.com/spring-projects/spring-ldap/releases)
- [Changelog](https://github.com/spring-projects/spring-ldap/blob/main/changelog.txt)
- [Commits](https://github.com/spring-projects/spring-ldap/compare/3.2.3...3.2.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-13 20:22:50 -07:00
Steve Riesenberg e41771491b Merge branch '5.8.x' into 6.2.x
Closes gh-15244
2024-06-13 16:02:05 -05: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] 0913903110 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:54:09 -07:00
github-actions[bot] eb0babc5a6 Merge branch '5.8.x' into 6.2.x 2024-06-13 03:46:58 +00: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
github-actions[bot] a7f8a8a42c Merge branch '5.8.x' into 6.2.x 2024-06-12 04:30:59 +00: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
github-actions[bot] 329130f244 Merge branch '5.8.x' into 6.2.x 2024-06-12 04:14:02 +00: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
dependabot[bot] 50235efb75 Bump io.projectreactor:reactor-bom from 2023.0.6 to 2023.0.7
Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2023.0.6 to 2023.0.7.
- [Release notes](https://github.com/reactor/reactor/releases)
- [Commits](https://github.com/reactor/reactor/compare/2023.0.6...2023.0.7)

---
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:13:16 -07:00
dependabot[bot] ec5cee416e Bump io.micrometer:micrometer-observation from 1.12.6 to 1.12.7
Bumps [io.micrometer:micrometer-observation](https://github.com/micrometer-metrics/micrometer) from 1.12.6 to 1.12.7.
- [Release notes](https://github.com/micrometer-metrics/micrometer/releases)
- [Commits](https://github.com/micrometer-metrics/micrometer/compare/v1.12.6...v1.12.7)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-10 20:42:20 -07:00
dependabot[bot] 122b47e6f9 Bump io-spring-javaformat from 0.0.41 to 0.0.42
Bumps `io-spring-javaformat` from 0.0.41 to 0.0.42.

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

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

---
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-06-09 20:50:17 -07:00
Josh Cummings 0532659245 Fix Typo in Oidc Logout Docs
Closes gh-15198
2024-06-06 13:45:35 -06:00
Josh Cummings f231ea277d Merge branch '5.8.x' into 6.2.x
Closes gh-15210
2024-06-06 13:35:56 -06:00
Josh Cummings 6aabd768a8 Pick MvcRequestMatcher for MockMvc requests
Closes gh-13849
2024-06-06 13:17:43 -06:00
Josh Cummings d6228e0882 Merge branch '5.8.x' into 6.2.x
Closes gh-15196
2024-06-03 17:42:25 -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] 8743186423 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:50:24 -07:00
github-actions[bot] 2dd1c58450 Merge branch '5.8.x' into 6.2.x 2024-06-03 03:35:21 +00: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 3fc7b6e921 Merge branch '5.8.x' into 6.2.x
Closes gh-15185
2024-05-31 18:34:14 -05:00
Steve Riesenberg dcb8c563e8 Fix ArrayIndexOutOfBoundsException
Issue gh-13310
Closes gh-15184
2024-05-31 18:12:21 -05:00
Josh Cummings 1cc66faaef Polish Update Signature Validator
Issue gh-15022
2024-05-31 14:13:23 -06:00
Josh Cummings 99f233f98c Update Signature Validator
Closes gh-15022
2024-05-31 13:29:28 -06:00
Steve Riesenberg 5a1d261ce0 Merge branch '5.8.x' into 6.2.x 2024-05-29 16:23:37 -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
dependabot[bot] 130e7d1b3f Bump org.apache.maven:maven-resolver-provider from 3.9.6 to 3.9.7
Bumps [org.apache.maven:maven-resolver-provider](https://github.com/apache/maven) from 3.9.6 to 3.9.7.
- [Release notes](https://github.com/apache/maven/releases)
- [Commits](https://github.com/apache/maven/compare/maven-3.9.6...maven-3.9.7)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-26 21:09:20 -07:00
Josh Cummings 797550fee7 Merge branch '5.8.x' into 6.2.x
Closes gh-15166
2024-05-24 15:04:04 -06:00
Josh Cummings e7ea4091a0 Migrate SampleLDIF to UnboundID
Closes gh-15089
2024-05-24 15:03:53 -06:00
Josh Cummings d0f95c90c5 Merge branch '5.8.x' into 6.2.x 2024-05-24 14:25:23 -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
dependabot[bot] a30088fc0a 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:44:23 -07:00
Josh Cummings 092a63d1e0 Conditionally Add Conventions Plugin 2024-05-23 16:56:35 -06:00
Marcus Hert Da Coregio 58cbc47376 Merge branch '5.8.x' into 6.2.x
Closes gh-15150
2024-05-23 14:17:09 -03: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 47ad405063 Merge branch '5.8.x' into 6.2.x
Closes gh-15148
2024-05-23 14:04:35 -03:00
Marcus Hert Da Coregio c7b739eb3f Fix broken link to jaspan article
Closes gh-14358
2024-05-23 14:04:10 -03:00
douxf bd72741879 Fix wrong class on documentation
Closes gh-15045
2024-05-23 08:15:56 -03:00
github-actions[bot] 08d8f56cd6 Merge branch '5.8.x' into 6.2.x 2024-05-23 11:11:07 +00: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] cda45d6f2c 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:02:50 -07:00
github-actions[bot] 80737c1a39 Merge branch '5.8.x' into 6.2.x 2024-05-23 04:10:07 +00: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] 395310deb6 Bump org.springframework:spring-framework-bom from 6.1.7 to 6.1.8
Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 6.1.7 to 6.1.8.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v6.1.7...v6.1.8)

---
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 20:53:56 -07:00
github-actions[bot] 2ff8dfe239 Merge branch '5.8.x' into 6.2.x 2024-05-21 17:39:12 +00: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
github-actions[bot] 3d7bcd2fad Merge branch '5.8.x' into 6.2.x 2024-05-21 15:37:45 +00: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
github-actions[bot] ca40d90d2c Merge branch '5.8.x' into 6.2.x 2024-05-21 01:06:14 +00: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
github-actions[bot] c6009ae007 Merge branch '5.8.x' into 6.2.x 2024-05-20 22:49:19 +00: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
github-actions[bot] a57ba8a8d0 Merge branch '5.8.x' into 6.2.x 2024-05-20 22:36:48 +00: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 013c46098e Merge branch '6.1.x' into 6.2.x 2024-05-20 16:21:15 -06:00
Josh Cummings 5bfd1f59d1 Merge branch '5.8.x' into 6.1.x 2024-05-20 16:21:04 -06: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 491f494991 Merge branch '6.1.x' into 6.2.x 2024-05-20 12:43:41 -06:00
Josh Cummings 0e4d29763f Merge branch '5.8.x' into 6.1.x 2024-05-20 12:42:52 -06:00
Josh Cummings 63e24a3026 Adjust JavaDoc Classpath
Issue gh-14931
2024-05-20 12:42:01 -06:00
Marcus Hert Da Coregio 7251c7ffb7 Merge branch '6.1.x' into 6.2.x 2024-05-20 13:52:07 -03:00
Marcus Hert Da Coregio 514600721c Merge branch '5.8.x' into 6.1.x 2024-05-20 13:51:57 -03:00
github-actions[bot] 86de2dbf6f Update Antora Spring UI to v0.4.15 2024-05-20 13:51:35 -03:00
github-actions[bot] 2563b854ea Update Antora Spring UI to v0.4.15 2024-05-20 13:51:29 -03:00
github-actions[bot] 9027dceb71 Update Antora Spring UI to v0.4.15 2024-05-20 13:51:18 -03:00
dependabot[bot] d61a85f74b Bump com.gradle.develocity from 3.17.3 to 3.17.4
Bumps com.gradle.develocity from 3.17.3 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-19 20:39:20 -07:00
dependabot[bot] 8b3d33ed64 Bump org.springframework:spring-framework-bom from 6.0.19 to 6.0.20
Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 6.0.19 to 6.0.20.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v6.0.19...v6.0.20)

---
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-16 20:31:22 -07:00
dependabot[bot] defe27d53e Bump io.mockk:mockk from 1.13.10 to 1.13.11
Bumps [io.mockk:mockk](https://github.com/mockk/mockk) from 1.13.10 to 1.13.11.
- [Release notes](https://github.com/mockk/mockk/releases)
- [Commits](https://github.com/mockk/mockk/compare/1.13.10...1.13.11)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-16 20:31:02 -07:00
dependabot[bot] c5b86370ff Bump org-eclipse-jetty from 11.0.20 to 11.0.21
Bumps `org-eclipse-jetty` from 11.0.20 to 11.0.21.

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

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

---
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-05-16 20:29:46 -07:00
353 changed files with 1900 additions and 31328 deletions
-41
View File
@@ -1,41 +0,0 @@
version: 2
registries:
spring-milestones:
type: maven-repository
url: https://repo.spring.io/milestone
updates:
- package-ecosystem: "gradle"
target-branch: "main"
directory: "/"
schedule:
interval: "daily"
time: "03:00"
timezone: "Etc/UTC"
labels: [ "type: dependency-upgrade" ]
registries:
- "spring-milestones"
ignore:
- dependency-name: "com.nimbusds:nimbus-jose-jwt" # nimbus-jose-jwt gets updated when oauth2-oidc-sdk is updated to ensure consistency
- dependency-name: "org.python:jython" # jython updates break integration tests
- dependency-name: "org.apache.directory.server:*" # ApacheDS version > 1.5.5 contains break changes
- dependency-name: "org.junit:junit-bom"
update-types: [ "version-update:semver-major" ]
- dependency-name: "org.mockito:mockito-bom"
update-types: [ "version-update:semver-major" ]
- dependency-name: "*"
update-types: [ "version-update:semver-major", "version-update:semver-minor" ]
# GitHub Actions
- package-ecosystem: github-actions
target-branch: "main"
directory: "/"
schedule:
interval: weekly
ignore:
- dependency-name: "sjohnr/*"
- dependency-name: "spring-io/*"
- dependency-name: "spring-security-release-tools/*"
+65 -169
View File
@@ -1,176 +1,72 @@
version: 2
registries:
spring-milestones:
type: maven-repository
url: https://repo.spring.io/milestone
updates:
- package-ecosystem: gradle
target-branch: 5.8.x
directory: /
schedule:
interval: daily
time: '03:00'
timezone: Etc/UTC
labels:
- 'type: dependency-upgrade'
registries:
- spring-milestones
ignore:
- dependency-name: com.nimbusds:nimbus-jose-jwt
- dependency-name: org.python:jython
- dependency-name: org.apache.directory.server:*
- dependency-name: org.junit:junit-bom
update-types:
- version-update:semver-major
- dependency-name: org.mockito:mockito-bom
update-types:
- version-update:semver-major
- dependency-name: '*'
update-types:
- version-update:semver-major
- version-update:semver-minor
- package-ecosystem: gradle
target-branch: 6.1.x
directory: /
schedule:
interval: daily
time: '03:00'
timezone: Etc/UTC
labels:
- 'type: dependency-upgrade'
registries:
- spring-milestones
ignore:
- dependency-name: com.nimbusds:nimbus-jose-jwt
- dependency-name: org.python:jython
- dependency-name: org.apache.directory.server:*
- dependency-name: org.junit:junit-bom
update-types:
- version-update:semver-major
- dependency-name: org.mockito:mockito-bom
update-types:
- version-update:semver-major
- dependency-name: '*'
update-types:
- version-update:semver-major
- version-update:semver-minor
- package-ecosystem: gradle
target-branch: 6.2.x
directory: /
schedule:
interval: daily
time: '03:00'
timezone: Etc/UTC
labels:
- 'type: dependency-upgrade'
registries:
- spring-milestones
ignore:
- dependency-name: com.nimbusds:nimbus-jose-jwt
- dependency-name: org.python:jython
- dependency-name: org.apache.directory.server:*
- dependency-name: org.junit:junit-bom
update-types:
- version-update:semver-major
- dependency-name: org.mockito:mockito-bom
update-types:
- version-update:semver-major
- dependency-name: '*'
update-types:
- version-update:semver-major
- version-update:semver-minor
- package-ecosystem: gradle
target-branch: main
directory: /
schedule:
interval: daily
time: '03:00'
timezone: Etc/UTC
labels:
- 'type: dependency-upgrade'
registries:
- spring-milestones
ignore:
- dependency-name: com.nimbusds:nimbus-jose-jwt
- dependency-name: org.python:jython
- dependency-name: org.apache.directory.server:*
- dependency-name: org.junit:junit-bom
update-types:
- version-update:semver-major
- dependency-name: org.mockito:mockito-bom
update-types:
- version-update:semver-major
- dependency-name: com.gradle.enterprise
update-types:
- version-update:semver-major
- version-update:semver-minor
- dependency-name: '*'
update-types:
- version-update:semver-major
- version-update:semver-minor
- package-ecosystem: github-actions
target-branch: 5.8.x
directory: /
schedule:
interval: weekly
labels:
- 'type: task'
- 'in: build'
ignore:
- dependency-name: sjohnr/*
- dependency-name: spring-io/*
- dependency-name: spring-security-release-tools/*
- package-ecosystem: github-actions
target-branch: 6.1.x
directory: /
schedule:
interval: weekly
labels:
- 'type: task'
- 'in: build'
ignore:
- dependency-name: sjohnr/*
- dependency-name: spring-io/*
- dependency-name: spring-security-release-tools/*
- package-ecosystem: github-actions
target-branch: 6.2.x
directory: /
schedule:
interval: weekly
labels:
- 'type: task'
- 'in: build'
ignore:
- dependency-name: sjohnr/*
- dependency-name: spring-io/*
- dependency-name: spring-security-release-tools/*
- package-ecosystem: github-actions
target-branch: main
directory: /
schedule:
interval: weekly
labels:
- 'type: task'
- 'in: build'
ignore:
- dependency-name: sjohnr/*
- dependency-name: spring-io/*
- dependency-name: spring-security-release-tools/*
- package-ecosystem: github-actions
target-branch: docs-build
directory: /
schedule:
interval: weekly
labels:
- 'type: task'
- 'in: build'
ignore:
- dependency-name: sjohnr/*
- dependency-name: spring-io/*
- dependency-name: spring-security-release-tools/*
- package-ecosystem: npm
target-branch: docs-build
directory: /
updates:
- package-ecosystem: "gradle"
target-branch: "main"
milestone: 319 # 6.2.x
directory: "/"
schedule:
interval: weekly
interval: "daily"
time: "03:00"
timezone: "Etc/UTC"
labels: [ "type: dependency-upgrade" ]
registries:
- "spring-milestones"
ignore:
- dependency-name: "com.nimbusds:nimbus-jose-jwt" # nimbus-jose-jwt gets updated when oauth2-oidc-sdk is updated to ensure consistency
- dependency-name: "org.python:jython" # jython updates break integration tests
- dependency-name: "org.apache.directory.server:*" # ApacheDS version > 1.5.5 contains break changes
- dependency-name: "org.junit:junit-bom"
update-types: [ "version-update:semver-major" ]
- dependency-name: "org.mockito:mockito-bom"
update-types: [ "version-update:semver-major" ]
- dependency-name: "*"
update-types: [ "version-update:semver-major", "version-update:semver-minor" ]
- package-ecosystem: "gradle"
target-branch: "6.1.x"
milestone: 318 # 6.1.x
directory: "/"
schedule:
interval: "daily"
time: "03:00"
timezone: "Etc/UTC"
labels: [ "type: dependency-upgrade" ]
ignore:
- dependency-name: "com.nimbusds:nimbus-jose-jwt" # nimbus-jose-jwt gets updated when oauth2-oidc-sdk is updated to ensure consistency
- dependency-name: "org.python:jython" # jython updates break integration tests
- dependency-name: "org.apache.directory.server:*" # ApacheDS version > 1.5.5 contains break changes
- dependency-name: "org.junit:junit-bom"
update-types: [ "version-update:semver-major" ]
- dependency-name: "org.mockito:mockito-bom"
update-types: [ "version-update:semver-major" ]
- dependency-name: "*"
update-types: [ "version-update:semver-major", "version-update:semver-minor" ]
- package-ecosystem: "gradle"
target-branch: "5.8.x"
milestone: 246 # 5.8.x
directory: "/"
schedule:
interval: "daily"
time: "03:00"
timezone: "Etc/UTC"
labels: [ "type: dependency-upgrade" ]
ignore:
- dependency-name: "com.nimbusds:nimbus-jose-jwt" # nimbus-jose-jwt gets updated when oauth2-oidc-sdk is updated to ensure consistency
- dependency-name: "org.python:jython" # jython updates break integration tests
- dependency-name: "org.apache.directory.server:*" # ApacheDS version > 1.5.5 contains break changes
- dependency-name: "io.mockk:mockk" # mockk updates break tests
- dependency-name: "org.opensaml:*" # org.opensaml maintains two different versions, so it must be updated manually
- dependency-name: "org.junit:junit-bom"
update-types: [ "version-update:semver-major" ]
- dependency-name: "org.mockito:mockito-bom"
update-types: [ "version-update:semver-major" ]
- dependency-name: "*"
update-types: [ "version-update:semver-major", "version-update:semver-minor" ]
@@ -1,57 +0,0 @@
name: Auto Merge Forward Dependabot Commits
on:
workflow_dispatch:
permissions:
contents: read
concurrency:
group: dependabot-auto-merge-forward
jobs:
get-supported-branches:
uses: spring-io/spring-security-release-tools/.github/workflows/retrieve-spring-supported-versions.yml@actions-v1
with:
project: spring-security
type: oss
repository_name: spring-projects/spring-security
auto-merge-forward-dependabot:
name: Auto Merge Forward Dependabot Commits
runs-on: ubuntu-latest
needs: [get-supported-branches]
permissions:
contents: write
steps:
- name: Checkout
id: checkout
uses: actions/checkout@v4
with:
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
- name: Setup GitHub User
id: setup-gh-user
run: |
git config user.name 'github-actions[bot]'
git config user.email 'github-actions[bot]@users.noreply.github.com'
- name: Run Auto Merge Forward
id: run-auto-merge-forward
uses: spring-io/spring-security-release-tools/.github/actions/auto-merge-forward@actions-v1
with:
branches: ${{ needs.get-supported-branches.outputs.supported_versions }},main
from-author: dependabot[bot]
notify_result:
name: Check for failures
needs: [ auto-merge-forward-dependabot ]
if: failure()
runs-on: ubuntu-latest
permissions:
actions: read
steps:
- name: Send Slack message
uses: Gamesight/slack-workflow-status@v1.3.0
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
slack_webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }}
channel: '#spring-security-ci'
name: 'CI Notifier'
+1 -1
View File
@@ -21,7 +21,7 @@ jobs:
java-version: '17'
distribution: 'temurin'
- name: Build with Gradle
run: ./gradlew clean build --continue --scan
run: ./gradlew clean build --continue
generate-docs:
name: Generate Docs
runs-on: ubuntu-latest
+1 -1
View File
@@ -11,7 +11,7 @@ jobs:
strategy:
matrix:
# List of active maintenance branches.
branch: [ main, 6.2.x, 6.1.x, 5.8.x ]
branch: [ main, 6.1.x, 5.8.x ]
runs-on: ubuntu-latest
steps:
- name: Checkout
@@ -1,29 +0,0 @@
name: Update Antora UI Spring
on:
workflow_dispatch:
permissions:
pull-requests: write
issues: write
contents: write
jobs:
update-antora-ui-spring:
runs-on: ubuntu-latest
name: Update
strategy:
matrix:
branch: [ '5.8.x', '6.1.x', '6.2.x', 'main' ]
steps:
- uses: spring-io/spring-doc-actions/update-antora-spring-ui@17ed79ea5fbd65813c69ef1062a024d4a37ff0ca
name: Update on Supported Branches
with:
docs-branch: ${{ matrix.branch }}
token: ${{ secrets.GITHUB_TOKEN }}
antora-file-path: 'docs/antora-playbook.yml'
- uses: spring-io/spring-doc-actions/update-antora-spring-ui@17ed79ea5fbd65813c69ef1062a024d4a37ff0ca
name: Update on docs-build
with:
docs-branch: 'docs-build'
token: ${{ secrets.GITHUB_TOKEN }}
-36
View File
@@ -1,36 +0,0 @@
name: Update dependabot.yml
on:
workflow_dispatch:
permissions:
contents: read
jobs:
get-supported-branches:
uses: spring-io/spring-security-release-tools/.github/workflows/retrieve-spring-supported-versions.yml@actions-v1
with:
project: spring-security
type: oss
repository_name: spring-projects/spring-security
main:
runs-on: ubuntu-latest
needs: [get-supported-branches]
if: ${{ (github.repository == 'spring-projects/spring-security') && (github.ref == 'refs/heads/main') }}
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- uses: spring-io/spring-security-release-tools/.github/actions/generate-dependabot-yml@actions-v1
name: Update dependabot.yml
with:
gradle-branches: ${{ needs.get-supported-branches.outputs.supported_versions }},main
github-actions-branches: ${{ needs.get-supported-branches.outputs.supported_versions }},main,docs-build
gh-token: ${{ secrets.GITHUB_TOKEN }}
- uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: Update dependabot.yml
+1 -1
View File
@@ -105,7 +105,7 @@ Once merged, the fix will be forwarded-ported to applicable branches including `
1. Create a local branch
If this is for an issue, consider a branch name with the issue number, like `gh-22276`.
[[write-tests]]
1. Add documentation and JUnit Tests for your changes.
1. Add JUnit Tests for your changes
[[update-copyright]]
1. In all files you edited, if the copyright header is of the form 2002-20xx, update the final copyright year to the current year.
[[add-since]]
+4 -4
View File
@@ -1,8 +1,8 @@
image::https://badges.gitter.im/Join%20Chat.svg[Gitter,link=https://gitter.im/spring-projects/spring-security?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge]
image:https://github.com/spring-projects/spring-security/actions/workflows/continuous-integration-workflow.yml/badge.svg?branch=main["Build Status", link="https://github.com/spring-projects/spring-security/actions/workflows/continuous-integration-workflow.yml"]
image:https://github.com/spring-projects/spring-security/workflows/CI/badge.svg?branch=main["Build Status", link="https://github.com/spring-projects/spring-security/actions?query=workflow%3ACI"]
image:https://img.shields.io/badge/Revved%20up%20by-Develocity-06A0CE?logo=Gradle&labelColor=02303A["Revved up by Develocity", link="https://ge.spring.io/scans?search.rootProjectNames=spring-security"]
image:https://img.shields.io/badge/Revved%20up%20by-Gradle%20Enterprise-06A0CE?logo=Gradle&labelColor=02303A["Revved up by Gradle Enterprise", link="https://ge.spring.io/scans?search.rootProjectNames=spring-security"]
= Spring Security
@@ -15,14 +15,14 @@ For a detailed list of features and access to the latest release, please visit h
Please see our https://github.com/spring-projects/.github/blob/main/CODE_OF_CONDUCT.md[code of conduct]
== Downloading Artifacts
See https://docs.spring.io/spring-security/reference/getting-spring-security.html[Getting Spring Security] for how to obtain Spring Security.
See https://docs.spring.io/spring-security/site/docs/current/reference/html5/#getting[Getting Spring Security] for how to obtain Spring Security.
== Documentation
Be sure to read the https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/[Spring Security Reference].
Extensive JavaDoc for the Spring Security code is also available in the https://docs.spring.io/spring-security/site/docs/current/api/[Spring Security API Documentation].
== Quick Start
See https://docs.spring.io/spring-security/reference/servlet/getting-started.html[Hello Spring Security] to get started with a "Hello, World" application.
See https://docs.spring.io/spring-security/site/docs/current/reference/html5/#servlet-hello[Hello Spring Security] to get started with a "Hello, World" application.
== Building from Source
Spring Security uses a https://gradle.org[Gradle]-based build system.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 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.
@@ -103,13 +103,6 @@ public class PostAuthorizeAspectTests {
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.prePostSecured::denyAllMethod);
}
@Test
public void nestedDenyAllPostAuthorizeDeniesAccess() {
SecurityContextHolder.getContext().setAuthentication(this.anne);
assertThatExceptionOfType(AccessDeniedException.class)
.isThrownBy(() -> this.secured.myObject().denyAllMethod());
}
interface SecuredInterface {
@PostAuthorize("hasRole('X')")
@@ -141,10 +134,6 @@ public class PostAuthorizeAspectTests {
privateMethod();
}
NestedObject myObject() {
return new NestedObject();
}
}
static class SecuredImplSubclass extends SecuredImpl {
@@ -168,13 +157,4 @@ public class PostAuthorizeAspectTests {
}
static class NestedObject {
@PostAuthorize("denyAll")
void denyAllMethod() {
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 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.
@@ -54,11 +54,6 @@ public class PostFilterAspectTests {
assertThat(this.prePostSecured.postFilterMethod(objects)).containsExactly("apple", "aubergine");
}
@Test
public void nestedDenyAllPostFilterDeniesAccess() {
assertThat(this.prePostSecured.myObject().denyAllMethod()).isEmpty();
}
static class PrePostSecured {
@PostFilter("filterObject.startsWith('a')")
@@ -66,19 +61,6 @@ public class PostFilterAspectTests {
return objects;
}
NestedObject myObject() {
return new NestedObject();
}
}
static class NestedObject {
@PostFilter("filterObject == null")
List<String> denyAllMethod() {
return new ArrayList<>(List.of("deny"));
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 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.
@@ -103,13 +103,6 @@ public class PreAuthorizeAspectTests {
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.prePostSecured::denyAllMethod);
}
@Test
public void nestedDenyAllPreAuthorizeDeniesAccess() {
SecurityContextHolder.getContext().setAuthentication(this.anne);
assertThatExceptionOfType(AccessDeniedException.class)
.isThrownBy(() -> this.secured.myObject().denyAllMethod());
}
interface SecuredInterface {
@PreAuthorize("hasRole('X')")
@@ -141,10 +134,6 @@ public class PreAuthorizeAspectTests {
privateMethod();
}
NestedObject myObject() {
return new NestedObject();
}
}
static class SecuredImplSubclass extends SecuredImpl {
@@ -168,13 +157,4 @@ public class PreAuthorizeAspectTests {
}
static class NestedObject {
@PreAuthorize("denyAll")
void denyAllMethod() {
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 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.
@@ -54,11 +54,6 @@ public class PreFilterAspectTests {
assertThat(this.prePostSecured.preFilterMethod(objects)).containsExactly("apple", "aubergine");
}
@Test
public void nestedDenyAllPreFilterDeniesAccess() {
assertThat(this.prePostSecured.myObject().denyAllMethod(new ArrayList<>(List.of("deny")))).isEmpty();
}
static class PrePostSecured {
@PreFilter("filterObject.startsWith('a')")
@@ -66,19 +61,6 @@ public class PreFilterAspectTests {
return objects;
}
NestedObject myObject() {
return new NestedObject();
}
}
static class NestedObject {
@PreFilter("filterObject == null")
List<String> denyAllMethod(List<String> list) {
return list;
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 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-2024 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.
@@ -28,7 +28,6 @@ import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.util.Assert;
/**
@@ -62,8 +61,6 @@ public class CasAuthenticationEntryPoint implements AuthenticationEntryPoint, In
*/
private boolean encodeServiceUrlWithSessionId = true;
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
@Override
public void afterPropertiesSet() {
Assert.hasLength(this.loginUrl, "loginUrl must be specified");
@@ -77,7 +74,8 @@ public class CasAuthenticationEntryPoint implements AuthenticationEntryPoint, In
String urlEncodedService = createServiceUrl(servletRequest, response);
String redirectUrl = createRedirectUrl(urlEncodedService);
preCommence(servletRequest, response);
this.redirectStrategy.sendRedirect(servletRequest, response, redirectUrl);
new DefaultRedirectStrategy().sendRedirect(servletRequest, response, redirectUrl);
// response.sendRedirect(redirectUrl);
}
/**
@@ -151,14 +149,4 @@ public class CasAuthenticationEntryPoint implements AuthenticationEntryPoint, In
return this.encodeServiceUrlWithSessionId;
}
/**
* Sets the {@link RedirectStrategy} to use
* @param redirectStrategy the {@link RedirectStrategy} to use
* @since 6.3
*/
public void setRedirectStrategy(RedirectStrategy redirectStrategy) {
Assert.notNull(redirectStrategy, "redirectStrategy cannot be null");
this.redirectStrategy = redirectStrategy;
}
}
@@ -22,7 +22,6 @@ import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.apereo.cas.client.proxy.ProxyGrantingTicketStorage;
import org.apereo.cas.client.util.WebUtils;
import org.apereo.cas.client.validation.TicketValidator;
@@ -41,20 +40,14 @@ import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.SavedRequest;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Processes a CAS service ticket, obtains proxy granting tickets, and processes proxy
@@ -62,14 +55,14 @@ import org.springframework.util.StringUtils;
* <h2>Service Tickets</h2>
* <p>
* A service ticket consists of an opaque ticket string. It arrives at this filter by the
* user's browser successfully authenticating using CAS, and then receiving an HTTP
* user's browser successfully authenticating using CAS, and then receiving a HTTP
* redirect to a <code>service</code>. The opaque ticket string is presented in the
* <code>ticket</code> request parameter.
* <p>
* This filter monitors the <code>service</code> URL so that it can receive the service
* ticket and process it. By default, this filter processes the URL <tt>/login/cas</tt>.
* When processing this URL, the value of {@link ServiceProperties#getService()} is used
* as the <tt>service</tt> when validating the <code>ticket</code>. This means that it is
* This filter monitors the <code>service</code> URL so it can receive the service ticket
* and process it. By default this filter processes the URL <tt>/login/cas</tt>. When
* processing this URL, the value of {@link ServiceProperties#getService()} is used as the
* <tt>service</tt> when validating the <code>ticket</code>. This means that it is
* important that {@link ServiceProperties#getService()} specifies the same value as the
* <tt>filterProcessesUrl</tt>.
* <p>
@@ -80,22 +73,22 @@ import org.springframework.util.StringUtils;
* <h2>Obtaining Proxy Granting Tickets</h2>
* <p>
* If specified, the filter can also monitor the <code>proxyReceptorUrl</code>. The filter
* will respond to the requests matching this url so that the CAS Server can provide a PGT
* to the filter. Note that in addition to the <code>proxyReceptorUrl</code> a non-null
* will respond to requests matching this url so that the CAS Server can provide a PGT to
* the filter. Note that in addition to the <code>proxyReceptorUrl</code> a non-null
* <code>proxyGrantingTicketStorage</code> must be provided in order for the filter to
* respond to proxy receptor requests. By configuring a shared
* {@link ProxyGrantingTicketStorage} between the {@link TicketValidator} and the
* <code>CasAuthenticationFilter</code>, one can have the
* <code>CasAuthenticationFilter</code> handling the proxying requirements for CAS.
* CasAuthenticationFilter one can have the CasAuthenticationFilter handle the proxying
* requirements for CAS.
* <h2>Proxy Tickets</h2>
* <p>
* The filter can process tickets present on any url. This is useful when one wants to
* process proxy tickets. In order for proxy tickets to get processed,
* The filter can process tickets present on any url. This is useful when wanting to
* process proxy tickets. In order for proxy tickets to get processed
* {@link ServiceProperties#isAuthenticateAllArtifacts()} must return <code>true</code>.
* Additionally, if the request is already authenticated, authentication will <b>not</b>
* occur. Last, {@link AuthenticationDetailsSource#buildDetails(Object)} must return a
* {@link ServiceAuthenticationDetails}. This can be accomplished using the
* {@link ServiceAuthenticationDetailsSource}. In this case,
* {@link ServiceAuthenticationDetailsSource}. In this case
* {@link ServiceAuthenticationDetails#getServiceUrl()} will be used for the service url.
* <p>
* Processing the proxy ticket involves creating a
@@ -207,10 +200,6 @@ public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFil
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
.getContextHolderStrategy();
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
private RequestCache requestCache = new HttpSessionRequestCache();
private final AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
public CasAuthenticationFilter() {
@@ -251,22 +240,7 @@ public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFil
return null;
}
String serviceTicket = obtainArtifact(request);
if (!StringUtils.hasText(serviceTicket)) {
HttpSession session = request.getSession(false);
if (session != null && session
.getAttribute(CasGatewayAuthenticationRedirectFilter.CAS_GATEWAY_AUTHENTICATION_ATTR) != null) {
this.logger.debug("Failed authentication response from CAS gateway request");
session.removeAttribute(CasGatewayAuthenticationRedirectFilter.CAS_GATEWAY_AUTHENTICATION_ATTR);
SavedRequest savedRequest = this.requestCache.getRequest(request, response);
if (savedRequest != null) {
String redirectUrl = savedRequest.getRedirectUrl();
this.logger.debug(LogMessage.format("Redirecting to: %s", redirectUrl));
this.requestCache.removeRequest(request, response);
this.redirectStrategy.sendRedirect(request, response, redirectUrl);
return null;
}
}
if (serviceTicket == null) {
this.logger.debug("Failed to obtain an artifact (cas ticket)");
serviceTicket = "";
}
@@ -344,28 +318,6 @@ public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFil
this.securityContextHolderStrategy = securityContextHolderStrategy;
}
/**
* Set the {@link RedirectStrategy} used to redirect to the saved request if there is
* one saved. Defaults to {@link DefaultRedirectStrategy}.
* @param redirectStrategy the redirect strategy to use
* @since 6.3
*/
public final void setRedirectStrategy(RedirectStrategy redirectStrategy) {
Assert.notNull(redirectStrategy, "redirectStrategy cannot be null");
this.redirectStrategy = redirectStrategy;
}
/**
* The {@link RequestCache} used to retrieve the saved request in failed gateway
* authentication scenarios.
* @param requestCache the request cache to use
* @since 6.3
*/
public final void setRequestCache(RequestCache requestCache) {
Assert.notNull(requestCache, "requestCache cannot be null");
this.requestCache = requestCache;
}
/**
* Indicates if the request is elgible to process a service ticket. This method exists
* for readability.
@@ -1,126 +0,0 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.cas.web;
import java.io.IOException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.apereo.cas.client.util.CommonUtils;
import org.apereo.cas.client.util.WebUtils;
import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.web.filter.GenericFilterBean;
/**
* Redirects the request to the CAS server appending {@code gateway=true} to the URL. Upon
* redirection, the {@link ServiceProperties#isSendRenew()} is ignored and considered as
* {@code false} to align with the specification says that the {@code sendRenew} parameter
* is not compatible with the {@code gateway} parameter. See the <a href=
* "https://apereo.github.io/cas/6.6.x/protocol/CAS-Protocol-V2-Specification.html#:~:text=This%20parameter%20is%20not%20compatible%20with%20the%20%E2%80%9Crenew%E2%80%9D%20parameter.%20Behavior%20is%20undefined%20if%20both%20are%20set.">CAS
* Protocol Specification</a> for more details. To allow other filters to know if the
* request is a gateway request, this filter creates a session and add an attribute with
* name {@link #CAS_GATEWAY_AUTHENTICATION_ATTR} which can be checked by other filters if
* needed. It is recommended that this filter is placed after
* {@link CasAuthenticationFilter} if it is defined.
*
* @author Michael Remond
* @author Jerome LELEU
* @author Marcus da Coregio
* @since 6.3
*/
public final class CasGatewayAuthenticationRedirectFilter extends GenericFilterBean {
public static final String CAS_GATEWAY_AUTHENTICATION_ATTR = "CAS_GATEWAY_AUTHENTICATION";
private final String casLoginUrl;
private final ServiceProperties serviceProperties;
private RequestMatcher requestMatcher;
private RequestCache requestCache = new HttpSessionRequestCache();
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
/**
* Constructs a new instance of this class
* @param serviceProperties the {@link ServiceProperties}
*/
public CasGatewayAuthenticationRedirectFilter(String casLoginUrl, ServiceProperties serviceProperties) {
Assert.hasText(casLoginUrl, "casLoginUrl cannot be null or empty");
Assert.notNull(serviceProperties, "serviceProperties cannot be null");
this.casLoginUrl = casLoginUrl;
this.serviceProperties = serviceProperties;
this.requestMatcher = new CasGatewayResolverRequestMatcher(this.serviceProperties);
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (!this.requestMatcher.matches(request)) {
chain.doFilter(request, response);
return;
}
this.requestCache.saveRequest(request, response);
HttpSession session = request.getSession(true);
session.setAttribute(CAS_GATEWAY_AUTHENTICATION_ATTR, true);
String urlEncodedService = WebUtils.constructServiceUrl(request, response, this.serviceProperties.getService(),
null, this.serviceProperties.getServiceParameter(), this.serviceProperties.getArtifactParameter(),
true);
String redirectUrl = CommonUtils.constructRedirectUrl(this.casLoginUrl,
this.serviceProperties.getServiceParameter(), urlEncodedService, false, true);
this.redirectStrategy.sendRedirect(request, response, redirectUrl);
}
/**
* Sets the {@link RequestMatcher} used to trigger this filter. Defaults to
* {@link CasGatewayResolverRequestMatcher}.
* @param requestMatcher the {@link RequestMatcher} to use
*/
public void setRequestMatcher(RequestMatcher requestMatcher) {
Assert.notNull(requestMatcher, "requestMatcher cannot be null");
this.requestMatcher = requestMatcher;
}
/**
* Sets the {@link RequestCache} used to store the current request to be replayed
* after redirect from the CAS server. Defaults to {@link HttpSessionRequestCache}.
* @param requestCache the {@link RequestCache} to use
*/
public void setRequestCache(RequestCache requestCache) {
Assert.notNull(requestCache, "requestCache cannot be null");
this.requestCache = requestCache;
}
}
@@ -1,67 +0,0 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.cas.web;
import jakarta.servlet.http.HttpServletRequest;
import org.apereo.cas.client.authentication.DefaultGatewayResolverImpl;
import org.apereo.cas.client.authentication.GatewayResolver;
import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
/**
* A {@link RequestMatcher} implementation that delegates the check to an instance of
* {@link GatewayResolver}. The request is marked as "gatewayed" using the configured
* {@link GatewayResolver} to avoid infinite loop.
*
* @author Michael Remond
* @author Marcus da Coregio
* @since 6.3
*/
public final class CasGatewayResolverRequestMatcher implements RequestMatcher {
private final ServiceProperties serviceProperties;
private GatewayResolver gatewayStorage = new DefaultGatewayResolverImpl();
public CasGatewayResolverRequestMatcher(ServiceProperties serviceProperties) {
Assert.notNull(serviceProperties, "serviceProperties cannot be null");
this.serviceProperties = serviceProperties;
}
@Override
public boolean matches(HttpServletRequest request) {
boolean wasGatewayed = this.gatewayStorage.hasGatewayedAlready(request, this.serviceProperties.getService());
if (!wasGatewayed) {
this.gatewayStorage.storeGatewayInformation(request, this.serviceProperties.getService());
return true;
}
return false;
}
/**
* Sets the {@link GatewayResolver} to check if the request was already gatewayed.
* Defaults to {@link DefaultGatewayResolverImpl}
* @param gatewayStorage the {@link GatewayResolver} to use. Cannot be null.
*/
public void setGatewayStorage(GatewayResolver gatewayStorage) {
Assert.notNull(gatewayStorage, "gatewayStorage cannot be null");
this.gatewayStorage = gatewayStorage;
}
}
@@ -16,22 +16,16 @@
package org.springframework.security.cas.web;
import java.io.IOException;
import java.net.URLEncoder;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.web.RedirectStrategy;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/**
* Tests {@link CasAuthenticationEntryPoint}.
@@ -101,25 +95,4 @@ public class CasAuthenticationEntryPointTests {
.isEqualTo(response.getRedirectedUrl());
}
@Test
void setRedirectStrategyThenUses() throws IOException {
CasAuthenticationEntryPoint ep = new CasAuthenticationEntryPoint();
ServiceProperties sp = new ServiceProperties();
sp.setService("https://mycompany.com/login/cas");
ep.setServiceProperties(sp);
ep.setLoginUrl("https://cas/login");
RedirectStrategy redirectStrategy = mock();
ep.setRedirectStrategy(redirectStrategy);
MockHttpServletRequest req = new MockHttpServletRequest();
MockHttpServletResponse res = new MockHttpServletResponse();
ep.commence(req, res, new BadCredentialsException("bad credentials"));
verify(redirectStrategy).sendRedirect(eq(req), eq(res),
eq("https://cas/login?service=https%3A%2F%2Fmycompany.com%2Flogin%2Fcas"));
}
}
@@ -20,7 +20,6 @@ import java.io.IOException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpSession;
import org.apereo.cas.client.proxy.ProxyGrantingTicketStorage;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
@@ -42,7 +41,6 @@ import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
@@ -226,25 +224,6 @@ public class CasAuthenticationFilterTests {
verify(securityContextRepository).saveContext(any(SecurityContext.class), eq(request), eq(response));
}
@Test
public void attemptAuthenticationWhenNoServiceTicketAndIsGatewayRequestThenRedirectToSavedRequestAndClearAttribute()
throws Exception {
CasAuthenticationFilter filter = new CasAuthenticationFilter();
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
HttpSession session = request.getSession(true);
session.setAttribute(CasGatewayAuthenticationRedirectFilter.CAS_GATEWAY_AUTHENTICATION_ATTR, true);
new HttpSessionRequestCache().saveRequest(request, response);
Authentication authn = filter.attemptAuthentication(request, response);
assertThat(authn).isNull();
assertThat(response.getStatus()).isEqualTo(302);
assertThat(response.getRedirectedUrl()).isEqualTo("http://localhost?continue");
assertThat(session.getAttribute(CasGatewayAuthenticationRedirectFilter.CAS_GATEWAY_AUTHENTICATION_ATTR))
.isNull();
}
@Test
void successfulAuthenticationWhenSecurityContextRepositorySetThenUses() throws ServletException, IOException {
SecurityContextRepository securityContextRepository = mock(SecurityContextRepository.class);
@@ -1,95 +0,0 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.cas.web;
import java.io.IOException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpStatus;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.web.savedrequest.RequestCache;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
/**
* Tests for {@link CasGatewayAuthenticationRedirectFilter}.
*
* @author Jerome LELEU
* @author Marcus da Coregio
*/
public class CasGatewayAuthenticationRedirectFilterTests {
private static final String CAS_LOGIN_URL = "http://mycasserver/login";
CasGatewayAuthenticationRedirectFilter filter = new CasGatewayAuthenticationRedirectFilter(CAS_LOGIN_URL,
serviceProperties());
@Test
void doFilterWhenMatchesThenSavesRequestAndSavesAttributeAndSendRedirect() throws IOException, ServletException {
RequestCache requestCache = mock();
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
this.filter.setRequestMatcher((req) -> true);
this.filter.setRequestCache(requestCache);
this.filter.doFilter(request, response, new MockFilterChain());
assertThat(response.getStatus()).isEqualTo(HttpStatus.FOUND.value());
assertThat(response.getHeader("Location"))
.isEqualTo("http://mycasserver/login?service=http%3A%2F%2Flocalhost%2Flogin%2Fcas&gateway=true");
verify(requestCache).saveRequest(request, response);
}
@Test
void doFilterWhenNotMatchThenContinueFilter() throws ServletException, IOException {
this.filter.setRequestMatcher((req) -> false);
FilterChain chain = mock();
MockHttpServletResponse response = mock();
this.filter.doFilter(new MockHttpServletRequest(), response, chain);
verify(chain).doFilter(any(), any());
verifyNoInteractions(response);
}
@Test
void doFilterWhenSendRenewTrueThenIgnores() throws ServletException, IOException {
ServiceProperties serviceProperties = serviceProperties();
serviceProperties.setSendRenew(true);
this.filter = new CasGatewayAuthenticationRedirectFilter(CAS_LOGIN_URL, serviceProperties);
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
this.filter.setRequestMatcher((req) -> true);
this.filter.doFilter(request, response, new MockFilterChain());
assertThat(response.getStatus()).isEqualTo(HttpStatus.FOUND.value());
assertThat(response.getHeader("Location"))
.isEqualTo("http://mycasserver/login?service=http%3A%2F%2Flocalhost%2Flogin%2Fcas&gateway=true");
}
private static ServiceProperties serviceProperties() {
ServiceProperties serviceProperties = new ServiceProperties();
serviceProperties.setService("http://localhost/login/cas");
return serviceProperties;
}
}
@@ -1,74 +0,0 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.cas.web;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.security.cas.ServiceProperties;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests {@link CasGatewayResolverRequestMatcher}.
*
* @author Marcus da Coregio
*/
class CasGatewayResolverRequestMatcherTests {
CasGatewayResolverRequestMatcher matcher = new CasGatewayResolverRequestMatcher(new ServiceProperties());
@Test
void constructorWhenServicePropertiesNullThenException() {
assertThatIllegalArgumentException().isThrownBy(() -> new CasGatewayResolverRequestMatcher(null))
.withMessage("serviceProperties cannot be null");
}
@Test
void matchesWhenAlreadyGatewayedThenReturnsFalse() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.getSession().setAttribute("_const_cas_gateway_", "yes");
boolean matches = this.matcher.matches(request);
assertThat(matches).isFalse();
}
@Test
void matchesWhenNotGatewayedThenReturnsTrue() {
MockHttpServletRequest request = new MockHttpServletRequest();
boolean matches = this.matcher.matches(request);
assertThat(matches).isTrue();
}
@Test
void matchesWhenNoSessionThenReturnsTrue() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setSession(null);
boolean matches = this.matcher.matches(request);
assertThat(matches).isTrue();
}
@Test
void matchesWhenNotGatewayedAndCheckedAgainThenSavesAsGatewayedAndReturnsFalse() {
MockHttpServletRequest request = new MockHttpServletRequest();
boolean matches = this.matcher.matches(request);
boolean secondMatch = this.matcher.matches(request);
assertThat(matches).isTrue();
assertThat(secondMatch).isFalse();
}
}
@@ -96,7 +96,7 @@ public final class SecurityNamespaceHandler implements NamespaceHandler {
pc.getReaderContext()
.fatal("You cannot use a spring-security-2.0.xsd or spring-security-3.0.xsd or "
+ "spring-security-3.1.xsd schema or spring-security-3.2.xsd schema or spring-security-4.0.xsd schema "
+ "with Spring Security 6.3. Please update your schema declarations to the 6.3 schema.",
+ "with Spring Security 6.2. Please update your schema declarations to the 6.2 schema.",
element);
}
String name = pc.getDelegate().getLocalName(element);
@@ -221,7 +221,7 @@ public final class SecurityNamespaceHandler implements NamespaceHandler {
private boolean matchesVersionInternal(Element element) {
String schemaLocation = element.getAttributeNS("http://www.w3.org/2001/XMLSchema-instance", "schemaLocation");
return schemaLocation.matches("(?m).*spring-security-6\\.3.*.xsd.*")
return schemaLocation.matches("(?m).*spring-security-6\\.2.*.xsd.*")
|| schemaLocation.matches("(?m).*spring-security.xsd.*")
|| !schemaLocation.matches("(?m).*spring-security.*");
}
@@ -16,15 +16,8 @@
package org.springframework.security.config.annotation.authentication.configuration;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.Order;
import org.springframework.core.log.LogMessage;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
@@ -56,33 +49,16 @@ class InitializeAuthenticationProviderBeanManagerConfigurer extends GlobalAuthen
class InitializeAuthenticationProviderManagerConfigurer extends GlobalAuthenticationConfigurerAdapter {
private final Log logger = LogFactory.getLog(getClass());
@Override
public void configure(AuthenticationManagerBuilder auth) {
if (auth.isConfigured()) {
return;
}
List<BeanWithName<AuthenticationProvider>> authenticationProviders = getBeansWithName(
AuthenticationProvider.class);
if (authenticationProviders.isEmpty()) {
AuthenticationProvider authenticationProvider = getBeanOrNull(AuthenticationProvider.class);
if (authenticationProvider == null) {
return;
}
else if (authenticationProviders.size() > 1) {
List<String> beanNames = authenticationProviders.stream().map(BeanWithName::getName).toList();
this.logger.info(LogMessage.format("Found %s AuthenticationProvider beans, with names %s. "
+ "Global Authentication Manager will not be configured with AuthenticationProviders. "
+ "Consider publishing a single AuthenticationProvider bean, or wiring your Providers directly "
+ "using the DSL.", authenticationProviders.size(), beanNames));
return;
}
var authenticationProvider = authenticationProviders.get(0).getBean();
var authenticationProviderBeanName = authenticationProviders.get(0).getName();
auth.authenticationProvider(authenticationProvider);
this.logger.info(LogMessage.format(
"Global AuthenticationManager configured with AuthenticationProvider bean with name %s",
authenticationProviderBeanName));
}
/**
@@ -98,42 +74,6 @@ class InitializeAuthenticationProviderBeanManagerConfigurer extends GlobalAuthen
return InitializeAuthenticationProviderBeanManagerConfigurer.this.context.getBean(beanNames[0], type);
}
/**
* @return a list of beans of the requested class, along with their names. If
* there are no registered beans of that type, the list is empty.
*/
private <T> List<BeanWithName<T>> getBeansWithName(Class<T> type) {
List<BeanWithName<T>> beanWithNames = new ArrayList<>();
String[] beanNames = InitializeAuthenticationProviderBeanManagerConfigurer.this.context
.getBeanNamesForType(type);
for (String beanName : beanNames) {
T bean = InitializeAuthenticationProviderBeanManagerConfigurer.this.context.getBean(beanNames[0], type);
beanWithNames.add(new BeanWithName<T>(bean, beanName));
}
return beanWithNames;
}
static class BeanWithName<T> {
private final T bean;
private final String name;
BeanWithName(T bean, String name) {
this.bean = bean;
this.name = name;
}
T getBean() {
return this.bean;
}
String getName() {
return this.name;
}
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2019 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,18 +16,10 @@
package org.springframework.security.config.annotation.authentication.configuration;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.log.LogMessage;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.authentication.password.CompromisedPasswordChecker;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.core.userdetails.UserDetailsPasswordService;
import org.springframework.security.core.userdetails.UserDetailsService;
@@ -62,57 +54,27 @@ class InitializeUserDetailsBeanManagerConfigurer extends GlobalAuthenticationCon
class InitializeUserDetailsManagerConfigurer extends GlobalAuthenticationConfigurerAdapter {
private final Log logger = LogFactory.getLog(getClass());
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
List<BeanWithName<UserDetailsService>> userDetailsServices = getBeansWithName(UserDetailsService.class);
if (auth.isConfigured()) {
if (!userDetailsServices.isEmpty()) {
this.logger.warn("Global AuthenticationManager configured with an AuthenticationProvider bean. "
+ "UserDetailsService beans will not be used for username/password login. "
+ "Consider removing the AuthenticationProvider bean. "
+ "Alternatively, consider using the UserDetailsService in a manually instantiated "
+ "DaoAuthenticationProvider.");
}
return;
}
if (userDetailsServices.isEmpty()) {
UserDetailsService userDetailsService = getBeanOrNull(UserDetailsService.class);
if (userDetailsService == null) {
return;
}
else if (userDetailsServices.size() > 1) {
List<String> beanNames = userDetailsServices.stream().map(BeanWithName::getName).toList();
this.logger.warn(LogMessage.format("Found %s UserDetailsService beans, with names %s. "
+ "Global Authentication Manager will not use a UserDetailsService for username/password login. "
+ "Consider publishing a single UserDetailsService bean.", userDetailsServices.size(),
beanNames));
return;
}
var userDetailsService = userDetailsServices.get(0).getBean();
var userDetailsServiceBeanName = userDetailsServices.get(0).getName();
PasswordEncoder passwordEncoder = getBeanOrNull(PasswordEncoder.class);
UserDetailsPasswordService passwordManager = getBeanOrNull(UserDetailsPasswordService.class);
CompromisedPasswordChecker passwordChecker = getBeanOrNull(CompromisedPasswordChecker.class);
DaoAuthenticationProvider provider;
if (passwordEncoder != null) {
provider = new DaoAuthenticationProvider(passwordEncoder);
}
else {
provider = new DaoAuthenticationProvider();
}
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
if (passwordEncoder != null) {
provider.setPasswordEncoder(passwordEncoder);
}
if (passwordManager != null) {
provider.setUserDetailsPasswordService(passwordManager);
}
if (passwordChecker != null) {
provider.setCompromisedPasswordChecker(passwordChecker);
}
provider.afterPropertiesSet();
auth.authenticationProvider(provider);
this.logger.info(LogMessage.format(
"Global AuthenticationManager configured with UserDetailsService bean with name %s",
userDetailsServiceBeanName));
}
/**
@@ -127,41 +89,6 @@ class InitializeUserDetailsBeanManagerConfigurer extends GlobalAuthenticationCon
return InitializeUserDetailsBeanManagerConfigurer.this.context.getBean(beanNames[0], type);
}
/**
* @return a list of beans of the requested class, along with their names. If
* there are no registered beans of that type, the list is empty.
*/
private <T> List<BeanWithName<T>> getBeansWithName(Class<T> type) {
List<BeanWithName<T>> beanWithNames = new ArrayList<>();
String[] beanNames = InitializeUserDetailsBeanManagerConfigurer.this.context.getBeanNamesForType(type);
for (String beanName : beanNames) {
T bean = InitializeUserDetailsBeanManagerConfigurer.this.context.getBean(beanNames[0], type);
beanWithNames.add(new BeanWithName<T>(bean, beanName));
}
return beanWithNames;
}
static class BeanWithName<T> {
private final T bean;
private final String name;
BeanWithName(T bean, String name) {
this.bean = bean;
this.name = name;
}
T getBean() {
return this.bean;
}
String getName() {
return this.name;
}
}
}
}
@@ -1,63 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.method.configuration;
import java.util.ArrayList;
import java.util.List;
import org.aopalliance.intercept.MethodInterceptor;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
import org.springframework.security.authorization.method.AuthorizationAdvisor;
import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory;
import org.springframework.security.authorization.method.AuthorizeReturnObjectMethodInterceptor;
import org.springframework.security.config.Customizer;
@Configuration(proxyBeanMethods = false)
final class AuthorizationProxyConfiguration implements AopInfrastructureBean {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static AuthorizationAdvisorProxyFactory authorizationProxyFactory(ObjectProvider<AuthorizationAdvisor> provider,
ObjectProvider<Customizer<AuthorizationAdvisorProxyFactory>> customizers) {
List<AuthorizationAdvisor> advisors = new ArrayList<>();
provider.forEach(advisors::add);
AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
customizers.forEach((c) -> c.customize(factory));
factory.setAdvisors(advisors);
return factory;
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static MethodInterceptor authorizeReturnObjectMethodInterceptor(ObjectProvider<AuthorizationAdvisor> provider,
AuthorizationAdvisorProxyFactory authorizationProxyFactory) {
AuthorizeReturnObjectMethodInterceptor interceptor = new AuthorizeReturnObjectMethodInterceptor(
authorizationProxyFactory);
List<AuthorizationAdvisor> advisors = new ArrayList<>();
provider.forEach(advisors::add);
advisors.add(interceptor);
authorizationProxyFactory.setAdvisors(advisors);
return interceptor;
}
}
@@ -19,26 +19,18 @@ package org.springframework.security.config.annotation.method.configuration;
import java.util.function.Supplier;
import io.micrometer.observation.ObservationRegistry;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.authorization.ObservationAuthorizationManager;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
import org.springframework.security.authorization.method.MethodInvocationResult;
import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedHandler;
import org.springframework.security.core.Authentication;
import org.springframework.util.function.SingletonSupplier;
final class DeferringObservationAuthorizationManager<T>
implements AuthorizationManager<T>, MethodAuthorizationDeniedHandler {
final class DeferringObservationAuthorizationManager<T> implements AuthorizationManager<T> {
private final Supplier<AuthorizationManager<T>> delegate;
private MethodAuthorizationDeniedHandler handler = new ThrowingMethodAuthorizationDeniedHandler();
DeferringObservationAuthorizationManager(ObjectProvider<ObservationRegistry> provider,
AuthorizationManager<T> delegate) {
this.delegate = SingletonSupplier.of(() -> {
@@ -48,9 +40,6 @@ final class DeferringObservationAuthorizationManager<T>
}
return new ObservationAuthorizationManager<>(registry, delegate);
});
if (delegate instanceof MethodAuthorizationDeniedHandler h) {
this.handler = h;
}
}
@Override
@@ -58,15 +47,4 @@ final class DeferringObservationAuthorizationManager<T>
return this.delegate.get().check(authentication, object);
}
@Override
public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
return this.handler.handleDeniedInvocation(methodInvocation, authorizationResult);
}
@Override
public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult,
AuthorizationResult authorizationResult) {
return this.handler.handleDeniedInvocationResult(methodInvocationResult, authorizationResult);
}
}
@@ -19,27 +19,19 @@ package org.springframework.security.config.annotation.method.configuration;
import java.util.function.Supplier;
import io.micrometer.observation.ObservationRegistry;
import org.aopalliance.intercept.MethodInvocation;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.authorization.ObservationReactiveAuthorizationManager;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
import org.springframework.security.authorization.method.MethodInvocationResult;
import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedHandler;
import org.springframework.security.core.Authentication;
import org.springframework.util.function.SingletonSupplier;
final class DeferringObservationReactiveAuthorizationManager<T>
implements ReactiveAuthorizationManager<T>, MethodAuthorizationDeniedHandler {
final class DeferringObservationReactiveAuthorizationManager<T> implements ReactiveAuthorizationManager<T> {
private final Supplier<ReactiveAuthorizationManager<T>> delegate;
private MethodAuthorizationDeniedHandler handler = new ThrowingMethodAuthorizationDeniedHandler();
DeferringObservationReactiveAuthorizationManager(ObjectProvider<ObservationRegistry> provider,
ReactiveAuthorizationManager<T> delegate) {
this.delegate = SingletonSupplier.of(() -> {
@@ -49,9 +41,6 @@ final class DeferringObservationReactiveAuthorizationManager<T>
}
return new ObservationReactiveAuthorizationManager<>(registry, delegate);
});
if (delegate instanceof MethodAuthorizationDeniedHandler h) {
this.handler = h;
}
}
@Override
@@ -59,15 +48,4 @@ final class DeferringObservationReactiveAuthorizationManager<T>
return this.delegate.get().check(authentication, object);
}
@Override
public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
return this.handler.handleDeniedInvocation(methodInvocation, authorizationResult);
}
@Override
public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult,
AuthorizationResult authorizationResult) {
return this.handler.handleDeniedInvocationResult(methodInvocationResult, authorizationResult);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2021 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.
@@ -87,14 +87,4 @@ public @interface EnableMethodSecurity {
*/
AdviceMode mode() default AdviceMode.PROXY;
/**
* Indicate additional offset in the ordering of the execution of the security
* interceptors when multiple advices are applied at a specific joinpoint. I.e.,
* precedence of each security interceptor enabled by this annotation will be
* calculated as sum of its default precedence and offset. The default is 0.
* @return the offset in the order the security advisor should be applied
* @since 6.3
*/
int offset() default 0;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 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.
@@ -20,18 +20,11 @@ import io.micrometer.observation.ObservationRegistry;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportAware;
import org.springframework.context.annotation.Role;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.authorization.AuthoritiesAuthorizationManager;
import org.springframework.security.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
import org.springframework.security.authorization.method.Jsr250AuthorizationManager;
@@ -49,23 +42,15 @@ import org.springframework.security.core.context.SecurityContextHolderStrategy;
*/
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
final class Jsr250MethodSecurityConfiguration implements ImportAware, AopInfrastructureBean {
private int interceptorOrderOffset;
final class Jsr250MethodSecurityConfiguration {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static MethodInterceptor jsr250AuthorizationMethodInterceptor(
ObjectProvider<GrantedAuthorityDefaults> defaultsProvider,
ObjectProvider<SecurityContextHolderStrategy> strategyProvider,
ObjectProvider<AuthorizationEventPublisher> eventPublisherProvider,
ObjectProvider<ObservationRegistry> registryProvider, ObjectProvider<RoleHierarchy> roleHierarchyProvider,
Jsr250MethodSecurityConfiguration configuration) {
ObjectProvider<ObservationRegistry> registryProvider) {
Jsr250AuthorizationManager jsr250 = new Jsr250AuthorizationManager();
AuthoritiesAuthorizationManager authoritiesAuthorizationManager = new AuthoritiesAuthorizationManager();
RoleHierarchy roleHierarchy = roleHierarchyProvider.getIfAvailable(NullRoleHierarchy::new);
authoritiesAuthorizationManager.setRoleHierarchy(roleHierarchy);
jsr250.setAuthoritiesAuthorizationManager(authoritiesAuthorizationManager);
defaultsProvider.ifAvailable((d) -> jsr250.setRolePrefix(d.getRolePrefix()));
SecurityContextHolderStrategy strategy = strategyProvider
.getIfAvailable(SecurityContextHolder::getContextHolderStrategy);
@@ -73,16 +58,8 @@ final class Jsr250MethodSecurityConfiguration implements ImportAware, AopInfrast
registryProvider, jsr250);
AuthorizationManagerBeforeMethodInterceptor interceptor = AuthorizationManagerBeforeMethodInterceptor
.jsr250(manager);
interceptor.setOrder(interceptor.getOrder() + configuration.interceptorOrderOffset);
interceptor.setSecurityContextHolderStrategy(strategy);
eventPublisherProvider.ifAvailable(interceptor::setAuthorizationEventPublisher);
return interceptor;
}
@Override
public void setImportMetadata(AnnotationMetadata importMetadata) {
EnableMethodSecurity annotation = importMetadata.getAnnotations().get(EnableMethodSecurity.class).synthesize();
this.interceptorOrderOffset = annotation.offset();
}
}
@@ -33,7 +33,6 @@ class MethodSecurityAdvisorRegistrar implements ImportBeanDefinitionRegistrar {
registerAsAdvisor("postAuthorizeAuthorization", registry);
registerAsAdvisor("securedAuthorization", registry);
registerAsAdvisor("jsr250Authorization", registry);
registerAsAdvisor("authorizeReturnObject", registry);
}
private void registerAsAdvisor(String prefix, BeanDefinitionRegistry registry) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 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.
@@ -56,7 +56,6 @@ final class MethodSecuritySelector implements ImportSelector {
if (annotation.jsr250Enabled()) {
imports.add(Jsr250MethodSecurityConfiguration.class.getName());
}
imports.add(AuthorizationProxyConfiguration.class.getName());
return imports.toArray(new String[0]);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,41 +16,33 @@
package org.springframework.security.config.annotation.method.configuration;
import java.util.function.Consumer;
import java.util.function.Supplier;
import io.micrometer.observation.ObservationRegistry;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.aop.Pointcut;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportAware;
import org.springframework.context.annotation.Role;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.method.AuthorizationAdvisor;
import org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor;
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
import org.springframework.security.authorization.method.PostAuthorizeAuthorizationManager;
import org.springframework.security.authorization.method.PostFilterAuthorizationMethodInterceptor;
import org.springframework.security.authorization.method.PreAuthorizeAuthorizationManager;
import org.springframework.security.authorization.method.PreFilterAuthorizationMethodInterceptor;
import org.springframework.security.authorization.method.PrePostTemplateDefaults;
import org.springframework.security.config.core.GrantedAuthorityDefaults;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.util.function.SingletonSupplier;
@@ -64,102 +56,73 @@ import org.springframework.util.function.SingletonSupplier;
*/
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
final class PrePostMethodSecurityConfiguration implements ImportAware, AopInfrastructureBean {
private int interceptorOrderOffset;
final class PrePostMethodSecurityConfiguration {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static MethodInterceptor preFilterAuthorizationMethodInterceptor(
ObjectProvider<GrantedAuthorityDefaults> defaultsProvider,
ObjectProvider<PrePostTemplateDefaults> methodSecurityDefaultsProvider,
ObjectProvider<MethodSecurityExpressionHandler> expressionHandlerProvider,
ObjectProvider<SecurityContextHolderStrategy> strategyProvider,
ObjectProvider<RoleHierarchy> roleHierarchyProvider, PrePostMethodSecurityConfiguration configuration,
ApplicationContext context) {
ObjectProvider<SecurityContextHolderStrategy> strategyProvider, ApplicationContext context) {
PreFilterAuthorizationMethodInterceptor preFilter = new PreFilterAuthorizationMethodInterceptor();
preFilter.setOrder(preFilter.getOrder() + configuration.interceptorOrderOffset);
return new DeferringMethodInterceptor<>(preFilter, (f) -> {
methodSecurityDefaultsProvider.ifAvailable(f::setTemplateDefaults);
f.setExpressionHandler(expressionHandlerProvider
.getIfAvailable(() -> defaultExpressionHandler(defaultsProvider, roleHierarchyProvider, context)));
strategyProvider.ifAvailable(f::setSecurityContextHolderStrategy);
});
strategyProvider.ifAvailable(preFilter::setSecurityContextHolderStrategy);
preFilter.setExpressionHandler(
new DeferringMethodSecurityExpressionHandler(expressionHandlerProvider, defaultsProvider, context));
return preFilter;
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static MethodInterceptor preAuthorizeAuthorizationMethodInterceptor(
ObjectProvider<GrantedAuthorityDefaults> defaultsProvider,
ObjectProvider<PrePostTemplateDefaults> methodSecurityDefaultsProvider,
ObjectProvider<MethodSecurityExpressionHandler> expressionHandlerProvider,
ObjectProvider<SecurityContextHolderStrategy> strategyProvider,
ObjectProvider<AuthorizationEventPublisher> eventPublisherProvider,
ObjectProvider<ObservationRegistry> registryProvider, ObjectProvider<RoleHierarchy> roleHierarchyProvider,
PrePostMethodSecurityConfiguration configuration, ApplicationContext context) {
ObjectProvider<ObservationRegistry> registryProvider, ApplicationContext context) {
PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
manager.setApplicationContext(context);
manager.setExpressionHandler(
new DeferringMethodSecurityExpressionHandler(expressionHandlerProvider, defaultsProvider, context));
AuthorizationManagerBeforeMethodInterceptor preAuthorize = AuthorizationManagerBeforeMethodInterceptor
.preAuthorize(manager(manager, registryProvider));
preAuthorize.setOrder(preAuthorize.getOrder() + configuration.interceptorOrderOffset);
return new DeferringMethodInterceptor<>(preAuthorize, (f) -> {
methodSecurityDefaultsProvider.ifAvailable(manager::setTemplateDefaults);
manager.setExpressionHandler(expressionHandlerProvider
.getIfAvailable(() -> defaultExpressionHandler(defaultsProvider, roleHierarchyProvider, context)));
strategyProvider.ifAvailable(f::setSecurityContextHolderStrategy);
eventPublisherProvider.ifAvailable(f::setAuthorizationEventPublisher);
});
strategyProvider.ifAvailable(preAuthorize::setSecurityContextHolderStrategy);
eventPublisherProvider.ifAvailable(preAuthorize::setAuthorizationEventPublisher);
return preAuthorize;
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static MethodInterceptor postAuthorizeAuthorizationMethodInterceptor(
ObjectProvider<GrantedAuthorityDefaults> defaultsProvider,
ObjectProvider<PrePostTemplateDefaults> methodSecurityDefaultsProvider,
ObjectProvider<MethodSecurityExpressionHandler> expressionHandlerProvider,
ObjectProvider<SecurityContextHolderStrategy> strategyProvider,
ObjectProvider<AuthorizationEventPublisher> eventPublisherProvider,
ObjectProvider<ObservationRegistry> registryProvider, ObjectProvider<RoleHierarchy> roleHierarchyProvider,
PrePostMethodSecurityConfiguration configuration, ApplicationContext context) {
ObjectProvider<ObservationRegistry> registryProvider, ApplicationContext context) {
PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
manager.setApplicationContext(context);
manager.setExpressionHandler(
new DeferringMethodSecurityExpressionHandler(expressionHandlerProvider, defaultsProvider, context));
AuthorizationManagerAfterMethodInterceptor postAuthorize = AuthorizationManagerAfterMethodInterceptor
.postAuthorize(manager(manager, registryProvider));
postAuthorize.setOrder(postAuthorize.getOrder() + configuration.interceptorOrderOffset);
return new DeferringMethodInterceptor<>(postAuthorize, (f) -> {
methodSecurityDefaultsProvider.ifAvailable(manager::setTemplateDefaults);
manager.setExpressionHandler(expressionHandlerProvider
.getIfAvailable(() -> defaultExpressionHandler(defaultsProvider, roleHierarchyProvider, context)));
strategyProvider.ifAvailable(f::setSecurityContextHolderStrategy);
eventPublisherProvider.ifAvailable(f::setAuthorizationEventPublisher);
});
strategyProvider.ifAvailable(postAuthorize::setSecurityContextHolderStrategy);
eventPublisherProvider.ifAvailable(postAuthorize::setAuthorizationEventPublisher);
return postAuthorize;
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static MethodInterceptor postFilterAuthorizationMethodInterceptor(
ObjectProvider<GrantedAuthorityDefaults> defaultsProvider,
ObjectProvider<PrePostTemplateDefaults> methodSecurityDefaultsProvider,
ObjectProvider<MethodSecurityExpressionHandler> expressionHandlerProvider,
ObjectProvider<SecurityContextHolderStrategy> strategyProvider,
ObjectProvider<RoleHierarchy> roleHierarchyProvider, PrePostMethodSecurityConfiguration configuration,
ApplicationContext context) {
ObjectProvider<SecurityContextHolderStrategy> strategyProvider, ApplicationContext context) {
PostFilterAuthorizationMethodInterceptor postFilter = new PostFilterAuthorizationMethodInterceptor();
postFilter.setOrder(postFilter.getOrder() + configuration.interceptorOrderOffset);
return new DeferringMethodInterceptor<>(postFilter, (f) -> {
methodSecurityDefaultsProvider.ifAvailable(f::setTemplateDefaults);
f.setExpressionHandler(expressionHandlerProvider
.getIfAvailable(() -> defaultExpressionHandler(defaultsProvider, roleHierarchyProvider, context)));
strategyProvider.ifAvailable(f::setSecurityContextHolderStrategy);
});
strategyProvider.ifAvailable(postFilter::setSecurityContextHolderStrategy);
postFilter.setExpressionHandler(
new DeferringMethodSecurityExpressionHandler(expressionHandlerProvider, defaultsProvider, context));
return postFilter;
}
private static MethodSecurityExpressionHandler defaultExpressionHandler(
ObjectProvider<GrantedAuthorityDefaults> defaultsProvider,
ObjectProvider<RoleHierarchy> roleHierarchyProvider, ApplicationContext context) {
ObjectProvider<GrantedAuthorityDefaults> defaultsProvider, ApplicationContext context) {
DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
RoleHierarchy roleHierarchy = roleHierarchyProvider.getIfAvailable(NullRoleHierarchy::new);
handler.setRoleHierarchy(roleHierarchy);
defaultsProvider.ifAvailable((d) -> handler.setDefaultRolePrefix(d.getRolePrefix()));
handler.setApplicationContext(context);
return handler;
@@ -170,54 +133,41 @@ final class PrePostMethodSecurityConfiguration implements ImportAware, AopInfras
return new DeferringObservationAuthorizationManager<>(registryProvider, delegate);
}
@Override
public void setImportMetadata(AnnotationMetadata importMetadata) {
EnableMethodSecurity annotation = importMetadata.getAnnotations().get(EnableMethodSecurity.class).synthesize();
this.interceptorOrderOffset = annotation.offset();
}
private static final class DeferringMethodSecurityExpressionHandler implements MethodSecurityExpressionHandler {
private static final class DeferringMethodInterceptor<M extends AuthorizationAdvisor>
implements AuthorizationAdvisor {
private final Supplier<MethodSecurityExpressionHandler> expressionHandler;
private final Pointcut pointcut;
private final int order;
private final Supplier<M> delegate;
DeferringMethodInterceptor(M delegate, Consumer<M> supplier) {
this.pointcut = delegate.getPointcut();
this.order = delegate.getOrder();
this.delegate = SingletonSupplier.of(() -> {
supplier.accept(delegate);
return delegate;
});
}
@Nullable
@Override
public Object invoke(@NotNull MethodInvocation invocation) throws Throwable {
return this.delegate.get().invoke(invocation);
private DeferringMethodSecurityExpressionHandler(
ObjectProvider<MethodSecurityExpressionHandler> expressionHandlerProvider,
ObjectProvider<GrantedAuthorityDefaults> defaultsProvider, ApplicationContext applicationContext) {
this.expressionHandler = SingletonSupplier.of(() -> expressionHandlerProvider
.getIfAvailable(() -> defaultExpressionHandler(defaultsProvider, applicationContext)));
}
@Override
public Pointcut getPointcut() {
return this.pointcut;
public ExpressionParser getExpressionParser() {
return this.expressionHandler.get().getExpressionParser();
}
@Override
public Advice getAdvice() {
return this;
public EvaluationContext createEvaluationContext(Authentication authentication, MethodInvocation invocation) {
return this.expressionHandler.get().createEvaluationContext(authentication, invocation);
}
@Override
public int getOrder() {
return this.order;
public EvaluationContext createEvaluationContext(Supplier<Authentication> authentication,
MethodInvocation invocation) {
return this.expressionHandler.get().createEvaluationContext(authentication, invocation);
}
@Override
public boolean isPerInstance() {
return true;
public Object filter(Object filterTarget, Expression filterExpression, EvaluationContext ctx) {
return this.expressionHandler.get().filter(filterTarget, filterExpression, ctx);
}
@Override
public void setReturnObject(Object returnObject, EvaluationContext ctx) {
this.expressionHandler.get().setReturnObject(returnObject, ctx);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,22 +16,12 @@
package org.springframework.security.config.annotation.method.configuration;
import java.util.function.Consumer;
import java.util.function.Supplier;
import io.micrometer.observation.ObservationRegistry;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.aop.Pointcut;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
@@ -39,7 +29,6 @@ import org.springframework.security.access.expression.method.DefaultMethodSecuri
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.authorization.method.AuthorizationAdvisor;
import org.springframework.security.authorization.method.AuthorizationManagerAfterReactiveMethodInterceptor;
import org.springframework.security.authorization.method.AuthorizationManagerBeforeReactiveMethodInterceptor;
import org.springframework.security.authorization.method.MethodInvocationResult;
@@ -47,9 +36,7 @@ import org.springframework.security.authorization.method.PostAuthorizeReactiveAu
import org.springframework.security.authorization.method.PostFilterAuthorizationReactiveMethodInterceptor;
import org.springframework.security.authorization.method.PreAuthorizeReactiveAuthorizationManager;
import org.springframework.security.authorization.method.PreFilterAuthorizationReactiveMethodInterceptor;
import org.springframework.security.authorization.method.PrePostTemplateDefaults;
import org.springframework.security.config.core.GrantedAuthorityDefaults;
import org.springframework.util.function.SingletonSupplier;
/**
* Configuration for a {@link ReactiveAuthenticationManager} based Method Security.
@@ -58,58 +45,38 @@ import org.springframework.util.function.SingletonSupplier;
* @since 5.8
*/
@Configuration(proxyBeanMethods = false)
final class ReactiveAuthorizationManagerMethodSecurityConfiguration implements AopInfrastructureBean {
final class ReactiveAuthorizationManagerMethodSecurityConfiguration {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static MethodInterceptor preFilterAuthorizationMethodInterceptor(MethodSecurityExpressionHandler expressionHandler,
ObjectProvider<PrePostTemplateDefaults> defaultsObjectProvider) {
PreFilterAuthorizationReactiveMethodInterceptor interceptor = new PreFilterAuthorizationReactiveMethodInterceptor(
expressionHandler);
return new DeferringMethodInterceptor<>(interceptor,
(i) -> defaultsObjectProvider.ifAvailable(i::setTemplateDefaults));
static PreFilterAuthorizationReactiveMethodInterceptor preFilterInterceptor(
MethodSecurityExpressionHandler expressionHandler) {
return new PreFilterAuthorizationReactiveMethodInterceptor(expressionHandler);
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static MethodInterceptor preAuthorizeAuthorizationMethodInterceptor(
MethodSecurityExpressionHandler expressionHandler,
ObjectProvider<PrePostTemplateDefaults> defaultsObjectProvider,
ObjectProvider<ObservationRegistry> registryProvider, ApplicationContext context) {
PreAuthorizeReactiveAuthorizationManager manager = new PreAuthorizeReactiveAuthorizationManager(
expressionHandler);
manager.setApplicationContext(context);
ReactiveAuthorizationManager<MethodInvocation> authorizationManager = manager(manager, registryProvider);
AuthorizationAdvisor interceptor = AuthorizationManagerBeforeReactiveMethodInterceptor
.preAuthorize(authorizationManager);
return new DeferringMethodInterceptor<>(interceptor,
(i) -> defaultsObjectProvider.ifAvailable(manager::setTemplateDefaults));
static AuthorizationManagerBeforeReactiveMethodInterceptor preAuthorizeInterceptor(
MethodSecurityExpressionHandler expressionHandler, ObjectProvider<ObservationRegistry> registryProvider) {
ReactiveAuthorizationManager<MethodInvocation> authorizationManager = manager(
new PreAuthorizeReactiveAuthorizationManager(expressionHandler), registryProvider);
return AuthorizationManagerBeforeReactiveMethodInterceptor.preAuthorize(authorizationManager);
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static MethodInterceptor postFilterAuthorizationMethodInterceptor(MethodSecurityExpressionHandler expressionHandler,
ObjectProvider<PrePostTemplateDefaults> defaultsObjectProvider) {
PostFilterAuthorizationReactiveMethodInterceptor interceptor = new PostFilterAuthorizationReactiveMethodInterceptor(
expressionHandler);
return new DeferringMethodInterceptor<>(interceptor,
(i) -> defaultsObjectProvider.ifAvailable(i::setTemplateDefaults));
static PostFilterAuthorizationReactiveMethodInterceptor postFilterInterceptor(
MethodSecurityExpressionHandler expressionHandler) {
return new PostFilterAuthorizationReactiveMethodInterceptor(expressionHandler);
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static MethodInterceptor postAuthorizeAuthorizationMethodInterceptor(
MethodSecurityExpressionHandler expressionHandler,
ObjectProvider<PrePostTemplateDefaults> defaultsObjectProvider,
ObjectProvider<ObservationRegistry> registryProvider, ApplicationContext context) {
PostAuthorizeReactiveAuthorizationManager manager = new PostAuthorizeReactiveAuthorizationManager(
expressionHandler);
manager.setApplicationContext(context);
ReactiveAuthorizationManager<MethodInvocationResult> authorizationManager = manager(manager, registryProvider);
AuthorizationAdvisor interceptor = AuthorizationManagerAfterReactiveMethodInterceptor
.postAuthorize(authorizationManager);
return new DeferringMethodInterceptor<>(interceptor,
(i) -> defaultsObjectProvider.ifAvailable(manager::setTemplateDefaults));
static AuthorizationManagerAfterReactiveMethodInterceptor postAuthorizeInterceptor(
MethodSecurityExpressionHandler expressionHandler, ObjectProvider<ObservationRegistry> registryProvider) {
ReactiveAuthorizationManager<MethodInvocationResult> authorizationManager = manager(
new PostAuthorizeReactiveAuthorizationManager(expressionHandler), registryProvider);
return AuthorizationManagerAfterReactiveMethodInterceptor.postAuthorize(authorizationManager);
}
@Bean
@@ -128,50 +95,4 @@ final class ReactiveAuthorizationManagerMethodSecurityConfiguration implements A
return new DeferringObservationReactiveAuthorizationManager<>(registryProvider, delegate);
}
private static final class DeferringMethodInterceptor<M extends AuthorizationAdvisor>
implements AuthorizationAdvisor {
private final Pointcut pointcut;
private final int order;
private final Supplier<M> delegate;
DeferringMethodInterceptor(M delegate, Consumer<M> supplier) {
this.pointcut = delegate.getPointcut();
this.order = delegate.getOrder();
this.delegate = SingletonSupplier.of(() -> {
supplier.accept(delegate);
return delegate;
});
}
@Nullable
@Override
public Object invoke(@NotNull MethodInvocation invocation) throws Throwable {
return this.delegate.get().invoke(invocation);
}
@Override
public Pointcut getPointcut() {
return this.pointcut;
}
@Override
public Advice getAdvice() {
return this;
}
@Override
public int getOrder() {
return this.order;
}
@Override
public boolean isPerInstance() {
return true;
}
}
}
@@ -1,63 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.method.configuration;
import java.util.ArrayList;
import java.util.List;
import org.aopalliance.intercept.MethodInterceptor;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
import org.springframework.security.authorization.method.AuthorizationAdvisor;
import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory;
import org.springframework.security.authorization.method.AuthorizeReturnObjectMethodInterceptor;
import org.springframework.security.config.Customizer;
@Configuration(proxyBeanMethods = false)
final class ReactiveAuthorizationProxyConfiguration implements AopInfrastructureBean {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static AuthorizationAdvisorProxyFactory authorizationProxyFactory(ObjectProvider<AuthorizationAdvisor> provider,
ObjectProvider<Customizer<AuthorizationAdvisorProxyFactory>> customizers) {
List<AuthorizationAdvisor> advisors = new ArrayList<>();
provider.forEach(advisors::add);
AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withReactiveDefaults();
customizers.forEach((c) -> c.customize(factory));
factory.setAdvisors(advisors);
return factory;
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static MethodInterceptor authorizeReturnObjectMethodInterceptor(ObjectProvider<AuthorizationAdvisor> provider,
AuthorizationAdvisorProxyFactory authorizationProxyFactory) {
AuthorizeReturnObjectMethodInterceptor interceptor = new AuthorizeReturnObjectMethodInterceptor(
authorizationProxyFactory);
List<AuthorizationAdvisor> advisors = new ArrayList<>();
provider.forEach(advisors::add);
advisors.add(interceptor);
authorizationProxyFactory.setAdvisors(advisors);
return interceptor;
}
}
@@ -51,15 +51,13 @@ class ReactiveMethodSecuritySelector implements ImportSelector {
else {
imports.add(ReactiveMethodSecurityConfiguration.class.getName());
}
imports.add(ReactiveAuthorizationProxyConfiguration.class.getName());
return imports.toArray(new String[0]);
}
private static final class AutoProxyRegistrarSelector
extends AdviceModeImportSelector<EnableReactiveMethodSecurity> {
private static final String[] IMPORTS = new String[] { AutoProxyRegistrar.class.getName(),
MethodSecurityAdvisorRegistrar.class.getName() };
private static final String[] IMPORTS = new String[] { AutoProxyRegistrar.class.getName() };
@Override
protected String[] selectImports(@NonNull AdviceMode adviceMode) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 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.
@@ -20,19 +20,12 @@ import io.micrometer.observation.ObservationRegistry;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportAware;
import org.springframework.context.annotation.Role;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.authorization.AuthoritiesAuthorizationManager;
import org.springframework.security.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
import org.springframework.security.authorization.method.SecuredAuthorizationManager;
@@ -49,38 +42,22 @@ import org.springframework.security.core.context.SecurityContextHolderStrategy;
*/
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
final class SecuredMethodSecurityConfiguration implements ImportAware, AopInfrastructureBean {
private int interceptorOrderOffset;
final class SecuredMethodSecurityConfiguration {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static MethodInterceptor securedAuthorizationMethodInterceptor(
ObjectProvider<SecurityContextHolderStrategy> strategyProvider,
ObjectProvider<AuthorizationEventPublisher> eventPublisherProvider,
ObjectProvider<ObservationRegistry> registryProvider, ObjectProvider<RoleHierarchy> roleHierarchyProvider,
SecuredMethodSecurityConfiguration configuration) {
ObjectProvider<ObservationRegistry> registryProvider) {
SecuredAuthorizationManager secured = new SecuredAuthorizationManager();
AuthoritiesAuthorizationManager authoritiesAuthorizationManager = new AuthoritiesAuthorizationManager();
RoleHierarchy roleHierarchy = roleHierarchyProvider.getIfAvailable(NullRoleHierarchy::new);
authoritiesAuthorizationManager.setRoleHierarchy(roleHierarchy);
secured.setAuthoritiesAuthorizationManager(authoritiesAuthorizationManager);
SecurityContextHolderStrategy strategy = strategyProvider
.getIfAvailable(SecurityContextHolder::getContextHolderStrategy);
AuthorizationManager<MethodInvocation> manager = new DeferringObservationAuthorizationManager<>(
registryProvider, secured);
AuthorizationManagerBeforeMethodInterceptor interceptor = AuthorizationManagerBeforeMethodInterceptor
.secured(manager);
interceptor.setOrder(interceptor.getOrder() + configuration.interceptorOrderOffset);
interceptor.setSecurityContextHolderStrategy(strategy);
eventPublisherProvider.ifAvailable(interceptor::setAuthorizationEventPublisher);
return interceptor;
}
@Override
public void setImportMetadata(AnnotationMetadata importMetadata) {
EnableMethodSecurity annotation = importMetadata.getAnnotations().get(EnableMethodSecurity.class).synthesize();
this.interceptorOrderOffset = annotation.offset();
}
}
@@ -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;
@@ -42,11 +43,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;
/**
@@ -203,43 +206,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) {
@@ -474,34 +456,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
@@ -511,27 +488,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);
@@ -539,27 +559,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 + "]";
@@ -51,13 +51,11 @@ import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.PasswordOAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.RefreshTokenOAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.TokenExchangeOAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.endpoint.JwtBearerGrantRequest;
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest;
import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest;
import org.springframework.security.oauth2.client.endpoint.TokenExchangeGrantRequest;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
@@ -185,8 +183,7 @@ final class OAuth2ClientConfiguration {
RefreshTokenOAuth2AuthorizedClientProvider.class,
ClientCredentialsOAuth2AuthorizedClientProvider.class,
PasswordOAuth2AuthorizedClientProvider.class,
JwtBearerOAuth2AuthorizedClientProvider.class,
TokenExchangeOAuth2AuthorizedClientProvider.class
JwtBearerOAuth2AuthorizedClientProvider.class
);
// @formatter:on
@@ -258,12 +255,6 @@ final class OAuth2ClientConfiguration {
authorizedClientProviders.add(jwtBearerAuthorizedClientProvider);
}
OAuth2AuthorizedClientProvider tokenExchangeAuthorizedClientProvider = getTokenExchangeAuthorizedClientProvider(
authorizedClientProviderBeans);
if (tokenExchangeAuthorizedClientProvider != null) {
authorizedClientProviders.add(tokenExchangeAuthorizedClientProvider);
}
authorizedClientProviders.addAll(getAdditionalAuthorizedClientProviders(authorizedClientProviderBeans));
authorizedClientProvider = new DelegatingOAuth2AuthorizedClientProvider(authorizedClientProviders);
}
@@ -373,25 +364,6 @@ final class OAuth2ClientConfiguration {
return authorizedClientProvider;
}
private OAuth2AuthorizedClientProvider getTokenExchangeAuthorizedClientProvider(
Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders) {
TokenExchangeOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType(
authorizedClientProviders, TokenExchangeOAuth2AuthorizedClientProvider.class);
OAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> accessTokenResponseClient = getBeanOfType(
ResolvableType.forClassWithGenerics(OAuth2AccessTokenResponseClient.class,
TokenExchangeGrantRequest.class));
if (accessTokenResponseClient != null) {
if (authorizedClientProvider == null) {
authorizedClientProvider = new TokenExchangeOAuth2AuthorizedClientProvider();
}
authorizedClientProvider.setAccessTokenResponseClient(accessTokenResponseClient);
}
return authorizedClientProvider;
}
private List<OAuth2AuthorizedClientProvider> getAdditionalAuthorizedClientProviders(
Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders) {
List<OAuth2AuthorizedClientProvider> additionalAuthorizedClientProviders = new ArrayList<>(
@@ -17,7 +17,6 @@
package org.springframework.security.config.annotation.web.configurers;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
import io.micrometer.observation.ObservationRegistry;
@@ -31,14 +30,12 @@ import org.springframework.security.authorization.AuthorityAuthorizationManager;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.AuthorizationManagers;
import org.springframework.security.authorization.ObservationAuthorizationManager;
import org.springframework.security.authorization.SpringAuthorizationEventPublisher;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.core.GrantedAuthorityDefaults;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.access.intercept.AuthorizationFilter;
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
import org.springframework.security.web.access.intercept.RequestMatcherDelegatingAuthorizationManager;
@@ -247,14 +244,11 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
* {@link RequestMatcher}s.
*
* @author Evgeniy Cheban
* @author Josh Cummings
*/
public class AuthorizedUrl {
private final List<? extends RequestMatcher> matchers;
private boolean not;
/**
* Creates an instance.
* @param matchers the {@link RequestMatcher} instances to map
@@ -267,16 +261,6 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
return this.matchers;
}
/**
* Negates the following authorization rule.
* @return the {@link AuthorizedUrl} for further customization
* @since 6.3
*/
public AuthorizedUrl not() {
this.not = true;
return this;
}
/**
* Specify that URLs are allowed by anyone.
* @return the {@link AuthorizationManagerRequestMatcherRegistry} for further
@@ -389,21 +373,6 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
return access(AuthenticatedAuthorizationManager.anonymous());
}
/**
* Specify that a path variable in URL to be compared.
*
* <p>
* For example, <pre>
* requestMatchers("/user/{username}").hasVariable("username").equalTo(Authentication::getName)
* </pre>
* @param variable the variable in URL template to compare.
* @return {@link AuthorizedUrlVariable} for further customization.
* @since 6.3
*/
public AuthorizedUrlVariable hasVariable(String variable) {
return new AuthorizedUrlVariable(variable);
}
/**
* Allows specifying a custom {@link AuthorizationManager}.
* @param manager the {@link AuthorizationManager} to use
@@ -413,44 +382,7 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
public AuthorizationManagerRequestMatcherRegistry access(
AuthorizationManager<RequestAuthorizationContext> manager) {
Assert.notNull(manager, "manager cannot be null");
return (this.not)
? AuthorizeHttpRequestsConfigurer.this.addMapping(this.matchers, AuthorizationManagers.not(manager))
: AuthorizeHttpRequestsConfigurer.this.addMapping(this.matchers, manager);
}
/**
* An object that allows configuring {@link RequestMatcher}s with URI path
* variables
*
* @author Taehong Kim
* @since 6.3
*/
public final class AuthorizedUrlVariable {
private final String variable;
private AuthorizedUrlVariable(String variable) {
this.variable = variable;
}
/**
* Compares the value of a path variable in the URI with an `Authentication`
* attribute
* <p>
* For example, <pre>
* requestMatchers("/user/{username}").hasVariable("username").equalTo(Authentication::getName));
* </pre>
* @param function a function to get value from {@link Authentication}.
* @return the {@link AuthorizationManagerRequestMatcherRegistry} for further
* customization.
*/
public AuthorizationManagerRequestMatcherRegistry equalTo(Function<Authentication, String> function) {
return access((auth, requestContext) -> {
String value = requestContext.getVariables().get(this.variable);
return new AuthorizationDecision(function.apply(auth.get()).equals(value));
});
}
return AuthorizeHttpRequestsConfigurer.this.addMapping(this.matchers, manager);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 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.
@@ -19,15 +19,17 @@ package org.springframework.security.config.annotation.web.configurers.oauth2.cl
import java.util.function.Function;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtValidators;
import org.springframework.security.oauth2.jwt.JwtTimestampValidator;
final class DefaultOidcLogoutTokenValidatorFactory implements Function<ClientRegistration, OAuth2TokenValidator<Jwt>> {
@Override
public OAuth2TokenValidator<Jwt> apply(ClientRegistration clientRegistration) {
return JwtValidators.createDefaultWithValidators(new OidcBackChannelLogoutTokenValidator(clientRegistration));
return new DelegatingOAuth2TokenValidator<>(new JwtTimestampValidator(),
new OidcBackChannelLogoutTokenValidator(clientRegistration));
}
}
@@ -1,413 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.reactive;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.ResolvableType;
import org.springframework.security.oauth2.client.AuthorizationCodeReactiveOAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.ClientCredentialsReactiveOAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.DelegatingReactiveOAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.JwtBearerReactiveOAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.PasswordReactiveOAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.RefreshTokenReactiveOAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.TokenExchangeReactiveOAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.endpoint.JwtBearerGrantRequest;
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest;
import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest;
import org.springframework.security.oauth2.client.endpoint.ReactiveOAuth2AccessTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.TokenExchangeGrantRequest;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.DefaultReactiveOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.web.reactive.result.method.annotation.OAuth2AuthorizedClientArgumentResolver;
import org.springframework.security.oauth2.client.web.server.AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
import org.springframework.web.reactive.config.WebFluxConfigurer;
import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer;
/**
* {@link Configuration} for OAuth 2.0 Client support.
*
* <p>
* This {@code Configuration} is conditionally imported by
* {@link ReactiveOAuth2ClientImportSelector} when the
* {@code spring-security-oauth2-client} module is present on the classpath.
*
* @author Steve Riesenberg
* @since 6.3
* @see ReactiveOAuth2ClientImportSelector
*/
@Import({ ReactiveOAuth2ClientConfiguration.ReactiveOAuth2AuthorizedClientManagerConfiguration.class,
ReactiveOAuth2ClientConfiguration.OAuth2ClientWebFluxSecurityConfiguration.class })
final class ReactiveOAuth2ClientConfiguration {
@Configuration(proxyBeanMethods = false)
static class ReactiveOAuth2AuthorizedClientManagerConfiguration {
@Bean(name = ReactiveOAuth2AuthorizedClientManagerRegistrar.BEAN_NAME)
ReactiveOAuth2AuthorizedClientManagerRegistrar authorizedClientManagerRegistrar() {
return new ReactiveOAuth2AuthorizedClientManagerRegistrar();
}
}
@Configuration(proxyBeanMethods = false)
static class OAuth2ClientWebFluxSecurityConfiguration implements WebFluxConfigurer {
private ReactiveOAuth2AuthorizedClientManager authorizedClientManager;
private ReactiveOAuth2AuthorizedClientManagerRegistrar authorizedClientManagerRegistrar;
@Override
public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) {
ReactiveOAuth2AuthorizedClientManager authorizedClientManager = getAuthorizedClientManager();
if (authorizedClientManager != null) {
configurer.addCustomResolver(new OAuth2AuthorizedClientArgumentResolver(authorizedClientManager));
}
}
@Autowired(required = false)
void setAuthorizedClientManager(List<ReactiveOAuth2AuthorizedClientManager> authorizedClientManager) {
if (authorizedClientManager.size() == 1) {
this.authorizedClientManager = authorizedClientManager.get(0);
}
}
@Autowired
void setAuthorizedClientManagerRegistrar(
ReactiveOAuth2AuthorizedClientManagerRegistrar authorizedClientManagerRegistrar) {
this.authorizedClientManagerRegistrar = authorizedClientManagerRegistrar;
}
private ReactiveOAuth2AuthorizedClientManager getAuthorizedClientManager() {
if (this.authorizedClientManager != null) {
return this.authorizedClientManager;
}
return this.authorizedClientManagerRegistrar.getAuthorizedClientManagerIfAvailable();
}
}
/**
* A registrar for registering the default
* {@link ReactiveOAuth2AuthorizedClientManager} bean definition, if not already
* present.
*/
static final class ReactiveOAuth2AuthorizedClientManagerRegistrar
implements BeanDefinitionRegistryPostProcessor, BeanFactoryAware {
static final String BEAN_NAME = "authorizedClientManagerRegistrar";
static final String FACTORY_METHOD_NAME = "getAuthorizedClientManager";
// @formatter:off
private static final Set<Class<?>> KNOWN_AUTHORIZED_CLIENT_PROVIDERS = Set.of(
AuthorizationCodeReactiveOAuth2AuthorizedClientProvider.class,
RefreshTokenReactiveOAuth2AuthorizedClientProvider.class,
ClientCredentialsReactiveOAuth2AuthorizedClientProvider.class,
PasswordReactiveOAuth2AuthorizedClientProvider.class,
JwtBearerReactiveOAuth2AuthorizedClientProvider.class,
TokenExchangeReactiveOAuth2AuthorizedClientProvider.class
);
// @formatter:on
private final AnnotationBeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator();
private ListableBeanFactory beanFactory;
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
if (getBeanNamesForType(ReactiveOAuth2AuthorizedClientManager.class).length != 0
|| getBeanNamesForType(ReactiveClientRegistrationRepository.class).length != 1
|| getBeanNamesForType(ServerOAuth2AuthorizedClientRepository.class).length != 1
&& getBeanNamesForType(ReactiveOAuth2AuthorizedClientService.class).length != 1) {
return;
}
BeanDefinition beanDefinition = BeanDefinitionBuilder
.rootBeanDefinition(ReactiveOAuth2AuthorizedClientManager.class)
.setFactoryMethodOnBean(FACTORY_METHOD_NAME, BEAN_NAME)
.getBeanDefinition();
registry.registerBeanDefinition(this.beanNameGenerator.generateBeanName(beanDefinition, registry),
beanDefinition);
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = (ListableBeanFactory) beanFactory;
}
ReactiveOAuth2AuthorizedClientManager getAuthorizedClientManagerIfAvailable() {
if (getBeanNamesForType(ReactiveClientRegistrationRepository.class).length != 1
|| getBeanNamesForType(ServerOAuth2AuthorizedClientRepository.class).length != 1
&& getBeanNamesForType(ReactiveOAuth2AuthorizedClientService.class).length != 1) {
return null;
}
return getAuthorizedClientManager();
}
ReactiveOAuth2AuthorizedClientManager getAuthorizedClientManager() {
ReactiveClientRegistrationRepository clientRegistrationRepository = BeanFactoryUtils
.beanOfTypeIncludingAncestors(this.beanFactory, ReactiveClientRegistrationRepository.class, true, true);
ServerOAuth2AuthorizedClientRepository authorizedClientRepository;
try {
authorizedClientRepository = BeanFactoryUtils.beanOfTypeIncludingAncestors(this.beanFactory,
ServerOAuth2AuthorizedClientRepository.class, true, true);
}
catch (NoSuchBeanDefinitionException ex) {
ReactiveOAuth2AuthorizedClientService authorizedClientService = BeanFactoryUtils
.beanOfTypeIncludingAncestors(this.beanFactory, ReactiveOAuth2AuthorizedClientService.class, true,
true);
authorizedClientRepository = new AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository(
authorizedClientService);
}
Collection<ReactiveOAuth2AuthorizedClientProvider> authorizedClientProviderBeans = BeanFactoryUtils
.beansOfTypeIncludingAncestors(this.beanFactory, ReactiveOAuth2AuthorizedClientProvider.class, true,
true)
.values();
ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider;
if (hasDelegatingAuthorizedClientProvider(authorizedClientProviderBeans)) {
authorizedClientProvider = authorizedClientProviderBeans.iterator().next();
}
else {
List<ReactiveOAuth2AuthorizedClientProvider> authorizedClientProviders = new ArrayList<>();
authorizedClientProviders
.add(getAuthorizationCodeAuthorizedClientProvider(authorizedClientProviderBeans));
authorizedClientProviders.add(getRefreshTokenAuthorizedClientProvider(authorizedClientProviderBeans));
authorizedClientProviders
.add(getClientCredentialsAuthorizedClientProvider(authorizedClientProviderBeans));
authorizedClientProviders.add(getPasswordAuthorizedClientProvider(authorizedClientProviderBeans));
ReactiveOAuth2AuthorizedClientProvider jwtBearerAuthorizedClientProvider = getJwtBearerAuthorizedClientProvider(
authorizedClientProviderBeans);
if (jwtBearerAuthorizedClientProvider != null) {
authorizedClientProviders.add(jwtBearerAuthorizedClientProvider);
}
ReactiveOAuth2AuthorizedClientProvider tokenExchangeAuthorizedClientProvider = getTokenExchangeAuthorizedClientProvider(
authorizedClientProviderBeans);
if (tokenExchangeAuthorizedClientProvider != null) {
authorizedClientProviders.add(tokenExchangeAuthorizedClientProvider);
}
authorizedClientProviders.addAll(getAdditionalAuthorizedClientProviders(authorizedClientProviderBeans));
authorizedClientProvider = new DelegatingReactiveOAuth2AuthorizedClientProvider(
authorizedClientProviders);
}
DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager = new DefaultReactiveOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
Consumer<DefaultReactiveOAuth2AuthorizedClientManager> authorizedClientManagerConsumer = getBeanOfType(
ResolvableType.forClassWithGenerics(Consumer.class,
DefaultReactiveOAuth2AuthorizedClientManager.class));
if (authorizedClientManagerConsumer != null) {
authorizedClientManagerConsumer.accept(authorizedClientManager);
}
return authorizedClientManager;
}
private boolean hasDelegatingAuthorizedClientProvider(
Collection<ReactiveOAuth2AuthorizedClientProvider> authorizedClientProviders) {
if (authorizedClientProviders.size() != 1) {
return false;
}
return authorizedClientProviders.iterator()
.next() instanceof DelegatingReactiveOAuth2AuthorizedClientProvider;
}
private ReactiveOAuth2AuthorizedClientProvider getAuthorizationCodeAuthorizedClientProvider(
Collection<ReactiveOAuth2AuthorizedClientProvider> authorizedClientProviders) {
AuthorizationCodeReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType(
authorizedClientProviders, AuthorizationCodeReactiveOAuth2AuthorizedClientProvider.class);
if (authorizedClientProvider == null) {
authorizedClientProvider = new AuthorizationCodeReactiveOAuth2AuthorizedClientProvider();
}
return authorizedClientProvider;
}
private ReactiveOAuth2AuthorizedClientProvider getRefreshTokenAuthorizedClientProvider(
Collection<ReactiveOAuth2AuthorizedClientProvider> authorizedClientProviders) {
RefreshTokenReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType(
authorizedClientProviders, RefreshTokenReactiveOAuth2AuthorizedClientProvider.class);
if (authorizedClientProvider == null) {
authorizedClientProvider = new RefreshTokenReactiveOAuth2AuthorizedClientProvider();
}
ReactiveOAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> accessTokenResponseClient = getBeanOfType(
ResolvableType.forClassWithGenerics(ReactiveOAuth2AccessTokenResponseClient.class,
OAuth2RefreshTokenGrantRequest.class));
if (accessTokenResponseClient != null) {
authorizedClientProvider.setAccessTokenResponseClient(accessTokenResponseClient);
}
return authorizedClientProvider;
}
private ReactiveOAuth2AuthorizedClientProvider getClientCredentialsAuthorizedClientProvider(
Collection<ReactiveOAuth2AuthorizedClientProvider> authorizedClientProviders) {
ClientCredentialsReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType(
authorizedClientProviders, ClientCredentialsReactiveOAuth2AuthorizedClientProvider.class);
if (authorizedClientProvider == null) {
authorizedClientProvider = new ClientCredentialsReactiveOAuth2AuthorizedClientProvider();
}
ReactiveOAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> accessTokenResponseClient = getBeanOfType(
ResolvableType.forClassWithGenerics(ReactiveOAuth2AccessTokenResponseClient.class,
OAuth2ClientCredentialsGrantRequest.class));
if (accessTokenResponseClient != null) {
authorizedClientProvider.setAccessTokenResponseClient(accessTokenResponseClient);
}
return authorizedClientProvider;
}
private ReactiveOAuth2AuthorizedClientProvider getPasswordAuthorizedClientProvider(
Collection<ReactiveOAuth2AuthorizedClientProvider> authorizedClientProviders) {
PasswordReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType(
authorizedClientProviders, PasswordReactiveOAuth2AuthorizedClientProvider.class);
if (authorizedClientProvider == null) {
authorizedClientProvider = new PasswordReactiveOAuth2AuthorizedClientProvider();
}
ReactiveOAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> accessTokenResponseClient = getBeanOfType(
ResolvableType.forClassWithGenerics(ReactiveOAuth2AccessTokenResponseClient.class,
OAuth2PasswordGrantRequest.class));
if (accessTokenResponseClient != null) {
authorizedClientProvider.setAccessTokenResponseClient(accessTokenResponseClient);
}
return authorizedClientProvider;
}
private ReactiveOAuth2AuthorizedClientProvider getJwtBearerAuthorizedClientProvider(
Collection<ReactiveOAuth2AuthorizedClientProvider> authorizedClientProviders) {
JwtBearerReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType(
authorizedClientProviders, JwtBearerReactiveOAuth2AuthorizedClientProvider.class);
ReactiveOAuth2AccessTokenResponseClient<JwtBearerGrantRequest> accessTokenResponseClient = getBeanOfType(
ResolvableType.forClassWithGenerics(ReactiveOAuth2AccessTokenResponseClient.class,
JwtBearerGrantRequest.class));
if (accessTokenResponseClient != null) {
if (authorizedClientProvider == null) {
authorizedClientProvider = new JwtBearerReactiveOAuth2AuthorizedClientProvider();
}
authorizedClientProvider.setAccessTokenResponseClient(accessTokenResponseClient);
}
return authorizedClientProvider;
}
private ReactiveOAuth2AuthorizedClientProvider getTokenExchangeAuthorizedClientProvider(
Collection<ReactiveOAuth2AuthorizedClientProvider> authorizedClientProviders) {
TokenExchangeReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType(
authorizedClientProviders, TokenExchangeReactiveOAuth2AuthorizedClientProvider.class);
ReactiveOAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> accessTokenResponseClient = getBeanOfType(
ResolvableType.forClassWithGenerics(ReactiveOAuth2AccessTokenResponseClient.class,
TokenExchangeGrantRequest.class));
if (accessTokenResponseClient != null) {
if (authorizedClientProvider == null) {
authorizedClientProvider = new TokenExchangeReactiveOAuth2AuthorizedClientProvider();
}
authorizedClientProvider.setAccessTokenResponseClient(accessTokenResponseClient);
}
return authorizedClientProvider;
}
private List<ReactiveOAuth2AuthorizedClientProvider> getAdditionalAuthorizedClientProviders(
Collection<ReactiveOAuth2AuthorizedClientProvider> authorizedClientProviders) {
List<ReactiveOAuth2AuthorizedClientProvider> additionalAuthorizedClientProviders = new ArrayList<>(
authorizedClientProviders);
additionalAuthorizedClientProviders
.removeIf((provider) -> KNOWN_AUTHORIZED_CLIENT_PROVIDERS.contains(provider.getClass()));
return additionalAuthorizedClientProviders;
}
private <T extends ReactiveOAuth2AuthorizedClientProvider> T getAuthorizedClientProviderByType(
Collection<ReactiveOAuth2AuthorizedClientProvider> authorizedClientProviders, Class<T> providerClass) {
T authorizedClientProvider = null;
for (ReactiveOAuth2AuthorizedClientProvider current : authorizedClientProviders) {
if (providerClass.isInstance(current)) {
assertAuthorizedClientProviderIsNull(authorizedClientProvider);
authorizedClientProvider = providerClass.cast(current);
}
}
return authorizedClientProvider;
}
private static void assertAuthorizedClientProviderIsNull(
ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider) {
if (authorizedClientProvider != null) {
// @formatter:off
throw new BeanInitializationException(String.format(
"Unable to create a %s bean. Expected one bean of type %s, but found multiple. " +
"Please consider defining only a single bean of this type, or define a %s bean yourself.",
ReactiveOAuth2AuthorizedClientManager.class.getName(),
authorizedClientProvider.getClass().getName(),
ReactiveOAuth2AuthorizedClientManager.class.getName()));
// @formatter:on
}
}
private <T> String[] getBeanNamesForType(Class<T> beanClass) {
return BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, beanClass, true, true);
}
private <T> T getBeanOfType(ResolvableType resolvableType) {
ObjectProvider<T> objectProvider = this.beanFactory.getBeanProvider(resolvableType, true);
return objectProvider.getIfAvailable();
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 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,13 +16,27 @@
package org.springframework.security.config.annotation.web.reactive;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProviderBuilder;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.DefaultReactiveOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.web.reactive.result.method.annotation.OAuth2AuthorizedClientArgumentResolver;
import org.springframework.security.oauth2.client.web.server.AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
import org.springframework.util.ClassUtils;
import org.springframework.web.reactive.config.WebFluxConfigurer;
import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer;
/**
* Used by {@link EnableWebFluxSecurity} to conditionally import
* {@link ReactiveOAuth2ClientConfiguration}.
* {@link Configuration} for OAuth 2.0 Client support.
*
* <p>
* This {@code Configuration} is imported by {@link EnableWebFluxSecurity}
@@ -46,8 +60,85 @@ final class ReactiveOAuth2ClientImportSelector implements ImportSelector {
if (!oauth2ClientPresent) {
return new String[0];
}
return new String[] {
"org.springframework.security.config.annotation.web.reactive.ReactiveOAuth2ClientConfiguration" };
return new String[] { "org.springframework.security.config.annotation.web.reactive."
+ "ReactiveOAuth2ClientImportSelector$OAuth2ClientWebFluxSecurityConfiguration" };
}
@Configuration(proxyBeanMethods = false)
static class OAuth2ClientWebFluxSecurityConfiguration implements WebFluxConfigurer {
private ReactiveClientRegistrationRepository clientRegistrationRepository;
private ServerOAuth2AuthorizedClientRepository authorizedClientRepository;
private ReactiveOAuth2AuthorizedClientService authorizedClientService;
private ReactiveOAuth2AuthorizedClientManager authorizedClientManager;
@Override
public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) {
ReactiveOAuth2AuthorizedClientManager authorizedClientManager = getAuthorizedClientManager();
if (authorizedClientManager != null) {
configurer.addCustomResolver(new OAuth2AuthorizedClientArgumentResolver(authorizedClientManager));
}
}
@Autowired(required = false)
void setClientRegistrationRepository(ReactiveClientRegistrationRepository clientRegistrationRepository) {
this.clientRegistrationRepository = clientRegistrationRepository;
}
@Autowired(required = false)
void setAuthorizedClientRepository(ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
this.authorizedClientRepository = authorizedClientRepository;
}
@Autowired(required = false)
void setAuthorizedClientService(List<ReactiveOAuth2AuthorizedClientService> authorizedClientService) {
if (authorizedClientService.size() == 1) {
this.authorizedClientService = authorizedClientService.get(0);
}
}
@Autowired(required = false)
void setAuthorizedClientManager(List<ReactiveOAuth2AuthorizedClientManager> authorizedClientManager) {
if (authorizedClientManager.size() == 1) {
this.authorizedClientManager = authorizedClientManager.get(0);
}
}
private ServerOAuth2AuthorizedClientRepository getAuthorizedClientRepository() {
if (this.authorizedClientRepository != null) {
return this.authorizedClientRepository;
}
if (this.authorizedClientService != null) {
return new AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository(this.authorizedClientService);
}
return null;
}
private ReactiveOAuth2AuthorizedClientManager getAuthorizedClientManager() {
if (this.authorizedClientManager != null) {
return this.authorizedClientManager;
}
ReactiveOAuth2AuthorizedClientManager authorizedClientManager = null;
if (this.authorizedClientRepository != null && this.clientRegistrationRepository != null) {
ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder
.builder()
.authorizationCode()
.refreshToken()
.clientCredentials()
.password()
.build();
DefaultReactiveOAuth2AuthorizedClientManager defaultReactiveOAuth2AuthorizedClientManager = new DefaultReactiveOAuth2AuthorizedClientManager(
this.clientRegistrationRepository, getAuthorizedClientRepository());
defaultReactiveOAuth2AuthorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
authorizedClientManager = defaultReactiveOAuth2AuthorizedClientManager;
}
return authorizedClientManager;
}
}
}
@@ -32,7 +32,6 @@ import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.security.authentication.ObservationReactiveAuthenticationManager;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager;
import org.springframework.security.authentication.password.ReactiveCompromisedPasswordChecker;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.userdetails.ReactiveUserDetailsPasswordService;
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
@@ -64,8 +63,6 @@ class ServerHttpSecurityConfiguration {
private ReactiveUserDetailsPasswordService userDetailsPasswordService;
private ReactiveCompromisedPasswordChecker compromisedPasswordChecker;
private ObservationRegistry observationRegistry = ObservationRegistry.NOOP;
@Autowired(required = false)
@@ -101,11 +98,6 @@ class ServerHttpSecurityConfiguration {
this.observationRegistry = observationRegistry;
}
@Autowired(required = false)
void setCompromisedPasswordChecker(ReactiveCompromisedPasswordChecker compromisedPasswordChecker) {
this.compromisedPasswordChecker = compromisedPasswordChecker;
}
@Bean
static WebFluxConfigurer authenticationPrincipalArgumentResolverConfigurer(
ObjectProvider<AuthenticationPrincipalArgumentResolver> authenticationPrincipalArgumentResolver) {
@@ -161,7 +153,6 @@ class ServerHttpSecurityConfiguration {
manager.setPasswordEncoder(this.passwordEncoder);
}
manager.setUserDetailsPasswordService(this.userDetailsPasswordService);
manager.setCompromisedPasswordChecker(this.compromisedPasswordChecker);
if (!this.observationRegistry.isNoop()) {
return new ObservationReactiveAuthenticationManager(this.observationRegistry, manager);
}
@@ -44,13 +44,11 @@ import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.PasswordOAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.RefreshTokenOAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.TokenExchangeOAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.endpoint.JwtBearerGrantRequest;
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest;
import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest;
import org.springframework.security.oauth2.client.endpoint.TokenExchangeGrantRequest;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
@@ -78,8 +76,7 @@ final class OAuth2AuthorizedClientManagerRegistrar implements BeanDefinitionRegi
RefreshTokenOAuth2AuthorizedClientProvider.class,
ClientCredentialsOAuth2AuthorizedClientProvider.class,
PasswordOAuth2AuthorizedClientProvider.class,
JwtBearerOAuth2AuthorizedClientProvider.class,
TokenExchangeOAuth2AuthorizedClientProvider.class
JwtBearerOAuth2AuthorizedClientProvider.class
);
// @formatter:on
@@ -140,12 +137,6 @@ final class OAuth2AuthorizedClientManagerRegistrar implements BeanDefinitionRegi
authorizedClientProviders.add(jwtBearerAuthorizedClientProvider);
}
OAuth2AuthorizedClientProvider tokenExchangeAuthorizedClientProvider = getTokenExchangeAuthorizedClientProvider(
authorizedClientProviderBeans);
if (tokenExchangeAuthorizedClientProvider != null) {
authorizedClientProviders.add(tokenExchangeAuthorizedClientProvider);
}
authorizedClientProviders.addAll(getAdditionalAuthorizedClientProviders(authorizedClientProviderBeans));
authorizedClientProvider = new DelegatingOAuth2AuthorizedClientProvider(authorizedClientProviders);
}
@@ -254,25 +245,6 @@ final class OAuth2AuthorizedClientManagerRegistrar implements BeanDefinitionRegi
return authorizedClientProvider;
}
private OAuth2AuthorizedClientProvider getTokenExchangeAuthorizedClientProvider(
Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders) {
TokenExchangeOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType(
authorizedClientProviders, TokenExchangeOAuth2AuthorizedClientProvider.class);
OAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> accessTokenResponseClient = getBeanOfType(
ResolvableType.forClassWithGenerics(OAuth2AccessTokenResponseClient.class,
TokenExchangeGrantRequest.class));
if (accessTokenResponseClient != null) {
if (authorizedClientProvider == null) {
authorizedClientProvider = new TokenExchangeOAuth2AuthorizedClientProvider();
}
authorizedClientProvider.setAccessTokenResponseClient(accessTokenResponseClient);
}
return authorizedClientProvider;
}
private List<OAuth2AuthorizedClientProvider> getAdditionalAuthorizedClientProviders(
Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders) {
List<OAuth2AuthorizedClientProvider> additionalAuthorizedClientProviders = new ArrayList<>(
@@ -64,8 +64,6 @@ public final class RelyingPartyRegistrationsBeanDefinitionParser implements Bean
private static final String ELT_ENCRYPTION_CREDENTIAL = "encryption-credential";
private static final String ATT_ID = "id";
private static final String ATT_REGISTRATION_ID = "registration-id";
private static final String ATT_ASSERTING_PARTY_ID = "asserting-party-id";
@@ -110,11 +108,8 @@ public final class RelyingPartyRegistrationsBeanDefinitionParser implements Bean
.rootBeanDefinition(InMemoryRelyingPartyRegistrationRepository.class)
.addConstructorArgValue(relyingPartyRegistrations)
.getBeanDefinition();
String relyingPartyRegistrationRepositoryId = element.getAttribute(ATT_ID);
if (!StringUtils.hasText(relyingPartyRegistrationRepositoryId)) {
relyingPartyRegistrationRepositoryId = parserContext.getReaderContext()
.generateBeanName(relyingPartyRegistrationRepositoryBean);
}
String relyingPartyRegistrationRepositoryId = parserContext.getReaderContext()
.generateBeanName(relyingPartyRegistrationRepositoryBean);
parserContext.registerBeanComponent(new BeanComponentDefinition(relyingPartyRegistrationRepositoryBean,
relyingPartyRegistrationRepositoryId));
parserContext.popAndRegisterContainingComponent();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 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.
@@ -19,15 +19,17 @@ package org.springframework.security.config.web.server;
import java.util.function.Function;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtValidators;
import org.springframework.security.oauth2.jwt.JwtTimestampValidator;
final class DefaultOidcLogoutTokenValidatorFactory implements Function<ClientRegistration, OAuth2TokenValidator<Jwt>> {
@Override
public OAuth2TokenValidator<Jwt> apply(ClientRegistration clientRegistration) {
return JwtValidators.createDefaultWithValidators(new OidcBackChannelLogoutTokenValidator(clientRegistration));
return new DelegatingOAuth2TokenValidator<>(new JwtTimestampValidator(),
new OidcBackChannelLogoutTokenValidator(clientRegistration));
}
}
@@ -63,7 +63,6 @@ import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.core.session.ReactiveSessionRegistry;
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
import org.springframework.security.oauth2.client.InMemoryReactiveOAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService;
@@ -127,26 +126,19 @@ import org.springframework.security.web.server.WebFilterExchange;
import org.springframework.security.web.server.authentication.AnonymousAuthenticationWebFilter;
import org.springframework.security.web.server.authentication.AuthenticationConverterServerWebExchangeMatcher;
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
import org.springframework.security.web.server.authentication.ConcurrentSessionControlServerAuthenticationSuccessHandler;
import org.springframework.security.web.server.authentication.DelegatingServerAuthenticationSuccessHandler;
import org.springframework.security.web.server.authentication.HttpBasicServerAuthenticationEntryPoint;
import org.springframework.security.web.server.authentication.HttpStatusServerEntryPoint;
import org.springframework.security.web.server.authentication.InvalidateLeastUsedServerMaximumSessionsExceededHandler;
import org.springframework.security.web.server.authentication.ReactivePreAuthenticatedAuthenticationManager;
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationEntryPoint;
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationFailureHandler;
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationSuccessHandler;
import org.springframework.security.web.server.authentication.RegisterSessionServerAuthenticationSuccessHandler;
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter;
import org.springframework.security.web.server.authentication.ServerAuthenticationEntryPointFailureHandler;
import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler;
import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;
import org.springframework.security.web.server.authentication.ServerFormLoginAuthenticationConverter;
import org.springframework.security.web.server.authentication.ServerHttpBasicAuthenticationConverter;
import org.springframework.security.web.server.authentication.ServerMaximumSessionsExceededHandler;
import org.springframework.security.web.server.authentication.ServerX509AuthenticationConverter;
import org.springframework.security.web.server.authentication.SessionLimit;
import org.springframework.security.web.server.authentication.WebFilterChainServerAuthenticationSuccessHandler;
import org.springframework.security.web.server.authentication.logout.DelegatingServerLogoutHandler;
import org.springframework.security.web.server.authentication.logout.LogoutWebFilter;
import org.springframework.security.web.server.authentication.logout.SecurityContextServerLogoutHandler;
@@ -216,8 +208,6 @@ import org.springframework.web.server.ServerWebExchangeDecorator;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import org.springframework.web.server.WebSession;
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
import org.springframework.web.server.session.DefaultWebSessionManager;
import org.springframework.web.util.pattern.PathPatternParser;
/**
@@ -324,8 +314,6 @@ public class ServerHttpSecurity {
private LoginPageSpec loginPage = new LoginPageSpec();
private SessionManagementSpec sessionManagement;
private ReactiveAuthenticationManager authenticationManager;
private ServerSecurityContextRepository securityContextRepository;
@@ -374,7 +362,6 @@ public class ServerHttpSecurity {
}
/**
*
* Adds a {@link WebFilter} before specific position.
* @param webFilter the {@link WebFilter} to add
* @param order the place before which to insert the {@link WebFilter}
@@ -758,36 +745,6 @@ public class ServerHttpSecurity {
return this;
}
/**
* Configures Session Management. An example configuration is provided below:
* <pre class="code">
* &#064;Bean
* SecurityWebFilterChain filterChain(ServerHttpSecurity http, ReactiveSessionRegistry sessionRegistry) {
* http
* // ...
* .sessionManagement((sessionManagement) -> sessionManagement
* .concurrentSessions((concurrentSessions) -> concurrentSessions
* .maxSessions(1)
* .maxSessionsPreventsLogin(true)
* .sessionRegistry(sessionRegistry)
* )
* );
* return http.build();
* }
* </pre>
* @param customizer the {@link Customizer} to provide more options for the
* {@link SessionManagementSpec}
* @return the {@link ServerHttpSecurity} to continue configuring
* @since 6.3
*/
public ServerHttpSecurity sessionManagement(Customizer<SessionManagementSpec> customizer) {
if (this.sessionManagement == null) {
this.sessionManagement = new SessionManagementSpec();
}
customizer.customize(this.sessionManagement);
return this;
}
/**
* Configures password management. An example configuration is provided below:
*
@@ -1562,9 +1519,6 @@ public class ServerHttpSecurity {
}
WebFilter securityContextRepositoryWebFilter = securityContextRepositoryWebFilter();
this.webFilters.add(securityContextRepositoryWebFilter);
if (this.sessionManagement != null) {
this.sessionManagement.configure(this);
}
if (this.httpsRedirectSpec != null) {
this.httpsRedirectSpec.configure(this);
}
@@ -1955,270 +1909,6 @@ public class ServerHttpSecurity {
}
/**
* Configures how sessions are managed.
*/
public class SessionManagementSpec {
private ConcurrentSessionsSpec concurrentSessions;
private ServerAuthenticationSuccessHandler authenticationSuccessHandler;
private ReactiveSessionRegistry sessionRegistry;
private SessionLimit sessionLimit = SessionLimit.UNLIMITED;
private ServerMaximumSessionsExceededHandler maximumSessionsExceededHandler;
/**
* Configures how many sessions are allowed for a given user.
* @param customizer the customizer to provide more options
* @return the {@link SessionManagementSpec} to customize
*/
public SessionManagementSpec concurrentSessions(Customizer<ConcurrentSessionsSpec> customizer) {
if (this.concurrentSessions == null) {
this.concurrentSessions = new ConcurrentSessionsSpec();
}
customizer.customize(this.concurrentSessions);
return this;
}
void configure(ServerHttpSecurity http) {
if (this.concurrentSessions != null) {
ReactiveSessionRegistry reactiveSessionRegistry = getSessionRegistry();
ConcurrentSessionControlServerAuthenticationSuccessHandler concurrentSessionControlStrategy = new ConcurrentSessionControlServerAuthenticationSuccessHandler(
reactiveSessionRegistry, getMaximumSessionsExceededHandler());
concurrentSessionControlStrategy.setSessionLimit(this.sessionLimit);
RegisterSessionServerAuthenticationSuccessHandler registerSessionAuthenticationStrategy = new RegisterSessionServerAuthenticationSuccessHandler(
reactiveSessionRegistry);
this.authenticationSuccessHandler = new DelegatingServerAuthenticationSuccessHandler(
concurrentSessionControlStrategy, registerSessionAuthenticationStrategy);
SessionRegistryWebFilter sessionRegistryWebFilter = new SessionRegistryWebFilter(
reactiveSessionRegistry);
configureSuccessHandlerOnAuthenticationFilters();
http.addFilterAfter(sessionRegistryWebFilter, SecurityWebFiltersOrder.HTTP_HEADERS_WRITER);
}
}
private ServerMaximumSessionsExceededHandler getMaximumSessionsExceededHandler() {
if (this.maximumSessionsExceededHandler != null) {
return this.maximumSessionsExceededHandler;
}
DefaultWebSessionManager webSessionManager = getBeanOrNull(
WebHttpHandlerBuilder.WEB_SESSION_MANAGER_BEAN_NAME, DefaultWebSessionManager.class);
if (webSessionManager != null) {
this.maximumSessionsExceededHandler = new InvalidateLeastUsedServerMaximumSessionsExceededHandler(
webSessionManager.getSessionStore());
}
if (this.maximumSessionsExceededHandler == null) {
throw new IllegalStateException(
"Could not create a default ServerMaximumSessionsExceededHandler. Please provide "
+ "a ServerMaximumSessionsExceededHandler via DSL");
}
return this.maximumSessionsExceededHandler;
}
private void configureSuccessHandlerOnAuthenticationFilters() {
if (ServerHttpSecurity.this.formLogin != null) {
ServerHttpSecurity.this.formLogin.defaultSuccessHandlers.add(0, this.authenticationSuccessHandler);
}
if (ServerHttpSecurity.this.oauth2Login != null) {
ServerHttpSecurity.this.oauth2Login.defaultSuccessHandlers.add(0, this.authenticationSuccessHandler);
}
if (ServerHttpSecurity.this.httpBasic != null) {
ServerHttpSecurity.this.httpBasic.defaultSuccessHandlers.add(0, this.authenticationSuccessHandler);
}
}
private ReactiveSessionRegistry getSessionRegistry() {
if (this.sessionRegistry == null) {
this.sessionRegistry = getBeanOrNull(ReactiveSessionRegistry.class);
}
if (this.sessionRegistry == null) {
throw new IllegalStateException(
"A ReactiveSessionRegistry is needed for concurrent session management");
}
return this.sessionRegistry;
}
/**
* Configures how many sessions are allowed for a given user.
*/
public class ConcurrentSessionsSpec {
/**
* Sets the {@link ReactiveSessionRegistry} to use.
* @param reactiveSessionRegistry the {@link ReactiveSessionRegistry} to use
* @return the {@link ConcurrentSessionsSpec} to continue customizing
*/
public ConcurrentSessionsSpec sessionRegistry(ReactiveSessionRegistry reactiveSessionRegistry) {
SessionManagementSpec.this.sessionRegistry = reactiveSessionRegistry;
return this;
}
/**
* Sets the maximum number of sessions allowed for any user. You can use
* {@link SessionLimit#of(int)} to specify a positive integer or
* {@link SessionLimit#UNLIMITED} to allow unlimited sessions. To customize
* the maximum number of sessions on a per-user basis, you can provide a
* custom {@link SessionLimit} implementation, like so: <pre>
* http
* .sessionManagement((sessions) -> sessions
* .concurrentSessions((concurrency) -> concurrency
* .maximumSessions((authentication) -> {
* if (authentication.getName().equals("admin")) {
* return Mono.empty() // unlimited sessions for admin
* }
* return Mono.just(1); // one session for every other user
* })
* )
* )
* </pre>
* @param sessionLimit the maximum number of sessions allowed for any user
* @return the {@link ConcurrentSessionsSpec} to continue customizing
*/
public ConcurrentSessionsSpec maximumSessions(SessionLimit sessionLimit) {
Assert.notNull(sessionLimit, "sessionLimit cannot be null");
SessionManagementSpec.this.sessionLimit = sessionLimit;
return this;
}
/**
* Sets the {@link ServerMaximumSessionsExceededHandler} to use when the
* maximum number of sessions is exceeded.
* @param maximumSessionsExceededHandler the
* {@link ServerMaximumSessionsExceededHandler} to use
* @return the {@link ConcurrentSessionsSpec} to continue customizing
*/
public ConcurrentSessionsSpec maximumSessionsExceededHandler(
ServerMaximumSessionsExceededHandler maximumSessionsExceededHandler) {
Assert.notNull(maximumSessionsExceededHandler, "maximumSessionsExceededHandler cannot be null");
SessionManagementSpec.this.maximumSessionsExceededHandler = maximumSessionsExceededHandler;
return this;
}
}
private static final class SessionRegistryWebFilter implements WebFilter {
private final ReactiveSessionRegistry sessionRegistry;
private SessionRegistryWebFilter(ReactiveSessionRegistry sessionRegistry) {
Assert.notNull(sessionRegistry, "sessionRegistry cannot be null");
this.sessionRegistry = sessionRegistry;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return chain.filter(new SessionRegistryWebExchange(exchange));
}
private final class SessionRegistryWebExchange extends ServerWebExchangeDecorator {
private final Mono<WebSession> sessionMono;
private SessionRegistryWebExchange(ServerWebExchange delegate) {
super(delegate);
this.sessionMono = delegate.getSession()
.flatMap((session) -> SessionRegistryWebFilter.this.sessionRegistry
.updateLastAccessTime(session.getId())
.thenReturn(session))
.map(SessionRegistryWebSession::new);
}
@Override
public Mono<WebSession> getSession() {
return this.sessionMono;
}
}
private final class SessionRegistryWebSession implements WebSession {
private final WebSession session;
private SessionRegistryWebSession(WebSession session) {
this.session = session;
}
@Override
public String getId() {
return this.session.getId();
}
@Override
public Map<String, Object> getAttributes() {
return this.session.getAttributes();
}
@Override
public void start() {
this.session.start();
}
@Override
public boolean isStarted() {
return this.session.isStarted();
}
@Override
public Mono<Void> changeSessionId() {
String currentId = this.session.getId();
return this.session.changeSessionId()
.then(Mono.defer(
() -> SessionRegistryWebFilter.this.sessionRegistry.removeSessionInformation(currentId)
.flatMap((information) -> {
information = information.withSessionId(this.session.getId());
return SessionRegistryWebFilter.this.sessionRegistry
.saveSessionInformation(information);
})));
}
@Override
public Mono<Void> invalidate() {
String currentId = this.session.getId();
return this.session.invalidate()
.then(Mono.defer(() -> SessionRegistryWebFilter.this.sessionRegistry
.removeSessionInformation(currentId)))
.then();
}
@Override
public Mono<Void> save() {
return this.session.save();
}
@Override
public boolean isExpired() {
return this.session.isExpired();
}
@Override
public Instant getCreationTime() {
return this.session.getCreationTime();
}
@Override
public Instant getLastAccessTime() {
return this.session.getLastAccessTime();
}
@Override
public void setMaxIdleTime(Duration maxIdleTime) {
this.session.setMaxIdleTime(maxIdleTime);
}
@Override
public Duration getMaxIdleTime() {
return this.session.getMaxIdleTime();
}
}
}
}
/**
* Configures HTTPS redirection rules
*
@@ -2523,11 +2213,6 @@ public class ServerHttpSecurity {
private ServerAuthenticationFailureHandler authenticationFailureHandler;
private final List<ServerAuthenticationSuccessHandler> defaultSuccessHandlers = new ArrayList<>(
List.of(new WebFilterChainServerAuthenticationSuccessHandler()));
private List<ServerAuthenticationSuccessHandler> authenticationSuccessHandlers = new ArrayList<>();
private HttpBasicSpec() {
List<DelegateEntry> entryPoints = new ArrayList<>();
entryPoints
@@ -2538,40 +2223,6 @@ public class ServerHttpSecurity {
this.entryPoint = defaultEntryPoint;
}
/**
* The {@link ServerAuthenticationSuccessHandler} used after authentication
* success. Defaults to {@link WebFilterChainServerAuthenticationSuccessHandler}.
* Note that this method clears previously added success handlers via
* {@link #authenticationSuccessHandler(Consumer)}
* @param authenticationSuccessHandler the success handler to use
* @return the {@link HttpBasicSpec} to continue configuring
* @since 6.3
*/
public HttpBasicSpec authenticationSuccessHandler(
ServerAuthenticationSuccessHandler authenticationSuccessHandler) {
Assert.notNull(authenticationSuccessHandler, "authenticationSuccessHandler cannot be null");
authenticationSuccessHandler((handlers) -> {
handlers.clear();
handlers.add(authenticationSuccessHandler);
});
return this;
}
/**
* Allows customizing the list of {@link ServerAuthenticationSuccessHandler}. The
* default list contains a
* {@link WebFilterChainServerAuthenticationSuccessHandler}.
* @param handlersConsumer the handlers consumer
* @return the {@link HttpBasicSpec} to continue configuring
* @since 6.3
*/
public HttpBasicSpec authenticationSuccessHandler(
Consumer<List<ServerAuthenticationSuccessHandler>> handlersConsumer) {
Assert.notNull(handlersConsumer, "handlersConsumer cannot be null");
handlersConsumer.accept(this.authenticationSuccessHandlers);
return this;
}
/**
* The {@link ReactiveAuthenticationManager} used to authenticate. Defaults to
* {@link ServerHttpSecurity#authenticationManager(ReactiveAuthenticationManager)}.
@@ -2657,17 +2308,9 @@ public class ServerHttpSecurity {
authenticationFilter.setAuthenticationFailureHandler(authenticationFailureHandler());
authenticationFilter.setAuthenticationConverter(new ServerHttpBasicAuthenticationConverter());
authenticationFilter.setSecurityContextRepository(this.securityContextRepository);
authenticationFilter.setAuthenticationSuccessHandler(getAuthenticationSuccessHandler(http));
http.addFilterAt(authenticationFilter, SecurityWebFiltersOrder.HTTP_BASIC);
}
private ServerAuthenticationSuccessHandler getAuthenticationSuccessHandler(ServerHttpSecurity http) {
if (this.authenticationSuccessHandlers.isEmpty()) {
return new DelegatingServerAuthenticationSuccessHandler(this.defaultSuccessHandlers);
}
return new DelegatingServerAuthenticationSuccessHandler(this.authenticationSuccessHandlers);
}
private ServerAuthenticationFailureHandler authenticationFailureHandler() {
if (this.authenticationFailureHandler != null) {
return this.authenticationFailureHandler;
@@ -2739,9 +2382,6 @@ public class ServerHttpSecurity {
private final RedirectServerAuthenticationSuccessHandler defaultSuccessHandler = new RedirectServerAuthenticationSuccessHandler(
"/");
private final List<ServerAuthenticationSuccessHandler> defaultSuccessHandlers = new ArrayList<>(
List.of(this.defaultSuccessHandler));
private RedirectServerAuthenticationEntryPoint defaultEntryPoint;
private ReactiveAuthenticationManager authenticationManager;
@@ -2756,7 +2396,7 @@ public class ServerHttpSecurity {
private ServerAuthenticationFailureHandler authenticationFailureHandler;
private List<ServerAuthenticationSuccessHandler> authenticationSuccessHandlers = new ArrayList<>();
private ServerAuthenticationSuccessHandler authenticationSuccessHandler = this.defaultSuccessHandler;
private FormLoginSpec() {
}
@@ -2774,34 +2414,14 @@ public class ServerHttpSecurity {
/**
* The {@link ServerAuthenticationSuccessHandler} used after authentication
* success. Defaults to {@link RedirectServerAuthenticationSuccessHandler}. Note
* that this method clears previously added success handlers via
* {@link #authenticationSuccessHandler(Consumer)}
* success. Defaults to {@link RedirectServerAuthenticationSuccessHandler}.
* @param authenticationSuccessHandler the success handler to use
* @return the {@link FormLoginSpec} to continue configuring
*/
public FormLoginSpec authenticationSuccessHandler(
ServerAuthenticationSuccessHandler authenticationSuccessHandler) {
Assert.notNull(authenticationSuccessHandler, "authenticationSuccessHandler cannot be null");
authenticationSuccessHandler((handlers) -> {
handlers.clear();
handlers.add(authenticationSuccessHandler);
});
return this;
}
/**
* Allows customizing the list of {@link ServerAuthenticationSuccessHandler}. The
* default list contains a {@link RedirectServerAuthenticationSuccessHandler} that
* redirects to "/".
* @param handlersConsumer the handlers consumer
* @return the {@link FormLoginSpec} to continue configuring
* @since 6.3
*/
public FormLoginSpec authenticationSuccessHandler(
Consumer<List<ServerAuthenticationSuccessHandler>> handlersConsumer) {
Assert.notNull(handlersConsumer, "handlersConsumer cannot be null");
handlersConsumer.accept(this.authenticationSuccessHandlers);
this.authenticationSuccessHandler = authenticationSuccessHandler;
return this;
}
@@ -2934,18 +2554,11 @@ public class ServerHttpSecurity {
authenticationFilter.setRequiresAuthenticationMatcher(this.requiresAuthenticationMatcher);
authenticationFilter.setAuthenticationFailureHandler(this.authenticationFailureHandler);
authenticationFilter.setAuthenticationConverter(new ServerFormLoginAuthenticationConverter());
authenticationFilter.setAuthenticationSuccessHandler(getAuthenticationSuccessHandler(http));
authenticationFilter.setAuthenticationSuccessHandler(this.authenticationSuccessHandler);
authenticationFilter.setSecurityContextRepository(this.securityContextRepository);
http.addFilterAt(authenticationFilter, SecurityWebFiltersOrder.FORM_LOGIN);
}
private ServerAuthenticationSuccessHandler getAuthenticationSuccessHandler(ServerHttpSecurity http) {
if (this.authenticationSuccessHandlers.isEmpty()) {
return new DelegatingServerAuthenticationSuccessHandler(this.defaultSuccessHandlers);
}
return new DelegatingServerAuthenticationSuccessHandler(this.authenticationSuccessHandlers);
}
}
private final class LoginPageSpec {
@@ -4124,12 +3737,7 @@ public class ServerHttpSecurity {
private ReactiveOidcSessionRegistry oidcSessionRegistry;
private final RedirectServerAuthenticationSuccessHandler defaultAuthenticationSuccessHandler = new RedirectServerAuthenticationSuccessHandler();
private final List<ServerAuthenticationSuccessHandler> defaultSuccessHandlers = new ArrayList<>(
List.of(this.defaultAuthenticationSuccessHandler));
private List<ServerAuthenticationSuccessHandler> authenticationSuccessHandlers = new ArrayList<>();
private ServerAuthenticationSuccessHandler authenticationSuccessHandler;
private ServerAuthenticationFailureHandler authenticationFailureHandler;
@@ -4177,8 +3785,7 @@ public class ServerHttpSecurity {
/**
* The {@link ServerAuthenticationSuccessHandler} used after authentication
* success. Defaults to {@link RedirectServerAuthenticationSuccessHandler}
* redirecting to "/". Note that this method clears previously added success
* handlers via {@link #authenticationSuccessHandler(Consumer)}
* redirecting to "/".
* @param authenticationSuccessHandler the success handler to use
* @return the {@link OAuth2LoginSpec} to customize
* @since 5.2
@@ -4186,25 +3793,7 @@ public class ServerHttpSecurity {
public OAuth2LoginSpec authenticationSuccessHandler(
ServerAuthenticationSuccessHandler authenticationSuccessHandler) {
Assert.notNull(authenticationSuccessHandler, "authenticationSuccessHandler cannot be null");
authenticationSuccessHandler((handlers) -> {
handlers.clear();
handlers.add(authenticationSuccessHandler);
});
return this;
}
/**
* Allows customizing the list of {@link ServerAuthenticationSuccessHandler}. The
* default list contains a {@link RedirectServerAuthenticationSuccessHandler} that
* redirects to "/".
* @param handlersConsumer the handlers consumer
* @return the {@link OAuth2LoginSpec} to continue configuring
* @since 6.3
*/
public OAuth2LoginSpec authenticationSuccessHandler(
Consumer<List<ServerAuthenticationSuccessHandler>> handlersConsumer) {
Assert.notNull(handlersConsumer, "handlersConsumer cannot be null");
handlersConsumer.accept(this.authenticationSuccessHandlers);
this.authenticationSuccessHandler = authenticationSuccessHandler;
return this;
}
@@ -4461,11 +4050,12 @@ public class ServerHttpSecurity {
}
private ServerAuthenticationSuccessHandler getAuthenticationSuccessHandler(ServerHttpSecurity http) {
this.defaultAuthenticationSuccessHandler.setRequestCache(http.requestCache.requestCache);
if (this.authenticationSuccessHandlers.isEmpty()) {
return new DelegatingServerAuthenticationSuccessHandler(this.defaultSuccessHandlers);
if (this.authenticationSuccessHandler == null) {
RedirectServerAuthenticationSuccessHandler handler = new RedirectServerAuthenticationSuccessHandler();
handler.setRequestCache(http.requestCache.requestCache);
this.authenticationSuccessHandler = handler;
}
return new DelegatingServerAuthenticationSuccessHandler(this.authenticationSuccessHandlers);
return this.authenticationSuccessHandler;
}
private ServerAuthenticationFailureHandler getAuthenticationFailureHandler() {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2022 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.
@@ -26,7 +26,6 @@ import org.springframework.security.config.annotation.web.configurers.AuthorizeH
import org.springframework.security.core.Authentication
import org.springframework.security.web.access.intercept.AuthorizationFilter
import org.springframework.security.web.access.intercept.RequestAuthorizationContext
import org.springframework.security.web.access.IpAddressAuthorizationManager
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher
import org.springframework.security.web.util.matcher.AnyRequestMatcher
import org.springframework.security.web.util.matcher.RequestMatcher
@@ -223,13 +222,6 @@ class AuthorizeHttpRequestsDsl : AbstractRequestMatcherDsl() {
return AuthorityAuthorizationManager.hasAnyRole(*roles)
}
/**
* Require a specific IP or range of IP addresses.
* @since 6.3
*/
fun hasIpAddress(ipAddress: String): AuthorizationManager<RequestAuthorizationContext> =
IpAddressAuthorizationManager.hasIpAddress(ipAddress)
/**
* Specify that URLs are allowed by anyone.
*/
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -38,8 +38,6 @@ import jakarta.servlet.http.HttpServletRequest
* @property loginProcessingUrl the URL to validate the credentials
* @property permitAll whether to grant access to the urls for [failureUrl] as well as
* for the [HttpSecurityBuilder], the [loginPage] and [loginProcessingUrl] for every user
* @property usernameParameter the HTTP parameter to look for the username when performing authentication
* @property passwordParameter the HTTP parameter to look for the password when performing authentication
*/
@SecurityMarker
class FormLoginDsl {
@@ -50,8 +48,6 @@ class FormLoginDsl {
var loginProcessingUrl: String? = null
var permitAll: Boolean? = null
var authenticationDetailsSource: AuthenticationDetailsSource<HttpServletRequest, *>? = null
var usernameParameter: String? = null
var passwordParameter: String? = null
private var defaultSuccessUrlOption: Pair<String, Boolean>? = null
@@ -99,8 +95,6 @@ class FormLoginDsl {
authenticationSuccessHandler?.also { login.successHandler(authenticationSuccessHandler) }
authenticationFailureHandler?.also { login.failureHandler(authenticationFailureHandler) }
authenticationDetailsSource?.also { login.authenticationDetailsSource(authenticationDetailsSource) }
usernameParameter?.also { login.usernameParameter(usernameParameter) }
passwordParameter?.also { login.passwordParameter(passwordParameter) }
if (disabled) {
login.disable()
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 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.
@@ -707,69 +707,6 @@ class HttpSecurityDsl(private val http: HttpSecurity, private val init: HttpSecu
this.http.saml2Login(saml2LoginCustomizer)
}
/**
* Configures logout support for a SAML 2.0 Service Provider. <br>
* <br>
*
* Implements the <b>Single Logout Profile, using POST and REDIRECT bindings</b>, as
* documented in the
* <a target="_blank" href="https://docs.oasis-open.org/security/saml/">SAML V2.0
* Core, Profiles and Bindings</a> specifications. <br>
* <br>
*
* As a prerequisite to using this feature, is that you have a SAML v2.0 Asserting
* Party to send a logout request to. The representation of the relying party and the
* asserting party is contained within [RelyingPartyRegistration]. <br>
* <br>
*
* [RelyingPartyRegistration] (s) are composed within a
* [RelyingPartyRegistrationRepository], which is <b>required</b> and must be
* registered with the [ApplicationContext] or configured via
* [HttpSecurityDsl.saml2Login].<br>
* <br>
*
* The default configuration provides an auto-generated logout endpoint at
* `/logout` and redirects to `/login?logout` when
* logout completes. <br>
* <br>
*
* <p>
* <h2>Example Configuration</h2>
*
* The following example shows the minimal configuration required, using a
* hypothetical asserting party.
*
* Example:
*
* ```
* @Configuration
* @EnableWebSecurity
* class SecurityConfig {
*
* @Bean
* fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
* http {
* saml2Login {
* relyingPartyRegistration = getSaml2RelyingPartyRegistration()
* }
* saml2Logout { }
* }
* return http.build()
* }
* }
* ```
*
* <p>
* @param saml2LogoutConfiguration custom configuration to configure the
* SAML 2.0 service provider
* @since 6.3
* @see [Saml2LogoutDsl]
*/
fun saml2Logout(saml2LogoutConfiguration: Saml2LogoutDsl.() -> Unit) {
val saml2LogoutCustomizer = Saml2LogoutDsl().apply(saml2LogoutConfiguration).get()
this.http.saml2Logout(saml2LogoutCustomizer)
}
/**
* Configures a SAML 2.0 relying party metadata endpoint.
*
@@ -1,69 +0,0 @@
/*
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.saml2.Saml2LogoutConfigurer
import org.springframework.security.config.annotation.web.saml2.LogoutRequestDsl
import org.springframework.security.config.annotation.web.saml2.LogoutResponseDsl
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository
/**
* A Kotlin DSL to configure [HttpSecurity] SAML2 logout using idiomatic Kotlin code.
*
* @author Josh Cummings
* @since 6.3
* @property relyingPartyRegistrationRepository the [RelyingPartyRegistrationRepository] of relying parties,
* each party representing a service provider, SP and this host, and identity provider, IDP pair that
* communicate with each other.
* @property logoutUrl the logout page to begin the SLO redirect flow
*/
@SecurityMarker
class Saml2LogoutDsl {
var relyingPartyRegistrationRepository: RelyingPartyRegistrationRepository? = null
var logoutUrl: String? = null
private var logoutRequest: ((Saml2LogoutConfigurer<HttpSecurity>.LogoutRequestConfigurer) -> Unit)? = null
private var logoutResponse: ((Saml2LogoutConfigurer<HttpSecurity>.LogoutResponseConfigurer) -> Unit)? = null
/**
* Configures SAML 2.0 Logout Request components
* @param logoutRequestConfig the {@link Customizer} to provide more
* options for the {@link LogoutRequestConfigurer}
*/
fun logoutRequest(logoutRequestConfig: LogoutRequestDsl.() -> Unit) {
this.logoutRequest = LogoutRequestDsl().apply(logoutRequestConfig).get()
}
/**
* Configures SAML 2.0 Logout Response components
* @param logoutResponseConfig the {@link Customizer} to provide more
* options for the {@link LogoutResponseConfigurer}
*/
fun logoutResponse(logoutResponseConfig: LogoutResponseDsl.() -> Unit) {
this.logoutResponse = LogoutResponseDsl().apply(logoutResponseConfig).get()
}
internal fun get(): (Saml2LogoutConfigurer<HttpSecurity>) -> Unit {
return { saml2Logout ->
relyingPartyRegistrationRepository?.also { saml2Logout.relyingPartyRegistrationRepository(relyingPartyRegistrationRepository) }
logoutUrl?.also { saml2Logout.logoutUrl(logoutUrl) }
logoutRequest?.also { saml2Logout.logoutRequest(logoutRequest) }
logoutResponse?.also { saml2Logout.logoutResponse(logoutResponse) }
}
}
}
@@ -1,53 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.saml2
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.saml2.Saml2LogoutConfigurer
import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequestValidator
import org.springframework.security.saml2.provider.service.web.authentication.logout.HttpSessionLogoutRequestRepository
import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestRepository
import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestResolver
/**
* A Kotlin DSL to configure SAML 2.0 Logout Request components using idiomatic Kotlin code.
*
* @author Josh Cummings
* @since 6.3
* @property logoutUrl The URL by which the asserting party can send a SAML 2.0 Logout Request.
* The Asserting Party should use whatever HTTP method specified in {@link RelyingPartyRegistration#getSingleLogoutServiceBindings()}.
* @property logoutRequestValidator the [Saml2LogoutRequestValidator] to use for validating incoming {@code LogoutRequest}s.
* @property logoutRequestResolver the [Saml2LogoutRequestResolver] to use for generating outgoing {@code LogoutRequest}s.
* @property logoutRequestRepository the [Saml2LogoutRequestRepository] to use for storing outgoing {@code LogoutRequest}s for
* linking to the corresponding {@code LogoutResponse} from the asserting party
*/
@Saml2SecurityMarker
class LogoutRequestDsl {
var logoutUrl = "/logout/saml2/slo"
var logoutRequestValidator: Saml2LogoutRequestValidator? = null
var logoutRequestResolver: Saml2LogoutRequestResolver? = null
var logoutRequestRepository: Saml2LogoutRequestRepository = HttpSessionLogoutRequestRepository()
internal fun get(): (Saml2LogoutConfigurer<HttpSecurity>.LogoutRequestConfigurer) -> Unit {
return { logoutRequest ->
logoutUrl.also { logoutRequest.logoutUrl(logoutUrl) }
logoutRequestValidator?.also { logoutRequest.logoutRequestValidator(logoutRequestValidator) }
logoutRequestResolver?.also { logoutRequest.logoutRequestResolver(logoutRequestResolver) }
logoutRequestRepository.also { logoutRequest.logoutRequestRepository(logoutRequestRepository) }
}
}
}
@@ -1,47 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.saml2
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.saml2.Saml2LogoutConfigurer
import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutResponseValidator
import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutResponseResolver
/**
* A Kotlin DSL to configure SAML 2.0 Logout Response components using idiomatic Kotlin code.
*
* @author Josh Cummings
* @since 6.3
* @property logoutUrl The URL by which the asserting party can send a SAML 2.0 Logout Response.
* The Asserting Party should use whatever HTTP method specified in {@link RelyingPartyRegistration#getSingleLogoutServiceBindings()}.
* @property logoutResponseValidator the [Saml2LogoutResponseValidator] to use for validating incoming {@code LogoutResponse}s.
* @property logoutResponseResolver the [Saml2LogoutResponseResolver] to use for generating outgoing {@code LogoutResponse}s.
*/
@Saml2SecurityMarker
class LogoutResponseDsl {
var logoutUrl = "/logout/saml2/slo"
var logoutResponseValidator: Saml2LogoutResponseValidator? = null
var logoutResponseResolver: Saml2LogoutResponseResolver? = null
internal fun get(): (Saml2LogoutConfigurer<HttpSecurity>.LogoutResponseConfigurer) -> Unit {
return { logoutResponse ->
logoutUrl.also { logoutResponse.logoutUrl(logoutUrl) }
logoutResponseValidator?.also { logoutResponse.logoutResponseValidator(logoutResponseValidator) }
logoutResponseResolver?.also { logoutResponse.logoutResponseResolver(logoutResponseResolver) }
}
}
}
@@ -1,26 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.saml2
/**
* Marker annotation indicating that the annotated class is part of the SAML 2.0 logout security DSL.
*
* @author Josh Cummings
* @since 6.3
*/
@DslMarker
annotation class Saml2SecurityMarker
@@ -682,36 +682,6 @@ class ServerHttpSecurityDsl(private val http: ServerHttpSecurity, private val in
this.http.oidcLogout(oidcLogoutCustomizer)
}
/**
* Configures Session Management support.
*
* Example:
*
* ```
* @Configuration
* @EnableWebFluxSecurity
* open class SecurityConfig {
*
* @Bean
* open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
* return http {
* sessionManagement {
* sessionConcurrency { }
* }
* }
* }
* }
* ```
*
* @param sessionManagementConfig custom configuration to configure the Session Management
* @since 6.3
* @see [ServerSessionManagementDsl]
*/
fun sessionManagement(sessionManagementConfig: ServerSessionManagementDsl.() -> Unit) {
val sessionManagementCustomizer = ServerSessionManagementDsl().apply(sessionManagementConfig).get()
this.http.sessionManagement(sessionManagementCustomizer)
}
/**
* Apply all configurations to the provided [ServerHttpSecurity]
*/
@@ -1,48 +0,0 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.web.server
import org.springframework.security.core.session.ReactiveSessionRegistry
import org.springframework.security.web.server.authentication.ServerMaximumSessionsExceededHandler
import org.springframework.security.web.server.authentication.SessionLimit
/**
* A Kotlin DSL to configure [ServerHttpSecurity] Session Concurrency support using idiomatic Kotlin code.
*
* @author Marcus da Coregio
* @since 6.3
*/
@ServerSecurityMarker
class ServerSessionConcurrencyDsl {
var maximumSessions: SessionLimit? = null
var maximumSessionsExceededHandler: ServerMaximumSessionsExceededHandler? = null
var sessionRegistry: ReactiveSessionRegistry? = null
internal fun get(): (ServerHttpSecurity.SessionManagementSpec.ConcurrentSessionsSpec) -> Unit {
return { sessionConcurrency ->
maximumSessions?.also {
sessionConcurrency.maximumSessions(maximumSessions!!)
}
maximumSessionsExceededHandler?.also {
sessionConcurrency.maximumSessionsExceededHandler(maximumSessionsExceededHandler!!)
}
sessionRegistry?.also {
sessionConcurrency.sessionRegistry(sessionRegistry!!)
}
}
}
}
@@ -1,64 +0,0 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.web.server
/**
* A Kotlin DSL to configure [ServerHttpSecurity] Session Management using idiomatic Kotlin code.
*
* @author Marcus da Coregio
* @since 6.3
*/
@ServerSecurityMarker
class ServerSessionManagementDsl {
private var sessionConcurrency: ((ServerHttpSecurity.SessionManagementSpec.ConcurrentSessionsSpec) -> Unit)? = null
/**
* Enables Session Management support.
*
* Example:
*
* ```
* @Configuration
* @EnableWebFluxSecurity
* open class SecurityConfig {
*
* @Bean
* open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
* return http {
* sessionManagement {
* sessionConcurrency {
* maximumSessions = { authentication -> Mono.just(1) }
* }
* }
* }
* }
* }
* ```
*
* @param backChannelConfig custom configurations to configure OIDC 1.0 Back-Channel Logout support
* @see [ServerOidcBackChannelLogoutDsl]
*/
fun sessionConcurrency(sessionConcurrencyConfig: ServerSessionConcurrencyDsl.() -> Unit) {
this.sessionConcurrency = ServerSessionConcurrencyDsl().apply(sessionConcurrencyConfig).get()
}
internal fun get(): (ServerHttpSecurity.SessionManagementSpec) -> Unit {
return { sessionManagement ->
sessionConcurrency?.also { sessionManagement.concurrentSessions(sessionConcurrency) }
}
}
}
@@ -1,5 +1,4 @@
http\://www.springframework.org/schema/security/spring-security.xsd=org/springframework/security/config/spring-security-6.3.xsd
http\://www.springframework.org/schema/security/spring-security-6.3.xsd=org/springframework/security/config/spring-security-6.3.xsd
http\://www.springframework.org/schema/security/spring-security.xsd=org/springframework/security/config/spring-security-6.2.xsd
http\://www.springframework.org/schema/security/spring-security-6.2.xsd=org/springframework/security/config/spring-security-6.2.xsd
http\://www.springframework.org/schema/security/spring-security-6.1.xsd=org/springframework/security/config/spring-security-6.1.xsd
http\://www.springframework.org/schema/security/spring-security-6.0.xsd=org/springframework/security/config/spring-security-6.0.xsd
@@ -23,8 +22,7 @@ http\://www.springframework.org/schema/security/spring-security-2.0.xsd=org/spri
http\://www.springframework.org/schema/security/spring-security-2.0.1.xsd=org/springframework/security/config/spring-security-2.0.1.xsd
http\://www.springframework.org/schema/security/spring-security-2.0.2.xsd=org/springframework/security/config/spring-security-2.0.2.xsd
http\://www.springframework.org/schema/security/spring-security-2.0.4.xsd=org/springframework/security/config/spring-security-2.0.4.xsd
https\://www.springframework.org/schema/security/spring-security.xsd=org/springframework/security/config/spring-security-6.3.xsd
https\://www.springframework.org/schema/security/spring-security-6.3.xsd=org/springframework/security/config/spring-security-6.3.xsd
https\://www.springframework.org/schema/security/spring-security.xsd=org/springframework/security/config/spring-security-6.2.xsd
https\://www.springframework.org/schema/security/spring-security-6.2.xsd=org/springframework/security/config/spring-security-6.2.xsd
https\://www.springframework.org/schema/security/spring-security-6.1.xsd=org/springframework/security/config/spring-security-6.1.xsd
https\://www.springframework.org/schema/security/spring-security-6.0.xsd=org/springframework/security/config/spring-security-6.0.xsd
@@ -248,6 +248,7 @@ class SpringSecurityCoreVersionSerializableTests {
@ParameterizedTest
@MethodSource("getFilesToDeserialize")
@Disabled("The feature is only supported for versions >= 6.3")
void shouldBeAbleToDeserializeClassFromPreviousVersion(Path filePath) {
try (FileInputStream fileInputStream = new FileInputStream(filePath.toFile());
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream)) {
@@ -1,137 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.method.configuration;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authentication.TestAuthentication;
import org.springframework.security.authorization.AuthorizationProxyFactory;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Tests for {@link PrePostMethodSecurityConfiguration}.
*
* @author Evgeniy Cheban
* @author Josh Cummings
*/
@ExtendWith({ SpringExtension.class, SpringTestContextExtension.class })
@SecurityTestExecutionListeners
public class AuthorizationProxyConfigurationTests {
public final SpringTestContext spring = new SpringTestContext(this);
@Autowired
AuthorizationProxyFactory proxyFactory;
@WithMockUser
@Test
public void proxyWhenNotPreAuthorizedThenDenies() {
this.spring.register(DefaultsConfig.class).autowire();
Toaster toaster = (Toaster) this.proxyFactory.proxy(new Toaster());
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(toaster::makeToast)
.withMessage("Access Denied");
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(toaster::extractBread)
.withMessage("Access Denied");
}
@WithMockUser(roles = "ADMIN")
@Test
public void proxyWhenPreAuthorizedThenAllows() {
this.spring.register(DefaultsConfig.class).autowire();
Toaster toaster = (Toaster) this.proxyFactory.proxy(new Toaster());
toaster.makeToast();
assertThat(toaster.extractBread()).isEqualTo("yummy");
}
@Test
public void proxyReactiveWhenNotPreAuthorizedThenDenies() {
this.spring.register(ReactiveDefaultsConfig.class).autowire();
Toaster toaster = (Toaster) this.proxyFactory.proxy(new Toaster());
Authentication user = TestAuthentication.authenticatedUser();
StepVerifier
.create(toaster.reactiveMakeToast().contextWrite(ReactiveSecurityContextHolder.withAuthentication(user)))
.verifyError(AccessDeniedException.class);
StepVerifier
.create(toaster.reactiveExtractBread().contextWrite(ReactiveSecurityContextHolder.withAuthentication(user)))
.verifyError(AccessDeniedException.class);
}
@Test
public void proxyReactiveWhenPreAuthorizedThenAllows() {
this.spring.register(ReactiveDefaultsConfig.class).autowire();
Toaster toaster = (Toaster) this.proxyFactory.proxy(new Toaster());
Authentication admin = TestAuthentication.authenticatedAdmin();
StepVerifier
.create(toaster.reactiveMakeToast().contextWrite(ReactiveSecurityContextHolder.withAuthentication(admin)))
.expectNext()
.verifyComplete();
}
@EnableMethodSecurity
@Configuration
static class DefaultsConfig {
}
@EnableReactiveMethodSecurity
@Configuration
static class ReactiveDefaultsConfig {
}
static class Toaster {
@PreAuthorize("hasRole('ADMIN')")
void makeToast() {
}
@PostAuthorize("hasRole('ADMIN')")
String extractBread() {
return "yummy";
}
@PreAuthorize("hasRole('ADMIN')")
Mono<Void> reactiveMakeToast() {
return Mono.empty();
}
@PostAuthorize("hasRole('ADMIN')")
Mono<String> reactiveExtractBread() {
return Mono.just("yummy");
}
}
}
@@ -18,8 +18,6 @@ package org.springframework.security.config.annotation.method.configuration;
import reactor.core.publisher.Mono;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
@@ -47,20 +45,4 @@ public class Authz {
return message != null && message.contains(authentication.getName());
}
public AuthorizationResult checkResult(boolean result) {
return new AuthzResult(result);
}
public Mono<AuthorizationResult> checkReactiveResult(boolean result) {
return Mono.just(checkResult(result));
}
public static class AuthzResult extends AuthorizationDecision {
public AuthzResult(boolean granted) {
super(granted);
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 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,42 +16,23 @@
package org.springframework.security.config.annotation.method.configuration;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.List;
import jakarta.annotation.security.DenyAll;
import jakarta.annotation.security.PermitAll;
import jakarta.annotation.security.RolesAllowed;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PostFilter;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.access.prepost.PreFilter;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.authorization.method.AuthorizeReturnObject;
import org.springframework.security.authorization.method.HandleAuthorizationDenied;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
import org.springframework.security.authorization.method.MethodInvocationResult;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.parameters.P;
import org.springframework.util.StringUtils;
/**
* @author Rob Winch
*/
@MethodSecurityService.Mask("classmask")
public interface MethodSecurityService {
@PreAuthorize("denyAll")
@@ -72,9 +53,6 @@ public interface MethodSecurityService {
@RolesAllowed("ADMIN")
String jsr250RolesAllowed();
@RolesAllowed("USER")
String jsr250RolesAllowedUser();
@Secured({ "ROLE_USER", "RUN_AS_SUPER" })
Authentication runAs();
@@ -90,9 +68,6 @@ public interface MethodSecurityService {
@PreAuthorize("hasRole('ADMIN')")
void preAuthorizeAdmin();
@PreAuthorize("hasRole('USER')")
void preAuthorizeUser();
@PreAuthorize("hasPermission(#object,'read')")
String hasPermission(String object);
@@ -115,248 +90,8 @@ public interface MethodSecurityService {
@PostAuthorize("returnObject.size == 2")
List<String> manyAnnotations(List<String> array);
@PreFilter("filterObject != 'DropOnPreFilter'")
@PreAuthorize("#list.remove('DropOnPreAuthorize')")
@Secured("ROLE_SECURED")
@RolesAllowed("JSR250")
@PostAuthorize("#list.remove('DropOnPostAuthorize')")
@PostFilter("filterObject != 'DropOnPostFilter'")
List<String> allAnnotations(List<String> list);
@RequireUserRole
@RequireAdminRole
void repeatedAnnotations();
@PreAuthorize("hasRole('ADMIN')")
@HandleAuthorizationDenied(handlerClass = StarMaskingHandler.class)
String preAuthorizeGetCardNumberIfAdmin(String cardNumber);
@PreAuthorize("hasRole('ADMIN')")
@HandleAuthorizationDenied(handlerClass = StartMaskingHandlerChild.class)
String preAuthorizeWithHandlerChildGetCardNumberIfAdmin(String cardNumber);
@PreAuthorize("hasRole('ADMIN')")
@HandleAuthorizationDenied(handlerClass = StarMaskingHandler.class)
String preAuthorizeThrowAccessDeniedManually();
@PostAuthorize("hasRole('ADMIN')")
@HandleAuthorizationDenied(handlerClass = CardNumberMaskingPostProcessor.class)
String postAuthorizeGetCardNumberIfAdmin(String cardNumber);
@PostAuthorize("hasRole('ADMIN')")
@HandleAuthorizationDenied(handlerClass = PostMaskingPostProcessor.class)
String postAuthorizeThrowAccessDeniedManually();
@PreAuthorize("denyAll()")
@Mask("methodmask")
@HandleAuthorizationDenied(handlerClass = MaskAnnotationHandler.class)
String preAuthorizeDeniedMethodWithMaskAnnotation();
@PreAuthorize("denyAll()")
@HandleAuthorizationDenied(handlerClass = MaskAnnotationHandler.class)
String preAuthorizeDeniedMethodWithNoMaskAnnotation();
@NullDenied(role = "ADMIN")
String postAuthorizeDeniedWithNullDenied();
@PostAuthorize("denyAll()")
@Mask("methodmask")
@HandleAuthorizationDenied(handlerClass = MaskAnnotationPostProcessor.class)
String postAuthorizeDeniedMethodWithMaskAnnotation();
@PostAuthorize("denyAll()")
@HandleAuthorizationDenied(handlerClass = MaskAnnotationPostProcessor.class)
String postAuthorizeDeniedMethodWithNoMaskAnnotation();
@PreAuthorize("hasRole('ADMIN')")
@Mask(expression = "@myMasker.getMask()")
@HandleAuthorizationDenied(handlerClass = MaskAnnotationHandler.class)
String preAuthorizeWithMaskAnnotationUsingBean();
@PostAuthorize("hasRole('ADMIN')")
@Mask(expression = "@myMasker.getMask(returnObject)")
@HandleAuthorizationDenied(handlerClass = MaskAnnotationPostProcessor.class)
String postAuthorizeWithMaskAnnotationUsingBean();
@AuthorizeReturnObject
UserRecordWithEmailProtected getUserRecordWithEmailProtected();
@PreAuthorize("hasRole('ADMIN')")
@HandleAuthorizationDenied(handlerClass = UserFallbackDeniedHandler.class)
UserRecordWithEmailProtected getUserWithFallbackWhenUnauthorized();
@PreAuthorize("@authz.checkResult(#result)")
@PostAuthorize("@authz.checkResult(!#result)")
@HandleAuthorizationDenied(handlerClass = MethodAuthorizationDeniedHandler.class)
String checkCustomResult(boolean result);
class StarMaskingHandler implements MethodAuthorizationDeniedHandler {
@Override
public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult result) {
return "***";
}
}
class StartMaskingHandlerChild extends StarMaskingHandler {
@Override
public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult result) {
return super.handleDeniedInvocation(methodInvocation, result) + "-child";
}
}
class MaskAnnotationHandler implements MethodAuthorizationDeniedHandler {
MaskValueResolver maskValueResolver;
MaskAnnotationHandler(ApplicationContext context) {
this.maskValueResolver = new MaskValueResolver(context);
}
public Object handle(MethodInvocation methodInvocation, AuthorizationResult result) {
Mask mask = AnnotationUtils.getAnnotation(methodInvocation.getMethod(), Mask.class);
if (mask == null) {
mask = AnnotationUtils.getAnnotation(methodInvocation.getMethod().getDeclaringClass(), Mask.class);
}
return this.maskValueResolver.resolveValue(mask, methodInvocation, null);
}
@Override
public Object handleDeniedInvocation(MethodInvocation methodInvocation,
AuthorizationResult authorizationResult) {
return handle(methodInvocation, authorizationResult);
}
}
class MaskAnnotationPostProcessor implements MethodAuthorizationDeniedHandler {
MaskValueResolver maskValueResolver;
MaskAnnotationPostProcessor(ApplicationContext context) {
this.maskValueResolver = new MaskValueResolver(context);
}
@Override
public Object handleDeniedInvocation(MethodInvocation mi, AuthorizationResult authorizationResult) {
Mask mask = AnnotationUtils.getAnnotation(mi.getMethod(), Mask.class);
if (mask == null) {
mask = AnnotationUtils.getAnnotation(mi.getMethod().getDeclaringClass(), Mask.class);
}
return this.maskValueResolver.resolveValue(mask, mi, null);
}
@Override
public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult,
AuthorizationResult authorizationResult) {
MethodInvocation mi = methodInvocationResult.getMethodInvocation();
Mask mask = AnnotationUtils.getAnnotation(mi.getMethod(), Mask.class);
if (mask == null) {
mask = AnnotationUtils.getAnnotation(mi.getMethod().getDeclaringClass(), Mask.class);
}
return this.maskValueResolver.resolveValue(mask, mi, methodInvocationResult.getResult());
}
}
class MaskValueResolver {
DefaultMethodSecurityExpressionHandler expressionHandler;
MaskValueResolver(ApplicationContext context) {
this.expressionHandler = new DefaultMethodSecurityExpressionHandler();
this.expressionHandler.setApplicationContext(context);
}
String resolveValue(Mask mask, MethodInvocation mi, Object returnObject) {
if (StringUtils.hasText(mask.value())) {
return mask.value();
}
Expression expression = this.expressionHandler.getExpressionParser().parseExpression(mask.expression());
EvaluationContext evaluationContext = this.expressionHandler
.createEvaluationContext(() -> SecurityContextHolder.getContext().getAuthentication(), mi);
if (returnObject != null) {
this.expressionHandler.setReturnObject(returnObject, evaluationContext);
}
return expression.getValue(evaluationContext, String.class);
}
}
class PostMaskingPostProcessor implements MethodAuthorizationDeniedHandler {
@Override
public Object handleDeniedInvocation(MethodInvocation methodInvocation,
AuthorizationResult authorizationResult) {
return "***";
}
}
class CardNumberMaskingPostProcessor implements MethodAuthorizationDeniedHandler {
static String MASK = "****-****-****-";
@Override
public Object handleDeniedInvocation(MethodInvocation methodInvocation,
AuthorizationResult authorizationResult) {
return "***";
}
@Override
public Object handleDeniedInvocationResult(MethodInvocationResult contextObject, AuthorizationResult result) {
String cardNumber = (String) contextObject.getResult();
return MASK + cardNumber.substring(cardNumber.length() - 4);
}
}
class NullPostProcessor implements MethodAuthorizationDeniedHandler {
@Override
public Object handleDeniedInvocation(MethodInvocation methodInvocation,
AuthorizationResult authorizationResult) {
return null;
}
}
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@interface Mask {
String value() default "";
String expression() default "";
}
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@PostAuthorize("hasRole('{role}')")
@HandleAuthorizationDenied(handlerClass = NullPostProcessor.class)
@interface NullDenied {
String role();
}
class UserFallbackDeniedHandler implements MethodAuthorizationDeniedHandler {
private static final UserRecordWithEmailProtected FALLBACK = new UserRecordWithEmailProtected("Protected",
"Protected");
@Override
public Object handleDeniedInvocation(MethodInvocation methodInvocation,
AuthorizationResult authorizationResult) {
return FALLBACK;
}
}
}
@@ -28,14 +28,4 @@ public class MethodSecurityServiceConfig {
return new MethodSecurityServiceImpl();
}
@Bean
ReactiveMethodSecurityService reactiveService() {
return new ReactiveMethodSecurityServiceImpl();
}
@Bean
Authz authz() {
return new Authz();
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,8 +18,6 @@ package org.springframework.security.config.annotation.method.configuration;
import java.util.List;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationDeniedException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
@@ -58,11 +56,6 @@ public class MethodSecurityServiceImpl implements MethodSecurityService {
return null;
}
@Override
public String jsr250RolesAllowedUser() {
return null;
}
@Override
public Authentication runAs() {
return SecurityContextHolder.getContext().getAuthentication();
@@ -80,10 +73,6 @@ public class MethodSecurityServiceImpl implements MethodSecurityService {
public void preAuthorizeAdmin() {
}
@Override
public void preAuthorizeUser() {
}
@Override
public String preAuthorizePermitAll() {
return null;
@@ -119,88 +108,8 @@ public class MethodSecurityServiceImpl implements MethodSecurityService {
return object;
}
@Override
public List<String> allAnnotations(List<String> list) {
return null;
}
@Override
public void repeatedAnnotations() {
}
@Override
public String postAuthorizeGetCardNumberIfAdmin(String cardNumber) {
return cardNumber;
}
@Override
public String preAuthorizeGetCardNumberIfAdmin(String cardNumber) {
return cardNumber;
}
@Override
public String preAuthorizeWithHandlerChildGetCardNumberIfAdmin(String cardNumber) {
return cardNumber;
}
@Override
public String preAuthorizeThrowAccessDeniedManually() {
throw new AuthorizationDeniedException("Access Denied", new AuthorizationDecision(false));
}
@Override
public String postAuthorizeThrowAccessDeniedManually() {
throw new AuthorizationDeniedException("Access Denied", new AuthorizationDecision(false));
}
@Override
public String preAuthorizeDeniedMethodWithMaskAnnotation() {
return "ok";
}
@Override
public String preAuthorizeDeniedMethodWithNoMaskAnnotation() {
return "ok";
}
@Override
public String postAuthorizeDeniedWithNullDenied() {
return "ok";
}
@Override
public String postAuthorizeDeniedMethodWithMaskAnnotation() {
return "ok";
}
@Override
public String postAuthorizeDeniedMethodWithNoMaskAnnotation() {
return "ok";
}
@Override
public String preAuthorizeWithMaskAnnotationUsingBean() {
return "ok";
}
@Override
public String postAuthorizeWithMaskAnnotationUsingBean() {
return "ok";
}
@Override
public UserRecordWithEmailProtected getUserRecordWithEmailProtected() {
return new UserRecordWithEmailProtected("username", "useremail@example.com");
}
@Override
public UserRecordWithEmailProtected getUserWithFallbackWhenUnauthorized() {
return new UserRecordWithEmailProtected("username", "useremail@example.com");
}
@Override
public String checkCustomResult(boolean result) {
return "ok";
}
}
@@ -1,29 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.method.configuration;
public class MyMasker {
public String getMask(String value) {
return value + "-masked";
}
public String getMask() {
return "mask";
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 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.
@@ -17,14 +17,9 @@
package org.springframework.security.config.annotation.method.configuration;
import java.io.Serializable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -41,7 +36,6 @@ import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.AdviceMode;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Role;
import org.springframework.core.annotation.AnnotationConfigurationException;
import org.springframework.security.access.AccessDeniedException;
@@ -52,24 +46,12 @@ import org.springframework.security.access.annotation.ExpressionProtectedBusines
import org.springframework.security.access.annotation.Jsr250BusinessServiceImpl;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PostFilter;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.access.prepost.PreFilter;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory;
import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory.TargetVisitor;
import org.springframework.security.authorization.method.AuthorizationInterceptorsOrder;
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
import org.springframework.security.authorization.method.AuthorizeReturnObject;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
import org.springframework.security.authorization.method.MethodInvocationResult;
import org.springframework.security.authorization.method.PrePostTemplateDefaults;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig;
import org.springframework.security.config.core.GrantedAuthorityDefaults;
import org.springframework.security.config.test.SpringTestContext;
@@ -88,12 +70,9 @@ import org.springframework.web.context.support.AnnotationConfigWebApplicationCon
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
/**
@@ -460,6 +439,7 @@ public class PrePostMethodSecurityConfigurationTests {
assertThat(this.spring.getContext().containsBean("annotationSecurityAspect$0")).isFalse();
}
// gh-13572
@Test
public void configureWhenBeanOverridingDisallowedThenWorks() {
this.spring.register(MethodSecurityServiceConfig.class, BusinessServiceConfig.class)
@@ -467,506 +447,10 @@ public class PrePostMethodSecurityConfigurationTests {
.autowire();
}
@WithMockUser(roles = "ADMIN")
@Test
public void methodSecurityAdminWhenRoleHierarchyBeanAvailableThenUses() {
this.spring.register(RoleHierarchyConfig.class, MethodSecurityServiceConfig.class).autowire();
this.methodSecurityService.preAuthorizeUser();
this.methodSecurityService.securedUser();
this.methodSecurityService.jsr250RolesAllowedUser();
}
@WithMockUser
@Test
public void methodSecurityUserWhenRoleHierarchyBeanAvailableThenUses() {
this.spring.register(RoleHierarchyConfig.class, MethodSecurityServiceConfig.class).autowire();
this.methodSecurityService.preAuthorizeUser();
this.methodSecurityService.securedUser();
this.methodSecurityService.jsr250RolesAllowedUser();
}
@WithMockUser(roles = "ADMIN")
@Test
public void methodSecurityAdminWhenAuthorizationEventPublisherBeanAvailableThenUses() {
this.spring
.register(RoleHierarchyConfig.class, MethodSecurityServiceConfig.class,
AuthorizationEventPublisherConfig.class)
.autowire();
this.methodSecurityService.preAuthorizeUser();
this.methodSecurityService.securedUser();
this.methodSecurityService.jsr250RolesAllowedUser();
}
@WithMockUser
@Test
public void methodSecurityUserWhenAuthorizationEventPublisherBeanAvailableThenUses() {
this.spring
.register(RoleHierarchyConfig.class, MethodSecurityServiceConfig.class,
AuthorizationEventPublisherConfig.class)
.autowire();
this.methodSecurityService.preAuthorizeUser();
this.methodSecurityService.securedUser();
this.methodSecurityService.jsr250RolesAllowedUser();
}
@Test
public void allAnnotationsWhenAdviceBeforeOffsetPreFilterThenReturnsFilteredList() {
this.spring.register(ReturnBeforeOffsetPreFilterConfig.class).autowire();
List<String> list = Arrays.asList("DropOnPreFilter", "DropOnPreAuthorize", "DropOnPostAuthorize",
"DropOnPostFilter", "DoNotDrop");
List<String> filtered = this.methodSecurityService.allAnnotations(new ArrayList<>(list));
assertThat(filtered).hasSize(5);
assertThat(filtered).containsExactly("DropOnPreFilter", "DropOnPreAuthorize", "DropOnPostAuthorize",
"DropOnPostFilter", "DoNotDrop");
}
@Test
public void allAnnotationsWhenAdviceBeforeOffsetPreAuthorizeThenReturnsFilteredList() {
this.spring.register(ReturnBeforeOffsetPreAuthorizeConfig.class).autowire();
List<String> list = Arrays.asList("DropOnPreFilter", "DropOnPreAuthorize", "DropOnPostAuthorize",
"DropOnPostFilter", "DoNotDrop");
List<String> filtered = this.methodSecurityService.allAnnotations(new ArrayList<>(list));
assertThat(filtered).hasSize(4);
assertThat(filtered).containsExactly("DropOnPreAuthorize", "DropOnPostAuthorize", "DropOnPostFilter",
"DoNotDrop");
}
@Test
public void allAnnotationsWhenAdviceBeforeOffsetSecuredThenReturnsFilteredList() {
this.spring.register(ReturnBeforeOffsetSecuredConfig.class).autowire();
List<String> list = Arrays.asList("DropOnPreFilter", "DropOnPreAuthorize", "DropOnPostAuthorize",
"DropOnPostFilter", "DoNotDrop");
List<String> filtered = this.methodSecurityService.allAnnotations(new ArrayList<>(list));
assertThat(filtered).hasSize(3);
assertThat(filtered).containsExactly("DropOnPostAuthorize", "DropOnPostFilter", "DoNotDrop");
}
@Test
@WithMockUser
public void allAnnotationsWhenAdviceBeforeOffsetJsr250WithInsufficientRolesThenFails() {
this.spring.register(ReturnBeforeOffsetJsr250Config.class).autowire();
List<String> list = Arrays.asList("DropOnPreFilter", "DropOnPreAuthorize", "DropOnPostAuthorize",
"DropOnPostFilter", "DoNotDrop");
assertThatExceptionOfType(AccessDeniedException.class)
.isThrownBy(() -> this.methodSecurityService.allAnnotations(new ArrayList<>(list)));
}
@Test
@WithMockUser(roles = "SECURED")
public void allAnnotationsWhenAdviceBeforeOffsetJsr250ThenReturnsFilteredList() {
this.spring.register(ReturnBeforeOffsetJsr250Config.class).autowire();
List<String> list = Arrays.asList("DropOnPreFilter", "DropOnPreAuthorize", "DropOnPostAuthorize",
"DropOnPostFilter", "DoNotDrop");
List<String> filtered = this.methodSecurityService.allAnnotations(new ArrayList<>(list));
assertThat(filtered).hasSize(3);
assertThat(filtered).containsExactly("DropOnPostAuthorize", "DropOnPostFilter", "DoNotDrop");
}
@Test
@WithMockUser(roles = { "SECURED" })
public void allAnnotationsWhenAdviceBeforeOffsetPostAuthorizeWithInsufficientRolesThenFails() {
this.spring.register(ReturnBeforeOffsetPostAuthorizeConfig.class).autowire();
List<String> list = Arrays.asList("DropOnPreFilter", "DropOnPreAuthorize", "DropOnPostAuthorize",
"DropOnPostFilter", "DoNotDrop");
assertThatExceptionOfType(AccessDeniedException.class)
.isThrownBy(() -> this.methodSecurityService.allAnnotations(new ArrayList<>(list)));
}
@Test
@WithMockUser(roles = { "SECURED", "JSR250" })
public void allAnnotationsWhenAdviceBeforeOffsetPostAuthorizeThenReturnsFilteredList() {
this.spring.register(ReturnBeforeOffsetPostAuthorizeConfig.class).autowire();
List<String> list = Arrays.asList("DropOnPreFilter", "DropOnPreAuthorize", "DropOnPostAuthorize",
"DropOnPostFilter", "DoNotDrop");
List<String> filtered = this.methodSecurityService.allAnnotations(new ArrayList<>(list));
assertThat(filtered).hasSize(3);
assertThat(filtered).containsExactly("DropOnPostAuthorize", "DropOnPostFilter", "DoNotDrop");
}
@Test
@WithMockUser(roles = { "SECURED", "JSR250" })
public void allAnnotationsWhenAdviceBeforeOffsetPostFilterThenReturnsFilteredList() {
this.spring.register(ReturnBeforeOffsetPostFilterConfig.class).autowire();
List<String> list = Arrays.asList("DropOnPreFilter", "DropOnPreAuthorize", "DropOnPostAuthorize",
"DropOnPostFilter", "DoNotDrop");
List<String> filtered = this.methodSecurityService.allAnnotations(new ArrayList<>(list));
assertThat(filtered).hasSize(2);
assertThat(filtered).containsExactly("DropOnPostFilter", "DoNotDrop");
}
@Test
@WithMockUser(roles = { "SECURED", "JSR250" })
public void allAnnotationsWhenAdviceAfterAllOffsetThenReturnsFilteredList() {
this.spring.register(ReturnAfterAllOffsetConfig.class).autowire();
List<String> list = Arrays.asList("DropOnPreFilter", "DropOnPreAuthorize", "DropOnPostAuthorize",
"DropOnPostFilter", "DoNotDrop");
List<String> filtered = this.methodSecurityService.allAnnotations(new ArrayList<>(list));
assertThat(filtered).hasSize(1);
assertThat(filtered).containsExactly("DoNotDrop");
}
@Test
@WithMockUser
public void methodeWhenParameterizedPreAuthorizeMetaAnnotationThenPasses() {
this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire();
MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class);
assertThat(service.hasRole("USER")).isTrue();
}
@Test
@WithMockUser
public void methodRoleWhenPreAuthorizeMetaAnnotationHardcodedParameterThenPasses() {
this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire();
MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class);
assertThat(service.hasUserRole()).isTrue();
}
@Test
public void methodWhenParameterizedAnnotationThenFails() {
this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire();
MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class);
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(service::placeholdersOnlyResolvedByMetaAnnotations);
}
@Test
@WithMockUser(authorities = "SCOPE_message:read")
public void methodWhenMultiplePlaceholdersHasAuthorityThenPasses() {
this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire();
MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class);
assertThat(service.readMessage()).isEqualTo("message");
}
@Test
@WithMockUser(roles = "ADMIN")
public void methodWhenMultiplePlaceholdersHasRoleThenPasses() {
this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire();
MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class);
assertThat(service.readMessage()).isEqualTo("message");
}
@Test
@WithMockUser
public void methodWhenPostAuthorizeMetaAnnotationThenAuthorizes() {
this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire();
MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class);
service.startsWithDave("daveMatthews");
assertThatExceptionOfType(AccessDeniedException.class)
.isThrownBy(() -> service.startsWithDave("jenniferHarper"));
}
@Test
@WithMockUser
public void methodWhenPreFilterMetaAnnotationThenFilters() {
this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire();
MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class);
assertThat(service.parametersContainDave(new ArrayList<>(List.of("dave", "carla", "vanessa", "paul"))))
.containsExactly("dave");
}
@Test
@WithMockUser
public void methodWhenPostFilterMetaAnnotationThenFilters() {
this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire();
MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class);
assertThat(service.resultsContainDave(new ArrayList<>(List.of("dave", "carla", "vanessa", "paul"))))
.containsExactly("dave");
}
@Test
@WithMockUser(authorities = "airplane:read")
public void findByIdWhenAuthorizedResultThenAuthorizes() {
this.spring.register(AuthorizeResultConfig.class).autowire();
FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class);
Flight flight = flights.findById("1");
assertThatNoException().isThrownBy(flight::getAltitude);
assertThatNoException().isThrownBy(flight::getSeats);
}
@Test
@WithMockUser(authorities = "seating:read")
public void findByIdWhenUnauthorizedResultThenDenies() {
this.spring.register(AuthorizeResultConfig.class).autowire();
FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class);
Flight flight = flights.findById("1");
assertThatNoException().isThrownBy(flight::getSeats);
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(flight::getAltitude);
}
@Test
@WithMockUser(authorities = "seating:read")
public void findAllWhenUnauthorizedResultThenDenies() {
this.spring.register(AuthorizeResultConfig.class).autowire();
FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class);
flights.findAll().forEachRemaining((flight) -> {
assertThatNoException().isThrownBy(flight::getSeats);
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(flight::getAltitude);
});
}
@Test
public void removeWhenAuthorizedResultThenRemoves() {
this.spring.register(AuthorizeResultConfig.class).autowire();
FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class);
flights.remove("1");
}
@Test
@WithMockUser(authorities = "airplane:read")
public void findAllWhenPostFilterThenFilters() {
this.spring.register(AuthorizeResultConfig.class).autowire();
FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class);
flights.findAll()
.forEachRemaining((flight) -> assertThat(flight.getPassengers()).extracting(Passenger::getName)
.doesNotContain("Kevin Mitnick"));
}
@Test
@WithMockUser(authorities = "airplane:read")
public void findAllWhenPreFilterThenFilters() {
this.spring.register(AuthorizeResultConfig.class).autowire();
FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class);
flights.findAll().forEachRemaining((flight) -> {
flight.board(new ArrayList<>(List.of("John")));
assertThat(flight.getPassengers()).extracting(Passenger::getName).doesNotContain("John");
flight.board(new ArrayList<>(List.of("John Doe")));
assertThat(flight.getPassengers()).extracting(Passenger::getName).contains("John Doe");
});
}
@Test
@WithMockUser(authorities = "seating:read")
public void findAllWhenNestedPreAuthorizeThenAuthorizes() {
this.spring.register(AuthorizeResultConfig.class).autowire();
FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class);
flights.findAll().forEachRemaining((flight) -> {
List<Passenger> passengers = flight.getPassengers();
passengers.forEach((passenger) -> assertThatExceptionOfType(AccessDeniedException.class)
.isThrownBy(passenger::getName));
});
}
@Test
@WithMockUser
void getCardNumberWhenPostAuthorizeAndNotAdminThenReturnMasked() {
this.spring
.register(MethodSecurityServiceEnabledConfig.class,
MethodSecurityService.CardNumberMaskingPostProcessor.class)
.autowire();
MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class);
String cardNumber = service.postAuthorizeGetCardNumberIfAdmin("4444-3333-2222-1111");
assertThat(cardNumber).isEqualTo("****-****-****-1111");
}
@Test
@WithMockUser
void getCardNumberWhenPreAuthorizeAndNotAdminThenReturnMasked() {
this.spring.register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.StarMaskingHandler.class)
.autowire();
MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class);
String cardNumber = service.preAuthorizeGetCardNumberIfAdmin("4444-3333-2222-1111");
assertThat(cardNumber).isEqualTo("***");
}
@Test
@WithMockUser
void getCardNumberWhenPreAuthorizeAndNotAdminAndChildHandlerThenResolveCorrectHandlerAndReturnMasked() {
this.spring
.register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.StarMaskingHandler.class,
MethodSecurityService.StartMaskingHandlerChild.class)
.autowire();
MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class);
String cardNumber = service.preAuthorizeWithHandlerChildGetCardNumberIfAdmin("4444-3333-2222-1111");
assertThat(cardNumber).isEqualTo("***-child");
}
@Test
@WithMockUser(roles = "ADMIN")
void preAuthorizeWhenHandlerAndAccessDeniedNotThrownFromPreAuthorizeThenHandled() {
this.spring.register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.StarMaskingHandler.class)
.autowire();
MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class);
assertThat(service.preAuthorizeThrowAccessDeniedManually()).isEqualTo("***");
}
@Test
@WithMockUser(roles = "ADMIN")
void postAuthorizeWhenHandlerAndAccessDeniedNotThrownFromPostAuthorizeThenHandled() {
this.spring
.register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.PostMaskingPostProcessor.class)
.autowire();
MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class);
assertThat(service.postAuthorizeThrowAccessDeniedManually()).isEqualTo("***");
}
@Test
@WithMockUser
void preAuthorizeWhenDeniedAndHandlerWithCustomAnnotationThenHandlerCanUseMaskFromOtherAnnotation() {
this.spring
.register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.MaskAnnotationHandler.class)
.autowire();
MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class);
String result = service.preAuthorizeDeniedMethodWithMaskAnnotation();
assertThat(result).isEqualTo("methodmask");
}
@Test
@WithMockUser
void preAuthorizeWhenDeniedAndHandlerWithCustomAnnotationInClassThenHandlerCanUseMaskFromOtherAnnotation() {
this.spring
.register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.MaskAnnotationHandler.class)
.autowire();
MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class);
String result = service.preAuthorizeDeniedMethodWithNoMaskAnnotation();
assertThat(result).isEqualTo("classmask");
}
@Test
@WithMockUser
void postAuthorizeWhenNullDeniedMetaAnnotationThanWorks() {
this.spring
.register(MethodSecurityServiceEnabledConfig.class, MetaAnnotationPlaceholderConfig.class,
MethodSecurityService.NullPostProcessor.class)
.autowire();
MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class);
String result = service.postAuthorizeDeniedWithNullDenied();
assertThat(result).isNull();
}
@Test
@WithMockUser
void postAuthorizeWhenDeniedAndHandlerWithCustomAnnotationThenHandlerCanUseMaskFromOtherAnnotation() {
this.spring
.register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.MaskAnnotationPostProcessor.class)
.autowire();
MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class);
String result = service.postAuthorizeDeniedMethodWithMaskAnnotation();
assertThat(result).isEqualTo("methodmask");
}
@Test
@WithMockUser
void postAuthorizeWhenDeniedAndHandlerWithCustomAnnotationInClassThenHandlerCanUseMaskFromOtherAnnotation() {
this.spring
.register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.MaskAnnotationPostProcessor.class)
.autowire();
MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class);
String result = service.postAuthorizeDeniedMethodWithNoMaskAnnotation();
assertThat(result).isEqualTo("classmask");
}
@Test
@WithMockUser
void postAuthorizeWhenDeniedAndHandlerWithCustomAnnotationUsingBeanThenHandlerCanUseMaskFromOtherAnnotation() {
this.spring
.register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.MaskAnnotationPostProcessor.class,
MyMasker.class)
.autowire();
MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class);
String result = service.postAuthorizeWithMaskAnnotationUsingBean();
assertThat(result).isEqualTo("ok-masked");
}
@Test
@WithMockUser(roles = "ADMIN")
void postAuthorizeWhenAllowedAndHandlerWithCustomAnnotationUsingBeanThenInvokeMethodNormally() {
this.spring
.register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.MaskAnnotationPostProcessor.class,
MyMasker.class)
.autowire();
MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class);
String result = service.postAuthorizeWithMaskAnnotationUsingBean();
assertThat(result).isEqualTo("ok");
}
@Test
@WithMockUser
void preAuthorizeWhenDeniedAndHandlerWithCustomAnnotationUsingBeanThenHandlerCanUseMaskFromOtherAnnotation() {
this.spring
.register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.MaskAnnotationHandler.class,
MyMasker.class)
.autowire();
MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class);
String result = service.preAuthorizeWithMaskAnnotationUsingBean();
assertThat(result).isEqualTo("mask");
}
@Test
@WithMockUser(roles = "ADMIN")
void preAuthorizeWhenAllowedAndHandlerWithCustomAnnotationUsingBeanThenInvokeMethodNormally() {
this.spring
.register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.MaskAnnotationHandler.class,
MyMasker.class)
.autowire();
MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class);
String result = service.preAuthorizeWithMaskAnnotationUsingBean();
assertThat(result).isEqualTo("ok");
}
@Test
@WithMockUser
void getUserWhenAuthorizedAndUserEmailIsProtectedAndNotAuthorizedThenReturnEmailMasked() {
this.spring
.register(MethodSecurityServiceEnabledConfig.class,
UserRecordWithEmailProtected.EmailMaskingPostProcessor.class)
.autowire();
MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class);
UserRecordWithEmailProtected user = service.getUserRecordWithEmailProtected();
assertThat(user.email()).isEqualTo("use******@example.com");
assertThat(user.name()).isEqualTo("username");
}
@Test
@WithMockUser
void getUserWhenNotAuthorizedAndHandlerFallbackValueThenReturnFallbackValue() {
this.spring
.register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.UserFallbackDeniedHandler.class)
.autowire();
MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class);
UserRecordWithEmailProtected user = service.getUserWithFallbackWhenUnauthorized();
assertThat(user.email()).isEqualTo("Protected");
assertThat(user.name()).isEqualTo("Protected");
}
@Test
@WithMockUser
void getUserWhenNotAuthorizedThenHandlerUsesCustomAuthorizationDecision() {
this.spring.register(MethodSecurityServiceConfig.class, CustomResultConfig.class).autowire();
MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class);
MethodAuthorizationDeniedHandler handler = this.spring.getContext()
.getBean(MethodAuthorizationDeniedHandler.class);
assertThat(service.checkCustomResult(false)).isNull();
verify(handler).handleDeniedInvocation(any(), any(Authz.AuthzResult.class));
verify(handler, never()).handleDeniedInvocationResult(any(), any(Authz.AuthzResult.class));
clearInvocations(handler);
assertThat(service.checkCustomResult(true)).isNull();
verify(handler).handleDeniedInvocationResult(any(), any(Authz.AuthzResult.class));
verify(handler, never()).handleDeniedInvocation(any(), any(Authz.AuthzResult.class));
}
private static Consumer<ConfigurableWebApplicationContext> disallowBeanOverriding() {
return (context) -> ((AnnotationConfigWebApplicationContext) context).setAllowBeanDefinitionOverriding(false);
}
private static Advisor returnAdvisor(int order) {
JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
pointcut.setPattern(".*MethodSecurityServiceImpl.*");
MethodInterceptor interceptor = (mi) -> mi.getArguments()[0];
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, interceptor);
advisor.setOrder(order);
return advisor;
}
@Configuration
static class AuthzConfig {
@Bean
Authz authz() {
return new Authz();
}
}
@Configuration
@EnableCustomMethodSecurity
static class CustomMethodSecurityServiceConfig {
@@ -1143,341 +627,4 @@ public class PrePostMethodSecurityConfigurationTests {
}
@Configuration
@EnableMethodSecurity(jsr250Enabled = true, securedEnabled = true)
static class RoleHierarchyConfig {
@Bean
static RoleHierarchy roleHierarchy() {
RoleHierarchyImpl roleHierarchyImpl = new RoleHierarchyImpl();
roleHierarchyImpl.setHierarchy("ROLE_ADMIN > ROLE_USER");
return roleHierarchyImpl;
}
}
@Import(OffsetConfig.class)
static class ReturnBeforeOffsetPreFilterConfig {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
Advisor returnBeforePreFilter() {
return returnAdvisor(AuthorizationInterceptorsOrder.PRE_FILTER.getOrder() + OffsetConfig.OFFSET - 1);
}
}
@Configuration
@Import(OffsetConfig.class)
static class ReturnBeforeOffsetPreAuthorizeConfig {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
Advisor returnBeforePreAuthorize() {
return returnAdvisor(AuthorizationInterceptorsOrder.PRE_AUTHORIZE.getOrder() + OffsetConfig.OFFSET - 1);
}
}
@Configuration
@Import(OffsetConfig.class)
static class ReturnBeforeOffsetSecuredConfig {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
Advisor returnBeforeSecured() {
return returnAdvisor(AuthorizationInterceptorsOrder.SECURED.getOrder() + OffsetConfig.OFFSET - 1);
}
}
@Configuration
@Import(OffsetConfig.class)
static class ReturnBeforeOffsetJsr250Config {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
Advisor returnBeforeJsr250() {
return returnAdvisor(AuthorizationInterceptorsOrder.JSR250.getOrder() + OffsetConfig.OFFSET - 1);
}
}
@Configuration
@Import(OffsetConfig.class)
static class ReturnBeforeOffsetPostAuthorizeConfig {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
Advisor returnBeforePreAuthorize() {
return returnAdvisor(AuthorizationInterceptorsOrder.POST_AUTHORIZE.getOrder() + OffsetConfig.OFFSET - 1);
}
}
@Configuration
@Import(OffsetConfig.class)
static class ReturnBeforeOffsetPostFilterConfig {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
Advisor returnBeforePostFilter() {
return returnAdvisor(AuthorizationInterceptorsOrder.POST_FILTER.getOrder() + OffsetConfig.OFFSET - 1);
}
}
@Configuration
@Import(OffsetConfig.class)
static class ReturnAfterAllOffsetConfig {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
Advisor returnAfterAll() {
return returnAdvisor(AuthorizationInterceptorsOrder.POST_FILTER.getOrder() + OffsetConfig.OFFSET + 1);
}
}
@Configuration
@EnableMethodSecurity(offset = OffsetConfig.OFFSET, jsr250Enabled = true, securedEnabled = true)
static class OffsetConfig {
static final int OFFSET = 2;
@Bean
MethodSecurityService methodSecurityService() {
return new MethodSecurityServiceImpl();
}
@Bean
Authz authz() {
return new Authz();
}
}
@Configuration
@EnableMethodSecurity
static class MetaAnnotationPlaceholderConfig {
@Bean
PrePostTemplateDefaults methodSecurityDefaults() {
return new PrePostTemplateDefaults();
}
@Bean
MetaAnnotationService metaAnnotationService() {
return new MetaAnnotationService();
}
}
static class MetaAnnotationService {
@RequireRole(role = "#role")
boolean hasRole(String role) {
return true;
}
@RequireRole(role = "'USER'")
boolean hasUserRole() {
return true;
}
@PreAuthorize("hasRole({role})")
void placeholdersOnlyResolvedByMetaAnnotations() {
}
@HasClaim(claim = "message:read", roles = { "'ADMIN'" })
String readMessage() {
return "message";
}
@ResultStartsWith("dave")
String startsWithDave(String value) {
return value;
}
@ParameterContains("dave")
List<String> parametersContainDave(List<String> list) {
return list;
}
@ResultContains("dave")
List<String> resultsContainDave(List<String> list) {
return list;
}
}
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasRole({role})")
@interface RequireRole {
String role();
}
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasAuthority('SCOPE_{claim}') || hasAnyRole({roles})")
@interface HasClaim {
String claim();
String[] roles() default {};
}
@Retention(RetentionPolicy.RUNTIME)
@PostAuthorize("returnObject.startsWith('{value}')")
@interface ResultStartsWith {
String value();
}
@Retention(RetentionPolicy.RUNTIME)
@PreFilter("filterObject.contains('{value}')")
@interface ParameterContains {
String value();
}
@Retention(RetentionPolicy.RUNTIME)
@PostFilter("filterObject.contains('{value}')")
@interface ResultContains {
String value();
}
@EnableMethodSecurity
@Configuration
static class AuthorizeResultConfig {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static Customizer<AuthorizationAdvisorProxyFactory> skipValueTypes() {
return (f) -> f.setTargetVisitor(TargetVisitor.defaultsSkipValueTypes());
}
@Bean
FlightRepository flights() {
FlightRepository flights = new FlightRepository();
Flight one = new Flight("1", 35000d, 35);
one.board(new ArrayList<>(List.of("Marie Curie", "Kevin Mitnick", "Ada Lovelace")));
flights.save(one);
Flight two = new Flight("2", 32000d, 72);
two.board(new ArrayList<>(List.of("Albert Einstein")));
flights.save(two);
return flights;
}
@Bean
RoleHierarchy roleHierarchy() {
return RoleHierarchyImpl.withRolePrefix("").role("airplane:read").implies("seating:read").build();
}
}
@AuthorizeReturnObject
static class FlightRepository {
private final Map<String, Flight> flights = new ConcurrentHashMap<>();
Iterator<Flight> findAll() {
return this.flights.values().iterator();
}
Flight findById(String id) {
return this.flights.get(id);
}
Flight save(Flight flight) {
this.flights.put(flight.getId(), flight);
return flight;
}
void remove(String id) {
this.flights.remove(id);
}
}
@AuthorizeReturnObject
static class Flight {
private final String id;
private final Double altitude;
private final Integer seats;
private final List<Passenger> passengers = new ArrayList<>();
Flight(String id, Double altitude, Integer seats) {
this.id = id;
this.altitude = altitude;
this.seats = seats;
}
String getId() {
return this.id;
}
@PreAuthorize("hasAuthority('airplane:read')")
Double getAltitude() {
return this.altitude;
}
@PreAuthorize("hasAuthority('seating:read')")
Integer getSeats() {
return this.seats;
}
@PostAuthorize("hasAuthority('seating:read')")
@PostFilter("filterObject.name != 'Kevin Mitnick'")
List<Passenger> getPassengers() {
return this.passengers;
}
@PreAuthorize("hasAuthority('seating:read')")
@PreFilter("filterObject.contains(' ')")
void board(List<String> passengers) {
for (String passenger : passengers) {
this.passengers.add(new Passenger(passenger));
}
}
}
public static class Passenger {
String name;
public Passenger(String name) {
this.name = name;
}
@PreAuthorize("hasAuthority('airplane:read')")
public String getName() {
return this.name;
}
}
@EnableMethodSecurity
static class CustomResultConfig {
MethodAuthorizationDeniedHandler handler = mock(MethodAuthorizationDeniedHandler.class);
@Bean
MethodAuthorizationDeniedHandler methodAuthorizationDeniedHandler() {
return this.handler;
}
}
}
@@ -1,215 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.method.configuration;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import reactor.test.StepVerifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@ExtendWith({ SpringExtension.class, SpringTestContextExtension.class })
@SecurityTestExecutionListeners
public class PrePostReactiveMethodSecurityConfigurationTests {
public final SpringTestContext spring = new SpringTestContext(this);
@Test
@WithMockUser
void getCardNumberWhenPostAuthorizeAndNotAdminThenReturnMasked() {
this.spring
.register(MethodSecurityServiceEnabledConfig.class,
ReactiveMethodSecurityService.CardNumberMaskingPostProcessor.class)
.autowire();
ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class);
StepVerifier.create(service.postAuthorizeGetCardNumberIfAdmin("4444-3333-2222-1111"))
.expectNext("****-****-****-1111")
.verifyComplete();
}
@Test
@WithMockUser
void getCardNumberWhenPreAuthorizeAndNotAdminThenReturnMasked() {
this.spring
.register(MethodSecurityServiceEnabledConfig.class, ReactiveMethodSecurityService.StarMaskingHandler.class)
.autowire();
ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class);
StepVerifier.create(service.preAuthorizeGetCardNumberIfAdmin("4444-3333-2222-1111"))
.expectNext("***")
.verifyComplete();
}
@Test
@WithMockUser
void getCardNumberWhenPreAuthorizeAndNotAdminAndChildHandlerThenResolveCorrectHandlerAndReturnMasked() {
this.spring
.register(MethodSecurityServiceEnabledConfig.class, ReactiveMethodSecurityService.StarMaskingHandler.class,
ReactiveMethodSecurityService.StartMaskingHandlerChild.class)
.autowire();
ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class);
StepVerifier.create(service.preAuthorizeWithHandlerChildGetCardNumberIfAdmin("4444-3333-2222-1111"))
.expectNext("***-child")
.verifyComplete();
}
@Test
@WithMockUser
void preAuthorizeWhenDeniedAndHandlerWithCustomAnnotationThenHandlerCanUseMaskFromOtherAnnotation() {
this.spring
.register(MethodSecurityServiceEnabledConfig.class,
ReactiveMethodSecurityService.MaskAnnotationHandler.class)
.autowire();
ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class);
StepVerifier.create(service.preAuthorizeDeniedMethodWithMaskAnnotation())
.expectNext("methodmask")
.verifyComplete();
}
@Test
@WithMockUser
void preAuthorizeWhenDeniedAndHandlerWithCustomAnnotationInClassThenHandlerCanUseMaskFromOtherAnnotation() {
this.spring
.register(MethodSecurityServiceEnabledConfig.class,
ReactiveMethodSecurityService.MaskAnnotationHandler.class)
.autowire();
ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class);
StepVerifier.create(service.preAuthorizeDeniedMethodWithNoMaskAnnotation())
.expectNext("classmask")
.verifyComplete();
}
@Test
@WithMockUser(roles = "ADMIN")
void postAuthorizeWhenHandlerAndAccessDeniedNotThrownFromPostAuthorizeThenNotHandled() {
this.spring
.register(MethodSecurityServiceEnabledConfig.class,
ReactiveMethodSecurityService.PostMaskingPostProcessor.class)
.autowire();
ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class);
StepVerifier.create(service.postAuthorizeThrowAccessDeniedManually()).expectNext("***").verifyComplete();
}
@Test
@WithMockUser(roles = "ADMIN")
void preAuthorizeWhenHandlerAndAccessDeniedNotThrownFromPreAuthorizeThenHandled() {
this.spring
.register(MethodSecurityServiceEnabledConfig.class, ReactiveMethodSecurityService.StarMaskingHandler.class)
.autowire();
ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class);
StepVerifier.create(service.preAuthorizeThrowAccessDeniedManually()).expectNext("***").verifyComplete();
}
@Test
@WithMockUser
void postAuthorizeWhenNullDeniedMetaAnnotationThanWorks() {
this.spring
.register(MethodSecurityServiceEnabledConfig.class, ReactiveMethodSecurityService.NullPostProcessor.class)
.autowire();
ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class);
StepVerifier.create(service.postAuthorizeDeniedWithNullDenied()).verifyComplete();
}
@Test
@WithMockUser
void postAuthorizeWhenDeniedAndHandlerWithCustomAnnotationThenHandlerCanUseMaskFromOtherAnnotation() {
this.spring
.register(MethodSecurityServiceEnabledConfig.class,
ReactiveMethodSecurityService.MaskAnnotationPostProcessor.class)
.autowire();
ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class);
StepVerifier.create(service.postAuthorizeDeniedMethodWithMaskAnnotation())
.expectNext("methodmask")
.verifyComplete();
}
@Test
@WithMockUser
void postAuthorizeWhenDeniedAndHandlerWithCustomAnnotationInClassThenHandlerCanUseMaskFromOtherAnnotation() {
this.spring
.register(MethodSecurityServiceEnabledConfig.class,
ReactiveMethodSecurityService.MaskAnnotationPostProcessor.class)
.autowire();
ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class);
StepVerifier.create(service.postAuthorizeDeniedMethodWithNoMaskAnnotation())
.expectNext("classmask")
.verifyComplete();
}
@Test
@WithMockUser
void postAuthorizeWhenDeniedAndHandlerWithCustomAnnotationUsingBeanThenHandlerCanUseMaskFromOtherAnnotation() {
this.spring
.register(MethodSecurityServiceEnabledConfig.class,
ReactiveMethodSecurityService.MaskAnnotationPostProcessor.class, MyMasker.class)
.autowire();
ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class);
StepVerifier.create(service.postAuthorizeWithMaskAnnotationUsingBean())
.expectNext("ok-masked")
.verifyComplete();
}
@Test
@WithMockUser(roles = "ADMIN")
void postAuthorizeWhenAllowedAndHandlerWithCustomAnnotationUsingBeanThenInvokeMethodNormally() {
this.spring
.register(MethodSecurityServiceEnabledConfig.class,
ReactiveMethodSecurityService.MaskAnnotationPostProcessor.class, MyMasker.class)
.autowire();
ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class);
StepVerifier.create(service.postAuthorizeWithMaskAnnotationUsingBean()).expectNext("ok").verifyComplete();
}
@Test
@WithMockUser
void preAuthorizeWhenDeniedAndHandlerWithCustomAnnotationUsingBeanThenHandlerCanUseMaskFromOtherAnnotation() {
this.spring
.register(MethodSecurityServiceEnabledConfig.class,
ReactiveMethodSecurityService.MaskAnnotationHandler.class, MyMasker.class)
.autowire();
ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class);
StepVerifier.create(service.preAuthorizeWithMaskAnnotationUsingBean()).expectNext("mask").verifyComplete();
}
@Test
@WithMockUser(roles = "ADMIN")
void preAuthorizeWhenAllowedAndHandlerWithCustomAnnotationUsingBeanThenInvokeMethodNormally() {
this.spring
.register(MethodSecurityServiceEnabledConfig.class,
ReactiveMethodSecurityService.MaskAnnotationHandler.class, MyMasker.class)
.autowire();
ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class);
StepVerifier.create(service.preAuthorizeWithMaskAnnotationUsingBean()).expectNext("ok").verifyComplete();
}
@Configuration
@EnableReactiveMethodSecurity
static class MethodSecurityServiceEnabledConfig {
@Bean
ReactiveMethodSecurityService methodSecurityService() {
return new ReactiveMethodSecurityServiceImpl();
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2019 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,53 +16,22 @@
package org.springframework.security.config.annotation.method.configuration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.function.Function;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
import org.springframework.expression.EvaluationContext;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.expression.SecurityExpressionRoot;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.intercept.method.MockMethodInvocation;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PostFilter;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.access.prepost.PreFilter;
import org.springframework.security.authentication.TestAuthentication;
import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory;
import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory.TargetVisitor;
import org.springframework.security.authorization.method.AuthorizeReturnObject;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
import org.springframework.security.config.Customizer;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.config.core.GrantedAuthorityDefaults;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.test.context.support.WithMockUser;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
/**
* @author Tadaya Tsuyukubo
@@ -72,13 +41,14 @@ public class ReactiveMethodSecurityConfigurationTests {
public final SpringTestContext spring = new SpringTestContext(this);
@Autowired(required = false)
@Autowired
DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler;
@Test
public void rolePrefixWithGrantedAuthorityDefaults() throws NoSuchMethodException {
this.spring.register(WithRolePrefixConfiguration.class).autowire();
Authentication authentication = TestAuthentication.authenticatedUser(authorities("CUSTOM_ABC"));
TestingAuthenticationToken authentication = new TestingAuthenticationToken("principal", "credential",
"CUSTOM_ABC");
MockMethodInvocation methodInvocation = new MockMethodInvocation(new Foo(), Foo.class, "bar", String.class);
EvaluationContext context = this.methodSecurityExpressionHandler.createEvaluationContext(authentication,
methodInvocation);
@@ -92,7 +62,8 @@ public class ReactiveMethodSecurityConfigurationTests {
@Test
public void rolePrefixWithDefaultConfig() throws NoSuchMethodException {
this.spring.register(ReactiveMethodSecurityConfiguration.class).autowire();
Authentication authentication = TestAuthentication.authenticatedUser(authorities("ROLE_ABC"));
TestingAuthenticationToken authentication = new TestingAuthenticationToken("principal", "credential",
"ROLE_ABC");
MockMethodInvocation methodInvocation = new MockMethodInvocation(new Foo(), Foo.class, "bar", String.class);
EvaluationContext context = this.methodSecurityExpressionHandler.createEvaluationContext(authentication,
methodInvocation);
@@ -104,7 +75,8 @@ public class ReactiveMethodSecurityConfigurationTests {
@Test
public void rolePrefixWithGrantedAuthorityDefaultsAndSubclassWithProxyingEnabled() throws NoSuchMethodException {
this.spring.register(SubclassConfig.class).autowire();
Authentication authentication = TestAuthentication.authenticatedUser(authorities("ROLE_ABC"));
TestingAuthenticationToken authentication = new TestingAuthenticationToken("principal", "credential",
"ROLE_ABC");
MockMethodInvocation methodInvocation = new MockMethodInvocation(new Foo(), Foo.class, "bar", String.class);
EvaluationContext context = this.methodSecurityExpressionHandler.createEvaluationContext(authentication,
methodInvocation);
@@ -113,132 +85,6 @@ public class ReactiveMethodSecurityConfigurationTests {
assertThat(root.hasRole("ABC")).isTrue();
}
@Test
public void findByIdWhenAuthorizedResultThenAuthorizes() {
this.spring.register(AuthorizeResultConfig.class).autowire();
FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class);
Authentication pilot = TestAuthentication.authenticatedUser(authorities("airplane:read"));
StepVerifier
.create(flights.findById("1")
.flatMap(Flight::getAltitude)
.contextWrite(ReactiveSecurityContextHolder.withAuthentication(pilot)))
.expectNextCount(1)
.verifyComplete();
StepVerifier
.create(flights.findById("1")
.flatMap(Flight::getSeats)
.contextWrite(ReactiveSecurityContextHolder.withAuthentication(pilot)))
.expectNextCount(1)
.verifyComplete();
}
@Test
public void findByIdWhenUnauthorizedResultThenDenies() {
this.spring.register(AuthorizeResultConfig.class).autowire();
FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class);
Authentication pilot = TestAuthentication.authenticatedUser(authorities("seating:read"));
StepVerifier
.create(flights.findById("1")
.flatMap(Flight::getSeats)
.contextWrite(ReactiveSecurityContextHolder.withAuthentication(pilot)))
.expectNextCount(1)
.verifyComplete();
StepVerifier
.create(flights.findById("1")
.flatMap(Flight::getAltitude)
.contextWrite(ReactiveSecurityContextHolder.withAuthentication(pilot)))
.verifyError(AccessDeniedException.class);
}
@Test
public void findAllWhenUnauthorizedResultThenDenies() {
this.spring.register(AuthorizeResultConfig.class).autowire();
FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class);
Authentication pilot = TestAuthentication.authenticatedUser(authorities("seating:read"));
StepVerifier
.create(flights.findAll()
.flatMap(Flight::getSeats)
.contextWrite(ReactiveSecurityContextHolder.withAuthentication(pilot)))
.expectNextCount(2)
.verifyComplete();
StepVerifier
.create(flights.findAll()
.flatMap(Flight::getAltitude)
.contextWrite(ReactiveSecurityContextHolder.withAuthentication(pilot)))
.verifyError(AccessDeniedException.class);
}
@Test
public void removeWhenAuthorizedResultThenRemoves() {
this.spring.register(AuthorizeResultConfig.class).autowire();
FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class);
Authentication pilot = TestAuthentication.authenticatedUser(authorities("seating:read"));
StepVerifier.create(flights.remove("1").contextWrite(ReactiveSecurityContextHolder.withAuthentication(pilot)))
.verifyComplete();
}
@Test
public void findAllWhenPostFilterThenFilters() {
this.spring.register(AuthorizeResultConfig.class).autowire();
FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class);
Authentication pilot = TestAuthentication.authenticatedUser(authorities("airplane:read"));
StepVerifier
.create(flights.findAll()
.flatMap(Flight::getPassengers)
.flatMap(Passenger::getName)
.contextWrite(ReactiveSecurityContextHolder.withAuthentication(pilot)))
.expectNext("Marie Curie", "Ada Lovelace", "Albert Einstein")
.verifyComplete();
}
@Test
public void findAllWhenPreFilterThenFilters() {
this.spring.register(AuthorizeResultConfig.class).autowire();
FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class);
Authentication pilot = TestAuthentication.authenticatedUser(authorities("airplane:read"));
StepVerifier
.create(flights.findAll()
.flatMap((flight) -> flight.board(Flux.just("John Doe", "John")).then(Mono.just(flight)))
.flatMap(Flight::getPassengers)
.flatMap(Passenger::getName)
.contextWrite(ReactiveSecurityContextHolder.withAuthentication(pilot)))
.expectNext("Marie Curie", "Ada Lovelace", "John Doe", "Albert Einstein", "John Doe")
.verifyComplete();
}
@Test
public void findAllWhenNestedPreAuthorizeThenAuthorizes() {
this.spring.register(AuthorizeResultConfig.class).autowire();
FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class);
Authentication pilot = TestAuthentication.authenticatedUser(authorities("seating:read"));
StepVerifier
.create(flights.findAll()
.flatMap(Flight::getPassengers)
.flatMap(Passenger::getName)
.contextWrite(ReactiveSecurityContextHolder.withAuthentication(pilot)))
.verifyError(AccessDeniedException.class);
}
@Test
@WithMockUser
void getUserWhenNotAuthorizedThenHandlerUsesCustomAuthorizationDecision() {
this.spring.register(MethodSecurityServiceConfig.class, CustomResultConfig.class).autowire();
ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class);
MethodAuthorizationDeniedHandler handler = this.spring.getContext()
.getBean(MethodAuthorizationDeniedHandler.class);
assertThat(service.checkCustomResult(false).block()).isNull();
verify(handler).handleDeniedInvocation(any(), any(Authz.AuthzResult.class));
verify(handler, never()).handleDeniedInvocationResult(any(), any(Authz.AuthzResult.class));
clearInvocations(handler);
assertThat(service.checkCustomResult(true).block()).isNull();
verify(handler).handleDeniedInvocationResult(any(), any(Authz.AuthzResult.class));
verify(handler, never()).handleDeniedInvocation(any(), any(Authz.AuthzResult.class));
}
private static Consumer<User.UserBuilder> authorities(String... authorities) {
return (builder) -> builder.authorities(authorities);
}
@Configuration
@EnableReactiveMethodSecurity // this imports ReactiveMethodSecurityConfiguration
static class WithRolePrefixConfiguration {
@@ -262,130 +108,4 @@ public class ReactiveMethodSecurityConfigurationTests {
}
@EnableReactiveMethodSecurity
@Configuration
static class AuthorizeResultConfig {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static Customizer<AuthorizationAdvisorProxyFactory> skipValueTypes() {
return (factory) -> factory.setTargetVisitor(TargetVisitor.defaultsSkipValueTypes());
}
@Bean
FlightRepository flights() {
FlightRepository flights = new FlightRepository();
Flight one = new Flight("1", 35000d, 35);
one.board(Flux.just("Marie Curie", "Kevin Mitnick", "Ada Lovelace")).block();
flights.save(one).block();
Flight two = new Flight("2", 32000d, 72);
two.board(Flux.just("Albert Einstein")).block();
flights.save(two).block();
return flights;
}
@Bean
Function<Passenger, Mono<Boolean>> isNotKevin() {
return (passenger) -> passenger.getName().map((name) -> !name.equals("Kevin Mitnick"));
}
}
@AuthorizeReturnObject
static class FlightRepository {
private final Map<String, Flight> flights = new ConcurrentHashMap<>();
Flux<Flight> findAll() {
return Flux.fromIterable(this.flights.values());
}
Mono<Flight> findById(String id) {
return Mono.just(this.flights.get(id));
}
Mono<Flight> save(Flight flight) {
this.flights.put(flight.getId(), flight);
return Mono.just(flight);
}
Mono<Void> remove(String id) {
this.flights.remove(id);
return Mono.empty();
}
}
@AuthorizeReturnObject
static class Flight {
private final String id;
private final Double altitude;
private final Integer seats;
private final List<Passenger> passengers = new ArrayList<>();
Flight(String id, Double altitude, Integer seats) {
this.id = id;
this.altitude = altitude;
this.seats = seats;
}
String getId() {
return this.id;
}
@PreAuthorize("hasAuthority('airplane:read')")
Mono<Double> getAltitude() {
return Mono.just(this.altitude);
}
@PreAuthorize("hasAnyAuthority('seating:read', 'airplane:read')")
Mono<Integer> getSeats() {
return Mono.just(this.seats);
}
@PostAuthorize("hasAnyAuthority('seating:read', 'airplane:read')")
@PostFilter("@isNotKevin.apply(filterObject)")
Flux<Passenger> getPassengers() {
return Flux.fromIterable(this.passengers);
}
@PreAuthorize("hasAnyAuthority('seating:read', 'airplane:read')")
@PreFilter("filterObject.contains(' ')")
Mono<Void> board(Flux<String> passengers) {
return passengers.doOnNext((passenger) -> this.passengers.add(new Passenger(passenger))).then();
}
}
public static class Passenger {
String name;
public Passenger(String name) {
this.name = name;
}
@PreAuthorize("hasAuthority('airplane:read')")
public Mono<String> getName() {
return Mono.just(this.name);
}
}
@EnableReactiveMethodSecurity
static class CustomResultConfig {
MethodAuthorizationDeniedHandler handler = mock(MethodAuthorizationDeniedHandler.class);
@Bean
MethodAuthorizationDeniedHandler methodAuthorizationDeniedHandler() {
return this.handler;
}
}
}
@@ -1,255 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.method.configuration;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.aopalliance.intercept.MethodInvocation;
import reactor.core.publisher.Mono;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.authorization.method.HandleAuthorizationDenied;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
import org.springframework.security.authorization.method.MethodInvocationResult;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.StringUtils;
/**
* @author Rob Winch
*/
@ReactiveMethodSecurityService.Mask("classmask")
public interface ReactiveMethodSecurityService {
@PreAuthorize("hasRole('ADMIN')")
@HandleAuthorizationDenied(handlerClass = StarMaskingHandler.class)
Mono<String> preAuthorizeGetCardNumberIfAdmin(String cardNumber);
@PreAuthorize("hasRole('ADMIN')")
@HandleAuthorizationDenied(handlerClass = StartMaskingHandlerChild.class)
Mono<String> preAuthorizeWithHandlerChildGetCardNumberIfAdmin(String cardNumber);
@PreAuthorize("hasRole('ADMIN')")
@HandleAuthorizationDenied(handlerClass = StarMaskingHandler.class)
Mono<String> preAuthorizeThrowAccessDeniedManually();
@PostAuthorize("hasRole('ADMIN')")
@HandleAuthorizationDenied(handlerClass = CardNumberMaskingPostProcessor.class)
Mono<String> postAuthorizeGetCardNumberIfAdmin(String cardNumber);
@PostAuthorize("hasRole('ADMIN')")
@HandleAuthorizationDenied(handlerClass = PostMaskingPostProcessor.class)
Mono<String> postAuthorizeThrowAccessDeniedManually();
@PreAuthorize("denyAll()")
@Mask("methodmask")
@HandleAuthorizationDenied(handlerClass = MaskAnnotationHandler.class)
Mono<String> preAuthorizeDeniedMethodWithMaskAnnotation();
@PreAuthorize("denyAll()")
@HandleAuthorizationDenied(handlerClass = MaskAnnotationHandler.class)
Mono<String> preAuthorizeDeniedMethodWithNoMaskAnnotation();
@NullDenied(role = "ADMIN")
Mono<String> postAuthorizeDeniedWithNullDenied();
@PostAuthorize("denyAll()")
@Mask("methodmask")
@HandleAuthorizationDenied(handlerClass = MaskAnnotationPostProcessor.class)
Mono<String> postAuthorizeDeniedMethodWithMaskAnnotation();
@PostAuthorize("denyAll()")
@HandleAuthorizationDenied(handlerClass = MaskAnnotationPostProcessor.class)
Mono<String> postAuthorizeDeniedMethodWithNoMaskAnnotation();
@PreAuthorize("hasRole('ADMIN')")
@Mask(expression = "@myMasker.getMask()")
@HandleAuthorizationDenied(handlerClass = MaskAnnotationHandler.class)
Mono<String> preAuthorizeWithMaskAnnotationUsingBean();
@PostAuthorize("hasRole('ADMIN')")
@Mask(expression = "@myMasker.getMask(returnObject)")
@HandleAuthorizationDenied(handlerClass = MaskAnnotationPostProcessor.class)
Mono<String> postAuthorizeWithMaskAnnotationUsingBean();
@PreAuthorize("@authz.checkReactiveResult(#result)")
@PostAuthorize("@authz.checkReactiveResult(!#result)")
@HandleAuthorizationDenied(handlerClass = MethodAuthorizationDeniedHandler.class)
Mono<String> checkCustomResult(boolean result);
class StarMaskingHandler implements MethodAuthorizationDeniedHandler {
@Override
public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult result) {
return "***";
}
}
class StartMaskingHandlerChild extends StarMaskingHandler {
@Override
public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult result) {
return super.handleDeniedInvocation(methodInvocation, result) + "-child";
}
}
class MaskAnnotationHandler implements MethodAuthorizationDeniedHandler {
MaskValueResolver maskValueResolver;
MaskAnnotationHandler(ApplicationContext context) {
this.maskValueResolver = new MaskValueResolver(context);
}
@Override
public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult result) {
Mask mask = AnnotationUtils.getAnnotation(methodInvocation.getMethod(), Mask.class);
if (mask == null) {
mask = AnnotationUtils.getAnnotation(methodInvocation.getMethod().getDeclaringClass(), Mask.class);
}
return this.maskValueResolver.resolveValue(mask, methodInvocation, null);
}
}
class MaskAnnotationPostProcessor implements MethodAuthorizationDeniedHandler {
MaskValueResolver maskValueResolver;
MaskAnnotationPostProcessor(ApplicationContext context) {
this.maskValueResolver = new MaskValueResolver(context);
}
@Override
public Object handleDeniedInvocation(MethodInvocation mi, AuthorizationResult authorizationResult) {
Mask mask = AnnotationUtils.getAnnotation(mi.getMethod(), Mask.class);
if (mask == null) {
mask = AnnotationUtils.getAnnotation(mi.getMethod().getDeclaringClass(), Mask.class);
}
return this.maskValueResolver.resolveValue(mask, mi, null);
}
@Override
public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult,
AuthorizationResult authorizationResult) {
MethodInvocation mi = methodInvocationResult.getMethodInvocation();
Mask mask = AnnotationUtils.getAnnotation(mi.getMethod(), Mask.class);
if (mask == null) {
mask = AnnotationUtils.getAnnotation(mi.getMethod().getDeclaringClass(), Mask.class);
}
return this.maskValueResolver.resolveValue(mask, mi, methodInvocationResult.getResult());
}
}
class MaskValueResolver {
DefaultMethodSecurityExpressionHandler expressionHandler;
MaskValueResolver(ApplicationContext context) {
this.expressionHandler = new DefaultMethodSecurityExpressionHandler();
this.expressionHandler.setApplicationContext(context);
}
Mono<String> resolveValue(Mask mask, MethodInvocation mi, Object returnObject) {
if (StringUtils.hasText(mask.value())) {
return Mono.just(mask.value());
}
Expression expression = this.expressionHandler.getExpressionParser().parseExpression(mask.expression());
EvaluationContext evaluationContext = this.expressionHandler
.createEvaluationContext(() -> SecurityContextHolder.getContext().getAuthentication(), mi);
if (returnObject != null) {
this.expressionHandler.setReturnObject(returnObject, evaluationContext);
}
return Mono.just(expression.getValue(evaluationContext, String.class));
}
}
class PostMaskingPostProcessor implements MethodAuthorizationDeniedHandler {
@Override
public Object handleDeniedInvocation(MethodInvocation methodInvocation,
AuthorizationResult authorizationResult) {
return "***";
}
}
class CardNumberMaskingPostProcessor implements MethodAuthorizationDeniedHandler {
static String MASK = "****-****-****-";
@Override
public Object handleDeniedInvocation(MethodInvocation methodInvocation,
AuthorizationResult authorizationResult) {
return "***";
}
@Override
public Object handleDeniedInvocationResult(MethodInvocationResult contextObject, AuthorizationResult result) {
String cardNumber = (String) contextObject.getResult();
return MASK + cardNumber.substring(cardNumber.length() - 4);
}
}
class NullPostProcessor implements MethodAuthorizationDeniedHandler {
@Override
public Object handleDeniedInvocation(MethodInvocation methodInvocation,
AuthorizationResult authorizationResult) {
return null;
}
}
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@interface Mask {
String value() default "";
String expression() default "";
}
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@PostAuthorize("hasRole('{value}')")
@HandleAuthorizationDenied(handlerClass = NullPostProcessor.class)
@interface NullDenied {
String role();
}
}
@@ -1,91 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.method.configuration;
import reactor.core.publisher.Mono;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationDeniedException;
public class ReactiveMethodSecurityServiceImpl implements ReactiveMethodSecurityService {
@Override
public Mono<String> preAuthorizeGetCardNumberIfAdmin(String cardNumber) {
return Mono.just(cardNumber);
}
@Override
public Mono<String> preAuthorizeWithHandlerChildGetCardNumberIfAdmin(String cardNumber) {
return Mono.just(cardNumber);
}
@Override
public Mono<String> preAuthorizeThrowAccessDeniedManually() {
return Mono.error(new AuthorizationDeniedException("Access Denied", new AuthorizationDecision(false)));
}
@Override
public Mono<String> postAuthorizeGetCardNumberIfAdmin(String cardNumber) {
return Mono.just(cardNumber);
}
@Override
public Mono<String> postAuthorizeThrowAccessDeniedManually() {
return Mono.error(new AuthorizationDeniedException("Access Denied", new AuthorizationDecision(false)));
}
@Override
public Mono<String> preAuthorizeDeniedMethodWithMaskAnnotation() {
return Mono.just("ok");
}
@Override
public Mono<String> preAuthorizeDeniedMethodWithNoMaskAnnotation() {
return Mono.just("ok");
}
@Override
public Mono<String> postAuthorizeDeniedWithNullDenied() {
return Mono.just("ok");
}
@Override
public Mono<String> postAuthorizeDeniedMethodWithMaskAnnotation() {
return Mono.just("ok");
}
@Override
public Mono<String> postAuthorizeDeniedMethodWithNoMaskAnnotation() {
return Mono.just("ok");
}
@Override
public Mono<String> preAuthorizeWithMaskAnnotationUsingBean() {
return Mono.just("ok");
}
@Override
public Mono<String> postAuthorizeWithMaskAnnotationUsingBean() {
return Mono.just("ok");
}
@Override
public Mono<String> checkCustomResult(boolean result) {
return Mono.just("ok");
}
}
@@ -1,65 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.method.configuration;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.authorization.method.HandleAuthorizationDenied;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
import org.springframework.security.authorization.method.MethodInvocationResult;
public class UserRecordWithEmailProtected {
private final String name;
private final String email;
public UserRecordWithEmailProtected(String name, String email) {
this.name = name;
this.email = email;
}
public String name() {
return this.name;
}
@PostAuthorize("hasRole('ADMIN')")
@HandleAuthorizationDenied(handlerClass = EmailMaskingPostProcessor.class)
public String email() {
return this.email;
}
public static class EmailMaskingPostProcessor implements MethodAuthorizationDeniedHandler {
@Override
public Object handleDeniedInvocation(MethodInvocation methodInvocation,
AuthorizationResult authorizationResult) {
return "***";
}
@Override
public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult,
AuthorizationResult authorizationResult) {
String email = (String) methodInvocationResult.getResult();
return email.replaceAll("(^[^@]{3}|(?!^)\\G)[^@]", "$1*");
}
}
}
@@ -1,68 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.method.configuration.issue14637;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.security.config.annotation.method.configuration.issue14637.domain.Entry;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
* @author Josh Cummings
*/
@Configuration
@EnableJpaRepositories("org.springframework.security.config.annotation.method.configuration.issue14637.repo")
@EnableTransactionManagement
public class ApplicationConfig {
@Bean
public DataSource dataSource() {
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
return builder.setType(EmbeddedDatabaseType.HSQL).build();
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setDatabase(Database.HSQL);
vendorAdapter.setGenerateDdl(true);
vendorAdapter.setShowSql(true);
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan(Entry.class.getPackage().getName());
factory.setDataSource(dataSource());
return factory;
}
@Bean
public PlatformTransactionManager transactionManager() {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory().getObject());
return txManager;
}
}
@@ -1,50 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.method.configuration.issue14637;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.config.annotation.method.configuration.issue14637.domain.Entry;
import org.springframework.security.config.annotation.method.configuration.issue14637.repo.EntryRepository;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* @author Josh Cummings
*/
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = { ApplicationConfig.class, SecurityConfig.class })
public class Issue14637Tests {
@Autowired
private EntryRepository entries;
@Test
@WithMockUser
public void authenticateWhenInvalidPasswordThenBadCredentialsException() {
Entry entry = new Entry();
entry.setId(123L);
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> this.entries.save(entry));
}
}
@@ -1,29 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.method.configuration.issue14637;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
/**
* @author Josh Cummings
*/
@Configuration
@EnableMethodSecurity
public class SecurityConfig {
}
@@ -1,42 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.method.configuration.issue14637.domain;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
/**
* @author Josh Cummings
*/
@Entity
public class Entry {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
}
@@ -1,31 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.method.configuration.issue14637.repo;
import org.springframework.data.repository.CrudRepository;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.config.annotation.method.configuration.issue14637.domain.Entry;
/**
* @author Josh Cummings
*/
public interface EntryRepository extends CrudRepository<Entry, String> {
@PreAuthorize("#entry.id == null")
Entry save(Entry entry);
}
@@ -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.
@@ -26,27 +26,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}.
@@ -167,18 +175,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
@@ -303,11 +358,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);
@@ -318,4 +375,11 @@ public class AbstractRequestMatcherRegistryTests {
}
@Configuration
@EnableWebSecurity
@EnableWebMvc
static class MockMvcConfiguration {
}
}
@@ -47,9 +47,6 @@ import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authentication.event.AbstractAuthenticationEvent;
import org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent;
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
import org.springframework.security.authentication.password.CompromisedPasswordChecker;
import org.springframework.security.authentication.password.CompromisedPasswordDecision;
import org.springframework.security.authentication.password.CompromisedPasswordException;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@@ -63,8 +60,8 @@ import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.provisioning.UserDetailsManager;
import org.springframework.security.test.web.servlet.RequestCacheResultMatcher;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@@ -398,41 +395,6 @@ public class HttpSecurityConfigurationTests {
this.mockMvc.perform(formLogin()).andExpectAll(status().isNotFound(), unauthenticated());
}
@Test
void loginWhenCompromisePasswordCheckerConfiguredAndPasswordCompromisedThenUnauthorized() throws Exception {
this.spring
.register(SecurityEnabledConfig.class, UserDetailsConfig.class, CompromisedPasswordCheckerConfig.class)
.autowire();
this.mockMvc.perform(formLogin().password("password"))
.andExpectAll(status().isFound(), redirectedUrl("/login?error"), unauthenticated());
}
@Test
void loginWhenCompromisedPasswordAndRedirectIfPasswordExceptionThenRedirectedToResetPassword() throws Exception {
this.spring
.register(SecurityEnabledRedirectIfPasswordExceptionConfig.class, UserDetailsConfig.class,
CompromisedPasswordCheckerConfig.class)
.autowire();
this.mockMvc.perform(formLogin().password("password"))
.andExpectAll(status().isFound(), redirectedUrl("/reset-password"), unauthenticated());
}
@Test
void loginWhenCompromisePasswordCheckerConfiguredAndPasswordNotCompromisedThenSuccess() throws Exception {
this.spring
.register(SecurityEnabledConfig.class, UserDetailsConfig.class, CompromisedPasswordCheckerConfig.class)
.autowire();
UserDetailsManager userDetailsManager = this.spring.getContext().getBean(UserDetailsManager.class);
UserDetails notCompromisedPwUser = User.withDefaultPasswordEncoder()
.username("user2")
.password("password2")
.roles("USER")
.build();
userDetailsManager.createUser(notCompromisedPwUser);
this.mockMvc.perform(formLogin().user("user2").password("password2"))
.andExpectAll(status().isFound(), redirectedUrl("/"), authenticated());
}
@RestController
static class NameController {
@@ -493,7 +455,7 @@ public class HttpSecurityConfigurationTests {
static class UserDetailsConfig {
@Bean
InMemoryUserDetailsManager userDetailsService() {
UserDetailsService userDetailsService() {
// @formatter:off
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
@@ -770,52 +732,4 @@ public class HttpSecurityConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
static class CompromisedPasswordCheckerConfig {
@Bean
TestCompromisedPasswordChecker compromisedPasswordChecker() {
return new TestCompromisedPasswordChecker();
}
}
@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
static class SecurityEnabledRedirectIfPasswordExceptionConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
return http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.formLogin((form) -> form
.failureHandler((request, response, exception) -> {
if (exception instanceof CompromisedPasswordException) {
response.sendRedirect("/reset-password");
return;
}
response.sendRedirect("/login?error");
})
)
.build();
// @formatter:on
}
}
private static class TestCompromisedPasswordChecker implements CompromisedPasswordChecker {
@Override
public CompromisedPasswordDecision check(String password) {
if ("password".equals(password)) {
return new CompromisedPasswordDecision(true);
}
return new CompromisedPasswordDecision(false);
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -38,6 +38,8 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.oauth2.client.CommonOAuth2Provider;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.oauth2.client.AuthorizationCodeOAuth2AuthorizedClientProvider;
@@ -50,7 +52,6 @@ import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.PasswordOAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.RefreshTokenOAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.TokenExchangeOAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.endpoint.AbstractOAuth2AuthorizationGrantRequest;
import org.springframework.security.oauth2.client.endpoint.JwtBearerGrantRequest;
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
@@ -58,14 +59,17 @@ import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCo
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest;
import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest;
import org.springframework.security.oauth2.client.endpoint.TokenExchangeGrantRequest;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
import org.springframework.security.oauth2.core.OAuth2Error;
@@ -73,10 +77,13 @@ import org.springframework.security.oauth2.core.TestOAuth2RefreshTokens;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.oauth2.jwt.JoseHeaderNames;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtClaimNames;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.util.StringUtils;
import static org.assertj.core.api.Assertions.assertThat;
@@ -320,47 +327,6 @@ public class OAuth2AuthorizedClientManagerConfigurationTests {
assertThat(grantRequest.getJwt().getSubject()).isEqualTo("user");
}
@Test
public void authorizeWhenTokenExchangeAccessTokenResponseClientBeanThenUsed() {
this.spring.register(CustomAccessTokenResponseClientsConfig.class).autowire();
testTokenExchangeGrant();
}
@Test
public void authorizeWhenTokenExchangeAuthorizedClientProviderBeanThenUsed() {
this.spring.register(CustomAuthorizedClientProvidersConfig.class).autowire();
testTokenExchangeGrant();
}
private void testTokenExchangeGrant() {
OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build();
given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(TokenExchangeGrantRequest.class)))
.willReturn(accessTokenResponse);
JwtAuthenticationToken authentication = new JwtAuthenticationToken(getJwt());
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("auth0");
// @formatter:off
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
.withClientRegistrationId(clientRegistration.getRegistrationId())
.principal(authentication)
.attribute(HttpServletRequest.class.getName(), this.request)
.attribute(HttpServletResponse.class.getName(), this.response)
.build();
// @formatter:on
OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest);
assertThat(authorizedClient).isNotNull();
ArgumentCaptor<TokenExchangeGrantRequest> grantRequestCaptor = ArgumentCaptor
.forClass(TokenExchangeGrantRequest.class);
verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture());
TokenExchangeGrantRequest grantRequest = grantRequestCaptor.getValue();
assertThat(grantRequest.getClientRegistration().getRegistrationId())
.isEqualTo(clientRegistration.getRegistrationId());
assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE);
assertThat(grantRequest.getSubjectToken()).isEqualTo(authentication.getToken());
}
private static OAuth2AccessToken getExpiredAccessToken() {
Instant expiresAt = Instant.now().minusSeconds(60);
Instant issuedAt = expiresAt.minus(Duration.ofDays(1));
@@ -387,32 +353,37 @@ public class OAuth2AuthorizedClientManagerConfigurationTests {
@Bean
OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> authorizationCodeTokenResponseClient() {
return new MockAccessTokenResponseClient<>();
return new MockAuthorizationCodeClient();
}
@Bean
OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> refreshTokenTokenResponseClient() {
return new MockAccessTokenResponseClient<>();
return new MockRefreshTokenClient();
}
@Bean
OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsTokenResponseClient() {
return new MockAccessTokenResponseClient<>();
return new MockClientCredentialsClient();
}
@Bean
OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> passwordTokenResponseClient() {
return new MockAccessTokenResponseClient<>();
return new MockPasswordClient();
}
@Bean
OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> jwtBearerTokenResponseClient() {
return new MockAccessTokenResponseClient<>();
return new MockJwtBearerClient();
}
@Bean
OAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> tokenExchangeTokenResponseClient() {
return new MockAccessTokenResponseClient<>();
OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService() {
return mock(DefaultOAuth2UserService.class);
}
@Bean
OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
return mock(OidcUserService.class);
}
}
@@ -429,35 +400,28 @@ public class OAuth2AuthorizedClientManagerConfigurationTests {
@Bean
RefreshTokenOAuth2AuthorizedClientProvider refreshTokenProvider() {
RefreshTokenOAuth2AuthorizedClientProvider authorizedClientProvider = new RefreshTokenOAuth2AuthorizedClientProvider();
authorizedClientProvider.setAccessTokenResponseClient(new MockAccessTokenResponseClient<>());
authorizedClientProvider.setAccessTokenResponseClient(new MockRefreshTokenClient());
return authorizedClientProvider;
}
@Bean
ClientCredentialsOAuth2AuthorizedClientProvider clientCredentialsProvider() {
ClientCredentialsOAuth2AuthorizedClientProvider authorizedClientProvider = new ClientCredentialsOAuth2AuthorizedClientProvider();
authorizedClientProvider.setAccessTokenResponseClient(new MockAccessTokenResponseClient<>());
authorizedClientProvider.setAccessTokenResponseClient(new MockClientCredentialsClient());
return authorizedClientProvider;
}
@Bean
PasswordOAuth2AuthorizedClientProvider passwordProvider() {
PasswordOAuth2AuthorizedClientProvider authorizedClientProvider = new PasswordOAuth2AuthorizedClientProvider();
authorizedClientProvider.setAccessTokenResponseClient(new MockAccessTokenResponseClient<>());
authorizedClientProvider.setAccessTokenResponseClient(new MockPasswordClient());
return authorizedClientProvider;
}
@Bean
JwtBearerOAuth2AuthorizedClientProvider jwtBearerAuthorizedClientProvider() {
JwtBearerOAuth2AuthorizedClientProvider authorizedClientProvider = new JwtBearerOAuth2AuthorizedClientProvider();
authorizedClientProvider.setAccessTokenResponseClient(new MockAccessTokenResponseClient<>());
return authorizedClientProvider;
}
@Bean
TokenExchangeOAuth2AuthorizedClientProvider tokenExchangeAuthorizedClientProvider() {
TokenExchangeOAuth2AuthorizedClientProvider authorizedClientProvider = new TokenExchangeOAuth2AuthorizedClientProvider();
authorizedClientProvider.setAccessTokenResponseClient(new MockAccessTokenResponseClient<>());
authorizedClientProvider.setAccessTokenResponseClient(new MockJwtBearerClient());
return authorizedClientProvider;
}
@@ -465,10 +429,21 @@ public class OAuth2AuthorizedClientManagerConfigurationTests {
abstract static class OAuth2ClientBaseConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated())
.oauth2Login(Customizer.withDefaults())
.oauth2Client(Customizer.withDefaults());
return http.build();
// @formatter:on
}
@Bean
ClientRegistrationRepository clientRegistrationRepository() {
// @formatter:off
return new InMemoryClientRegistrationRepository(
return new InMemoryClientRegistrationRepository(Arrays.asList(
CommonOAuth2Provider.GOOGLE.getBuilder("google")
.clientId("google-client-id")
.clientSecret("google-client-secret")
@@ -488,15 +463,7 @@ public class OAuth2AuthorizedClientManagerConfigurationTests {
.clientId("okta-client-id")
.clientSecret("okta-client-secret")
.authorizationGrantType(AuthorizationGrantType.JWT_BEARER)
.build(),
ClientRegistration.withRegistrationId("auth0")
.clientName("Auth0")
.clientId("auth0-client-id")
.clientSecret("auth0-client-secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE)
.scope("user.read", "user.write")
.build());
.build()));
// @formatter:on
}
@@ -527,11 +494,51 @@ public class OAuth2AuthorizedClientManagerConfigurationTests {
}
private static class MockAccessTokenResponseClient<T extends AbstractOAuth2AuthorizationGrantRequest>
implements OAuth2AccessTokenResponseClient<T> {
private static class MockAuthorizationCodeClient
implements OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
@Override
public OAuth2AccessTokenResponse getTokenResponse(T authorizationGrantRequest) {
public OAuth2AccessTokenResponse getTokenResponse(
OAuth2AuthorizationCodeGrantRequest authorizationGrantRequest) {
return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest);
}
}
private static class MockRefreshTokenClient
implements OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> {
@Override
public OAuth2AccessTokenResponse getTokenResponse(OAuth2RefreshTokenGrantRequest authorizationGrantRequest) {
return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest);
}
}
private static class MockClientCredentialsClient
implements OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> {
@Override
public OAuth2AccessTokenResponse getTokenResponse(
OAuth2ClientCredentialsGrantRequest authorizationGrantRequest) {
return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest);
}
}
private static class MockPasswordClient implements OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> {
@Override
public OAuth2AccessTokenResponse getTokenResponse(OAuth2PasswordGrantRequest authorizationGrantRequest) {
return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest);
}
}
private static class MockJwtBearerClient implements OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> {
@Override
public OAuth2AccessTokenResponse getTokenResponse(JwtBearerGrantRequest authorizationGrantRequest) {
return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest);
}
@@ -40,10 +40,8 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
import org.springframework.security.config.core.GrantedAuthorityDefaults;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
@@ -545,17 +543,6 @@ public class AuthorizeHttpRequestsConfigurerTests {
this.mvc.perform(request).andExpect(status().isOk());
request = get("/user/deny");
this.mvc.perform(request).andExpect(status().isUnauthorized());
UserDetails user = TestAuthentication.withUsername("taehong").build();
Authentication authentication = TestAuthentication.authenticated(user);
request = get("/v2/user/{username}", user.getUsername()).with(authentication(authentication));
this.mvc.perform(request).andExpect(status().isOk());
request = get("/v2/user/{username}", "withNoAuthentication");
this.mvc.perform(request).andExpect(status().isUnauthorized());
request = get("/v2/user/{username}", "another").with(authentication(authentication));
this.mvc.perform(request).andExpect(status().isForbidden());
}
private static RequestPostProcessor remoteAddress(String remoteAddress) {
@@ -609,20 +596,6 @@ public class AuthorizeHttpRequestsConfigurerTests {
this.mvc.perform(requestWithUser).andExpect(status().isForbidden());
}
@Test
public void getWhenNotConfigAndAuthenticatedThenRespondsWithForbidden() throws Exception {
this.spring.register(NotConfig.class, BasicController.class).autowire();
MockHttpServletRequestBuilder requestWithUser = get("/").with(user("user"));
this.mvc.perform(requestWithUser).andExpect(status().isForbidden());
}
@Test
public void getWhenNotConfigAndNotAuthenticatedThenRespondsWithOk() throws Exception {
this.spring.register(NotConfig.class, BasicController.class).autowire();
MockHttpServletRequestBuilder requestWithUser = get("/");
this.mvc.perform(requestWithUser).andExpect(status().isOk());
}
@Configuration
@EnableWebSecurity
static class GrantedAuthorityDefaultHasRoleConfig {
@@ -1080,7 +1053,6 @@ public class AuthorizeHttpRequestsConfigurerTests {
.httpBasic(withDefaults())
.authorizeHttpRequests((requests) -> requests
.requestMatchers("/user/{username}").access(new WebExpressionAuthorizationManager("#username == 'user'"))
.requestMatchers("/v2/user/{username}").hasVariable("username").equalTo(Authentication::getName)
);
// @formatter:on
return http.build();
@@ -1094,11 +1066,6 @@ public class AuthorizeHttpRequestsConfigurerTests {
return username;
}
@RequestMapping("/v2/user/{username}")
String pathV2(@PathVariable("username") String username) {
return username;
}
}
}
@@ -1169,24 +1136,6 @@ public class AuthorizeHttpRequestsConfigurerTests {
}
@Configuration
@EnableWebSecurity
static class NotConfig {
@Bean
SecurityFilterChain chain(HttpSecurity http) throws Exception {
// @formatter:off
http
.httpBasic(withDefaults())
.authorizeHttpRequests((requests) -> requests
.anyRequest().not().authenticated()
);
// @formatter:on
return http.build();
}
}
@Configuration
static class AuthorizationEventPublisherConfig {
@@ -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.
@@ -78,7 +78,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,589 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.reactive;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.config.oauth2.client.CommonOAuth2Provider;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.oauth2.client.AuthorizationCodeReactiveOAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.ClientAuthorizationRequiredException;
import org.springframework.security.oauth2.client.ClientCredentialsReactiveOAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.JwtBearerReactiveOAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.OAuth2AuthorizationContext;
import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.PasswordReactiveOAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.RefreshTokenReactiveOAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.TokenExchangeReactiveOAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.endpoint.AbstractOAuth2AuthorizationGrantRequest;
import org.springframework.security.oauth2.client.endpoint.JwtBearerGrantRequest;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest;
import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest;
import org.springframework.security.oauth2.client.endpoint.ReactiveOAuth2AccessTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.TokenExchangeGrantRequest;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.DefaultReactiveOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.client.web.server.WebSessionServerOAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.TestOAuth2RefreshTokens;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses;
import org.springframework.security.oauth2.jwt.JoseHeaderNames;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtClaimNames;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
/**
* Tests for
* {@link ReactiveOAuth2ClientConfiguration.ReactiveOAuth2AuthorizedClientManagerConfiguration}.
*
* @author Steve Riesenberg
*/
public class ReactiveOAuth2AuthorizedClientManagerConfigurationTests {
private static ReactiveOAuth2AccessTokenResponseClient<? super AbstractOAuth2AuthorizationGrantRequest> MOCK_RESPONSE_CLIENT;
public final SpringTestContext spring = new SpringTestContext(this);
@Autowired
private ReactiveOAuth2AuthorizedClientManager authorizedClientManager;
@Autowired
private ReactiveClientRegistrationRepository clientRegistrationRepository;
@Autowired(required = false)
private ServerOAuth2AuthorizedClientRepository authorizedClientRepository;
@Autowired(required = false)
private ReactiveOAuth2AuthorizedClientService authorizedClientService;
@Autowired(required = false)
private AuthorizationCodeReactiveOAuth2AuthorizedClientProvider authorizationCodeAuthorizedClientProvider;
private MockServerWebExchange exchange;
@BeforeEach
@SuppressWarnings("unchecked")
public void setUp() {
MOCK_RESPONSE_CLIENT = mock(ReactiveOAuth2AccessTokenResponseClient.class);
MockServerHttpRequest request = MockServerHttpRequest.get("/").build();
this.exchange = MockServerWebExchange.builder(request).build();
}
@Test
public void loadContextWhenOAuth2ClientEnabledThenConfigured() {
this.spring.register(MinimalOAuth2ClientConfig.class).autowire();
assertThat(this.authorizedClientManager).isNotNull();
}
@Test
public void authorizeWhenAuthorizationCodeAuthorizedClientProviderBeanThenUsed() {
this.spring.register(CustomAuthorizedClientProvidersConfig.class).autowire();
TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", null, "ROLE_USER");
// @formatter:off
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
.withClientRegistrationId("google")
.principal(authentication)
.attribute(ServerWebExchange.class.getName(), this.exchange)
.build();
assertThatExceptionOfType(ClientAuthorizationRequiredException.class)
.isThrownBy(() -> this.authorizedClientManager.authorize(authorizeRequest).block())
.extracting(OAuth2AuthorizationException::getError)
.extracting(OAuth2Error::getErrorCode)
.isEqualTo("client_authorization_required");
// @formatter:on
verify(this.authorizationCodeAuthorizedClientProvider).authorize(any(OAuth2AuthorizationContext.class));
}
@Test
public void authorizeWhenAuthorizedClientServiceBeanThenUsed() {
this.spring.register(CustomAuthorizedClientServiceConfig.class).autowire();
TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", null, "ROLE_USER");
// @formatter:off
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
.withClientRegistrationId("google")
.principal(authentication)
.attribute(ServerWebExchange.class.getName(), this.exchange)
.build();
assertThatExceptionOfType(ClientAuthorizationRequiredException.class)
.isThrownBy(() -> this.authorizedClientManager.authorize(authorizeRequest).block())
.extracting(OAuth2AuthorizationException::getError)
.extracting(OAuth2Error::getErrorCode)
.isEqualTo("client_authorization_required");
// @formatter:on
verify(this.authorizedClientService).loadAuthorizedClient(authorizeRequest.getClientRegistrationId(),
authentication.getName());
}
@Test
public void authorizeWhenRefreshTokenAccessTokenResponseClientBeanThenUsed() {
this.spring.register(CustomAccessTokenResponseClientsConfig.class).autowire();
testRefreshTokenGrant();
}
@Test
public void authorizeWhenRefreshTokenAuthorizedClientProviderBeanThenUsed() {
this.spring.register(CustomAuthorizedClientProvidersConfig.class).autowire();
testRefreshTokenGrant();
}
private void testRefreshTokenGrant() {
OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build();
given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(OAuth2RefreshTokenGrantRequest.class)))
.willReturn(Mono.just(accessTokenResponse));
TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", null, "ROLE_USER");
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("google")
.block();
assertThat(clientRegistration).isNotNull();
OAuth2AuthorizedClient existingAuthorizedClient = new OAuth2AuthorizedClient(clientRegistration,
authentication.getName(), getExpiredAccessToken(), TestOAuth2RefreshTokens.refreshToken());
this.authorizedClientRepository.saveAuthorizedClient(existingAuthorizedClient, authentication, this.exchange)
.block();
// @formatter:off
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
.withClientRegistrationId(clientRegistration.getRegistrationId())
.principal(authentication)
.attribute(ServerWebExchange.class.getName(), this.exchange)
.build();
// @formatter:on
OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest).block();
assertThat(authorizedClient).isNotNull();
ArgumentCaptor<OAuth2RefreshTokenGrantRequest> grantRequestCaptor = ArgumentCaptor
.forClass(OAuth2RefreshTokenGrantRequest.class);
verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture());
OAuth2RefreshTokenGrantRequest grantRequest = grantRequestCaptor.getValue();
assertThat(grantRequest.getClientRegistration().getRegistrationId())
.isEqualTo(clientRegistration.getRegistrationId());
assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.REFRESH_TOKEN);
assertThat(grantRequest.getAccessToken()).isEqualTo(existingAuthorizedClient.getAccessToken());
assertThat(grantRequest.getRefreshToken()).isEqualTo(existingAuthorizedClient.getRefreshToken());
}
@Test
public void authorizeWhenClientCredentialsAccessTokenResponseClientBeanThenUsed() {
this.spring.register(CustomAccessTokenResponseClientsConfig.class).autowire();
testClientCredentialsGrant();
}
@Test
public void authorizeWhenClientCredentialsAuthorizedClientProviderBeanThenUsed() {
this.spring.register(CustomAuthorizedClientProvidersConfig.class).autowire();
testClientCredentialsGrant();
}
private void testClientCredentialsGrant() {
OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build();
given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(OAuth2ClientCredentialsGrantRequest.class)))
.willReturn(Mono.just(accessTokenResponse));
TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", null, "ROLE_USER");
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("github")
.block();
assertThat(clientRegistration).isNotNull();
// @formatter:off
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
.withClientRegistrationId(clientRegistration.getRegistrationId())
.principal(authentication)
.attribute(ServerWebExchange.class.getName(), this.exchange)
.build();
// @formatter:on
OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest).block();
assertThat(authorizedClient).isNotNull();
ArgumentCaptor<OAuth2ClientCredentialsGrantRequest> grantRequestCaptor = ArgumentCaptor
.forClass(OAuth2ClientCredentialsGrantRequest.class);
verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture());
OAuth2ClientCredentialsGrantRequest grantRequest = grantRequestCaptor.getValue();
assertThat(grantRequest.getClientRegistration().getRegistrationId())
.isEqualTo(clientRegistration.getRegistrationId());
assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.CLIENT_CREDENTIALS);
}
@Test
public void authorizeWhenPasswordAccessTokenResponseClientBeanThenUsed() {
this.spring.register(CustomAccessTokenResponseClientsConfig.class).autowire();
testPasswordGrant();
}
@Test
public void authorizeWhenPasswordAuthorizedClientProviderBeanThenUsed() {
this.spring.register(CustomAuthorizedClientProvidersConfig.class).autowire();
testPasswordGrant();
}
private void testPasswordGrant() {
OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build();
given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(OAuth2PasswordGrantRequest.class)))
.willReturn(Mono.just(accessTokenResponse));
TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", "password", "ROLE_USER");
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("facebook")
.block();
assertThat(clientRegistration).isNotNull();
MockServerHttpRequest request = MockServerHttpRequest.post("/")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body("username=user&password=password");
this.exchange = MockServerWebExchange.builder(request).build();
// @formatter:off
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
.withClientRegistrationId(clientRegistration.getRegistrationId())
.principal(authentication)
.attribute(ServerWebExchange.class.getName(), this.exchange)
.build();
// @formatter:on
OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest).block();
assertThat(authorizedClient).isNotNull();
ArgumentCaptor<OAuth2PasswordGrantRequest> grantRequestCaptor = ArgumentCaptor
.forClass(OAuth2PasswordGrantRequest.class);
verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture());
OAuth2PasswordGrantRequest grantRequest = grantRequestCaptor.getValue();
assertThat(grantRequest.getClientRegistration().getRegistrationId())
.isEqualTo(clientRegistration.getRegistrationId());
assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.PASSWORD);
assertThat(grantRequest.getUsername()).isEqualTo("user");
assertThat(grantRequest.getPassword()).isEqualTo("password");
}
@Test
public void authorizeWhenJwtBearerAccessTokenResponseClientBeanThenUsed() {
this.spring.register(CustomAccessTokenResponseClientsConfig.class).autowire();
testJwtBearerGrant();
}
@Test
public void authorizeWhenJwtBearerAuthorizedClientProviderBeanThenUsed() {
this.spring.register(CustomAuthorizedClientProvidersConfig.class).autowire();
testJwtBearerGrant();
}
private void testJwtBearerGrant() {
OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build();
given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(JwtBearerGrantRequest.class)))
.willReturn(Mono.just(accessTokenResponse));
JwtAuthenticationToken authentication = new JwtAuthenticationToken(getJwt());
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("okta").block();
assertThat(clientRegistration).isNotNull();
// @formatter:off
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
.withClientRegistrationId(clientRegistration.getRegistrationId())
.principal(authentication)
.attribute(ServerWebExchange.class.getName(), this.exchange)
.build();
// @formatter:on
OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest).block();
assertThat(authorizedClient).isNotNull();
ArgumentCaptor<JwtBearerGrantRequest> grantRequestCaptor = ArgumentCaptor.forClass(JwtBearerGrantRequest.class);
verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture());
JwtBearerGrantRequest grantRequest = grantRequestCaptor.getValue();
assertThat(grantRequest.getClientRegistration().getRegistrationId())
.isEqualTo(clientRegistration.getRegistrationId());
assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.JWT_BEARER);
assertThat(grantRequest.getJwt().getSubject()).isEqualTo("user");
}
@Test
public void authorizeWhenTokenExchangeAccessTokenResponseClientBeanThenUsed() {
this.spring.register(CustomAccessTokenResponseClientsConfig.class).autowire();
testTokenExchangeGrant();
}
@Test
public void authorizeWhenTokenExchangeAuthorizedClientProviderBeanThenUsed() {
this.spring.register(CustomAuthorizedClientProvidersConfig.class).autowire();
testTokenExchangeGrant();
}
private void testTokenExchangeGrant() {
OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build();
given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(TokenExchangeGrantRequest.class)))
.willReturn(Mono.just(accessTokenResponse));
JwtAuthenticationToken authentication = new JwtAuthenticationToken(getJwt());
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("auth0").block();
assertThat(clientRegistration).isNotNull();
// @formatter:off
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
.withClientRegistrationId(clientRegistration.getRegistrationId())
.principal(authentication)
.attribute(ServerWebExchange.class.getName(), this.exchange)
.build();
// @formatter:on
OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest).block();
assertThat(authorizedClient).isNotNull();
ArgumentCaptor<TokenExchangeGrantRequest> grantRequestCaptor = ArgumentCaptor
.forClass(TokenExchangeGrantRequest.class);
verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture());
TokenExchangeGrantRequest grantRequest = grantRequestCaptor.getValue();
assertThat(grantRequest.getClientRegistration().getRegistrationId())
.isEqualTo(clientRegistration.getRegistrationId());
assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE);
assertThat(grantRequest.getSubjectToken()).isEqualTo(authentication.getToken());
}
private static OAuth2AccessToken getExpiredAccessToken() {
Instant expiresAt = Instant.now().minusSeconds(60);
Instant issuedAt = expiresAt.minus(Duration.ofDays(1));
return new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "scopes", issuedAt, expiresAt,
new HashSet<>(Arrays.asList("read", "write")));
}
private static Jwt getJwt() {
Instant issuedAt = Instant.now();
return new Jwt("token", issuedAt, issuedAt.plusSeconds(300),
Collections.singletonMap(JoseHeaderNames.ALG, "RS256"),
Collections.singletonMap(JwtClaimNames.SUB, "user"));
}
@Configuration
@EnableWebFluxSecurity
static class MinimalOAuth2ClientConfig extends OAuth2ClientBaseConfig {
@Bean
ServerOAuth2AuthorizedClientRepository authorizedClientRepository() {
return new WebSessionServerOAuth2AuthorizedClientRepository();
}
}
@Configuration
@EnableWebFluxSecurity
static class CustomAuthorizedClientServiceConfig extends OAuth2ClientBaseConfig {
@Bean
ReactiveOAuth2AuthorizedClientService authorizedClientService() {
ReactiveOAuth2AuthorizedClientService authorizedClientService = mock(
ReactiveOAuth2AuthorizedClientService.class);
given(authorizedClientService.loadAuthorizedClient(anyString(), anyString())).willReturn(Mono.empty());
return authorizedClientService;
}
}
@Configuration
@EnableWebFluxSecurity
static class CustomAccessTokenResponseClientsConfig extends MinimalOAuth2ClientConfig {
@Bean
ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> authorizationCodeAccessTokenResponseClient() {
return new MockAccessTokenResponseClient<>();
}
@Bean
ReactiveOAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> refreshTokenTokenAccessResponseClient() {
return new MockAccessTokenResponseClient<>();
}
@Bean
ReactiveOAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsAccessTokenResponseClient() {
return new MockAccessTokenResponseClient<>();
}
@Bean
ReactiveOAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> passwordAccessTokenResponseClient() {
return new MockAccessTokenResponseClient<>();
}
@Bean
ReactiveOAuth2AccessTokenResponseClient<JwtBearerGrantRequest> jwtBearerAccessTokenResponseClient() {
return new MockAccessTokenResponseClient<>();
}
@Bean
ReactiveOAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> tokenExchangeAccessTokenResponseClient() {
return new MockAccessTokenResponseClient<>();
}
}
@Configuration
@EnableWebFluxSecurity
static class CustomAuthorizedClientProvidersConfig extends MinimalOAuth2ClientConfig {
@Bean
AuthorizationCodeReactiveOAuth2AuthorizedClientProvider authorizationCode() {
return spy(new AuthorizationCodeReactiveOAuth2AuthorizedClientProvider());
}
@Bean
RefreshTokenReactiveOAuth2AuthorizedClientProvider refreshToken() {
RefreshTokenReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = new RefreshTokenReactiveOAuth2AuthorizedClientProvider();
authorizedClientProvider.setAccessTokenResponseClient(new MockAccessTokenResponseClient<>());
return authorizedClientProvider;
}
@Bean
ClientCredentialsReactiveOAuth2AuthorizedClientProvider clientCredentials() {
ClientCredentialsReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = new ClientCredentialsReactiveOAuth2AuthorizedClientProvider();
authorizedClientProvider.setAccessTokenResponseClient(new MockAccessTokenResponseClient<>());
return authorizedClientProvider;
}
@Bean
PasswordReactiveOAuth2AuthorizedClientProvider password() {
PasswordReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = new PasswordReactiveOAuth2AuthorizedClientProvider();
authorizedClientProvider.setAccessTokenResponseClient(new MockAccessTokenResponseClient<>());
return authorizedClientProvider;
}
@Bean
JwtBearerReactiveOAuth2AuthorizedClientProvider jwtBearer() {
JwtBearerReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = new JwtBearerReactiveOAuth2AuthorizedClientProvider();
authorizedClientProvider.setAccessTokenResponseClient(new MockAccessTokenResponseClient<>());
return authorizedClientProvider;
}
@Bean
TokenExchangeReactiveOAuth2AuthorizedClientProvider tokenExchange() {
TokenExchangeReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = new TokenExchangeReactiveOAuth2AuthorizedClientProvider();
authorizedClientProvider.setAccessTokenResponseClient(new MockAccessTokenResponseClient<>());
return authorizedClientProvider;
}
}
abstract static class OAuth2ClientBaseConfig {
@Bean
ReactiveClientRegistrationRepository clientRegistrationRepository() {
// @formatter:off
return new InMemoryReactiveClientRegistrationRepository(
CommonOAuth2Provider.GOOGLE.getBuilder("google")
.clientId("google-client-id")
.clientSecret("google-client-secret")
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.build(),
CommonOAuth2Provider.GITHUB.getBuilder("github")
.clientId("github-client-id")
.clientSecret("github-client-secret")
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.build(),
CommonOAuth2Provider.FACEBOOK.getBuilder("facebook")
.clientId("facebook-client-id")
.clientSecret("facebook-client-secret")
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
.build(),
CommonOAuth2Provider.OKTA.getBuilder("okta")
.clientId("okta-client-id")
.clientSecret("okta-client-secret")
.authorizationGrantType(AuthorizationGrantType.JWT_BEARER)
.build(),
ClientRegistration.withRegistrationId("auth0")
.clientName("Auth0")
.clientId("auth0-client-id")
.clientSecret("auth0-client-secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE)
.scope("user.read", "user.write")
.build());
// @formatter:on
}
@Bean
Consumer<DefaultReactiveOAuth2AuthorizedClientManager> authorizedClientManagerConsumer() {
return (authorizedClientManager) -> authorizedClientManager
.setContextAttributesMapper((authorizeRequest) -> {
ServerWebExchange exchange = Objects
.requireNonNull(authorizeRequest.getAttribute(ServerWebExchange.class.getName()));
return exchange.getFormData().map((parameters) -> {
String username = parameters.getFirst(OAuth2ParameterNames.USERNAME);
String password = parameters.getFirst(OAuth2ParameterNames.PASSWORD);
Map<String, Object> attributes = Collections.emptyMap();
if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
attributes = new HashMap<>();
attributes.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, username);
attributes.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, password);
}
return attributes;
});
});
}
}
private static class MockAccessTokenResponseClient<T extends AbstractOAuth2AuthorizationGrantRequest>
implements ReactiveOAuth2AccessTokenResponseClient<T> {
@Override
public Mono<OAuth2AccessTokenResponse> getTokenResponse(T grantRequest) {
return MOCK_RESPONSE_CLIENT.getTokenResponse(grantRequest);
}
}
}
@@ -16,39 +16,16 @@
package org.springframework.security.config.annotation.web.reactive;
import java.net.URI;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.password.CompromisedPasswordDecision;
import org.springframework.security.authentication.password.CompromisedPasswordException;
import org.springframework.security.authentication.password.ReactiveCompromisedPasswordChecker;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.config.users.ReactiveAuthenticationTestConfiguration;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
import org.springframework.security.core.userdetails.PasswordEncodedUser;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.server.DefaultServerRedirectStrategy;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.config.EnableWebFlux;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.csrf;
/**
* Tests for {@link ServerHttpSecurityConfiguration}.
@@ -60,16 +37,6 @@ public class ServerHttpSecurityConfigurationTests {
public final SpringTestContext spring = new SpringTestContext(this);
WebTestClient webClient;
@Autowired
void setup(ApplicationContext context) {
if (!context.containsBean(WebHttpHandlerBuilder.WEB_HANDLER_BEAN_NAME)) {
return;
}
this.webClient = WebTestClient.bindToApplicationContext(context).configureClient().build();
}
@Test
public void loadConfigWhenReactiveUserDetailsServiceConfiguredThenServerHttpSecurityExists() {
this.spring
@@ -90,151 +57,9 @@ public class ServerHttpSecurityConfigurationTests {
assertThat(serverHttpSecurity).isNotNull();
}
@Test
void loginWhenCompromisePasswordCheckerConfiguredAndPasswordCompromisedThenUnauthorized() {
this.spring.register(FormLoginConfig.class, UserDetailsConfig.class, CompromisedPasswordCheckerConfig.class)
.autowire();
MultiValueMap<String, String> data = new LinkedMultiValueMap<>();
data.add("username", "user");
data.add("password", "password");
// @formatter:off
this.webClient.mutateWith(csrf())
.post()
.uri("/login")
.body(BodyInserters.fromFormData(data))
.exchange()
.expectStatus().is3xxRedirection()
.expectHeader().location("/login?error");
// @formatter:on
}
@Test
void loginWhenCompromisePasswordCheckerConfiguredAndPasswordNotCompromisedThenUnauthorized() {
this.spring.register(FormLoginConfig.class, UserDetailsConfig.class, CompromisedPasswordCheckerConfig.class)
.autowire();
MultiValueMap<String, String> data = new LinkedMultiValueMap<>();
data.add("username", "admin");
data.add("password", "password2");
// @formatter:off
this.webClient.mutateWith(csrf())
.post()
.uri("/login")
.body(BodyInserters.fromFormData(data))
.exchange()
.expectStatus().is3xxRedirection()
.expectHeader().location("/");
// @formatter:on
}
@Test
void loginWhenCompromisedPasswordAndRedirectIfPasswordExceptionThenRedirectedToResetPassword() {
this.spring
.register(FormLoginRedirectToResetPasswordConfig.class, UserDetailsConfig.class,
CompromisedPasswordCheckerConfig.class)
.autowire();
MultiValueMap<String, String> data = new LinkedMultiValueMap<>();
data.add("username", "user");
data.add("password", "password");
// @formatter:off
this.webClient.mutateWith(csrf())
.post()
.uri("/login")
.body(BodyInserters.fromFormData(data))
.exchange()
.expectStatus().is3xxRedirection()
.expectHeader().location("/reset-password");
// @formatter:on
}
@Configuration
static class SubclassConfig extends ServerHttpSecurityConfiguration {
}
@Configuration(proxyBeanMethods = false)
@EnableWebFlux
@EnableWebFluxSecurity
static class FormLoginConfig {
@Bean
SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
// @formatter:off
http
.authorizeExchange((exchange) -> exchange
.anyExchange().authenticated()
)
.formLogin(Customizer.withDefaults());
// @formatter:on
return http.build();
}
}
@Configuration(proxyBeanMethods = false)
@EnableWebFlux
@EnableWebFluxSecurity
static class FormLoginRedirectToResetPasswordConfig {
@Bean
SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
// @formatter:off
http
.authorizeExchange((exchange) -> exchange
.anyExchange().authenticated()
)
.formLogin((form) -> form
.authenticationFailureHandler((webFilterExchange, exception) -> {
String redirectUrl = "/login?error";
if (exception instanceof CompromisedPasswordException) {
redirectUrl = "/reset-password";
}
return new DefaultServerRedirectStrategy().sendRedirect(webFilterExchange.getExchange(), URI.create(redirectUrl));
})
);
// @formatter:on
return http.build();
}
}
@Configuration(proxyBeanMethods = false)
static class UserDetailsConfig {
@Bean
MapReactiveUserDetailsService userDetailsService() {
// @formatter:off
UserDetails user = PasswordEncodedUser.user();
UserDetails admin = User.withDefaultPasswordEncoder()
.username("admin")
.password("password2")
.roles("USER", "ADMIN")
.build();
// @formatter:on
return new MapReactiveUserDetailsService(user, admin);
}
}
@Configuration(proxyBeanMethods = false)
static class CompromisedPasswordCheckerConfig {
@Bean
TestReactivePasswordChecker compromisedPasswordChecker() {
return new TestReactivePasswordChecker();
}
}
static class TestReactivePasswordChecker implements ReactiveCompromisedPasswordChecker {
@Override
public Mono<CompromisedPasswordDecision> check(String password) {
if ("password".equals(password)) {
return Mono.just(new CompromisedPasswordDecision(true));
}
return Mono.just(new CompromisedPasswordDecision(false));
}
}
}
@@ -65,7 +65,7 @@ public class XsdDocumentedTests {
String schema31xDocumentLocation = "org/springframework/security/config/spring-security-3.1.xsd";
String schemaDocumentLocation = "org/springframework/security/config/spring-security-6.3.xsd";
String schemaDocumentLocation = "org/springframework/security/config/spring-security-6.2.xsd";
XmlSupport xml = new XmlSupport();
@@ -151,8 +151,8 @@ public class XsdDocumentedTests {
.list((dir, name) -> name.endsWith(".xsd"));
// @formatter:on
assertThat(schemas.length)
.withFailMessage("the count is equal to 25, if not then schemaDocument needs updating")
.isEqualTo(25);
.withFailMessage("the count is equal to 24, if not then schemaDocument needs updating")
.isEqualTo(24);
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 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.
@@ -49,20 +49,18 @@ import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.PasswordOAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.RefreshTokenOAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.TokenExchangeOAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.endpoint.AbstractOAuth2AuthorizationGrantRequest;
import org.springframework.security.oauth2.client.endpoint.JwtBearerGrantRequest;
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest;
import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest;
import org.springframework.security.oauth2.client.endpoint.TokenExchangeGrantRequest;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
import org.springframework.security.oauth2.core.OAuth2Error;
@@ -318,47 +316,6 @@ public class OAuth2AuthorizedClientManagerRegistrarTests {
assertThat(grantRequest.getJwt().getSubject()).isEqualTo("user");
}
@Test
public void authorizeWhenTokenExchangeAccessTokenResponseClientBeanThenUsed() {
this.spring.configLocations(xml("clients")).autowire();
testTokenExchangeGrant();
}
@Test
public void authorizeWhenTokenExchangeAuthorizedClientProviderBeanThenUsed() {
this.spring.configLocations(xml("providers")).autowire();
testTokenExchangeGrant();
}
private void testTokenExchangeGrant() {
OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build();
given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(TokenExchangeGrantRequest.class)))
.willReturn(accessTokenResponse);
JwtAuthenticationToken authentication = new JwtAuthenticationToken(getJwt());
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("auth0");
// @formatter:off
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
.withClientRegistrationId(clientRegistration.getRegistrationId())
.principal(authentication)
.attribute(HttpServletRequest.class.getName(), this.request)
.attribute(HttpServletResponse.class.getName(), this.response)
.build();
// @formatter:on
OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest);
assertThat(authorizedClient).isNotNull();
ArgumentCaptor<TokenExchangeGrantRequest> grantRequestCaptor = ArgumentCaptor
.forClass(TokenExchangeGrantRequest.class);
verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture());
TokenExchangeGrantRequest grantRequest = grantRequestCaptor.getValue();
assertThat(grantRequest.getClientRegistration().getRegistrationId())
.isEqualTo(clientRegistration.getRegistrationId());
assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE);
assertThat(grantRequest.getSubjectToken()).isEqualTo(authentication.getToken());
}
private static OAuth2AccessToken getExpiredAccessToken() {
Instant expiresAt = Instant.now().minusSeconds(60);
Instant issuedAt = expiresAt.minus(Duration.ofDays(1));
@@ -399,14 +356,6 @@ public class OAuth2AuthorizedClientManagerRegistrarTests {
.clientId("okta-client-id")
.clientSecret("okta-client-secret")
.authorizationGrantType(AuthorizationGrantType.JWT_BEARER)
.build(),
ClientRegistration.withRegistrationId("auth0")
.clientName("Auth0")
.clientId("auth0-client-id")
.clientSecret("auth0-client-secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE)
.scope("user.read", "user.write")
.build());
// @formatter:on
}
@@ -429,65 +378,95 @@ public class OAuth2AuthorizedClientManagerRegistrarTests {
});
}
public static AuthorizationCodeOAuth2AuthorizedClientProvider authorizationCode() {
public static AuthorizationCodeOAuth2AuthorizedClientProvider authorizationCodeAuthorizedClientProvider() {
return spy(new AuthorizationCodeOAuth2AuthorizedClientProvider());
}
public static RefreshTokenOAuth2AuthorizedClientProvider refreshToken() {
public static RefreshTokenOAuth2AuthorizedClientProvider refreshTokenAuthorizedClientProvider() {
RefreshTokenOAuth2AuthorizedClientProvider authorizedClientProvider = new RefreshTokenOAuth2AuthorizedClientProvider();
authorizedClientProvider.setAccessTokenResponseClient(refreshTokenAccessTokenResponseClient());
return authorizedClientProvider;
}
public static OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> refreshTokenAccessTokenResponseClient() {
return new MockAccessTokenResponseClient<>();
public static MockRefreshTokenClient refreshTokenAccessTokenResponseClient() {
return new MockRefreshTokenClient();
}
public static ClientCredentialsOAuth2AuthorizedClientProvider clientCredentials() {
public static ClientCredentialsOAuth2AuthorizedClientProvider clientCredentialsAuthorizedClientProvider() {
ClientCredentialsOAuth2AuthorizedClientProvider authorizedClientProvider = new ClientCredentialsOAuth2AuthorizedClientProvider();
authorizedClientProvider.setAccessTokenResponseClient(clientCredentialsAccessTokenResponseClient());
return authorizedClientProvider;
}
public static OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsAccessTokenResponseClient() {
return new MockAccessTokenResponseClient<>();
return new MockClientCredentialsClient();
}
public static PasswordOAuth2AuthorizedClientProvider password() {
public static PasswordOAuth2AuthorizedClientProvider passwordAuthorizedClientProvider() {
PasswordOAuth2AuthorizedClientProvider authorizedClientProvider = new PasswordOAuth2AuthorizedClientProvider();
authorizedClientProvider.setAccessTokenResponseClient(passwordAccessTokenResponseClient());
return authorizedClientProvider;
}
public static OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> passwordAccessTokenResponseClient() {
return new MockAccessTokenResponseClient<>();
return new MockPasswordClient();
}
public static JwtBearerOAuth2AuthorizedClientProvider jwtBearer() {
public static JwtBearerOAuth2AuthorizedClientProvider jwtBearerAuthorizedClientProvider() {
JwtBearerOAuth2AuthorizedClientProvider authorizedClientProvider = new JwtBearerOAuth2AuthorizedClientProvider();
authorizedClientProvider.setAccessTokenResponseClient(jwtBearerAccessTokenResponseClient());
return authorizedClientProvider;
}
public static OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> jwtBearerAccessTokenResponseClient() {
return new MockAccessTokenResponseClient<>();
return new MockJwtBearerClient();
}
public static TokenExchangeOAuth2AuthorizedClientProvider tokenExchange() {
TokenExchangeOAuth2AuthorizedClientProvider authorizedClientProvider = new TokenExchangeOAuth2AuthorizedClientProvider();
authorizedClientProvider.setAccessTokenResponseClient(tokenExchangeAccessTokenResponseClient());
return authorizedClientProvider;
}
public static OAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> tokenExchangeAccessTokenResponseClient() {
return new MockAccessTokenResponseClient<>();
}
private static class MockAccessTokenResponseClient<T extends AbstractOAuth2AuthorizationGrantRequest>
implements OAuth2AccessTokenResponseClient<T> {
private static class MockAuthorizationCodeClient
implements OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
@Override
public OAuth2AccessTokenResponse getTokenResponse(T authorizationGrantRequest) {
public OAuth2AccessTokenResponse getTokenResponse(
OAuth2AuthorizationCodeGrantRequest authorizationGrantRequest) {
return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest);
}
}
private static class MockRefreshTokenClient
implements OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> {
@Override
public OAuth2AccessTokenResponse getTokenResponse(OAuth2RefreshTokenGrantRequest authorizationGrantRequest) {
return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest);
}
}
private static class MockClientCredentialsClient
implements OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> {
@Override
public OAuth2AccessTokenResponse getTokenResponse(
OAuth2ClientCredentialsGrantRequest authorizationGrantRequest) {
return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest);
}
}
private static class MockPasswordClient implements OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> {
@Override
public OAuth2AccessTokenResponse getTokenResponse(OAuth2PasswordGrantRequest authorizationGrantRequest) {
return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest);
}
}
private static class MockJwtBearerClient implements OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> {
@Override
public OAuth2AccessTokenResponse getTokenResponse(JwtBearerGrantRequest authorizationGrantRequest) {
return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest);
}
@@ -16,7 +16,6 @@
package org.springframework.security.config.saml2;
import jakarta.servlet.http.HttpServletRequest;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import org.junit.jupiter.api.AfterEach;
@@ -24,21 +23,16 @@ 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.annotation.Qualifier;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link RelyingPartyRegistrationsBeanDefinitionParser}.
@@ -124,7 +118,6 @@ public class RelyingPartyRegistrationsBeanDefinitionParserTests {
// @formatter:on
@Autowired
@Qualifier("registrations")
private RelyingPartyRegistrationRepository relyingPartyRegistrationRepository;
public final SpringTestContext spring = new SpringTestContext(this);
@@ -275,19 +268,6 @@ public class RelyingPartyRegistrationsBeanDefinitionParserTests {
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha384");
}
@Test
public void parseWhenRelayStateResolverThenUses() {
this.spring.configLocations(xml("RelayStateResolver")).autowire();
Converter<HttpServletRequest, String> relayStateResolver = this.spring.getContext().getBean(Converter.class);
OpenSaml4AuthenticationRequestResolver authenticationRequestResolver = this.spring.getContext()
.getBean(OpenSaml4AuthenticationRequestResolver.class);
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRequestURI("/saml2/authenticate/one");
request.setServletPath("/saml2/authenticate/one");
authenticationRequestResolver.resolve(request);
verify(relayStateResolver).convert(request);
}
private static MockResponse xmlResponse(String xml) {
return new MockResponse().setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).setBody(xml);
}
@@ -56,11 +56,9 @@ import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
import org.springframework.security.web.server.ServerRedirectStrategy;
import org.springframework.security.web.server.WebFilterChainProxy;
import org.springframework.security.web.server.authentication.AnonymousAuthenticationWebFilterTests;
import org.springframework.security.web.server.authentication.DelegatingServerAuthenticationSuccessHandler;
import org.springframework.security.web.server.authentication.HttpBasicServerAuthenticationEntryPoint;
import org.springframework.security.web.server.authentication.HttpStatusServerEntryPoint;
import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler;
import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;
import org.springframework.security.web.server.authentication.ServerX509AuthenticationConverter;
import org.springframework.security.web.server.authentication.logout.DelegatingServerLogoutHandler;
import org.springframework.security.web.server.authentication.logout.LogoutWebFilter;
@@ -594,7 +592,6 @@ public class ServerHttpSecurityTests {
}
@Test
@SuppressWarnings("unchecked")
public void shouldConfigureRequestCacheForOAuth2LoginAuthenticationEntryPointAndSuccessHandler() {
ServerRequestCache requestCache = spy(new WebSessionServerRequestCache());
ReactiveClientRegistrationRepository clientRegistrationRepository = mock(
@@ -616,11 +613,8 @@ public class ServerHttpSecurityTests {
OAuth2LoginAuthenticationWebFilter authenticationWebFilter = getWebFilter(securityFilterChain,
OAuth2LoginAuthenticationWebFilter.class)
.get();
DelegatingServerAuthenticationSuccessHandler handler = (DelegatingServerAuthenticationSuccessHandler) ReflectionTestUtils
.getField(authenticationWebFilter, "authenticationSuccessHandler");
List<ServerAuthenticationSuccessHandler> delegates = (List<ServerAuthenticationSuccessHandler>) ReflectionTestUtils
.getField(handler, "delegates");
assertThat(ReflectionTestUtils.getField(delegates.get(0), "requestCache")).isSameAs(requestCache);
Object handler = ReflectionTestUtils.getField(authenticationWebFilter, "authenticationSuccessHandler");
assertThat(ReflectionTestUtils.getField(handler, "requestCache")).isSameAs(requestCache);
}
@Test
@@ -1,587 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.web.server;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseCookie;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.config.users.ReactiveAuthenticationTestConfiguration;
import org.springframework.security.core.session.InMemoryReactiveSessionRegistry;
import org.springframework.security.core.session.ReactiveSessionRegistry;
import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken;
import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizationRequestResolver;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.TestOAuth2AccessTokens;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationExchanges;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.oauth2.core.user.TestOAuth2Users;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.authentication.InvalidateLeastUsedServerMaximumSessionsExceededHandler;
import org.springframework.security.web.server.authentication.PreventLoginServerMaximumSessionsExceededHandler;
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationSuccessHandler;
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter;
import org.springframework.security.web.server.authentication.SessionLimit;
import org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.config.EnableWebFlux;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
import org.springframework.web.server.session.DefaultWebSessionManager;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.csrf;
@ExtendWith({ SpringExtension.class, SpringTestContextExtension.class })
public class SessionManagementSpecTests {
public final SpringTestContext spring = new SpringTestContext(this);
WebTestClient client;
@Autowired
public void setApplicationContext(ApplicationContext context) {
this.client = WebTestClient.bindToApplicationContext(context).build();
}
@Test
void loginWhenMaxSessionPreventsLoginThenSecondLoginFails() {
this.spring.register(ConcurrentSessionsMaxSessionPreventsLoginConfig.class).autowire();
MultiValueMap<String, String> data = new LinkedMultiValueMap<>();
data.add("username", "user");
data.add("password", "password");
ResponseCookie firstLoginSessionCookie = loginReturningCookie(data);
// second login should fail
ResponseCookie secondLoginSessionCookie = this.client.mutateWith(csrf())
.post()
.uri("/login")
.contentType(MediaType.MULTIPART_FORM_DATA)
.body(BodyInserters.fromFormData(data))
.exchange()
.expectHeader()
.location("/login?error")
.returnResult(Void.class)
.getResponseCookies()
.getFirst("SESSION");
assertThat(secondLoginSessionCookie).isNull();
// first login should still be valid
this.client.mutateWith(csrf())
.get()
.uri("/")
.cookie(firstLoginSessionCookie.getName(), firstLoginSessionCookie.getValue())
.exchange()
.expectStatus()
.isOk();
}
@Test
void httpBasicWhenUsingSavingAuthenticationInWebSessionAndPreventLoginThenSecondRequestFails() {
this.spring.register(ConcurrentSessionsHttpBasicWithWebSessionMaxSessionPreventsLoginConfig.class).autowire();
MultiValueMap<String, String> data = new LinkedMultiValueMap<>();
data.add("username", "user");
data.add("password", "password");
// first request be successful
ResponseCookie sessionCookie = this.client.get()
.uri("/")
.headers((headers) -> headers.setBasicAuth("user", "password"))
.exchange()
.expectStatus()
.isOk()
.expectCookie()
.exists("SESSION")
.returnResult(Void.class)
.getResponseCookies()
.getFirst("SESSION");
// request with no session should fail
this.client.get()
.uri("/")
.headers((headers) -> headers.setBasicAuth("user", "password"))
.exchange()
.expectStatus()
.isUnauthorized();
// request with session obtained from first request should be successful
this.client.get()
.uri("/")
.headers((headers) -> headers.setBasicAuth("user", "password"))
.cookie(sessionCookie.getName(), sessionCookie.getValue())
.exchange()
.expectStatus()
.isOk();
}
@Test
void loginWhenMaxSessionPerAuthenticationThenUserLoginFailsAndAdminLoginSucceeds() {
ConcurrentSessionsMaxSessionPreventsLoginConfig.sessionLimit = (authentication) -> {
if (authentication.getName().equals("admin")) {
return Mono.empty();
}
return Mono.just(1);
};
this.spring.register(ConcurrentSessionsMaxSessionPreventsLoginConfig.class).autowire();
MultiValueMap<String, String> data = new LinkedMultiValueMap<>();
data.add("username", "user");
data.add("password", "password");
MultiValueMap<String, String> adminCreds = new LinkedMultiValueMap<>();
adminCreds.add("username", "admin");
adminCreds.add("password", "password");
ResponseCookie userFirstLoginSessionCookie = loginReturningCookie(data);
ResponseCookie adminFirstLoginSessionCookie = loginReturningCookie(adminCreds);
// second user login should fail
this.client.mutateWith(csrf())
.post()
.uri("/login")
.contentType(MediaType.MULTIPART_FORM_DATA)
.body(BodyInserters.fromFormData(data))
.exchange()
.expectHeader()
.location("/login?error");
// first login should still be valid
this.client.mutateWith(csrf())
.get()
.uri("/")
.cookie(userFirstLoginSessionCookie.getName(), userFirstLoginSessionCookie.getValue())
.exchange()
.expectStatus()
.isOk();
ResponseCookie adminSecondLoginSessionCookie = loginReturningCookie(adminCreds);
this.client.mutateWith(csrf())
.get()
.uri("/")
.cookie(adminFirstLoginSessionCookie.getName(), adminFirstLoginSessionCookie.getValue())
.exchange()
.expectStatus()
.isOk();
this.client.mutateWith(csrf())
.get()
.uri("/")
.cookie(adminSecondLoginSessionCookie.getName(), adminSecondLoginSessionCookie.getValue())
.exchange()
.expectStatus()
.isOk();
}
@Test
void loginWhenMaxSessionDoesNotPreventLoginThenSecondLoginSucceedsAndFirstSessionIsInvalidated() {
ConcurrentSessionsMaxSessionPreventsLoginFalseConfig.sessionLimit = SessionLimit.of(1);
this.spring.register(ConcurrentSessionsMaxSessionPreventsLoginFalseConfig.class).autowire();
MultiValueMap<String, String> data = new LinkedMultiValueMap<>();
data.add("username", "user");
data.add("password", "password");
ResponseCookie firstLoginSessionCookie = loginReturningCookie(data);
ResponseCookie secondLoginSessionCookie = loginReturningCookie(data);
// first login should not be valid
this.client.get()
.uri("/")
.cookie(firstLoginSessionCookie.getName(), firstLoginSessionCookie.getValue())
.exchange()
.expectStatus()
.isFound()
.expectHeader()
.location("/login");
// second login should be valid
this.client.get()
.uri("/")
.cookie(secondLoginSessionCookie.getName(), secondLoginSessionCookie.getValue())
.exchange()
.expectStatus()
.isOk();
}
@Test
void loginWhenMaxSessionDoesNotPreventLoginThenLeastRecentlyUsedSessionIsInvalidated() {
ConcurrentSessionsMaxSessionPreventsLoginFalseConfig.sessionLimit = SessionLimit.of(2);
this.spring.register(ConcurrentSessionsMaxSessionPreventsLoginFalseConfig.class).autowire();
MultiValueMap<String, String> data = new LinkedMultiValueMap<>();
data.add("username", "user");
data.add("password", "password");
ResponseCookie firstLoginSessionCookie = loginReturningCookie(data);
ResponseCookie secondLoginSessionCookie = loginReturningCookie(data);
// update last access time for first request
this.client.get()
.uri("/")
.cookie(firstLoginSessionCookie.getName(), firstLoginSessionCookie.getValue())
.exchange()
.expectStatus()
.isOk();
ResponseCookie thirdLoginSessionCookie = loginReturningCookie(data);
// second login should be invalid, it is the least recently used session
this.client.get()
.uri("/")
.cookie(secondLoginSessionCookie.getName(), secondLoginSessionCookie.getValue())
.exchange()
.expectStatus()
.isFound()
.expectHeader()
.location("/login");
// first login should be valid
this.client.get()
.uri("/")
.cookie(firstLoginSessionCookie.getName(), firstLoginSessionCookie.getValue())
.exchange()
.expectStatus()
.isOk();
// third login should be valid
this.client.get()
.uri("/")
.cookie(thirdLoginSessionCookie.getName(), thirdLoginSessionCookie.getValue())
.exchange()
.expectStatus()
.isOk();
}
@Test
void oauth2LoginWhenMaxSessionsThenPreventLogin() {
OAuth2LoginConcurrentSessionsConfig.maxSessions = 1;
OAuth2LoginConcurrentSessionsConfig.preventLogin = true;
this.spring.register(OAuth2LoginConcurrentSessionsConfig.class).autowire();
prepareOAuth2Config();
// @formatter:off
ResponseCookie sessionCookie = this.client.get()
.uri("/login/oauth2/code/client-credentials")
.exchange()
.expectStatus().is3xxRedirection()
.expectHeader().valueEquals("Location", "/")
.expectCookie().exists("SESSION")
.returnResult(Void.class)
.getResponseCookies()
.getFirst("SESSION");
this.client.get()
.uri("/login/oauth2/code/client-credentials")
.exchange()
.expectHeader().location("/login?error");
this.client.get().uri("/")
.cookie(sessionCookie.getName(), sessionCookie.getValue())
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("ok");
// @formatter:on
}
@Test
void oauth2LoginWhenMaxSessionDoesNotPreventLoginThenSecondLoginSucceedsAndFirstSessionIsInvalidated() {
OAuth2LoginConcurrentSessionsConfig.maxSessions = 1;
OAuth2LoginConcurrentSessionsConfig.preventLogin = false;
this.spring.register(OAuth2LoginConcurrentSessionsConfig.class).autowire();
prepareOAuth2Config();
// @formatter:off
ResponseCookie firstLoginCookie = this.client.get()
.uri("/login/oauth2/code/client-credentials")
.exchange()
.expectStatus().is3xxRedirection()
.expectHeader().valueEquals("Location", "/")
.expectCookie().exists("SESSION")
.returnResult(Void.class)
.getResponseCookies()
.getFirst("SESSION");
ResponseCookie secondLoginCookie = this.client.get()
.uri("/login/oauth2/code/client-credentials")
.exchange()
.expectStatus().is3xxRedirection()
.expectHeader().valueEquals("Location", "/")
.expectCookie().exists("SESSION")
.returnResult(Void.class)
.getResponseCookies()
.getFirst("SESSION");
this.client.get().uri("/")
.cookie(firstLoginCookie.getName(), firstLoginCookie.getValue())
.exchange()
.expectStatus().isFound()
.expectHeader().location("/login");
this.client.get().uri("/")
.cookie(secondLoginCookie.getName(), secondLoginCookie.getValue())
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("ok");
// @formatter:on
}
@Test
void loginWhenAuthenticationSuccessHandlerOverriddenThenConcurrentSessionHandlersBackOff() {
this.spring.register(ConcurrentSessionsFormLoginOverrideAuthenticationSuccessHandlerConfig.class).autowire();
MultiValueMap<String, String> data = new LinkedMultiValueMap<>();
data.add("username", "user");
data.add("password", "password");
// first login should be successful
login(data).expectStatus().isFound().expectHeader().location("/");
// second login should be successful, there should be no concurrent session
// control
login(data).expectStatus().isFound().expectHeader().location("/");
}
private void prepareOAuth2Config() {
OAuth2LoginConcurrentSessionsConfig config = this.spring.getContext()
.getBean(OAuth2LoginConcurrentSessionsConfig.class);
ServerAuthenticationConverter converter = config.authenticationConverter;
ReactiveAuthenticationManager manager = config.manager;
ServerOAuth2AuthorizationRequestResolver resolver = config.resolver;
OAuth2AuthorizationExchange exchange = TestOAuth2AuthorizationExchanges.success();
OAuth2User user = TestOAuth2Users.create();
OAuth2AccessToken accessToken = TestOAuth2AccessTokens.noScopes();
OAuth2LoginAuthenticationToken result = new OAuth2LoginAuthenticationToken(
TestClientRegistrations.clientRegistration().build(), exchange, user, user.getAuthorities(),
accessToken);
given(converter.convert(any())).willReturn(Mono.just(new TestingAuthenticationToken("a", "b", "c")));
given(manager.authenticate(any())).willReturn(Mono.just(result));
given(resolver.resolve(any())).willReturn(Mono.empty());
}
private ResponseCookie loginReturningCookie(MultiValueMap<String, String> data) {
return login(data).expectCookie()
.exists("SESSION")
.returnResult(Void.class)
.getResponseCookies()
.getFirst("SESSION");
}
private WebTestClient.ResponseSpec login(MultiValueMap<String, String> data) {
return this.client.mutateWith(csrf())
.post()
.uri("/login")
.contentType(MediaType.MULTIPART_FORM_DATA)
.body(BodyInserters.fromFormData(data))
.exchange();
}
@Configuration
@EnableWebFlux
@EnableWebFluxSecurity
@Import(Config.class)
static class ConcurrentSessionsMaxSessionPreventsLoginConfig {
static SessionLimit sessionLimit = SessionLimit.of(1);
@Bean
SecurityWebFilterChain springSecurity(ServerHttpSecurity http) {
// @formatter:off
http
.authorizeExchange((exchanges) -> exchanges.anyExchange().authenticated())
.formLogin(Customizer.withDefaults())
.sessionManagement((sessionManagement) -> sessionManagement
.concurrentSessions((concurrentSessions) -> concurrentSessions
.maximumSessions(sessionLimit)
.maximumSessionsExceededHandler(new PreventLoginServerMaximumSessionsExceededHandler())
)
);
// @formatter:on
return http.build();
}
}
@Configuration
@EnableWebFlux
@EnableWebFluxSecurity
@Import(Config.class)
static class OAuth2LoginConcurrentSessionsConfig {
static int maxSessions = 1;
static boolean preventLogin = true;
ReactiveAuthenticationManager manager = mock(ReactiveAuthenticationManager.class);
ServerAuthenticationConverter authenticationConverter = mock(ServerAuthenticationConverter.class);
ServerOAuth2AuthorizationRequestResolver resolver = mock(ServerOAuth2AuthorizationRequestResolver.class);
@Bean
SecurityWebFilterChain springSecurityFilter(ServerHttpSecurity http,
DefaultWebSessionManager webSessionManager) {
// @formatter:off
http
.authorizeExchange((exchanges) -> exchanges
.anyExchange().authenticated()
)
.oauth2Login((oauth2Login) -> oauth2Login
.authenticationConverter(this.authenticationConverter)
.authenticationManager(this.manager)
.authorizationRequestResolver(this.resolver)
)
.sessionManagement((sessionManagement) -> sessionManagement
.concurrentSessions((concurrentSessions) -> concurrentSessions
.maximumSessions(SessionLimit.of(maxSessions))
.maximumSessionsExceededHandler(preventLogin
? new PreventLoginServerMaximumSessionsExceededHandler()
: new InvalidateLeastUsedServerMaximumSessionsExceededHandler(webSessionManager.getSessionStore()))
)
);
// @formatter:on
return http.build();
}
@Bean
InMemoryReactiveClientRegistrationRepository clientRegistrationRepository() {
return new InMemoryReactiveClientRegistrationRepository(
TestClientRegistrations.clientCredentials().build());
}
}
@Configuration
@EnableWebFlux
@EnableWebFluxSecurity
@Import(Config.class)
static class ConcurrentSessionsMaxSessionPreventsLoginFalseConfig {
static SessionLimit sessionLimit = SessionLimit.of(1);
@Bean
SecurityWebFilterChain springSecurity(ServerHttpSecurity http) {
// @formatter:off
http
.authorizeExchange((exchanges) -> exchanges.anyExchange().authenticated())
.formLogin(Customizer.withDefaults())
.sessionManagement((sessionManagement) -> sessionManagement
.concurrentSessions((concurrentSessions) -> concurrentSessions
.maximumSessions(sessionLimit)
)
);
// @formatter:on
return http.build();
}
}
@Configuration
@EnableWebFlux
@EnableWebFluxSecurity
@Import(Config.class)
static class ConcurrentSessionsFormLoginOverrideAuthenticationSuccessHandlerConfig {
@Bean
SecurityWebFilterChain springSecurity(ServerHttpSecurity http) {
// @formatter:off
http
.authorizeExchange((exchanges) -> exchanges.anyExchange().authenticated())
.formLogin((login) -> login
.authenticationSuccessHandler(new RedirectServerAuthenticationSuccessHandler("/"))
)
.sessionManagement((sessionManagement) -> sessionManagement
.concurrentSessions((concurrentSessions) -> concurrentSessions
.maximumSessions(SessionLimit.of(1))
.maximumSessionsExceededHandler(new PreventLoginServerMaximumSessionsExceededHandler())
)
);
// @formatter:on
return http.build();
}
}
@Configuration
@EnableWebFlux
@EnableWebFluxSecurity
@Import(Config.class)
static class ConcurrentSessionsHttpBasicWithWebSessionMaxSessionPreventsLoginConfig {
@Bean
SecurityWebFilterChain springSecurity(ServerHttpSecurity http) {
// @formatter:off
http
.authorizeExchange((exchanges) -> exchanges.anyExchange().authenticated())
.httpBasic((basic) -> basic
.securityContextRepository(new WebSessionServerSecurityContextRepository())
)
.sessionManagement((sessionManagement) -> sessionManagement
.concurrentSessions((concurrentSessions) -> concurrentSessions
.maximumSessions(SessionLimit.of(1))
.maximumSessionsExceededHandler(new PreventLoginServerMaximumSessionsExceededHandler())
)
);
// @formatter:on
return http.build();
}
}
@Configuration
@Import({ ReactiveAuthenticationTestConfiguration.class, DefaultController.class })
static class Config {
@Bean(WebHttpHandlerBuilder.WEB_SESSION_MANAGER_BEAN_NAME)
DefaultWebSessionManager webSessionManager() {
return new DefaultWebSessionManager();
}
@Bean
ReactiveSessionRegistry reactiveSessionRegistry() {
return new InMemoryReactiveSessionRegistry();
}
}
@RestController
static class DefaultController {
@GetMapping("/")
String index() {
return "ok";
}
}
}
@@ -816,41 +816,4 @@ class AuthorizeHttpRequestsDslTests {
}
}
@Test
fun `request when ip address does not match then responds with forbidden`() {
this.spring.register(HasIpAddressConfig::class.java).autowire()
this.mockMvc.perform(get("/path")
.with { request ->
request.setAttribute(WebUtils.ERROR_REQUEST_URI_ATTRIBUTE, "/error")
request.apply {
dispatcherType = DispatcherType.ERROR
}
})
.andExpect(status().isForbidden)
}
@Configuration
@EnableWebSecurity
@EnableWebMvc
open class HasIpAddressConfig {
@Bean
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeHttpRequests {
authorize(anyRequest, hasIpAddress("10.0.0.0/24"))
}
}
return http.build()
}
@RestController
internal class PathController {
@RequestMapping("/path")
fun path() {
}
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 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.
@@ -33,7 +33,6 @@ import org.springframework.security.config.test.SpringTestContextExtension
import org.springframework.security.core.userdetails.User
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf
import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated
import org.springframework.security.web.SecurityFilterChain
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler
@@ -368,50 +367,6 @@ class FormLoginDslTests {
verify(exactly = 1) { CustomAuthenticationDetailsSourceConfig.AUTHENTICATION_DETAILS_SOURCE.buildDetails(any()) }
}
@Configuration
@EnableWebSecurity
open class CustomUsernameParameterConfig {
@Bean
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
formLogin {
usernameParameter = "custom-username"
}
}
return http.build()
}
}
@Test
fun `form login when custom username parameter then used`() {
this.spring.register(CustomUsernameParameterConfig::class.java, UserConfig::class.java).autowire()
this.mockMvc.perform(formLogin().userParameter("custom-username"))
.andExpect(authenticated())
}
@Configuration
@EnableWebSecurity
open class CustomPasswordParameterConfig {
@Bean
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
formLogin {
passwordParameter = "custom-password"
}
}
return http.build()
}
}
@Test
fun `form login when custom password parameter then used`() {
this.spring.register(CustomPasswordParameterConfig::class.java, UserConfig::class.java).autowire()
this.mockMvc.perform(formLogin().passwordParam("custom-password"))
.andExpect(authenticated())
}
@Configuration
@EnableWebSecurity
open class CustomAuthenticationDetailsSourceConfig {
@@ -1,126 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web
import org.assertj.core.api.Assertions
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.BeanCreationException
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.authentication.TestAuthentication
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.test.SpringTestContext
import org.springframework.security.config.test.SpringTestContextExtension
import org.springframework.security.core.authority.AuthorityUtils
import org.springframework.security.saml2.provider.service.authentication.DefaultSaml2AuthenticatedPrincipal
import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication
import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository
import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors
import org.springframework.security.web.SecurityFilterChain
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.MvcResult
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
import org.springframework.test.web.servlet.result.MockMvcResultMatchers
import java.util.*
/**
* Tests for [Saml2LogoutDsl]
*
* @author Josh Cummings
*/
@ExtendWith(SpringTestContextExtension::class)
class Saml2LogoutDslTests {
@JvmField
val spring = SpringTestContext(this)
@Autowired
lateinit var mockMvc: MockMvc
@Test
fun `saml2Logout when no relying party registration repository then exception`() {
Assertions.assertThatThrownBy { this.spring.register(Saml2LogoutNoRelyingPartyRegistrationRepoConfig::class.java).autowire() }
.isInstanceOf(BeanCreationException::class.java)
.hasMessageContaining("relyingPartyRegistrationRepository cannot be null")
}
@Test
@Throws(Exception::class)
fun `saml2Logout when defaults and not saml login then default logout`() {
this.spring.register(Saml2LogoutDefaultsConfig::class.java).autowire()
val user = TestAuthentication.authenticatedUser()
val result: MvcResult = this.mockMvc.perform(
MockMvcRequestBuilders.post("/logout").with(SecurityMockMvcRequestPostProcessors.authentication(user))
.with(SecurityMockMvcRequestPostProcessors.csrf()))
.andExpect(MockMvcResultMatchers.status().isFound())
.andReturn()
val location = result.response.getHeader("Location")
Assertions.assertThat(location).isEqualTo("/login?logout")
}
@Test
@Throws(Exception::class)
fun saml2LogoutWhenDefaultsThenLogsOutAndSendsLogoutRequest() {
this.spring.register(Saml2LogoutDefaultsConfig::class.java).autowire()
val principal = DefaultSaml2AuthenticatedPrincipal("user", emptyMap())
principal.relyingPartyRegistrationId = "registration-id"
val user = Saml2Authentication(principal, "response", AuthorityUtils.createAuthorityList("ROLE_USER"))
val result: MvcResult = this.mockMvc.perform(MockMvcRequestBuilders.post("/logout")
.with(SecurityMockMvcRequestPostProcessors.authentication(user))
.with(SecurityMockMvcRequestPostProcessors.csrf()))
.andExpect(MockMvcResultMatchers.status().isFound())
.andReturn()
val location = result.response.getHeader("Location")
Assertions.assertThat(location).startsWith("https://ap.example.org/logout/saml2/request")
}
@Configuration
@EnableWebSecurity
open class Saml2LogoutNoRelyingPartyRegistrationRepoConfig {
@Bean
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
saml2Logout { }
}
return http.build()
}
}
@Configuration
@EnableWebSecurity
open class Saml2LogoutDefaultsConfig {
@Bean
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
saml2Logout { }
}
return http.build()
}
@Bean
open fun registrations(): RelyingPartyRegistrationRepository =
InMemoryRelyingPartyRegistrationRepository(TestRelyingPartyRegistrations.full().build())
}
}
@@ -1,282 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.web.server
import org.junit.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.ApplicationContext
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Import
import org.springframework.http.MediaType
import org.springframework.http.ResponseCookie
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
import org.springframework.security.config.test.SpringTestContext
import org.springframework.security.config.test.SpringTestContextExtension
import org.springframework.security.config.users.ReactiveAuthenticationTestConfiguration
import org.springframework.security.core.session.InMemoryReactiveSessionRegistry
import org.springframework.security.core.session.ReactiveSessionRegistry
import org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers
import org.springframework.security.web.server.SecurityWebFilterChain
import org.springframework.security.web.server.authentication.InvalidateLeastUsedServerMaximumSessionsExceededHandler
import org.springframework.security.web.server.authentication.PreventLoginServerMaximumSessionsExceededHandler
import org.springframework.security.web.server.authentication.SessionLimit
import org.springframework.test.web.reactive.server.WebTestClient
import org.springframework.util.LinkedMultiValueMap
import org.springframework.util.MultiValueMap
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.reactive.config.EnableWebFlux
import org.springframework.web.reactive.function.BodyInserters
import org.springframework.web.server.adapter.WebHttpHandlerBuilder
import org.springframework.web.server.session.DefaultWebSessionManager
/**
* Tests for [ServerSessionManagementDsl]
*
* @author Marcus da Coregio
*/
@ExtendWith(SpringTestContextExtension::class)
class ServerSessionManagementDslTests {
@JvmField
val spring = SpringTestContext(this)
private lateinit var client: WebTestClient
@Autowired
fun setup(context: ApplicationContext) {
this.client = WebTestClient
.bindToApplicationContext(context)
.configureClient()
.build()
}
@Test
fun `login when max sessions prevent login then second login fails`() {
this.spring.register(ConcurrentSessionsMaxSessionPreventsLoginTrueConfig::class.java).autowire()
val data: MultiValueMap<String, String> = LinkedMultiValueMap()
data.add("username", "user")
data.add("password", "password")
val firstLoginSessionCookie = loginReturningCookie(data)
// second login should fail
this.client.mutateWith(SecurityMockServerConfigurers.csrf())
.post()
.uri("/login")
.contentType(MediaType.MULTIPART_FORM_DATA)
.body(BodyInserters.fromFormData(data))
.exchange()
.expectHeader()
.location("/login?error")
// first login should still be valid
this.client.mutateWith(SecurityMockServerConfigurers.csrf())
.get()
.uri("/")
.cookie(firstLoginSessionCookie!!.name, firstLoginSessionCookie.value)
.exchange()
.expectStatus()
.isOk()
}
@Test
fun `login when max sessions does not prevent login then seconds login succeeds and first session is invalidated`() {
ConcurrentSessionsMaxSessionPreventsLoginFalseConfig.maxSessions = 1
this.spring.register(SessionManagementSpecTests.ConcurrentSessionsMaxSessionPreventsLoginFalseConfig::class.java)
.autowire()
val data: MultiValueMap<String, String> = LinkedMultiValueMap()
data.add("username", "user")
data.add("password", "password")
val firstLoginSessionCookie = loginReturningCookie(data)
val secondLoginSessionCookie = loginReturningCookie(data)
// first login should not be valid
this.client.get()
.uri("/")
.cookie(firstLoginSessionCookie!!.name, firstLoginSessionCookie.value)
.exchange()
.expectStatus()
.isFound()
.expectHeader()
.location("/login")
// second login should be valid
this.client.get()
.uri("/")
.cookie(secondLoginSessionCookie!!.name, secondLoginSessionCookie.value)
.exchange()
.expectStatus()
.isOk()
}
@Test
fun `login when max sessions does not prevent login then least recently used session is invalidated`() {
ConcurrentSessionsMaxSessionPreventsLoginFalseConfig.maxSessions = 2
this.spring.register(ConcurrentSessionsMaxSessionPreventsLoginFalseConfig::class.java).autowire()
val data: MultiValueMap<String, String> = LinkedMultiValueMap()
data.add("username", "user")
data.add("password", "password")
val firstLoginSessionCookie = loginReturningCookie(data)
val secondLoginSessionCookie = loginReturningCookie(data)
// update last access time for first request
this.client.get()
.uri("/")
.cookie(firstLoginSessionCookie!!.name, firstLoginSessionCookie.value)
.exchange()
.expectStatus()
.isOk()
val thirdLoginSessionCookie = loginReturningCookie(data)
// second login should be invalid, it is the least recently used session
this.client.get()
.uri("/")
.cookie(secondLoginSessionCookie!!.name, secondLoginSessionCookie.value)
.exchange()
.expectStatus()
.isFound()
.expectHeader()
.location("/login")
// first login should be valid
this.client.get()
.uri("/")
.cookie(firstLoginSessionCookie.name, firstLoginSessionCookie.value)
.exchange()
.expectStatus()
.isOk()
// third login should be valid
this.client.get()
.uri("/")
.cookie(thirdLoginSessionCookie!!.name, thirdLoginSessionCookie.value)
.exchange()
.expectStatus()
.isOk()
}
private fun loginReturningCookie(data: MultiValueMap<String, String>): ResponseCookie? {
return login(data).expectCookie()
.exists("SESSION")
.returnResult(Void::class.java)
.responseCookies
.getFirst("SESSION")
}
private fun login(data: MultiValueMap<String, String>): WebTestClient.ResponseSpec {
return client.mutateWith(SecurityMockServerConfigurers.csrf())
.post()
.uri("/login")
.contentType(MediaType.MULTIPART_FORM_DATA)
.body(BodyInserters.fromFormData(data))
.exchange()
.expectStatus()
.is3xxRedirection()
.expectHeader()
.location("/")
}
@Configuration
@EnableWebFlux
@EnableWebFluxSecurity
@Import(Config::class)
open class ConcurrentSessionsMaxSessionPreventsLoginFalseConfig {
companion object {
var maxSessions = 1
}
@Bean
open fun springSecurity(http: ServerHttpSecurity, webSessionManager: DefaultWebSessionManager): SecurityWebFilterChain {
return http {
authorizeExchange {
authorize(anyExchange, authenticated)
}
formLogin { }
sessionManagement {
sessionConcurrency {
maximumSessions = SessionLimit.of(maxSessions)
maximumSessionsExceededHandler = InvalidateLeastUsedServerMaximumSessionsExceededHandler(webSessionManager.sessionStore)
}
}
}
}
}
@Configuration
@EnableWebFlux
@EnableWebFluxSecurity
@Import(Config::class)
open class ConcurrentSessionsMaxSessionPreventsLoginTrueConfig {
@Bean
open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
authorizeExchange {
authorize(anyExchange, authenticated)
}
formLogin { }
sessionManagement {
sessionConcurrency {
maximumSessions = SessionLimit.of(1)
maximumSessionsExceededHandler =
PreventLoginServerMaximumSessionsExceededHandler()
}
}
}
}
}
@Configuration
@Import(
ReactiveAuthenticationTestConfiguration::class,
DefaultController::class
)
open class Config {
@Bean(WebHttpHandlerBuilder.WEB_SESSION_MANAGER_BEAN_NAME)
open fun webSessionManager(): DefaultWebSessionManager {
return DefaultWebSessionManager()
}
@Bean
open fun reactiveSessionRegistry(): ReactiveSessionRegistry {
return InMemoryReactiveSessionRegistry()
}
}
@RestController
open class DefaultController {
@GetMapping("/")
fun index(): String {
return "ok"
}
}
}
@@ -53,7 +53,4 @@
<b:bean class="org.springframework.security.config.http.OAuth2AuthorizedClientManagerRegistrarTests"
factory-method="jwtBearerAccessTokenResponseClient"/>
<b:bean class="org.springframework.security.config.http.OAuth2AuthorizedClientManagerRegistrarTests"
factory-method="tokenExchangeAccessTokenResponseClient"/>
</b:beans>
@@ -42,21 +42,18 @@
factory-method="authorizedClientManagerConsumer"/>
<b:bean class="org.springframework.security.config.http.OAuth2AuthorizedClientManagerRegistrarTests"
factory-method="authorizationCode"/>
factory-method="authorizationCodeAuthorizedClientProvider"/>
<b:bean class="org.springframework.security.config.http.OAuth2AuthorizedClientManagerRegistrarTests"
factory-method="refreshToken"/>
factory-method="refreshTokenAuthorizedClientProvider"/>
<b:bean class="org.springframework.security.config.http.OAuth2AuthorizedClientManagerRegistrarTests"
factory-method="clientCredentials"/>
factory-method="clientCredentialsAuthorizedClientProvider"/>
<b:bean class="org.springframework.security.config.http.OAuth2AuthorizedClientManagerRegistrarTests"
factory-method="password"/>
factory-method="passwordAuthorizedClientProvider"/>
<b:bean class="org.springframework.security.config.http.OAuth2AuthorizedClientManagerRegistrarTests"
factory-method="jwtBearer"/>
<b:bean class="org.springframework.security.config.http.OAuth2AuthorizedClientManagerRegistrarTests"
factory-method="tokenExchange"/>
factory-method="jwtBearerAuthorizedClientProvider"/>
</b:beans>
@@ -6,7 +6,7 @@
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/security org/springframework/security/config/spring-security-6.3.xsd">
http://www.springframework.org/schema/security org/springframework/security/config/spring-security-6.2.xsd">
<tx:annotation-driven />

Some files were not shown because too many files have changed in this diff Show More