Compare commits

...

108 Commits

Author SHA1 Message Date
blake_bauman
a4f813ab29 Support Multiple ServerLogoutHandlers
This commit adds support to ServerHttpSecurity for registering
multiple ServerLogoutHandlers. This is handy so that an application
does not need to re-supply any handlers already configured by
the DSL.

Signed-off-by: blake_bauman <blake_bauman@apple.com>
2025-09-05 11:47:54 -06:00
Rob Winch
686f8398dd
Merge branch '6.5.x' 2025-09-04 22:40:45 -05:00
Rob Winch
653f22d4a1
Merge branch '6.4.x' into 6.5.x 2025-09-04 22:40:08 -05:00
Rob Winch
f54c293078
Bump org.springframework.data:spring-data-bom from 2024.1.8 to 2024.1.9 2025-09-04 22:39:33 -05:00
Rob Winch
34fccf45c2
Bump com.webauthn4j:webauthn4j-core from 0.29.5.RELEASE to 0.29.6.RELEASE 2025-09-04 22:39:31 -05:00
Rob Winch
f840ee06eb
Bump org.hibernate.orm:hibernate-core from 6.6.26.Final to 6.6.28.Final 2025-09-04 22:39:29 -05:00
Rob Winch
8429c23108
Bump io.micrometer:micrometer-observation from 1.14.9 to 1.14.10 2025-09-04 22:38:50 -05:00
Rob Winch
97f3567702
Bump org.hibernate.orm:hibernate-core from 6.6.23.Final to 6.6.28.Final 2025-09-04 22:38:46 -05:00
dependabot[bot]
2cfdcb9d95 Bump org-opensaml5 from 5.1.5 to 5.1.6
Bumps `org-opensaml5` from 5.1.5 to 5.1.6.

Updates `org.opensaml:opensaml-saml-api` from 5.1.5 to 5.1.6

Updates `org.opensaml:opensaml-saml-impl` from 5.1.5 to 5.1.6

---
updated-dependencies:
- dependency-name: org.opensaml:opensaml-saml-api
  dependency-version: 5.1.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.opensaml:opensaml-saml-impl
  dependency-version: 5.1.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-04 22:37:50 -05:00
dependabot[bot]
3c344ff491 Bump com.webauthn4j:webauthn4j-core
Bumps [com.webauthn4j:webauthn4j-core](https://github.com/webauthn4j/webauthn4j) from 0.29.5.RELEASE to 0.29.6.RELEASE.
- [Release notes](https://github.com/webauthn4j/webauthn4j/releases)
- [Changelog](https://github.com/webauthn4j/webauthn4j/blob/master/github-release-notes-generator.yml)
- [Commits](https://github.com/webauthn4j/webauthn4j/compare/0.29.5.RELEASE...0.29.6.RELEASE)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-04 22:37:36 -05:00
Josh Cummings
f30cc9c5a9
Update to PropertySourcesPlaceholderConfigurer
This commit replaces deprecated usage of PropertyPlaceholderConfigurer
in favor of PropertySourcesPlaceholderConfigurer
2025-09-04 11:32:04 -06:00
Josh Cummings
c64b086878
Add SecurityAssertions
This commit introduces a simple, internal test API for
verifying aspects of an Authentication, like its name
and authorities.

Closes gh-17844
2025-09-03 17:53:42 -06:00
Josh Cummings
de10e08348
Make withRoles Check Only Roles
This commit clarifies the semantics of withRoles,
which is to check the role-based authorities in an
authentication.

Closes gh-17843
2025-09-03 17:53:41 -06:00
Josh Cummings
bd119ac411
Implement Equals and HashCode
Internally, RequestMatcher is sometimes used as a key to a
HashMap. Accordingly, each implementation should implement
equals and hashCode.

Closes gh-17842
2025-09-03 17:48:50 -06:00
Rob Winch
24ffda28d8
Fixes for webauthn tests after JSpecify
Issue gh-17839
2025-09-03 14:44:58 -05:00
Rob Winch
6a84f96930
Enable Null checking in spring-security-test via JSpecify
Closes gh-17840
2025-09-03 12:59:46 -05:00
Rob Winch
194be8ffb6
Checkstyle fixes for webauthn JSpecify
Issue gh-17839
2025-09-03 12:58:27 -05:00
Rob Winch
47b4b155da
Add security-nullability to webauthn
Issue gh-17839
2025-09-03 12:17:56 -05:00
Rob Winch
0a991a91ce
Enable Null checking in spring-security-webauthn via JSpecify
Closes gh-17839
2025-09-03 12:06:53 -05:00
dependabot[bot]
d2e934ca54
Bump org.hibernate.orm:hibernate-core from 6.6.26.Final to 6.6.28.Final
Bumps [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm) from 6.6.26.Final to 6.6.28.Final.
- [Release notes](https://github.com/hibernate/hibernate-orm/releases)
- [Changelog](https://github.com/hibernate/hibernate-orm/blob/6.6.28/changelog.txt)
- [Commits](https://github.com/hibernate/hibernate-orm/compare/6.6.26...6.6.28)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-03 00:33:27 +00:00
dependabot[bot]
fee4d08de3
Bump com.webauthn4j:webauthn4j-core
Bumps [com.webauthn4j:webauthn4j-core](https://github.com/webauthn4j/webauthn4j) from 0.29.5.RELEASE to 0.29.6.RELEASE.
- [Release notes](https://github.com/webauthn4j/webauthn4j/releases)
- [Changelog](https://github.com/webauthn4j/webauthn4j/blob/master/github-release-notes-generator.yml)
- [Commits](https://github.com/webauthn4j/webauthn4j/compare/0.29.5.RELEASE...0.29.6.RELEASE)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-02 23:19:43 +00:00
Josh Cummings
3dbcf266e9
Merge branch '6.5.x' 2025-09-02 16:45:30 -06:00
Josh Cummings
eeb67650ee
Deprecate RequiresChannelDsl
Issue gh-16680
2025-09-02 16:41:39 -06:00
dependabot[bot]
b4fc01194f
Bump org.hibernate.orm:hibernate-core from 6.6.23.Final to 6.6.28.Final
Bumps [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm) from 6.6.23.Final to 6.6.28.Final.
- [Release notes](https://github.com/hibernate/hibernate-orm/releases)
- [Changelog](https://github.com/hibernate/hibernate-orm/blob/6.6.28/changelog.txt)
- [Commits](https://github.com/hibernate/hibernate-orm/compare/6.6.23...6.6.28)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-02 22:32:54 +00:00
Josh Cummings
3534b74945
Replace InteractiveAuthenticationSuccessEvent 7.0.x Sample
Given that 7e3bf9662cd6829982f3198d3049f4012df17395 changes
the InteractiveAuthenticationSuccessEvent serialization sample,
this commit syncs up the 7.0.x version to match.

Closes gh-16276
2025-09-02 14:18:25 -06:00
Josh Cummings
dc0ab4c805
Merge branch '6.5.x' 2025-09-02 14:15:20 -06:00
Josh Cummings
c982753d46
Replace InteractiveAuthenticationSuccessEvent 6.5.x Sample
Given that 7e3bf9662cd6829982f3198d3049f4012df17395 changes
the InteractiveAuthenticationSuccessEvent serialization sample,
this commit syncs up the 6.5.x version to match.

Issue gh-16276
2025-09-02 14:14:13 -06:00
Fridolin Jackstadt
910df479be Provider Default Timeouts For JWK Retrieval
Issue gh-14269

Signed-off-by: Fridolin Jackstadt <fridolin.jackstadt@unic.com>
2025-09-02 08:51:10 -06:00
Rob Winch
9866435946
Fix security-nullability plugin in taglibs
Issue gh-17828
2025-08-30 20:44:29 -05:00
Rob Winch
5370f1190f
Enable Null checking in spring-security-taglibs via JSpecify
Closes gh-17828
2025-08-30 20:40:34 -05:00
Rob Winch
f13d8d5c75
Fix Nullability in WebInvocationPrivilegeEvaluator
Issue gh-17535
2025-08-30 20:38:58 -05:00
Rob Winch
1216ee598f
Enable Null checking in spring-security-rsocket via JSpecify
Closes gh-16882
2025-08-30 20:04:32 -05:00
Rob Winch
a4a4908d71
Enable Null checking in spring-security-cas via JSpecify
Closes gh-16882
2025-08-30 11:22:30 -05:00
Josh Cummings
0ff9f10696
Merge branch '6.4.x' into 6.5.x 2025-08-30 10:00:45 -06:00
Josh Cummings
7e3bf9662c
Polish InteractiveAuthenticationSuccessEvent Sample
The sample better matches a value that would be used in the constructor

Issue gh-16276
2025-08-30 10:00:24 -06:00
Rob Winch
be64c67af5
Enable Null checking in spring-security-web via JSpecify
Closes gh-16882
2025-08-29 16:17:49 -05:00
Rob Winch
a58f3282d9
Fix config/src/test/kotlin nullability for web
Issue gh-17535
2025-08-29 15:46:08 -05:00
Rob Winch
c2ba662b91
Enable Null checking in spring-security-web via JSpecify
Closes gh-17535
2025-08-29 15:06:48 -05:00
Rob Winch
49f308adb0
Use Supplier<? extends @Nullable Authentication>
Previously Supplier<@Nullable Authentication> was used. This prevented
Supplier<Authentication> from being used. The code now uses
Supplier<? extends @Nullable Authentication> which allows for both
Supplier<@Nullable Authentication> and Supplier<Authentication>.

Closes gh-17814
2025-08-29 09:46:58 -05:00
Josh Cummings
4cbe8de7ea Polish RSocket Anonymous Support
Changed the DSL method name to anonymous to align with jwt.
Since basicAuthenication is deprecated, we don't need to
align with its naming convention.

Also added a since attribute to the method.

Issue gh-17132
2025-08-26 17:33:40 -06:00
Andrey Litvitski
559b73b39f Add Disabling Anonymous Authentication in RSocketSecurity
Closes: gh-17132

Signed-off-by: Andrey Litvitski <andrey1010102008@gmail.com>

1

Signed-off-by: Andrey Litvitski <andrey1010102008@gmail.com>

1

Signed-off-by: Andrey Litvitski <andrey1010102008@gmail.com>
2025-08-26 17:33:40 -06:00
Andrey Litvitski
3278f3a410 Add discoverJwsAlgorithms() in NimbusJwtDecoder
Closes: gh-17785
Signed-off-by: Andrey Litvitski <andrey1010102008@gmail.com>
2025-08-26 17:07:47 -06:00
Josh Cummings
36f1de945f
Add OneTimeTokenAuthentication
Closes gh-17799
2025-08-22 15:46:54 -06:00
Josh Cummings
6663eea65f
Polish OTT Tests
Improve tests so that they do not rely on OneTimeTokenAuthenticationToken
as the concrete type.

Issue gh-17799
2025-08-22 15:46:53 -06:00
Josh Cummings
89b2f9cf54
Improve Test Runnability in IDE
In some configurations, Configuration classes with static elements
may cause a test to hang. This commit changes JeeConfigurerTests
test configuration classes to use mock beans instead of referencing
them as static fields.
2025-08-22 15:46:53 -06:00
Josh Cummings
0e39685b9c Merge branch '6.5.x' 2025-08-22 12:40:41 -06:00
Josh Cummings
9d64880ea9 Merge branch '6.4.x' into 6.5.x 2025-08-22 12:40:12 -06:00
Josh Cummings
8b2a453301 Advise Favoring PostAuthorize on Reads
Closes gh-17797
2025-08-22 12:39:51 -06:00
Josh Cummings
d1962201b5 Merge branch '6.5.x' 2025-08-22 11:07:59 -06:00
Josh Cummings
857ca9c412 Merge remote-tracking branch 'origin/6.4.x' into 6.5.x 2025-08-22 11:07:37 -06:00
Nikita Konev
894105aab5 Fix traceId discrepancy in case error in servlet web
Signed-off-by: Nikita Konev <nikit.cpp@yandex.ru>
2025-08-22 11:06:37 -06:00
Rob Winch
f7f41ba6c4
Add missing @NullMarked to spring-data package-info
Issue gh-17789
2025-08-22 12:03:16 -05:00
Rob Winch
f496ded4e5
AuthorizationManager allows null Authentication
It is possible to have a null Authentication and so the
AuthorizationManager APIs should allow for passing it in.

Closes gh-17795
2025-08-22 12:03:16 -05:00
Josh Cummings
583e668c6b Remove opensaml5Test Task
Issue gh-17707
2025-08-22 09:19:20 -06:00
Rob Winch
d6a0e3bf78
Fix Nullability Imports
Issue gh-17789
2025-08-22 09:00:15 -05:00
dependabot[bot]
312c2049f3
Bump io.micrometer:micrometer-observation from 1.14.9 to 1.14.10
Bumps [io.micrometer:micrometer-observation](https://github.com/micrometer-metrics/micrometer) from 1.14.9 to 1.14.10.
- [Release notes](https://github.com/micrometer-metrics/micrometer/releases)
- [Commits](https://github.com/micrometer-metrics/micrometer/compare/v1.14.9...v1.14.10)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-22 04:10:44 +00:00
dependabot[bot]
3dd4686feb
Bump org.springframework.data:spring-data-bom from 2024.1.8 to 2024.1.9
Bumps [org.springframework.data:spring-data-bom](https://github.com/spring-projects/spring-data-bom) from 2024.1.8 to 2024.1.9.
- [Release notes](https://github.com/spring-projects/spring-data-bom/releases)
- [Commits](https://github.com/spring-projects/spring-data-bom/compare/2024.1.8...2024.1.9)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-22 03:38:28 +00:00
Rob Winch
29bb4919ca
Add Nullability to spring-security-data
Closes gh-17789
2025-08-21 13:42:27 -05:00
Rob Winch
d9210c6596
Fix Nullability 2025-08-21 13:41:02 -05:00
Rob Winch
b8b1a92ad4
Revert "Apply Nullability to spring-security-data"
This reverts commit bbcdb236984960416489b4f9d923f83d3a4cba39.
2025-08-21 13:35:39 -05:00
Rob Winch
bbcdb23698
Apply Nullability to spring-security-data 2025-08-21 13:27:47 -05:00
Rob Winch
9bbf837c7c
Merge branch '6.5.x' 2025-08-21 12:44:42 -05:00
Rob Winch
8a1e2a22f9
Merge branch 'gh-16226-servlet-test-method' into 6.5.x 2025-08-21 12:44:27 -05:00
Rob Winch
0404996f87 import Assertions.assertThat
This adds a static import for assertThat in the Kotlin docs code
2025-08-21 12:35:13 -05:00
Rob Winch
0f63d98c84 Use @EnableMethodSecurity in docs tests
Previously parameters were passed in unnecessarily. This removes
the unnecessary paramaters.
2025-08-21 12:35:13 -05:00
Rob Winch
fbfbb1e571 Use 2004-present for Copyright
Spring Security migrated the copyright to use -present to simplify
the headers. This commit aligns the header.
2025-08-21 12:35:13 -05:00
Joe Kuhel
d002e68231 Update servlet test method docs to use include-code
References gh-16226

Signed-off-by: Joe Kuhel <4983938+jkuhel@users.noreply.github.com>
2025-08-21 12:35:13 -05:00
Yanming Zhou
41162aa7e3 Polish WebFluxSecurityConfiguration
Signed-off-by: Yanming Zhou <zhouyanming@gmail.com>
2025-08-21 11:16:12 -06:00
Yanming Zhou
d86f2c957d Change @Bean method signature to return RsaKeyConversionServicePostProcessor instead of BeanFactoryPostProcessor
It's friendly for Spring Boot's `@ConditionalOnMissingBean`, and:

>> When defining a Spring `@Bean` method, it is generally recommended to declare the most specific type possible as the method's return type. This means returning the concrete class of the bean, or the most specific interface that the bean implements and through which it will be referenced in the application.

Signed-off-by: Yanming Zhou <zhouyanming@gmail.com>
2025-08-21 11:16:12 -06:00
Rob Winch
62b5b1a77c
import Assertions.assertThat
This adds a static import for assertThat in the Kotlin docs code
2025-08-21 11:19:05 -05:00
Rob Winch
523222c24d
Use @EnableMethodSecurity in docs tests
Previously parameters were passed in unnecessarily. This removes
the unnecessary paramaters.
2025-08-21 11:15:42 -05:00
Rob Winch
69f38d4933
Use 2004-present for Copyright
Spring Security migrated the copyright to use -present to simplify
the headers. This commit aligns the header.
2025-08-21 11:13:45 -05:00
Joe Kuhel
0179a811c7
Update servlet test method docs to use include-code
References gh-16226

Signed-off-by: Joe Kuhel <4983938+jkuhel@users.noreply.github.com>
2025-08-21 11:12:42 -05:00
Rob Winch
7ce2bdd701
Merge branch '6.5.x' 2025-08-21 08:55:57 -05:00
Rob Winch
de4ceffc4f
Merge branch '6.4.x' into 6.5.x 2025-08-21 08:55:48 -05:00
Rob Winch
8c920a7ee7
Bump org.springframework.data:spring-data-bom from 2024.1.8 to 2024.1.9 2025-08-21 08:55:15 -05:00
Rob Winch
b9653346a1
Bump org.hibernate.orm:hibernate-core from 6.6.23.Final to 6.6.26.Final 2025-08-21 08:54:19 -05:00
Rob Winch
9e6bcbd1d0
Bump io.micrometer:micrometer-observation from 1.14.9 to 1.14.10 2025-08-21 08:54:18 -05:00
dependabot[bot]
8d888edc71 Bump io.spring.nullability:io.spring.nullability.gradle.plugin
Bumps [io.spring.nullability:io.spring.nullability.gradle.plugin](https://github.com/spring-gradle-plugins/nullability-plugin) from 0.0.3 to 0.0.4.
- [Release notes](https://github.com/spring-gradle-plugins/nullability-plugin/releases)
- [Commits](https://github.com/spring-gradle-plugins/nullability-plugin/compare/v0.0.3...v0.0.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-20 17:10:31 -05:00
Rob Winch
f82fe9c8c6
Remove stray modular from the documentation
Issue gh-16258
2025-08-20 12:24:33 -05:00
Rob Winch
a8f045eb50
Add Modular Spring Security Configuration
Closes gh-16258
2025-08-20 12:16:08 -05:00
Rob Winch
5c5efc9092
SpringTestContext registers WebTestClient Bean
Closes gh-17780
2025-08-20 12:15:58 -05:00
dependabot[bot]
5453224377
Bump io.micrometer:micrometer-observation from 1.14.9 to 1.14.10
Bumps [io.micrometer:micrometer-observation](https://github.com/micrometer-metrics/micrometer) from 1.14.9 to 1.14.10.
- [Release notes](https://github.com/micrometer-metrics/micrometer/releases)
- [Commits](https://github.com/micrometer-metrics/micrometer/compare/v1.14.9...v1.14.10)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-20 06:23:02 +00:00
dependabot[bot]
a14ad770ab
Bump org.hibernate.orm:hibernate-core from 6.6.23.Final to 6.6.26.Final
Bumps [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm) from 6.6.23.Final to 6.6.26.Final.
- [Release notes](https://github.com/hibernate/hibernate-orm/releases)
- [Changelog](https://github.com/hibernate/hibernate-orm/blob/6.6.26/changelog.txt)
- [Commits](https://github.com/hibernate/hibernate-orm/compare/6.6.23...6.6.26)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-20 04:41:59 +00:00
Jaehwan Lee
806297da23 Fix misleading variable name in authentication filter
Rename DEFAULT_ANT_PATH_REQUEST_MATCHER to DEFAULT_PATH_REQUEST_MATCHER
to reflect PathPatternRequestMatcher usage instead of legacy Ant
pattern terminology.

Signed-off-by: Jaehwan Lee <jhrick0129@gmail.com>
2025-08-19 22:21:35 -05:00
dependabot[bot]
8846b44b81
Bump org.springframework.data:spring-data-bom from 2024.1.8 to 2024.1.9
Bumps [org.springframework.data:spring-data-bom](https://github.com/spring-projects/spring-data-bom) from 2024.1.8 to 2024.1.9.
- [Release notes](https://github.com/spring-projects/spring-data-bom/releases)
- [Commits](https://github.com/spring-projects/spring-data-bom/compare/2024.1.8...2024.1.9)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-20 03:21:13 +00:00
Rob Winch
7f103b2d0a
Merge branch '6.5.x' 2025-08-19 22:19:46 -05:00
dependabot[bot]
c06ff59747
Bump org.hibernate.orm:hibernate-core from 6.6.23.Final to 6.6.26.Final
Bumps [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm) from 6.6.23.Final to 6.6.26.Final.
- [Release notes](https://github.com/hibernate/hibernate-orm/releases)
- [Changelog](https://github.com/hibernate/hibernate-orm/blob/6.6.26/changelog.txt)
- [Commits](https://github.com/hibernate/hibernate-orm/compare/6.6.23...6.6.26)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-19 22:19:01 -05:00
dependabot[bot]
f1194de45e
Bump org.assertj:assertj-core from 3.27.3 to 3.27.4
Bumps [org.assertj:assertj-core](https://github.com/assertj/assertj) from 3.27.3 to 3.27.4.
- [Release notes](https://github.com/assertj/assertj/releases)
- [Commits](https://github.com/assertj/assertj/compare/assertj-build-3.27.3...assertj-build-3.27.4)

---
updated-dependencies:
- dependency-name: org.assertj:assertj-core
  dependency-version: 3.27.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-19 22:19:01 -05:00
dependabot[bot]
1f16009c8d
Bump org.springframework.ldap:spring-ldap-core from 3.2.13 to 3.2.14
Bumps [org.springframework.ldap:spring-ldap-core](https://github.com/spring-projects/spring-ldap) from 3.2.13 to 3.2.14.
- [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.13...3.2.14)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-19 22:19:01 -05:00
Rob Winch
3e13437b1a
Merge branch '6.4.x' into 6.5.x 2025-08-19 22:16:16 -05:00
Rob Winch
20a6863e07
Bump org.hibernate.orm:hibernate-core from 6.6.23.Final to 6.6.26.Final 2025-08-19 22:15:40 -05:00
Rob Winch
e145c07d5b
Bump io.micrometer:micrometer-observation from 1.14.9 to 1.14.10 2025-08-19 22:15:38 -05:00
Rob Winch
68a7f1702f
Merge branch '6.5.x' 2025-08-19 22:15:14 -05:00
Rob Winch
3e8b606aac
Merge branch '6.4.x' into 6.5.x 2025-08-19 22:14:37 -05:00
Tran Ngoc Nhan
ef5c703010 Remove unused import
Signed-off-by: Tran Ngoc Nhan <ngocnhan.tran1996@gmail.com>
2025-08-19 22:05:25 -05:00
Andrey Litvitski
47be93e694 Annotate AuthenticationTrustResolver methods with @Nullable
Since AuthenticationTrustResolver can handle null arguments (this is
also stated in the implementation of this interface), we should mark
these arguments as `@Nullable`.

Closes: gh-17764

Signed-off-by: Andrey Litvitski <andrey1010102008@gmail.com>
2025-08-19 22:02:59 -05:00
dependabot[bot]
9310153d16 Bump io.spring.nullability:io.spring.nullability.gradle.plugin
Bumps [io.spring.nullability:io.spring.nullability.gradle.plugin](https://github.com/spring-gradle-plugins/nullability-plugin) from 0.0.2 to 0.0.3.
- [Release notes](https://github.com/spring-gradle-plugins/nullability-plugin/releases)
- [Commits](https://github.com/spring-gradle-plugins/nullability-plugin/compare/v0.0.2...v0.0.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-19 21:41:13 -05:00
dependabot[bot]
a933089ec2
Bump io.micrometer:micrometer-observation from 1.14.9 to 1.14.10
Bumps [io.micrometer:micrometer-observation](https://github.com/micrometer-metrics/micrometer) from 1.14.9 to 1.14.10.
- [Release notes](https://github.com/micrometer-metrics/micrometer/releases)
- [Commits](https://github.com/micrometer-metrics/micrometer/compare/v1.14.9...v1.14.10)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-19 14:41:53 +00:00
dependabot[bot]
a8da9ec7f7
Bump org.hibernate.orm:hibernate-core from 6.6.23.Final to 6.6.26.Final
Bumps [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm) from 6.6.23.Final to 6.6.26.Final.
- [Release notes](https://github.com/hibernate/hibernate-orm/releases)
- [Changelog](https://github.com/hibernate/hibernate-orm/blob/6.6.26/changelog.txt)
- [Commits](https://github.com/hibernate/hibernate-orm/compare/6.6.23...6.6.26)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-19 14:40:42 +00:00
Josh Cummings
3396890d8b
Propagate AccessDeniedException Only to ExceptionTranslationFilter
Closes gh-17761
2025-08-18 17:04:19 -06:00
Josh Cummings
c45bc384da
Interpret Expression Templates by Default
Closes gh-17763
2025-08-18 15:45:57 -06:00
Josh Cummings
4da98dde2b
Update What's New
Issue gh-17707
2025-08-18 15:31:03 -06:00
Rob Winch
7575e4ef1c
Next development version 2025-08-18 15:17:59 -05:00
github-actions[bot]
acaed1ad96 Next development version 2025-08-18 18:15:02 +00:00
github-actions[bot]
0549bf566b Next development version 2025-08-18 18:06:07 +00:00
github-actions[bot]
44037c0ea4 Release 6.5.3 2025-08-18 17:37:38 +00:00
github-actions[bot]
01c8cea00f Release 6.4.9 2025-08-18 17:37:33 +00:00
585 changed files with 9203 additions and 1722 deletions

View File

@ -17,8 +17,6 @@ package io.spring.gradle;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.gradle.testkit.runner.GradleRunner; import org.gradle.testkit.runner.GradleRunner;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;

View File

@ -23,8 +23,6 @@ import org.gradle.testfixtures.ProjectBuilder;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.io.File;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
/** /**

View File

@ -5,7 +5,6 @@ import org.apache.commons.io.FileUtils;
import org.gradle.testkit.runner.BuildResult; import org.gradle.testkit.runner.BuildResult;
import org.gradle.testkit.runner.TaskOutcome; import org.gradle.testkit.runner.TaskOutcome;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.io.TempDir;

View File

@ -1,3 +1,7 @@
plugins {
id 'security-nullability'
}
apply plugin: 'io.spring.convention.spring-module' apply plugin: 'io.spring.convention.spring-module'
dependencies { dependencies {

View File

@ -16,6 +16,8 @@
package org.springframework.security.cas; package org.springframework.security.cas;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -34,7 +36,7 @@ public class ServiceProperties implements InitializingBean {
public static final String DEFAULT_CAS_SERVICE_PARAMETER = "service"; public static final String DEFAULT_CAS_SERVICE_PARAMETER = "service";
private String service; private @Nullable String service;
private boolean authenticateAllArtifacts; private boolean authenticateAllArtifacts;
@ -62,7 +64,7 @@ public class ServiceProperties implements InitializingBean {
* </pre> * </pre>
* @return the URL of the service the user is authenticating to * @return the URL of the service the user is authenticating to
*/ */
public final String getService() { public final @Nullable String getService() {
return this.service; return this.service;
} }

View File

@ -21,6 +21,8 @@ import org.apache.commons.logging.LogFactory;
import org.apereo.cas.client.validation.Assertion; import org.apereo.cas.client.validation.Assertion;
import org.apereo.cas.client.validation.TicketValidationException; import org.apereo.cas.client.validation.TicketValidationException;
import org.apereo.cas.client.validation.TicketValidator; import org.apereo.cas.client.validation.TicketValidator;
import org.jspecify.annotations.NullUnmarked;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.MessageSource; import org.springframework.context.MessageSource;
@ -62,6 +64,7 @@ public class CasAuthenticationProvider implements AuthenticationProvider, Initia
private static final Log logger = LogFactory.getLog(CasAuthenticationProvider.class); private static final Log logger = LogFactory.getLog(CasAuthenticationProvider.class);
@SuppressWarnings("NullAway.Init")
private AuthenticationUserDetailsService<CasAssertionAuthenticationToken> authenticationUserDetailsService; private AuthenticationUserDetailsService<CasAssertionAuthenticationToken> authenticationUserDetailsService;
private UserDetailsChecker userDetailsChecker = new AccountStatusUserDetailsChecker(); private UserDetailsChecker userDetailsChecker = new AccountStatusUserDetailsChecker();
@ -70,11 +73,13 @@ public class CasAuthenticationProvider implements AuthenticationProvider, Initia
private StatelessTicketCache statelessTicketCache = new NullStatelessTicketCache(); private StatelessTicketCache statelessTicketCache = new NullStatelessTicketCache();
@SuppressWarnings("NullAway.Init")
private String key; private String key;
@SuppressWarnings("NullAway.Init")
private TicketValidator ticketValidator; private TicketValidator ticketValidator;
private ServiceProperties serviceProperties; private @Nullable ServiceProperties serviceProperties;
private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper(); private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();
@ -89,7 +94,7 @@ public class CasAuthenticationProvider implements AuthenticationProvider, Initia
} }
@Override @Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException { public @Nullable Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (!supports(authentication.getClass())) { if (!supports(authentication.getClass())) {
return null; return null;
} }
@ -129,11 +134,14 @@ public class CasAuthenticationProvider implements AuthenticationProvider, Initia
private CasAuthenticationToken authenticateNow(final Authentication authentication) throws AuthenticationException { private CasAuthenticationToken authenticateNow(final Authentication authentication) throws AuthenticationException {
try { try {
Assertion assertion = this.ticketValidator.validate(authentication.getCredentials().toString(), Object credentials = authentication.getCredentials();
getServiceUrl(authentication)); if (credentials == null) {
throw new BadCredentialsException("Authentication.getCredentials() cannot be null");
}
Assertion assertion = this.ticketValidator.validate(credentials.toString(), getServiceUrl(authentication));
UserDetails userDetails = loadUserByAssertion(assertion); UserDetails userDetails = loadUserByAssertion(assertion);
this.userDetailsChecker.check(userDetails); this.userDetailsChecker.check(userDetails);
return new CasAuthenticationToken(this.key, userDetails, authentication.getCredentials(), return new CasAuthenticationToken(this.key, userDetails, credentials,
this.authoritiesMapper.mapAuthorities(userDetails.getAuthorities()), userDetails, assertion); this.authoritiesMapper.mapAuthorities(userDetails.getAuthorities()), userDetails, assertion);
} }
catch (TicketValidationException ex) { catch (TicketValidationException ex) {
@ -149,7 +157,8 @@ public class CasAuthenticationProvider implements AuthenticationProvider, Initia
* @param authentication * @param authentication
* @return * @return
*/ */
private String getServiceUrl(Authentication authentication) { @NullUnmarked
private @Nullable String getServiceUrl(Authentication authentication) {
String serviceUrl; String serviceUrl;
if (authentication.getDetails() instanceof ServiceAuthenticationDetails) { if (authentication.getDetails() instanceof ServiceAuthenticationDetails) {
return ((ServiceAuthenticationDetails) authentication.getDetails()).getServiceUrl(); return ((ServiceAuthenticationDetails) authentication.getDetails()).getServiceUrl();
@ -215,7 +224,7 @@ public class CasAuthenticationProvider implements AuthenticationProvider, Initia
return this.statelessTicketCache; return this.statelessTicketCache;
} }
protected TicketValidator getTicketValidator() { protected @Nullable TicketValidator getTicketValidator() {
return this.ticketValidator; return this.ticketValidator;
} }

View File

@ -19,6 +19,8 @@ package org.springframework.security.cas.authentication;
import java.io.Serial; import java.io.Serial;
import java.util.Collection; import java.util.Collection;
import org.jspecify.annotations.Nullable;
import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -41,7 +43,7 @@ public class CasServiceTicketAuthenticationToken extends AbstractAuthenticationT
private final String identifier; private final String identifier;
private Object credentials; private @Nullable Object credentials;
/** /**
* This constructor can be safely used by any code that wishes to create a * This constructor can be safely used by any code that wishes to create a
@ -86,7 +88,7 @@ public class CasServiceTicketAuthenticationToken extends AbstractAuthenticationT
} }
@Override @Override
public Object getCredentials() { public @Nullable Object getCredentials() {
return this.credentials; return this.credentials;
} }

View File

@ -16,6 +16,8 @@
package org.springframework.security.cas.authentication; package org.springframework.security.cas.authentication;
import org.jspecify.annotations.Nullable;
/** /**
* Implementation of @link {@link StatelessTicketCache} that has no backing cache. Useful * Implementation of @link {@link StatelessTicketCache} that has no backing cache. Useful
* in instances where storing of tickets for stateless session management is not required. * in instances where storing of tickets for stateless session management is not required.
@ -33,7 +35,7 @@ public final class NullStatelessTicketCache implements StatelessTicketCache {
* @return null since we are not storing any tickets. * @return null since we are not storing any tickets.
*/ */
@Override @Override
public CasAuthenticationToken getByTicketId(final String serviceTicket) { public @Nullable CasAuthenticationToken getByTicketId(final String serviceTicket) {
return null; return null;
} }

View File

@ -18,6 +18,7 @@ package org.springframework.security.cas.authentication;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
import org.springframework.cache.Cache; import org.springframework.cache.Cache;
import org.springframework.core.log.LogMessage; import org.springframework.core.log.LogMessage;
@ -42,7 +43,7 @@ public class SpringCacheBasedTicketCache implements StatelessTicketCache {
} }
@Override @Override
public CasAuthenticationToken getByTicketId(final String serviceTicket) { public @Nullable CasAuthenticationToken getByTicketId(final String serviceTicket) {
final Cache.ValueWrapper element = (serviceTicket != null) ? this.cache.get(serviceTicket) : null; final Cache.ValueWrapper element = (serviceTicket != null) ? this.cache.get(serviceTicket) : null;
logger.debug(LogMessage.of(() -> "Cache hit: " + (element != null) + "; service ticket: " + serviceTicket)); logger.debug(LogMessage.of(() -> "Cache hit: " + (element != null) + "; service ticket: " + serviceTicket));
return (element != null) ? (CasAuthenticationToken) element.get() : null; return (element != null) ? (CasAuthenticationToken) element.get() : null;

View File

@ -16,6 +16,8 @@
package org.springframework.security.cas.authentication; package org.springframework.security.cas.authentication;
import org.jspecify.annotations.Nullable;
/** /**
* Caches CAS service tickets and CAS proxy tickets for stateless connections. * Caches CAS service tickets and CAS proxy tickets for stateless connections.
* *
@ -69,7 +71,7 @@ public interface StatelessTicketCache {
* </p> * </p>
* @return the fully populated authentication token * @return the fully populated authentication token
*/ */
CasAuthenticationToken getByTicketId(String serviceTicket); @Nullable CasAuthenticationToken getByTicketId(String serviceTicket);
/** /**
* Adds the specified <code>CasAuthenticationToken</code> to the cache. * Adds the specified <code>CasAuthenticationToken</code> to the cache.

View File

@ -18,4 +18,7 @@
* An {@code AuthenticationProvider} that can process CAS service tickets and proxy * An {@code AuthenticationProvider} that can process CAS service tickets and proxy
* tickets. * tickets.
*/ */
@NullMarked
package org.springframework.security.cas.authentication; package org.springframework.security.cas.authentication;
import org.jspecify.annotations.NullMarked;

View File

@ -0,0 +1,23 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Jackson support for CAS.
*/
@NullMarked
package org.springframework.security.cas.jackson2;
import org.jspecify.annotations.NullMarked;

View File

@ -18,4 +18,7 @@
* Spring Security support for Apereo's Central Authentication Service * Spring Security support for Apereo's Central Authentication Service
* (<a href="https://github.com/apereo/cas">CAS</a>). * (<a href="https://github.com/apereo/cas">CAS</a>).
*/ */
@NullMarked
package org.springframework.security.cas; package org.springframework.security.cas;
import org.jspecify.annotations.NullMarked;

View File

@ -0,0 +1,23 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* {@link org.springframework.security.core.userdetails.UserDetails} abstractions for CAS.
*/
@NullMarked
package org.springframework.security.cas.userdetails;
import org.jspecify.annotations.NullMarked;

View File

@ -22,6 +22,7 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.apereo.cas.client.util.CommonUtils; import org.apereo.cas.client.util.CommonUtils;
import org.apereo.cas.client.util.WebUtils; import org.apereo.cas.client.util.WebUtils;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.cas.ServiceProperties; import org.springframework.security.cas.ServiceProperties;
@ -47,9 +48,10 @@ import org.springframework.util.Assert;
*/ */
public class CasAuthenticationEntryPoint implements AuthenticationEntryPoint, InitializingBean { public class CasAuthenticationEntryPoint implements AuthenticationEntryPoint, InitializingBean {
@SuppressWarnings("NullAway.Init")
private ServiceProperties serviceProperties; private ServiceProperties serviceProperties;
private String loginUrl; private @Nullable String loginUrl;
/** /**
* Determines whether the Service URL should include the session id for the specific * Determines whether the Service URL should include the session id for the specific
@ -117,7 +119,7 @@ public class CasAuthenticationEntryPoint implements AuthenticationEntryPoint, In
* <code>https://www.mycompany.com/cas/login</code>. * <code>https://www.mycompany.com/cas/login</code>.
* @return the enterprise-wide CAS login URL * @return the enterprise-wide CAS login URL
*/ */
public final String getLoginUrl() { public final @Nullable String getLoginUrl() {
return this.loginUrl; return this.loginUrl;
} }

View File

@ -26,6 +26,7 @@ import jakarta.servlet.http.HttpSession;
import org.apereo.cas.client.proxy.ProxyGrantingTicketStorage; import org.apereo.cas.client.proxy.ProxyGrantingTicketStorage;
import org.apereo.cas.client.util.WebUtils; import org.apereo.cas.client.util.WebUtils;
import org.apereo.cas.client.validation.TicketValidator; import org.apereo.cas.client.validation.TicketValidator;
import org.jspecify.annotations.Nullable;
import org.springframework.core.log.LogMessage; import org.springframework.core.log.LogMessage;
import org.springframework.security.authentication.AuthenticationDetailsSource; import org.springframework.security.authentication.AuthenticationDetailsSource;
@ -190,12 +191,12 @@ public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFil
/** /**
* The last portion of the receptor url, i.e. /proxy/receptor * The last portion of the receptor url, i.e. /proxy/receptor
*/ */
private RequestMatcher proxyReceptorMatcher; private @Nullable RequestMatcher proxyReceptorMatcher;
/** /**
* The backing storage to store ProxyGrantingTicket requests. * The backing storage to store ProxyGrantingTicket requests.
*/ */
private ProxyGrantingTicketStorage proxyGrantingTicketStorage; private @Nullable ProxyGrantingTicketStorage proxyGrantingTicketStorage;
private String artifactParameter = ServiceProperties.DEFAULT_CAS_ARTIFACT_PARAMETER; private String artifactParameter = ServiceProperties.DEFAULT_CAS_ARTIFACT_PARAMETER;
@ -244,7 +245,7 @@ public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFil
} }
@Override @Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) public @Nullable Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException { throws AuthenticationException, IOException {
// if the request is a proxy request process it and return null to indicate the // if the request is a proxy request process it and return null to indicate the
// request has been processed // request has been processed
@ -422,6 +423,7 @@ public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFil
* @param request * @param request
* @return * @return
*/ */
@SuppressWarnings("NullAway") // Dataflow analysis limitation
private boolean proxyReceptorRequest(HttpServletRequest request) { private boolean proxyReceptorRequest(HttpServletRequest request) {
final boolean result = proxyReceptorConfigured() && this.proxyReceptorMatcher.matches(request); final boolean result = proxyReceptorConfigured() && this.proxyReceptorMatcher.matches(request);
this.logger.debug(LogMessage.format("proxyReceptorRequest = %s", result)); this.logger.debug(LogMessage.format("proxyReceptorRequest = %s", result));

View File

@ -21,6 +21,7 @@ import java.net.URL;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import org.jspecify.annotations.Nullable;
import org.springframework.security.cas.authentication.ServiceAuthenticationDetails; import org.springframework.security.cas.authentication.ServiceAuthenticationDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetails; import org.springframework.security.web.authentication.WebAuthenticationDetails;
@ -49,8 +50,8 @@ final class DefaultServiceAuthenticationDetails extends WebAuthenticationDetails
* string from containing the artifact name and value. This can be created using * string from containing the artifact name and value. This can be created using
* {@link #createArtifactPattern(String)}. * {@link #createArtifactPattern(String)}.
*/ */
DefaultServiceAuthenticationDetails(String casService, HttpServletRequest request, Pattern artifactPattern) DefaultServiceAuthenticationDetails(@Nullable String casService, HttpServletRequest request,
throws MalformedURLException { Pattern artifactPattern) throws MalformedURLException {
super(request); super(request);
URL casServiceUrl = new URL(casService); URL casServiceUrl = new URL(casService);
int port = getServicePort(casServiceUrl); int port = getServicePort(casServiceUrl);
@ -104,7 +105,7 @@ final class DefaultServiceAuthenticationDetails extends WebAuthenticationDetails
* @return the query String minus the artifactParameterName and the corresponding * @return the query String minus the artifactParameterName and the corresponding
* value. * value.
*/ */
private String getQueryString(final HttpServletRequest request, final Pattern artifactPattern) { private @Nullable String getQueryString(final HttpServletRequest request, final Pattern artifactPattern) {
final String query = request.getQueryString(); final String query = request.getQueryString();
if (query == null) { if (query == null) {
return null; return null;

View File

@ -18,4 +18,7 @@
* Authentication processing mechanisms which respond to the submission of authentication * Authentication processing mechanisms which respond to the submission of authentication
* credentials using CAS. * credentials using CAS.
*/ */
@NullMarked
package org.springframework.security.cas.web.authentication; package org.springframework.security.cas.web.authentication;
import org.jspecify.annotations.NullMarked;

View File

@ -17,4 +17,7 @@
/** /**
* Authenticates standard web browser users via CAS. * Authenticates standard web browser users via CAS.
*/ */
@NullMarked
package org.springframework.security.cas.web; package org.springframework.security.cas.web;
import org.jspecify.annotations.NullMarked;

View File

@ -25,7 +25,6 @@ dependencies {
optional project(':spring-security-ldap') optional project(':spring-security-ldap')
optional project(':spring-security-messaging') optional project(':spring-security-messaging')
optional project(path: ':spring-security-saml2-service-provider') optional project(path: ':spring-security-saml2-service-provider')
opensaml5 project(path: ':spring-security-saml2-service-provider', configuration: 'opensamlFiveMain')
optional project(':spring-security-oauth2-client') optional project(':spring-security-oauth2-client')
optional project(':spring-security-oauth2-jose') optional project(':spring-security-oauth2-jose')
optional project(':spring-security-oauth2-resource-server') optional project(':spring-security-oauth2-resource-server')
@ -170,15 +169,3 @@ test {
} }
} }
} }
tasks.register("opensaml5Test", Test) {
filter {
includeTestsMatching "org.springframework.security.config.annotation.web.configurers.saml2.*"
}
useJUnitPlatform()
classpath = sourceSets.main.output + sourceSets.test.output + configurations.opensaml5
}
tasks.named("check") {
dependsOn opensaml5Test
}

View File

@ -18,7 +18,6 @@ package org.springframework.security.config.annotation.authentication.ldap;
import java.io.IOException; import java.io.IOException;
import java.net.ServerSocket; import java.net.ServerSocket;
import java.util.Collections;
import java.util.List; import java.util.List;
import javax.naming.directory.SearchControls; import javax.naming.directory.SearchControls;
@ -39,7 +38,6 @@ import org.springframework.security.config.annotation.configuration.ObjectPostPr
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource; import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
@ -120,8 +118,7 @@ public class LdapAuthenticationProviderBuilderSecurityBuilderTests {
this.spring.register(BindAuthenticationConfig.class).autowire(); this.spring.register(BindAuthenticationConfig.class).autowire();
this.mockMvc.perform(formLogin().user("bob").password("bobspassword")) this.mockMvc.perform(formLogin().user("bob").password("bobspassword"))
.andExpect(authenticated().withUsername("bob") .andExpect(authenticated().withUsername("bob").withRoles("DEVELOPERS"));
.withAuthorities(Collections.singleton(new SimpleGrantedAuthority("ROLE_DEVELOPERS"))));
} }
// SEC-2472 // SEC-2472
@ -130,8 +127,7 @@ public class LdapAuthenticationProviderBuilderSecurityBuilderTests {
this.spring.register(PasswordEncoderConfig.class).autowire(); this.spring.register(PasswordEncoderConfig.class).autowire();
this.mockMvc.perform(formLogin().user("bcrypt").password("password")) this.mockMvc.perform(formLogin().user("bcrypt").password("password"))
.andExpect(authenticated().withUsername("bcrypt") .andExpect(authenticated().withUsername("bcrypt").withRoles("DEVELOPERS"));
.withAuthorities(Collections.singleton(new SimpleGrantedAuthority("ROLE_DEVELOPERS"))));
} }
private LdapAuthenticationProvider ldapProvider() { private LdapAuthenticationProvider ldapProvider() {

View File

@ -16,8 +16,6 @@
package org.springframework.security.config.annotation.authentication.ldap; package org.springframework.security.config.annotation.authentication.ldap;
import java.util.Collections;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
@ -28,8 +26,6 @@ import org.springframework.security.config.annotation.authentication.ldap.LdapAu
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders; import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders;
import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers; import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
@ -64,7 +60,7 @@ public class LdapAuthenticationProviderConfigurerTests {
.password("bobspassword"); .password("bobspassword");
SecurityMockMvcResultMatchers.AuthenticatedMatcher expectedUser = authenticated() SecurityMockMvcResultMatchers.AuthenticatedMatcher expectedUser = authenticated()
.withUsername("bob") .withUsername("bob")
.withAuthorities(Collections.singleton(new SimpleGrantedAuthority("ROLE_DEVELOPERS"))); .withRoles("DEVELOPERS");
// @formatter:on // @formatter:on
this.mockMvc.perform(request).andExpect(expectedUser); this.mockMvc.perform(request).andExpect(expectedUser);
} }
@ -79,7 +75,7 @@ public class LdapAuthenticationProviderConfigurerTests {
.password("bobspassword"); .password("bobspassword");
SecurityMockMvcResultMatchers.AuthenticatedMatcher expectedUser = authenticated() SecurityMockMvcResultMatchers.AuthenticatedMatcher expectedUser = authenticated()
.withUsername("bob") .withUsername("bob")
.withAuthorities(Collections.singleton(new SimpleGrantedAuthority("ROL_DEVELOPERS"))); .withRoles("ROL_", new String[] { "DEVELOPERS" });
// @formatter:on // @formatter:on
this.mockMvc.perform(request).andExpect(expectedUser); this.mockMvc.perform(request).andExpect(expectedUser);
} }
@ -108,8 +104,7 @@ public class LdapAuthenticationProviderConfigurerTests {
.password("otherbenspassword"); .password("otherbenspassword");
SecurityMockMvcResultMatchers.AuthenticatedMatcher expectedUser = authenticated() SecurityMockMvcResultMatchers.AuthenticatedMatcher expectedUser = authenticated()
.withUsername("otherben") .withUsername("otherben")
.withAuthorities( .withRoles("SUBMANAGERS", "MANAGERS", "DEVELOPERS");
AuthorityUtils.createAuthorityList("ROLE_SUBMANAGERS", "ROLE_MANAGERS", "ROLE_DEVELOPERS"));
// @formatter:on // @formatter:on
this.mockMvc.perform(request).andExpect(expectedUser); this.mockMvc.perform(request).andExpect(expectedUser);
} }

View File

@ -16,7 +16,6 @@
package org.springframework.security.config.annotation.authentication.ldap; package org.springframework.security.config.annotation.authentication.ldap;
import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
@ -34,7 +33,6 @@ import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource; import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
import org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator; import org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator;
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders; import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders;
@ -79,7 +77,7 @@ public class NamespaceLdapAuthenticationProviderTests {
.user("bob") .user("bob")
.password("bobspassword"); .password("bobspassword");
SecurityMockMvcResultMatchers.AuthenticatedMatcher user = authenticated() SecurityMockMvcResultMatchers.AuthenticatedMatcher user = authenticated()
.withAuthorities(Collections.singleton(new SimpleGrantedAuthority("PREFIX_DEVELOPERS"))); .withRoles("PREFIX_", new String[] { "DEVELOPERS" });
// @formatter:on // @formatter:on
this.mockMvc.perform(request).andExpect(user); this.mockMvc.perform(request).andExpect(user);
} }
@ -103,7 +101,7 @@ public class NamespaceLdapAuthenticationProviderTests {
.user("bob") .user("bob")
.password("bobspassword"); .password("bobspassword");
SecurityMockMvcResultMatchers.AuthenticatedMatcher user = authenticated() SecurityMockMvcResultMatchers.AuthenticatedMatcher user = authenticated()
.withAuthorities(Collections.singleton(new SimpleGrantedAuthority("ROLE_EXTRA"))); .withRoles("EXTRA");
// @formatter:on // @formatter:on
this.mockMvc.perform(request).andExpect(user); this.mockMvc.perform(request).andExpect(user);
} }

View File

@ -0,0 +1,147 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.rsocket;
import java.util.ArrayList;
import java.util.List;
import io.rsocket.core.RSocketServer;
import io.rsocket.exceptions.RejectedSetupException;
import io.rsocket.frame.decoder.PayloadDecoder;
import io.rsocket.transport.netty.server.CloseableChannel;
import io.rsocket.transport.netty.server.TcpServerTransport;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.rsocket.RSocketRequester;
import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.rsocket.core.PayloadSocketAcceptorInterceptor;
import org.springframework.security.rsocket.core.SecuritySocketAcceptorInterceptor;
import org.springframework.security.rsocket.util.matcher.PayloadExchangeAuthorizationContext;
import org.springframework.stereotype.Controller;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* @author Andrey Litvitski
*/
@ContextConfiguration
@ExtendWith(SpringExtension.class)
public class AnonymousAuthenticationITests {
@Autowired
RSocketMessageHandler handler;
@Autowired
SecuritySocketAcceptorInterceptor interceptor;
@Autowired
ServerController controller;
private CloseableChannel server;
private RSocketRequester requester;
@BeforeEach
public void setup() {
// @formatter:off
this.server = RSocketServer.create()
.payloadDecoder(PayloadDecoder.ZERO_COPY)
.interceptors((registry) -> registry.forSocketAcceptor(this.interceptor)
)
.acceptor(this.handler.responder())
.bind(TcpServerTransport.create("localhost", 0))
.block();
// @formatter:on
}
@AfterEach
public void dispose() {
this.requester.rsocket().dispose();
this.server.dispose();
this.controller.payloads.clear();
}
@Test
public void requestWhenAnonymousDisabledThenRespondsWithForbidden() {
this.requester = RSocketRequester.builder()
.rsocketStrategies(this.handler.getRSocketStrategies())
.connectTcp("localhost", this.server.address().getPort())
.block();
String data = "andrew";
assertThatExceptionOfType(RejectedSetupException.class).isThrownBy(
() -> this.requester.route("secure.retrieve-mono").data(data).retrieveMono(String.class).block());
assertThat(this.controller.payloads).isEmpty();
}
@Configuration
@EnableRSocketSecurity
static class Config {
@Bean
ServerController controller() {
return new ServerController();
}
@Bean
RSocketMessageHandler messageHandler() {
return new RSocketMessageHandler();
}
@Bean
PayloadSocketAcceptorInterceptor rsocketInterceptor(RSocketSecurity rsocket) {
AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
ReactiveAuthorizationManager<PayloadExchangeAuthorizationContext> anonymous = (authentication,
exchange) -> authentication.map(trustResolver::isAnonymous).map(AuthorizationDecision::new);
rsocket.authorizePayload((authorize) -> authorize.anyExchange().access(anonymous));
rsocket.anonymous((anonymousAuthentication) -> anonymousAuthentication.disable());
return rsocket.build();
}
}
@Controller
static class ServerController {
private List<String> payloads = new ArrayList<>();
@MessageMapping("**")
String retrieveMono(String payload) {
add(payload);
return "Hi " + payload;
}
private void add(String p) {
this.payloads.add(p);
}
}
}

View File

@ -169,7 +169,7 @@ public class LdapProviderBeanDefinitionParserTests {
this.appCtx = new InMemoryXmlApplicationContext("<ldap-server />" + "<authentication-manager>" this.appCtx = new InMemoryXmlApplicationContext("<ldap-server />" + "<authentication-manager>"
+ " <ldap-authentication-provider user-dn-pattern='uid={0},ou=${udp}' group-search-filter='${gsf}={0}' />" + " <ldap-authentication-provider user-dn-pattern='uid={0},ou=${udp}' group-search-filter='${gsf}={0}' />"
+ "</authentication-manager>" + "</authentication-manager>"
+ "<b:bean id='org.springframework.beans.factory.config.PropertyPlaceholderConfigurer' class='org.springframework.beans.factory.config.PropertyPlaceholderConfigurer' />"); + "<b:bean id='org.springframework.context.support.PropertySourcesPlaceholderConfigurer' class='org.springframework.context.support.PropertySourcesPlaceholderConfigurer' />");
ProviderManager providerManager = this.appCtx.getBean(BeanIds.AUTHENTICATION_MANAGER, ProviderManager.class); ProviderManager providerManager = this.appCtx.getBean(BeanIds.AUTHENTICATION_MANAGER, ProviderManager.class);
assertThat(providerManager.getProviders()).hasSize(1); assertThat(providerManager.getProviders()).hasSize(1);

View File

@ -0,0 +1,52 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config;
/**
* A {@link Customizer} that allows invocation of code that throws a checked exception.
*
* @param <T> The type of input.
*/
@FunctionalInterface
public interface ThrowingCustomizer<T> extends Customizer<T> {
/**
* Default {@link Customizer#customize(Object)} that wraps any thrown checked
* exceptions (by default in a {@link RuntimeException}).
* @param t the object to customize
*/
default void customize(T t) {
try {
customizeWithException(t);
}
catch (RuntimeException ex) {
throw ex;
}
catch (Exception ex) {
throw new RuntimeException(ex);
}
}
/**
* Performs the customization on the given object, possibly throwing a checked
* exception.
* @param t the object to customize
* @throws Exception on error
*/
void customizeWithException(T t) throws Exception;
}

View File

@ -102,9 +102,7 @@ class AuthorizationProxyWebConfiguration implements WebMvcConfigurer {
Throwable accessDeniedException = this.throwableAnalyzer Throwable accessDeniedException = this.throwableAnalyzer
.getFirstThrowableOfType(AccessDeniedException.class, causeChain); .getFirstThrowableOfType(AccessDeniedException.class, causeChain);
if (accessDeniedException != null) { if (accessDeniedException != null) {
return new ModelAndView((model, req, res) -> { throw (AccessDeniedException) accessDeniedException;
throw ex;
});
} }
return null; return null;
} }

View File

@ -109,6 +109,7 @@ import org.springframework.security.rsocket.util.matcher.RoutePayloadExchangeMat
* @author Manuel Tejeda * @author Manuel Tejeda
* @author Ebert Toribio * @author Ebert Toribio
* @author Ngoc Nhan * @author Ngoc Nhan
* @author Andrey Litvitski
* @since 5.2 * @since 5.2
*/ */
public class RSocketSecurity { public class RSocketSecurity {
@ -119,6 +120,8 @@ public class RSocketSecurity {
private SimpleAuthenticationSpec simpleAuthSpec; private SimpleAuthenticationSpec simpleAuthSpec;
private AnonymousAuthenticationSpec anonymousAuthSpec = new AnonymousAuthenticationSpec(this);
private JwtSpec jwtSpec; private JwtSpec jwtSpec;
private AuthorizePayloadsSpec authorizePayload; private AuthorizePayloadsSpec authorizePayload;
@ -164,6 +167,20 @@ public class RSocketSecurity {
return this; return this;
} }
/**
* Adds anonymous authentication
* @param anonymous a customizer
* @return this instance
* @since 7.0
*/
public RSocketSecurity anonymous(Customizer<AnonymousAuthenticationSpec> anonymous) {
if (this.anonymousAuthSpec == null) {
this.anonymousAuthSpec = new AnonymousAuthenticationSpec(this);
}
anonymous.customize(this.anonymousAuthSpec);
return this;
}
/** /**
* Adds authentication with BasicAuthenticationPayloadExchangeConverter. * Adds authentication with BasicAuthenticationPayloadExchangeConverter.
* @param basic * @param basic
@ -214,7 +231,9 @@ public class RSocketSecurity {
if (this.jwtSpec != null) { if (this.jwtSpec != null) {
result.addAll(this.jwtSpec.build()); result.addAll(this.jwtSpec.build());
} }
result.add(anonymous()); if (this.anonymousAuthSpec != null) {
result.add(this.anonymousAuthSpec.build());
}
if (this.authorizePayload != null) { if (this.authorizePayload != null) {
result.add(this.authorizePayload.build()); result.add(this.authorizePayload.build());
} }
@ -222,12 +241,6 @@ public class RSocketSecurity {
return result; return result;
} }
private AnonymousPayloadInterceptor anonymous() {
AnonymousPayloadInterceptor result = new AnonymousPayloadInterceptor("anonymousUser");
result.setOrder(PayloadInterceptorOrder.ANONYMOUS.getOrder());
return result;
}
private <T> T getBean(Class<T> beanClass) { private <T> T getBean(Class<T> beanClass) {
if (this.context == null) { if (this.context == null) {
return null; return null;
@ -283,6 +296,26 @@ public class RSocketSecurity {
} }
public final class AnonymousAuthenticationSpec {
private RSocketSecurity parent;
private AnonymousAuthenticationSpec(RSocketSecurity parent) {
this.parent = parent;
}
protected AnonymousPayloadInterceptor build() {
AnonymousPayloadInterceptor result = new AnonymousPayloadInterceptor("anonymousUser");
result.setOrder(PayloadInterceptorOrder.ANONYMOUS.getOrder());
return result;
}
public void disable() {
this.parent.anonymousAuthSpec = null;
}
}
public final class BasicAuthenticationSpec { public final class BasicAuthenticationSpec {
private ReactiveAuthenticationManager authenticationManager; private ReactiveAuthenticationManager authenticationManager;

View File

@ -16,19 +16,24 @@
package org.springframework.security.config.annotation.web.configuration; package org.springframework.security.config.annotation.web.configuration;
import java.lang.reflect.Modifier;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.security.authentication.AuthenticationEventPublisher; import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher; import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.ObjectPostProcessor; import org.springframework.security.config.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
@ -46,6 +51,7 @@ import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter; import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.function.ThrowingSupplier; import org.springframework.util.function.ThrowingSupplier;
import org.springframework.web.accept.ContentNegotiationStrategy; import org.springframework.web.accept.ContentNegotiationStrategy;
import org.springframework.web.accept.HeaderContentNegotiationStrategy; import org.springframework.web.accept.HeaderContentNegotiationStrategy;
@ -131,6 +137,8 @@ class HttpSecurityConfiguration {
// @formatter:on // @formatter:on
applyCorsIfAvailable(http); applyCorsIfAvailable(http);
applyDefaultConfigurers(http); applyDefaultConfigurers(http);
applyHttpSecurityCustomizers(this.context, http);
applyTopLevelCustomizers(this.context, http);
return http; return http;
} }
@ -160,6 +168,73 @@ class HttpSecurityConfiguration {
} }
} }
/**
* Applies all {@code Customizer<HttpSecurity>} Bean instances to the
* {@link HttpSecurity} instance.
* @param applicationContext the {@link ApplicationContext} to lookup Bean instances
* @param http the {@link HttpSecurity} to apply the Beans to.
*/
private void applyHttpSecurityCustomizers(ApplicationContext applicationContext, HttpSecurity http) {
ResolvableType httpSecurityCustomizerType = ResolvableType.forClassWithGenerics(Customizer.class,
HttpSecurity.class);
ObjectProvider<Customizer<HttpSecurity>> customizerProvider = this.context
.getBeanProvider(httpSecurityCustomizerType);
// @formatter:off
customizerProvider.orderedStream().forEach((customizer) ->
customizer.customize(http)
);
// @formatter:on
}
/**
* Applies all {@link Customizer} Beans to {@link HttpSecurity}. For each public,
* non-static method in HttpSecurity that accepts a Customizer
* <ul>
* <li>Use the {@link MethodParameter} (this preserves generics) to resolve all Beans
* for that type</li>
* <li>For each {@link Customizer} Bean invoke the {@link java.lang.reflect.Method}
* with the {@link Customizer} Bean as the argument</li>
* </ul>
* @param context the {@link ApplicationContext}
* @param http the {@link HttpSecurity}
* @throws Exception
*/
private void applyTopLevelCustomizers(ApplicationContext context, HttpSecurity http) {
ReflectionUtils.MethodFilter isCustomizerMethod = (method) -> {
if (Modifier.isStatic(method.getModifiers())) {
return false;
}
if (!Modifier.isPublic(method.getModifiers())) {
return false;
}
if (!method.canAccess(http)) {
return false;
}
if (method.getParameterCount() != 1) {
return false;
}
if (method.getParameterTypes()[0] == Customizer.class) {
return true;
}
return false;
};
ReflectionUtils.MethodCallback invokeWithEachCustomizerBean = (customizerMethod) -> {
MethodParameter customizerParameter = new MethodParameter(customizerMethod, 0);
ResolvableType customizerType = ResolvableType.forMethodParameter(customizerParameter);
ObjectProvider<?> customizerProvider = context.getBeanProvider(customizerType);
// @formatter:off
customizerProvider.orderedStream().forEach((customizer) ->
ReflectionUtils.invokeMethod(customizerMethod, http, customizer)
);
// @formatter:on
};
ReflectionUtils.doWithMethods(HttpSecurity.class, invokeWithEachCustomizerBean, isCustomizerMethod);
}
private Map<Class<?>, Object> createSharedObjects() { private Map<Class<?>, Object> createSharedObjects() {
Map<Class<?>, Object> sharedObjects = new HashMap<>(); Map<Class<?>, Object> sharedObjects = new HashMap<>();
sharedObjects.put(ApplicationContext.class, this.context); sharedObjects.put(ApplicationContext.class, this.context);

View File

@ -33,7 +33,6 @@ import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistry;
@ -79,6 +78,7 @@ import org.springframework.web.filter.ServletRequestPathFilter;
* *
* @author Rob Winch * @author Rob Winch
* @author Keesun Baik * @author Keesun Baik
* @author Yanming Zhou
* @since 3.2 * @since 3.2
* @see EnableWebSecurity * @see EnableWebSecurity
* @see WebSecurity * @see WebSecurity
@ -190,7 +190,7 @@ public class WebSecurityConfiguration implements ImportAware {
} }
@Bean @Bean
public static BeanFactoryPostProcessor conversionServicePostProcessor() { public static RsaKeyConversionServicePostProcessor conversionServicePostProcessor() {
return new RsaKeyConversionServicePostProcessor(); return new RsaKeyConversionServicePostProcessor();
} }

View File

@ -16,6 +16,7 @@
package org.springframework.security.config.annotation.web.reactive; package org.springframework.security.config.annotation.web.reactive;
import java.lang.reflect.Modifier;
import java.util.Map; import java.util.Map;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
@ -28,10 +29,13 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import org.springframework.context.expression.BeanFactoryResolver; import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.core.MethodParameter;
import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.core.ResolvableType;
import org.springframework.security.authentication.ReactiveAuthenticationManager; import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager; import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager;
import org.springframework.security.authentication.password.ReactiveCompromisedPasswordChecker; import org.springframework.security.authentication.password.ReactiveCompromisedPasswordChecker;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.ObjectPostProcessor; import org.springframework.security.config.ObjectPostProcessor;
import org.springframework.security.config.web.server.ServerHttpSecurity; import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults; import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults;
@ -40,6 +44,7 @@ import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.reactive.result.method.annotation.AuthenticationPrincipalArgumentResolver; import org.springframework.security.web.reactive.result.method.annotation.AuthenticationPrincipalArgumentResolver;
import org.springframework.security.web.reactive.result.method.annotation.CurrentSecurityContextArgumentResolver; import org.springframework.security.web.reactive.result.method.annotation.CurrentSecurityContextArgumentResolver;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.reactive.config.WebFluxConfigurer; import org.springframework.web.reactive.config.WebFluxConfigurer;
import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer; import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer;
@ -154,6 +159,83 @@ class ServerHttpSecurityConfiguration {
@Bean(HTTPSECURITY_BEAN_NAME) @Bean(HTTPSECURITY_BEAN_NAME)
@Scope("prototype") @Scope("prototype")
ServerHttpSecurity httpSecurity(ApplicationContext context) {
ServerHttpSecurity http = httpSecurity();
applyServerHttpSecurityCustomizers(context, http);
applyTopLevelBeanCustomizers(context, http);
return http;
}
/**
* Applies all {@code Custmizer<ServerHttpSecurity>} Beans to
* {@link ServerHttpSecurity}.
* @param context the {@link ApplicationContext}
* @param http the {@link ServerHttpSecurity}
* @throws Exception
*/
private void applyServerHttpSecurityCustomizers(ApplicationContext context, ServerHttpSecurity http) {
ResolvableType httpSecurityCustomizerType = ResolvableType.forClassWithGenerics(Customizer.class,
ServerHttpSecurity.class);
ObjectProvider<Customizer<ServerHttpSecurity>> customizerProvider = context
.getBeanProvider(httpSecurityCustomizerType);
// @formatter:off
customizerProvider.orderedStream().forEach((customizer) ->
customizer.customize(http)
);
// @formatter:on
}
/**
* Applies all {@link Customizer} Beans to top level {@link ServerHttpSecurity}
* method.
*
* For each public, non-static method in ServerHttpSecurity that accepts a Customizer
* <ul>
* <li>Use the {@link MethodParameter} (this preserves generics) to resolve all Beans
* for that type</li>
* <li>For each {@link Customizer} Bean invoke the {@link java.lang.reflect.Method}
* with the {@link Customizer} Bean as the argument</li>
* </ul>
* @param context the {@link ApplicationContext}
* @param http the {@link ServerHttpSecurity}
* @throws Exception
*/
private void applyTopLevelBeanCustomizers(ApplicationContext context, ServerHttpSecurity http) {
ReflectionUtils.MethodFilter isCustomizerMethod = (method) -> {
if (Modifier.isStatic(method.getModifiers())) {
return false;
}
if (!Modifier.isPublic(method.getModifiers())) {
return false;
}
if (!method.canAccess(http)) {
return false;
}
if (method.getParameterCount() != 1) {
return false;
}
if (method.getParameterTypes()[0] == Customizer.class) {
return true;
}
return false;
};
ReflectionUtils.MethodCallback invokeWithEachCustomizerBean = (customizerMethod) -> {
MethodParameter customizerParameter = new MethodParameter(customizerMethod, 0);
ResolvableType customizerType = ResolvableType.forMethodParameter(customizerParameter);
ObjectProvider<?> customizerProvider = context.getBeanProvider(customizerType);
// @formatter:off
customizerProvider.orderedStream().forEach((customizer) ->
ReflectionUtils.invokeMethod(customizerMethod, http, customizer)
);
// @formatter:on
};
ReflectionUtils.doWithMethods(ServerHttpSecurity.class, invokeWithEachCustomizerBean, isCustomizerMethod);
}
ServerHttpSecurity httpSecurity() { ServerHttpSecurity httpSecurity() {
ContextAwareServerHttpSecurity http = new ContextAwareServerHttpSecurity(); ContextAwareServerHttpSecurity http = new ContextAwareServerHttpSecurity();
// @formatter:off // @formatter:off

View File

@ -21,7 +21,6 @@ import java.util.List;
import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -44,12 +43,13 @@ import static org.springframework.security.config.Customizer.withDefaults;
/** /**
* @author Rob Winch * @author Rob Winch
* @author Yanming Zhou
* @since 5.0 * @since 5.0
*/ */
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
class WebFluxSecurityConfiguration { class WebFluxSecurityConfiguration {
public static final int WEB_FILTER_CHAIN_FILTER_ORDER = 0 - 100; public static final int WEB_FILTER_CHAIN_FILTER_ORDER = -100;
private static final String BEAN_NAME_PREFIX = "org.springframework.security.config.annotation.web.reactive.WebFluxSecurityConfiguration."; private static final String BEAN_NAME_PREFIX = "org.springframework.security.config.annotation.web.reactive.WebFluxSecurityConfiguration.";
@ -100,7 +100,7 @@ class WebFluxSecurityConfiguration {
} }
@Bean @Bean
static BeanFactoryPostProcessor conversionServicePostProcessor() { static RsaKeyConversionServicePostProcessor conversionServicePostProcessor() {
return new RsaKeyConversionServicePostProcessor(); return new RsaKeyConversionServicePostProcessor();
} }

View File

@ -20,6 +20,7 @@ import java.util.Map;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.aopalliance.intercept.MethodInvocation; import org.aopalliance.intercept.MethodInvocation;
import org.jspecify.annotations.Nullable;
import org.springframework.aop.Pointcut; import org.springframework.aop.Pointcut;
import org.springframework.aop.support.AopUtils; import org.springframework.aop.support.AopUtils;
@ -37,7 +38,8 @@ class PointcutDelegatingAuthorizationManager implements AuthorizationManager<Met
} }
@Override @Override
public AuthorizationResult authorize(Supplier<Authentication> authentication, MethodInvocation object) { public AuthorizationResult authorize(Supplier<? extends @Nullable Authentication> authentication,
MethodInvocation object) {
for (Map.Entry<Pointcut, AuthorizationManager<MethodInvocation>> entry : this.managers.entrySet()) { for (Map.Entry<Pointcut, AuthorizationManager<MethodInvocation>> entry : this.managers.entrySet()) {
Class<?> targetClass = (object.getThis() != null) ? AopUtils.getTargetClass(object.getThis()) : null; Class<?> targetClass = (object.getThis() != null) ? AopUtils.getTargetClass(object.getThis()) : null;
if (entry.getKey().getClassFilter().matches(targetClass) if (entry.getKey().getClassFilter().matches(targetClass)

View File

@ -1316,6 +1316,10 @@ public class ServerHttpSecurity {
return this.context.getBeanNamesForType(beanClass); return this.context.getBeanNamesForType(beanClass);
} }
ApplicationContext getApplicationContext() {
return this.context;
}
protected void setApplicationContext(ApplicationContext applicationContext) throws BeansException { protected void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext; this.context = applicationContext;
} }
@ -3029,7 +3033,8 @@ public class ServerHttpSecurity {
/** /**
* Configures the logout handler. Default is * Configures the logout handler. Default is
* {@code SecurityContextServerLogoutHandler} * {@code SecurityContextServerLogoutHandler}. This clears any previous handlers
* configured.
* @param logoutHandler * @param logoutHandler
* @return the {@link LogoutSpec} to configure * @return the {@link LogoutSpec} to configure
*/ */
@ -3045,6 +3050,18 @@ public class ServerHttpSecurity {
return this; return this;
} }
/**
* Allows managing the list of {@link ServerLogoutHandler} instances.
* @param handlersConsumer {@link Consumer} for managing the list of handlers.
* @return the {@link LogoutSpec} to configure
* @since 7.0
*/
public LogoutSpec logoutHandler(Consumer<List<ServerLogoutHandler>> handlersConsumer) {
Assert.notNull(handlersConsumer, "consumer cannot be null");
handlersConsumer.accept(this.logoutHandlers);
return this;
}
/** /**
* Configures what URL a POST to will trigger a log out. * Configures what URL a POST to will trigger a log out.
* @param logoutUrl the url to trigger a log out (i.e. "/signout" would mean a * @param logoutUrl the url to trigger a log out (i.e. "/signout" would mean a

View File

@ -25,6 +25,7 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.jspecify.annotations.Nullable;
import org.w3c.dom.Element; import org.w3c.dom.Element;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
@ -458,7 +459,7 @@ public final class WebSocketMessageBrokerSecurityBeanDefinitionParser implements
} }
@Override @Override
public AuthorizationResult authorize(Supplier<Authentication> authentication, public AuthorizationResult authorize(Supplier<? extends @Nullable Authentication> authentication,
MessageAuthorizationContext<?> object) { MessageAuthorizationContext<?> object) {
EvaluationContext context = this.expressionHandler.createEvaluationContext(authentication, object); EvaluationContext context = this.expressionHandler.createEvaluationContext(authentication, object);
boolean granted = ExpressionUtils.evaluateAsBoolean(this.expression, context); boolean granted = ExpressionUtils.evaluateAsBoolean(this.expression, context);

View File

@ -29,7 +29,6 @@ import org.springframework.security.config.annotation.web.configurers.AuthorizeH
import org.springframework.security.config.core.GrantedAuthorityDefaults import org.springframework.security.config.core.GrantedAuthorityDefaults
import org.springframework.security.core.Authentication import org.springframework.security.core.Authentication
import org.springframework.security.web.access.IpAddressAuthorizationManager import org.springframework.security.web.access.IpAddressAuthorizationManager
import org.springframework.security.web.access.intercept.AuthorizationFilter
import org.springframework.security.web.access.intercept.RequestAuthorizationContext import org.springframework.security.web.access.intercept.RequestAuthorizationContext
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher
import org.springframework.security.web.util.matcher.AnyRequestMatcher import org.springframework.security.web.util.matcher.AnyRequestMatcher
@ -235,13 +234,13 @@ class AuthorizeHttpRequestsDsl : AbstractRequestMatcherDsl {
* Specify that URLs are allowed by anyone. * Specify that URLs are allowed by anyone.
*/ */
val permitAll: AuthorizationManager<RequestAuthorizationContext> = val permitAll: AuthorizationManager<RequestAuthorizationContext> =
AuthorizationManager { _: Supplier<Authentication>, _: RequestAuthorizationContext -> AuthorizationDecision(true) } AuthorizationManager { _: Supplier<out Authentication>, _: RequestAuthorizationContext -> AuthorizationDecision(true) }
/** /**
* Specify that URLs are not allowed by anyone. * Specify that URLs are not allowed by anyone.
*/ */
val denyAll: AuthorizationManager<RequestAuthorizationContext> = val denyAll: AuthorizationManager<RequestAuthorizationContext> =
AuthorizationManager { _: Supplier<Authentication>, _: RequestAuthorizationContext -> AuthorizationDecision(false) } AuthorizationManager { _: Supplier<out Authentication>, _: RequestAuthorizationContext -> AuthorizationDecision(false) }
/** /**
* Specify that URLs are allowed by any authenticated user. * Specify that URLs are allowed by any authenticated user.

View File

@ -18,14 +18,22 @@ package org.springframework.security.config.annotation.web
import jakarta.servlet.Filter import jakarta.servlet.Filter
import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletRequest
import org.springframework.beans.factory.ObjectProvider
import org.springframework.context.ApplicationContext import org.springframework.context.ApplicationContext
import org.springframework.core.MethodParameter
import org.springframework.core.ResolvableType
import org.springframework.core.annotation.AnnotationUtils
import org.springframework.security.authentication.AuthenticationManager import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.config.Customizer
import org.springframework.security.config.annotation.SecurityConfigurerAdapter import org.springframework.security.config.annotation.SecurityConfigurerAdapter
import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository
import org.springframework.security.web.DefaultSecurityFilterChain import org.springframework.security.web.DefaultSecurityFilterChain
import org.springframework.security.web.util.matcher.RequestMatcher import org.springframework.security.web.util.matcher.RequestMatcher
import org.springframework.util.ReflectionUtils
import java.lang.reflect.Method
import java.lang.reflect.Modifier
/** /**
* Configures [HttpSecurity] using a [HttpSecurity Kotlin DSL][HttpSecurityDsl]. * Configures [HttpSecurity] using a [HttpSecurity Kotlin DSL][HttpSecurityDsl].
@ -77,6 +85,117 @@ class HttpSecurityDsl(private val http: HttpSecurity, private val init: HttpSecu
var authenticationManager: AuthenticationManager? = null var authenticationManager: AuthenticationManager? = null
val context: ApplicationContext = http.getSharedObject(ApplicationContext::class.java) val context: ApplicationContext = http.getSharedObject(ApplicationContext::class.java)
init {
applyFunction1HttpSecurityDslBeans(this.context, this)
applyTopLevelFunction1SecurityDslBeans(this.context, this)
}
/**
* Applies all `Function1<HttpSecurity,Unit>` Beans which
* allows exposing the DSL as Beans to be applied.
*
* ```
* @Bean
* fun httpSecurityDslBean(): HttpSecurityDsl.() -> Unit {
* return {
* headers {
* contentSecurityPolicy {
* policyDirectives = "object-src 'none'"
* }
* }
* redirectToHttps { }
* }
* }
* ```
*/
private fun applyFunction1HttpSecurityDslBeans(context: ApplicationContext, http: HttpSecurityDsl) : Unit {
val httpSecurityDslFnType = ResolvableType.forClassWithGenerics(Function1::class.java,
HttpSecurityDsl::class.java, Unit::class.java)
val httpSecurityDslFnProvider = context
.getBeanProvider<Function1<HttpSecurityDsl,Unit>>(httpSecurityDslFnType)
// @formatter:off
httpSecurityDslFnProvider.orderedStream().forEach { fn -> fn.invoke(http) }
// @formatter:on
}
/**
* Applies all `Function1<T,Unit>` Beans such that `T` is a top level
* DSL on `HttpSecurityDsl`. This allows exposing the top level
* DSLs as Beans to be applied.
*
*
* ```
* @Bean
* fun httpSecurityCustomizer(): ThrowingCustomizer<HttpSecurity> {
* return ThrowingCustomizer { http -> http
* .headers { headers -> headers
* .contentSecurityPolicy { csp -> csp
* .policyDirectives("object-src 'none'")
* }
* }
* .redirectToHttps(Customizer.withDefaults())
* }
* }
* ```
*
* @param context the [ApplicationContext]
* @param http the [HttpSecurity]
* @throws Exception
*/
private fun applyTopLevelFunction1SecurityDslBeans(context: ApplicationContext, http: HttpSecurityDsl) {
val isCustomizerMethod = ReflectionUtils.MethodFilter { method: Method ->
if (Modifier.isStatic(method.modifiers)) {
return@MethodFilter false
}
if (!Modifier.isPublic(method.modifiers)) {
return@MethodFilter false
}
if (!method.canAccess(http)) {
return@MethodFilter false
}
if (method.parameterCount != 1) {
return@MethodFilter false
}
return@MethodFilter extractDslType(method) != null
}
val invokeWithEachDslBean = ReflectionUtils.MethodCallback { dslMethod: Method ->
val dslFunctionType = firstMethodResolvableType(dslMethod)!!
val dslFunctionProvider: ObjectProvider<*> = context.getBeanProvider<Any>(dslFunctionType)
// @formatter:off
dslFunctionProvider.orderedStream().forEach {customizer: Any -> ReflectionUtils.invokeMethod(dslMethod, http, customizer)}
}
ReflectionUtils.doWithMethods(HttpSecurityDsl::class.java, invokeWithEachDslBean, isCustomizerMethod)
}
/**
* From a `Method` with the first argument `Function<T,Unit>` return `T` or `null`
* if the first argument is not a `Function`.
* @return From a `Method` with the first argument `Function<T,Unit>` return `T`.
*/
private fun extractDslType(method: Method): ResolvableType? {
val functionType = firstMethodResolvableType(method)
if (!Function::class.java.isAssignableFrom(functionType.toClass())) {
return null
}
val functionInputType = functionType.getGeneric(0)
val securityMarker = AnnotationUtils.findAnnotation(functionInputType.toClass(), SecurityMarker::class.java)
val isSecurityDsl = securityMarker != null
if (!isSecurityDsl) {
return null
}
return functionInputType
}
private fun firstMethodResolvableType(method: Method): ResolvableType {
val parameter = MethodParameter(
method, 0
)
return ResolvableType.forMethodParameter(parameter)
}
/** /**
* Applies a [SecurityConfigurerAdapter] to this [HttpSecurity] * Applies a [SecurityConfigurerAdapter] to this [HttpSecurity]
* *

View File

@ -33,6 +33,7 @@ import org.springframework.security.web.util.matcher.RequestMatcher
* @property channelProcessors the [ChannelProcessor] instances to use in * @property channelProcessors the [ChannelProcessor] instances to use in
* [ChannelDecisionManagerImpl] * [ChannelDecisionManagerImpl]
*/ */
@Deprecated(message="since 6.5 use redirectToHttps instead")
class RequiresChannelDsl : AbstractRequestMatcherDsl() { class RequiresChannelDsl : AbstractRequestMatcherDsl() {
private val channelSecurityRules = mutableListOf<AuthorizationRule>() private val channelSecurityRules = mutableListOf<AuthorizationRule>()

View File

@ -16,13 +16,23 @@
package org.springframework.security.config.web.server package org.springframework.security.config.web.server
import org.springframework.beans.factory.ObjectProvider
import org.springframework.context.ApplicationContext
import org.springframework.core.MethodParameter
import org.springframework.core.ResolvableType
import org.springframework.core.annotation.AnnotationUtils
import org.springframework.security.authentication.ReactiveAuthenticationManager import org.springframework.security.authentication.ReactiveAuthenticationManager
import org.springframework.security.config.Customizer
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository
import org.springframework.security.web.server.SecurityWebFilterChain import org.springframework.security.web.server.SecurityWebFilterChain
import org.springframework.security.web.server.context.ServerSecurityContextRepository import org.springframework.security.web.server.context.ServerSecurityContextRepository
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher
import org.springframework.util.ReflectionUtils
import org.springframework.web.server.ServerWebExchange import org.springframework.web.server.ServerWebExchange
import org.springframework.web.server.WebFilter import org.springframework.web.server.WebFilter
import java.lang.reflect.Method
import java.lang.reflect.Modifier
/** /**
* Configures [ServerHttpSecurity] using a [ServerHttpSecurity Kotlin DSL][ServerHttpSecurityDsl]. * Configures [ServerHttpSecurity] using a [ServerHttpSecurity Kotlin DSL][ServerHttpSecurityDsl].
@ -68,6 +78,115 @@ class ServerHttpSecurityDsl(private val http: ServerHttpSecurity, private val in
var authenticationManager: ReactiveAuthenticationManager? = null var authenticationManager: ReactiveAuthenticationManager? = null
var securityContextRepository: ServerSecurityContextRepository? = null var securityContextRepository: ServerSecurityContextRepository? = null
init {
applyFunction1HttpSecurityDslBeans(this.http.applicationContext, this)
applyTopLevelFunction1SecurityDslBeans(this.http.applicationContext, this)
}
/**
* Applies all `Function1<ServerHttpSecurityDsl,Unit>` Beans which
* allows exposing the DSL as Beans to be applied.
*
* ```
* @Bean
* @Order(Ordered.LOWEST_PRECEDENCE)
* fun userAuthorization(): ServerHttpSecurityDsl.() -> Unit {
* // @formatter:off
* return {
* authorizeExchange {
* authorize("/user/profile", hasRole("USER"))
* }
* }
* // @formatter:on
* }
* ```
*/
private fun applyFunction1HttpSecurityDslBeans(context: ApplicationContext, http: ServerHttpSecurityDsl) : Unit {
val httpSecurityDslFnType = ResolvableType.forClassWithGenerics(Function1::class.java,
ServerHttpSecurityDsl::class.java, Unit::class.java)
val httpSecurityDslFnProvider = context
.getBeanProvider<Function1<ServerHttpSecurityDsl,Unit>>(httpSecurityDslFnType)
// @formatter:off
httpSecurityDslFnProvider.orderedStream().forEach { fn -> fn.invoke(http) }
// @formatter:on
}
/**
* Applies all `Function1<T,Unit>` Beans such that `T` is a top level
* DSL on `ServerHttpSecurityDsl`. This allows exposing the top level
* DSLs as Beans to be applied.
*
* ```
* @Bean
* fun headersSecurity(): Customizer<ServerHttpSecurity.HeaderSpec> {
* // @formatter:off
* return Customizer { headers -> headers
* .contentSecurityPolicy { csp -> csp
* .policyDirectives("object-src 'none'")
* }
* }
* // @formatter:on
* }
* ```
*
* @param context the [ApplicationContext]
* @param http the [HttpSecurity]
* @throws Exception
*/
private fun applyTopLevelFunction1SecurityDslBeans(context: ApplicationContext, http: ServerHttpSecurityDsl) {
val isCustomizerMethod = ReflectionUtils.MethodFilter { method: Method ->
if (Modifier.isStatic(method.modifiers)) {
return@MethodFilter false
}
if (!Modifier.isPublic(method.modifiers)) {
return@MethodFilter false
}
if (!method.canAccess(http)) {
return@MethodFilter false
}
if (method.parameterCount != 1) {
return@MethodFilter false
}
return@MethodFilter extractDslType(method) != null
}
val invokeWithEachDslBean = ReflectionUtils.MethodCallback { dslMethod: Method ->
val dslFunctionType = firstMethodResolvableType(dslMethod)!!
val dslFunctionProvider: ObjectProvider<*> = context.getBeanProvider<Any>(dslFunctionType)
// @formatter:off
dslFunctionProvider.orderedStream().forEach {customizer: Any -> ReflectionUtils.invokeMethod(dslMethod, http, customizer)}
}
ReflectionUtils.doWithMethods(ServerHttpSecurityDsl::class.java, invokeWithEachDslBean, isCustomizerMethod)
}
/**
* From a `Method` with the first argument `Function<T,Unit>` return `T` or `null`
* if the first argument is not a `Function`.
* @return From a `Method` with the first argument `Function<T,Unit>` return `T`.
*/
private fun extractDslType(method: Method): ResolvableType? {
val functionType = firstMethodResolvableType(method)
if (!Function::class.java.isAssignableFrom(functionType.toClass())) {
return null
}
val functionInputType = functionType.getGeneric(0)
val securityMarker = AnnotationUtils.findAnnotation(functionInputType.toClass(), ServerSecurityMarker::class.java)
val isSecurityDsl = securityMarker != null
if (!isSecurityDsl) {
return null
}
return functionInputType
}
private fun firstMethodResolvableType(method: Method): ResolvableType {
val parameter = MethodParameter(
method, 0
)
return ResolvableType.forMethodParameter(parameter)
}
/** /**
* Allows configuring the [ServerHttpSecurity] to only be invoked when matching the * Allows configuring the [ServerHttpSecurity] to only be invoked when matching the
* provided [ServerWebExchangeMatcher]. * provided [ServerWebExchangeMatcher].

View File

@ -78,6 +78,7 @@ import org.springframework.security.authentication.jaas.event.JaasAuthentication
import org.springframework.security.authentication.jaas.event.JaasAuthenticationSuccessEvent; import org.springframework.security.authentication.jaas.event.JaasAuthenticationSuccessEvent;
import org.springframework.security.authentication.ott.DefaultOneTimeToken; import org.springframework.security.authentication.ott.DefaultOneTimeToken;
import org.springframework.security.authentication.ott.InvalidOneTimeTokenException; import org.springframework.security.authentication.ott.InvalidOneTimeTokenException;
import org.springframework.security.authentication.ott.OneTimeTokenAuthentication;
import org.springframework.security.authentication.ott.OneTimeTokenAuthenticationToken; import org.springframework.security.authentication.ott.OneTimeTokenAuthenticationToken;
import org.springframework.security.authentication.password.CompromisedPasswordException; import org.springframework.security.authentication.password.CompromisedPasswordException;
import org.springframework.security.authorization.AuthorityAuthorizationDecision; import org.springframework.security.authorization.AuthorityAuthorizationDecision;
@ -189,6 +190,7 @@ import org.springframework.security.saml2.provider.service.registration.OpenSaml
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations; import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations;
import org.springframework.security.web.PortResolverImpl; import org.springframework.security.web.PortResolverImpl;
import org.springframework.security.web.authentication.AuthenticationFilter;
import org.springframework.security.web.authentication.WebAuthenticationDetails; import org.springframework.security.web.authentication.WebAuthenticationDetails;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedCredentialsNotFoundException; import org.springframework.security.web.authentication.preauth.PreAuthenticatedCredentialsNotFoundException;
@ -400,6 +402,8 @@ final class SerializationSamples {
}); });
generatorByClassName.put(OneTimeTokenAuthenticationToken.class, generatorByClassName.put(OneTimeTokenAuthenticationToken.class,
(r) -> applyDetails(new OneTimeTokenAuthenticationToken("username", "token"))); (r) -> applyDetails(new OneTimeTokenAuthenticationToken("username", "token")));
generatorByClassName.put(OneTimeTokenAuthentication.class,
(r) -> applyDetails(new OneTimeTokenAuthentication("username", authentication.getAuthorities())));
generatorByClassName.put(AccessDeniedException.class, generatorByClassName.put(AccessDeniedException.class,
(r) -> new AccessDeniedException("access denied", new RuntimeException())); (r) -> new AccessDeniedException("access denied", new RuntimeException()));
generatorByClassName.put(AuthorizationServiceException.class, generatorByClassName.put(AuthorizationServiceException.class,
@ -453,7 +457,7 @@ final class SerializationSamples {
generatorByClassName.put(AuthenticationSuccessEvent.class, generatorByClassName.put(AuthenticationSuccessEvent.class,
(r) -> new AuthenticationSuccessEvent(authentication)); (r) -> new AuthenticationSuccessEvent(authentication));
generatorByClassName.put(InteractiveAuthenticationSuccessEvent.class, generatorByClassName.put(InteractiveAuthenticationSuccessEvent.class,
(r) -> new InteractiveAuthenticationSuccessEvent(authentication, Authentication.class)); (r) -> new InteractiveAuthenticationSuccessEvent(authentication, AuthenticationFilter.class));
generatorByClassName.put(LogoutSuccessEvent.class, (r) -> new LogoutSuccessEvent(authentication)); generatorByClassName.put(LogoutSuccessEvent.class, (r) -> new LogoutSuccessEvent(authentication));
generatorByClassName.put(JaasAuthenticationFailedEvent.class, generatorByClassName.put(JaasAuthenticationFailedEvent.class,
(r) -> new JaasAuthenticationFailedEvent(authentication, new RuntimeException("message"))); (r) -> new JaasAuthenticationFailedEvent(authentication, new RuntimeException("message")));

View File

@ -32,6 +32,7 @@ import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager; import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.SecurityAssertions;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.ObjectPostProcessor; import org.springframework.security.config.ObjectPostProcessor;
@ -44,7 +45,6 @@ import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.PasswordEncodedUser; import org.springframework.security.core.userdetails.PasswordEncodedUser;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.NoOpPasswordEncoder;
@ -107,8 +107,7 @@ public class AuthenticationManagerBuilderTests {
.getAuthenticationManager(); .getAuthenticationManager();
Authentication auth = manager Authentication auth = manager
.authenticate(UsernamePasswordAuthenticationToken.unauthenticated("user", "password")); .authenticate(UsernamePasswordAuthenticationToken.unauthenticated("user", "password"));
assertThat(auth.getName()).isEqualTo("user"); SecurityAssertions.assertThat(auth).name("user").hasAuthority("ROLE_USER");
assertThat(auth.getAuthorities()).extracting(GrantedAuthority::getAuthority).containsOnly("ROLE_USER");
} }
@Test @Test
@ -119,8 +118,7 @@ public class AuthenticationManagerBuilderTests {
.getAuthenticationManager(); .getAuthenticationManager();
Authentication auth = manager Authentication auth = manager
.authenticate(UsernamePasswordAuthenticationToken.unauthenticated("user", "password")); .authenticate(UsernamePasswordAuthenticationToken.unauthenticated("user", "password"));
assertThat(auth.getName()).isEqualTo("user"); SecurityAssertions.assertThat(auth).name("user").hasAuthority("ROLE_USER");
assertThat(auth.getAuthorities()).extracting(GrantedAuthority::getAuthority).containsOnly("ROLE_USER");
} }
@Test @Test

View File

@ -33,6 +33,7 @@ import io.micrometer.observation.ObservationHandler;
import io.micrometer.observation.ObservationRegistry; import io.micrometer.observation.ObservationRegistry;
import io.micrometer.observation.ObservationTextPublisher; import io.micrometer.observation.ObservationTextPublisher;
import jakarta.annotation.security.DenyAll; import jakarta.annotation.security.DenyAll;
import jakarta.servlet.RequestDispatcher;
import org.aopalliance.aop.Advice; import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation; import org.aopalliance.intercept.MethodInvocation;
@ -138,6 +139,7 @@ import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatNoException; import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.hamcrest.Matchers.nullValue;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.clearInvocations;
@ -149,6 +151,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyNoInteractions;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/** /**
@ -1279,6 +1282,19 @@ public class PrePostMethodSecurityConfigurationTests {
this.mvc.perform(requestWithUser).andExpect(status().isForbidden()); this.mvc.perform(requestWithUser).andExpect(status().isForbidden());
} }
// gh-17761
@Test
void getWhenPostAuthorizeAuthenticationNameNotMatchThenNoExceptionExposedInRequest() throws Exception {
this.spring.register(WebMvcMethodSecurityConfig.class, BasicController.class).autowire();
// @formatter:off
MockHttpServletRequestBuilder requestWithUser = get("/authorized-person")
.param("name", "john")
.with(user("rob"));
// @formatter:on
this.mvc.perform(requestWithUser)
.andExpect(request().attribute(RequestDispatcher.ERROR_EXCEPTION, nullValue()));
}
@Test @Test
void getWhenPostAuthorizeWithinServiceAuthenticationNameMatchesThenRespondsWithOk() throws Exception { void getWhenPostAuthorizeWithinServiceAuthenticationNameMatchesThenRespondsWithOk() throws Exception {
this.spring.register(WebMvcMethodSecurityConfig.class, BasicController.class, BasicService.class).autowire(); this.spring.register(WebMvcMethodSecurityConfig.class, BasicController.class, BasicService.class).autowire();

View File

@ -25,6 +25,7 @@ import javax.security.auth.login.LoginContext;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession; import jakarta.servlet.http.HttpSession;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
@ -310,7 +311,7 @@ public class NamespaceHttpTests {
} }
@Override @Override
public AuthorizationResult authorize(Supplier<Authentication> authentication, public AuthorizationResult authorize(Supplier<? extends @Nullable Authentication> authentication,
RequestAuthorizationContext object) { RequestAuthorizationContext object) {
HttpServletRequest request = object.getRequest(); HttpServletRequest request = object.getRequest();
FilterInvocation invocation = new FilterInvocation(request.getContextPath(), request.getServletPath(), FilterInvocation invocation = new FilterInvocation(request.getContextPath(), request.getServletPath(),

View File

@ -28,14 +28,20 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockedStatic; import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.event.EventListener; import org.springframework.context.event.EventListener;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.mock.web.MockHttpSession; import org.springframework.mock.web.MockHttpSession;
import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.AccessDeniedException;
@ -54,6 +60,7 @@ import org.springframework.security.config.annotation.SecurityContextChangedList
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.AnonymousConfigurer; import org.springframework.security.config.annotation.web.configurers.AnonymousConfigurer;
import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;
import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer; import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer;
import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.config.test.SpringTestContextExtension;
@ -82,12 +89,15 @@ import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter; import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.willAnswer;
import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.withSettings;
import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.config.Customizer.withDefaults;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
@ -425,6 +435,77 @@ public class HttpSecurityConfigurationTests {
.andExpectAll(status().isFound(), redirectedUrl("/"), authenticated()); .andExpectAll(status().isFound(), redirectedUrl("/"), authenticated());
} }
@Test
void authorizeHttpRequestsCustomizerBean() throws Exception {
this.spring.register(AuthorizeRequestsBeanConfiguration.class, UserDetailsConfig.class).autowire();
Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> authorizeRequests = this.spring
.getContext()
.getBean("authorizeRequests", Customizer.class);
verify(authorizeRequests).customize(any());
}
@Test
void multiAuthorizeHttpRequestsCustomizerBean() throws Exception {
this.spring.register(MultiAuthorizeRequestsBeanConfiguration.class, UserDetailsConfig.class).autowire();
Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> authorizeRequests0 = this.spring
.getContext()
.getBean("authorizeRequests0", Customizer.class);
Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> authorizeRequests = this.spring
.getContext()
.getBean("authorizeRequests", Customizer.class);
InOrder inOrder = Mockito.inOrder(authorizeRequests0, authorizeRequests);
ArgumentCaptor<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> arg0 = ArgumentCaptor
.forClass(AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry.class);
ArgumentCaptor<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> arg1 = ArgumentCaptor
.forClass(AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry.class);
inOrder.verify(authorizeRequests0).customize(arg0.capture());
inOrder.verify(authorizeRequests).customize(arg1.capture());
}
@Test
void disableAuthorizeHttpRequestsCustomizerBean() throws Exception {
this.spring.register(AuthorizeRequestsBeanConfiguration.class, UserDetailsConfig.class).autowire();
Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> authorizeRequests = this.spring
.getContext()
.getBean("authorizeRequests", Customizer.class);
verify(authorizeRequests).customize(any());
}
@Test
void httpSecurityCustomizerBean() throws Exception {
this.spring.register(HttpSecurityCustomizerBeanConfiguration.class, UserDetailsConfig.class).autowire();
Customizer<HttpSecurity> httpSecurityCustomizer = this.spring.getContext()
.getBean("httpSecurityCustomizer", Customizer.class);
ArgumentCaptor<HttpSecurity> arg0 = ArgumentCaptor.forClass(HttpSecurity.class);
verify(httpSecurityCustomizer).customize(arg0.capture());
}
@Test
void multiHttpSecurityCustomizerBean() throws Exception {
this.spring.register(MultiHttpSecurityCustomizerBeanConfiguration.class, UserDetailsConfig.class).autowire();
Customizer<HttpSecurity> httpSecurityCustomizer = this.spring.getContext()
.getBean("httpSecurityCustomizer", Customizer.class);
Customizer<HttpSecurity> httpSecurityCustomizer0 = this.spring.getContext()
.getBean("httpSecurityCustomizer0", Customizer.class);
InOrder inOrder = Mockito.inOrder(httpSecurityCustomizer0, httpSecurityCustomizer);
ArgumentCaptor<HttpSecurity> arg0 = ArgumentCaptor.forClass(HttpSecurity.class);
ArgumentCaptor<HttpSecurity> arg1 = ArgumentCaptor.forClass(HttpSecurity.class);
inOrder.verify(httpSecurityCustomizer0).customize(arg0.capture());
inOrder.verify(httpSecurityCustomizer).customize(arg1.capture());
}
@RestController @RestController
static class NameController { static class NameController {
@ -785,6 +866,134 @@ public class HttpSecurityConfigurationTests {
} }
@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
@EnableWebMvc
static class AuthorizeRequestsBeanConfiguration {
@Bean
SecurityFilterChain noAuthorizeSecurity(HttpSecurity http) throws Exception {
http.httpBasic(withDefaults());
return http.build();
}
@Bean
static Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> authorizeRequests()
throws Exception {
Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> authz = mock(
Customizer.class, withSettings().name("authz"));
// prevent validation errors of no authorization rules being defined
willAnswer(((invocation) -> {
AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry requests = invocation
.getArgument(0);
requests.anyRequest().authenticated();
return null;
})).given(authz).customize(any());
return authz;
}
@RestController
static class PublicController {
@GetMapping("/public")
String permitAll() {
return "public";
}
}
}
@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
@EnableWebMvc
static class DisableAuthorizeRequestsBeanConfiguration {
@Bean
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
http.httpBasic(withDefaults());
// @formatter:off
http.authorizeHttpRequests((requests) -> requests
.anyRequest().permitAll()
);
// @formatter:on
return http.build();
}
@Bean
static Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> authorizeRequests()
throws Exception {
// @formatter:off
return (requests) -> requests
.anyRequest().denyAll();
// @formatter:on
}
@RestController
static class PublicController {
@GetMapping("/public")
String permitAll() {
return "public";
}
}
}
@Configuration(proxyBeanMethods = false)
@Import(AuthorizeRequestsBeanConfiguration.class)
static class MultiAuthorizeRequestsBeanConfiguration {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
static Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> authorizeRequests0()
throws Exception {
return mock(Customizer.class, withSettings().name("authz0"));
}
}
@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
@EnableWebMvc
static class HttpSecurityCustomizerBeanConfiguration {
@Bean
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
http.httpBasic(withDefaults());
return http.build();
}
@Bean
static Customizer<HttpSecurity> httpSecurityCustomizer() {
return mock(Customizer.class, withSettings().name("httpSecurityCustomizer"));
}
@RestController
static class PublicController {
@GetMapping("/public")
String permitAll() {
return "public";
}
}
}
@Configuration(proxyBeanMethods = false)
@Import(HttpSecurityCustomizerBeanConfiguration.class)
static class MultiHttpSecurityCustomizerBeanConfiguration {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
static Customizer<HttpSecurity> httpSecurityCustomizer0() throws Exception {
return mock(Customizer.class, withSettings().name("httpSecurityCustomizer0"));
}
}
private static class TestCompromisedPasswordChecker implements CompromisedPasswordChecker { private static class TestCompromisedPasswordChecker implements CompromisedPasswordChecker {
@Override @Override

View File

@ -24,6 +24,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.config.ObjectPostProcessor; import org.springframework.security.config.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@ -34,6 +35,7 @@ import org.springframework.security.core.userdetails.AuthenticationUserDetailsSe
import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.User;
import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers; import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.security.web.authentication.preauth.j2ee.J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource; import org.springframework.security.web.authentication.preauth.j2ee.J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource;
import org.springframework.security.web.authentication.preauth.j2ee.J2eePreAuthenticatedProcessingFilter; import org.springframework.security.web.authentication.preauth.j2ee.J2eePreAuthenticatedProcessingFilter;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
@ -64,18 +66,16 @@ public class JeeConfigurerTests {
@Test @Test
public void configureWhenRegisteringObjectPostProcessorThenInvokedOnJ2eePreAuthenticatedProcessingFilter() { public void configureWhenRegisteringObjectPostProcessorThenInvokedOnJ2eePreAuthenticatedProcessingFilter() {
ObjectPostProcessorConfig.objectPostProcessor = spy(ReflectingObjectPostProcessor.class);
this.spring.register(ObjectPostProcessorConfig.class).autowire(); this.spring.register(ObjectPostProcessorConfig.class).autowire();
verify(ObjectPostProcessorConfig.objectPostProcessor) ObjectPostProcessor<Object> objectPostProcessor = this.spring.getContext().getBean(ObjectPostProcessor.class);
.postProcess(any(J2eePreAuthenticatedProcessingFilter.class)); verify(objectPostProcessor).postProcess(any(J2eePreAuthenticatedProcessingFilter.class));
} }
@Test @Test
public void configureWhenRegisteringObjectPostProcessorThenInvokedOnJ2eeBasedPreAuthenticatedWebAuthenticationDetailsSource() { public void configureWhenRegisteringObjectPostProcessorThenInvokedOnJ2eeBasedPreAuthenticatedWebAuthenticationDetailsSource() {
ObjectPostProcessorConfig.objectPostProcessor = spy(ReflectingObjectPostProcessor.class);
this.spring.register(ObjectPostProcessorConfig.class).autowire(); this.spring.register(ObjectPostProcessorConfig.class).autowire();
verify(ObjectPostProcessorConfig.objectPostProcessor) ObjectPostProcessor<Object> objectPostProcessor = this.spring.getContext().getBean(ObjectPostProcessor.class);
.postProcess(any(J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource.class)); verify(objectPostProcessor).postProcess(any(J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource.class));
} }
@Test @Test
@ -135,12 +135,14 @@ public class JeeConfigurerTests {
public void requestWhenCustomAuthenticatedUserDetailsServiceInLambdaThenCustomAuthenticatedUserDetailsServiceUsed() public void requestWhenCustomAuthenticatedUserDetailsServiceInLambdaThenCustomAuthenticatedUserDetailsServiceUsed()
throws Exception { throws Exception {
this.spring.register(JeeCustomAuthenticatedUserDetailsServiceConfig.class).autowire(); this.spring.register(JeeCustomAuthenticatedUserDetailsServiceConfig.class).autowire();
AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> userDetailsService = this.spring
.getContext()
.getBean(AuthenticationUserDetailsService.class);
Principal user = mock(Principal.class); Principal user = mock(Principal.class);
User userDetails = new User("user", "N/A", true, true, true, true, User userDetails = new User("user", "N/A", true, true, true, true,
AuthorityUtils.createAuthorityList("ROLE_USER")); AuthorityUtils.createAuthorityList("ROLE_USER"));
given(user.getName()).willReturn("user"); given(user.getName()).willReturn("user");
given(JeeCustomAuthenticatedUserDetailsServiceConfig.authenticationUserDetailsService.loadUserDetails(any())) given(userDetailsService.loadUserDetails(any())).willReturn(userDetails);
.willReturn(userDetails);
// @formatter:off // @formatter:off
MockHttpServletRequestBuilder authRequest = get("/") MockHttpServletRequestBuilder authRequest = get("/")
.principal(user) .principal(user)
@ -157,7 +159,7 @@ public class JeeConfigurerTests {
@EnableWebSecurity @EnableWebSecurity
static class ObjectPostProcessorConfig { static class ObjectPostProcessorConfig {
static ObjectPostProcessor<Object> objectPostProcessor; ObjectPostProcessor<Object> objectPostProcessor = spy(ReflectingObjectPostProcessor.class);
@Bean @Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception { SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
@ -169,8 +171,9 @@ public class JeeConfigurerTests {
} }
@Bean @Bean
static ObjectPostProcessor<Object> objectPostProcessor() { @Primary
return objectPostProcessor; ObjectPostProcessor<Object> objectPostProcessor() {
return this.objectPostProcessor;
} }
} }
@ -245,7 +248,7 @@ public class JeeConfigurerTests {
@EnableWebSecurity @EnableWebSecurity
public static class JeeCustomAuthenticatedUserDetailsServiceConfig { public static class JeeCustomAuthenticatedUserDetailsServiceConfig {
static AuthenticationUserDetailsService authenticationUserDetailsService = mock( private AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> authenticationUserDetailsService = mock(
AuthenticationUserDetailsService.class); AuthenticationUserDetailsService.class);
@Bean @Bean
@ -256,12 +259,17 @@ public class JeeConfigurerTests {
.anyRequest().hasRole("USER") .anyRequest().hasRole("USER")
) )
.jee((jee) -> jee .jee((jee) -> jee
.authenticatedUserDetailsService(authenticationUserDetailsService) .authenticatedUserDetailsService(this.authenticationUserDetailsService)
); );
return http.build(); return http.build();
// @formatter:on // @formatter:on
} }
@Bean
AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> authenticationUserDetailsService() {
return this.authenticationUserDetailsService;
}
} }
} }

View File

@ -300,7 +300,15 @@ public class WebAuthnConfigurerTests {
@Bean @Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http.formLogin(Customizer.withDefaults()).webAuthn(Customizer.withDefaults()).build(); // @formatter:off
http
.formLogin(Customizer.withDefaults())
.webAuthn((authn) -> authn
.rpId("spring.io")
.rpName("spring")
);
// @formatter:on
return http.build();
} }
} }
@ -316,7 +324,14 @@ public class WebAuthnConfigurerTests {
@Bean @Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http.webAuthn(Customizer.withDefaults()).build(); // @formatter:off
http
.webAuthn((authn) -> authn
.rpId("spring.io")
.rpName("spring")
);
// @formatter:on
return http.build();
} }
} }
@ -332,9 +347,16 @@ public class WebAuthnConfigurerTests {
@Bean @Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http.formLogin(Customizer.withDefaults()) // @formatter:off
.webAuthn((webauthn) -> webauthn.disableDefaultRegistrationPage(true)) http
.build(); .formLogin(Customizer.withDefaults())
.webAuthn((authn) -> authn
.rpId("spring.io")
.rpName("spring")
.disableDefaultRegistrationPage(true)
);
// @formatter:on
return http.build();
} }
} }
@ -350,9 +372,18 @@ public class WebAuthnConfigurerTests {
@Bean @Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http.formLogin((login) -> login.loginPage("/custom-login-page")) // @formatter:off
.webAuthn((webauthn) -> webauthn.disableDefaultRegistrationPage(true)) http
.build(); .formLogin((login) -> login
.loginPage("/custom-login-page")
)
.webAuthn((authn) -> authn
.rpId("spring.io")
.rpName("spring")
.disableDefaultRegistrationPage(true)
);
// @formatter:on
return http.build();
} }
} }

View File

@ -45,6 +45,7 @@ import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.SecurityAssertions;
import org.springframework.security.authentication.event.AuthenticationSuccessEvent; import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
import org.springframework.security.config.Customizer; import org.springframework.security.config.Customizer;
import org.springframework.security.config.ObjectPostProcessor; import org.springframework.security.config.ObjectPostProcessor;
@ -217,10 +218,9 @@ public class OAuth2LoginConfigurerTests {
Authentication authentication = this.securityContextRepository Authentication authentication = this.securityContextRepository
.loadContext(new HttpRequestResponseHolder(this.request, this.response)) .loadContext(new HttpRequestResponseHolder(this.request, this.response))
.getAuthentication(); .getAuthentication();
assertThat(authentication.getAuthorities()).hasSize(1); SecurityAssertions.assertThat(authentication)
assertThat(authentication.getAuthorities()).first() .hasAuthority("OAUTH2_USER")
.isInstanceOf(OAuth2UserAuthority.class) .isInstanceOf(OAuth2UserAuthority.class);
.hasToString("OAUTH2_USER");
} }
@Test @Test
@ -234,10 +234,9 @@ public class OAuth2LoginConfigurerTests {
Authentication authentication = this.securityContextRepository Authentication authentication = this.securityContextRepository
.loadContext(new HttpRequestResponseHolder(this.request, this.response)) .loadContext(new HttpRequestResponseHolder(this.request, this.response))
.getAuthentication(); .getAuthentication();
assertThat(authentication.getAuthorities()).hasSize(1); SecurityAssertions.assertThat(authentication)
assertThat(authentication.getAuthorities()).first() .hasAuthority("OAUTH2_USER")
.isInstanceOf(OAuth2UserAuthority.class) .isInstanceOf(OAuth2UserAuthority.class);
.hasToString("OAUTH2_USER");
SecurityContextHolderStrategy strategy = this.context.getBean(SecurityContextHolderStrategy.class); SecurityContextHolderStrategy strategy = this.context.getBean(SecurityContextHolderStrategy.class);
verify(strategy, atLeastOnce()).getDeferredContext(); verify(strategy, atLeastOnce()).getDeferredContext();
SecurityContextChangedListener listener = this.context.getBean(SecurityContextChangedListener.class); SecurityContextChangedListener listener = this.context.getBean(SecurityContextChangedListener.class);
@ -255,10 +254,9 @@ public class OAuth2LoginConfigurerTests {
Authentication authentication = this.securityContextRepository Authentication authentication = this.securityContextRepository
.loadContext(new HttpRequestResponseHolder(this.request, this.response)) .loadContext(new HttpRequestResponseHolder(this.request, this.response))
.getAuthentication(); .getAuthentication();
assertThat(authentication.getAuthorities()).hasSize(1); SecurityAssertions.assertThat(authentication)
assertThat(authentication.getAuthorities()).first() .hasAuthority("OAUTH2_USER")
.isInstanceOf(OAuth2UserAuthority.class) .isInstanceOf(OAuth2UserAuthority.class);
.hasToString("OAUTH2_USER");
} }
// gh-6009 // gh-6009
@ -296,9 +294,7 @@ public class OAuth2LoginConfigurerTests {
Authentication authentication = this.securityContextRepository Authentication authentication = this.securityContextRepository
.loadContext(new HttpRequestResponseHolder(this.request, this.response)) .loadContext(new HttpRequestResponseHolder(this.request, this.response))
.getAuthentication(); .getAuthentication();
assertThat(authentication.getAuthorities()).hasSize(2); SecurityAssertions.assertThat(authentication).hasAuthorities("OAUTH2_USER", "ROLE_OAUTH2_USER");
assertThat(authentication.getAuthorities()).first().hasToString("OAUTH2_USER");
assertThat(authentication.getAuthorities()).last().hasToString("ROLE_OAUTH2_USER");
} }
@Test @Test
@ -317,9 +313,7 @@ public class OAuth2LoginConfigurerTests {
Authentication authentication = this.securityContextRepository Authentication authentication = this.securityContextRepository
.loadContext(new HttpRequestResponseHolder(this.request, this.response)) .loadContext(new HttpRequestResponseHolder(this.request, this.response))
.getAuthentication(); .getAuthentication();
assertThat(authentication.getAuthorities()).hasSize(2); SecurityAssertions.assertThat(authentication).hasAuthorities("OAUTH2_USER", "ROLE_OAUTH2_USER");
assertThat(authentication.getAuthorities()).first().hasToString("OAUTH2_USER");
assertThat(authentication.getAuthorities()).last().hasToString("ROLE_OAUTH2_USER");
} }
@Test @Test
@ -338,9 +332,7 @@ public class OAuth2LoginConfigurerTests {
Authentication authentication = this.securityContextRepository Authentication authentication = this.securityContextRepository
.loadContext(new HttpRequestResponseHolder(this.request, this.response)) .loadContext(new HttpRequestResponseHolder(this.request, this.response))
.getAuthentication(); .getAuthentication();
assertThat(authentication.getAuthorities()).hasSize(2); SecurityAssertions.assertThat(authentication).hasAuthorities("OAUTH2_USER", "ROLE_OAUTH2_USER");
assertThat(authentication.getAuthorities()).first().hasToString("OAUTH2_USER");
assertThat(authentication.getAuthorities()).last().hasToString("ROLE_OAUTH2_USER");
} }
// gh-5488 // gh-5488
@ -361,10 +353,9 @@ public class OAuth2LoginConfigurerTests {
Authentication authentication = this.securityContextRepository Authentication authentication = this.securityContextRepository
.loadContext(new HttpRequestResponseHolder(this.request, this.response)) .loadContext(new HttpRequestResponseHolder(this.request, this.response))
.getAuthentication(); .getAuthentication();
assertThat(authentication.getAuthorities()).hasSize(1); SecurityAssertions.assertThat(authentication)
assertThat(authentication.getAuthorities()).first() .hasAuthority("OAUTH2_USER")
.isInstanceOf(OAuth2UserAuthority.class) .isInstanceOf(OAuth2UserAuthority.class);
.hasToString("OAUTH2_USER");
} }
// gh-5521 // gh-5521
@ -570,10 +561,7 @@ public class OAuth2LoginConfigurerTests {
Authentication authentication = this.securityContextRepository Authentication authentication = this.securityContextRepository
.loadContext(new HttpRequestResponseHolder(this.request, this.response)) .loadContext(new HttpRequestResponseHolder(this.request, this.response))
.getAuthentication(); .getAuthentication();
assertThat(authentication.getAuthorities()).hasSize(1); SecurityAssertions.assertThat(authentication).hasAuthority("OIDC_USER").isInstanceOf(OidcUserAuthority.class);
assertThat(authentication.getAuthorities()).first()
.isInstanceOf(OidcUserAuthority.class)
.hasToString("OIDC_USER");
} }
@Test @Test
@ -593,9 +581,7 @@ public class OAuth2LoginConfigurerTests {
.loadContext(new HttpRequestResponseHolder(this.request, this.response)) .loadContext(new HttpRequestResponseHolder(this.request, this.response))
.getAuthentication(); .getAuthentication();
assertThat(authentication.getAuthorities()).hasSize(1); assertThat(authentication.getAuthorities()).hasSize(1);
assertThat(authentication.getAuthorities()).first() SecurityAssertions.assertThat(authentication).hasAuthority("OIDC_USER").isInstanceOf(OidcUserAuthority.class);
.isInstanceOf(OidcUserAuthority.class)
.hasToString("OIDC_USER");
} }
@Test @Test
@ -614,9 +600,7 @@ public class OAuth2LoginConfigurerTests {
Authentication authentication = this.securityContextRepository Authentication authentication = this.securityContextRepository
.loadContext(new HttpRequestResponseHolder(this.request, this.response)) .loadContext(new HttpRequestResponseHolder(this.request, this.response))
.getAuthentication(); .getAuthentication();
assertThat(authentication.getAuthorities()).hasSize(2); SecurityAssertions.assertThat(authentication).hasAuthorities("OIDC_USER", "ROLE_OIDC_USER");
assertThat(authentication.getAuthorities()).first().hasToString("OIDC_USER");
assertThat(authentication.getAuthorities()).last().hasToString("ROLE_OIDC_USER");
} }
@Test @Test
@ -635,9 +619,7 @@ public class OAuth2LoginConfigurerTests {
Authentication authentication = this.securityContextRepository Authentication authentication = this.securityContextRepository
.loadContext(new HttpRequestResponseHolder(this.request, this.response)) .loadContext(new HttpRequestResponseHolder(this.request, this.response))
.getAuthentication(); .getAuthentication();
assertThat(authentication.getAuthorities()).hasSize(2); SecurityAssertions.assertThat(authentication).hasAuthorities("OIDC_USER", "ROLE_OIDC_USER");
assertThat(authentication.getAuthorities()).first().hasToString("OIDC_USER");
assertThat(authentication.getAuthorities()).last().hasToString("ROLE_OIDC_USER");
} }
@Test @Test
@ -690,11 +672,7 @@ public class OAuth2LoginConfigurerTests {
Authentication authentication = this.securityContextRepository Authentication authentication = this.securityContextRepository
.loadContext(new HttpRequestResponseHolder(this.request, this.response)) .loadContext(new HttpRequestResponseHolder(this.request, this.response))
.getAuthentication(); .getAuthentication();
assertThat(authentication.getAuthorities()).hasSize(1); SecurityAssertions.assertThat(authentication).hasAuthority("OIDC_USER").isInstanceOf(OidcUserAuthority.class);
assertThat(authentication.getAuthorities()).first()
.isInstanceOf(OidcUserAuthority.class)
.hasToString("OIDC_USER");
// Ensure shared objects set for OAuth2 Client are not used // Ensure shared objects set for OAuth2 Client are not used
ClientRegistrationRepository clientRegistrationRepository = this.spring.getContext() ClientRegistrationRepository clientRegistrationRepository = this.spring.getContext()
.getBean(ClientRegistrationRepository.class); .getBean(ClientRegistrationRepository.class);

View File

@ -2674,6 +2674,7 @@ public class OAuth2ResourceServerConfigurerTests {
String requiresReadScope(JwtAuthenticationToken token) { String requiresReadScope(JwtAuthenticationToken token) {
return token.getAuthorities() return token.getAuthorities()
.stream() .stream()
.filter((ga) -> ga.getAuthority().startsWith("SCOPE_"))
.map(GrantedAuthority::getAuthority) .map(GrantedAuthority::getAuthority)
.collect(Collectors.toList()) .collect(Collectors.toList())
.toString(); .toString();

View File

@ -29,12 +29,17 @@ import io.micrometer.observation.ObservationRegistry;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mockito;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler; import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler;
import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authentication.password.CompromisedPasswordDecision; import org.springframework.security.authentication.password.CompromisedPasswordDecision;
@ -46,6 +51,7 @@ import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.config.users.ReactiveAuthenticationTestConfiguration; import org.springframework.security.config.users.ReactiveAuthenticationTestConfiguration;
import org.springframework.security.config.web.server.ServerHttpSecurity; import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity.AuthorizeExchangeSpec;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults; import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults;
import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.annotation.AuthenticationPrincipal;
@ -267,6 +273,47 @@ public class ServerHttpSecurityConfigurationTests {
assertThat(contexts.next().getContextualName()).isEqualTo("security filterchain after"); assertThat(contexts.next().getContextualName()).isEqualTo("security filterchain after");
} }
@Test
void authorizeExchangeCustomizerBean() {
this.spring.register(AuthorizeExchangeCustomizerBeanConfig.class).autowire();
Customizer<AuthorizeExchangeSpec> authzCustomizer = this.spring.getContext().getBean("authz", Customizer.class);
ArgumentCaptor<AuthorizeExchangeSpec> arg0 = ArgumentCaptor.forClass(AuthorizeExchangeSpec.class);
verify(authzCustomizer).customize(arg0.capture());
}
@Test
void multiAuthorizeExchangeCustomizerBean() {
this.spring.register(MultiAuthorizeExchangeCustomizerBeanConfig.class).autowire();
Customizer<AuthorizeExchangeSpec> authzCustomizer = this.spring.getContext().getBean("authz", Customizer.class);
ArgumentCaptor<AuthorizeExchangeSpec> arg0 = ArgumentCaptor.forClass(AuthorizeExchangeSpec.class);
verify(authzCustomizer).customize(arg0.capture());
}
@Test
void serverHttpSecurityCustomizerBean() {
this.spring.register(ServerHttpSecurityCustomizerConfig.class).autowire();
Customizer<ServerHttpSecurity> httpSecurityCustomizer = this.spring.getContext()
.getBean("httpSecurityCustomizer", Customizer.class);
ArgumentCaptor<ServerHttpSecurity> arg0 = ArgumentCaptor.forClass(ServerHttpSecurity.class);
verify(httpSecurityCustomizer).customize(arg0.capture());
}
@Test
void multiServerHttpSecurityCustomizerBean() {
this.spring.register(MultiServerHttpSecurityCustomizerConfig.class).autowire();
Customizer<ServerHttpSecurity> httpSecurityCustomizer = this.spring.getContext()
.getBean("httpSecurityCustomizer", Customizer.class);
Customizer<ServerHttpSecurity> httpSecurityCustomizer0 = this.spring.getContext()
.getBean("httpSecurityCustomizer0", Customizer.class);
InOrder inOrder = Mockito.inOrder(httpSecurityCustomizer0, httpSecurityCustomizer);
ArgumentCaptor<ServerHttpSecurity> arg0 = ArgumentCaptor.forClass(ServerHttpSecurity.class);
inOrder.verify(httpSecurityCustomizer0).customize(arg0.capture());
inOrder.verify(httpSecurityCustomizer).customize(arg0.capture());
}
@Configuration @Configuration
static class SubclassConfig extends ServerHttpSecurityConfiguration { static class SubclassConfig extends ServerHttpSecurityConfiguration {
@ -474,4 +521,64 @@ public class ServerHttpSecurityConfigurationTests {
} }
@Configuration(proxyBeanMethods = false)
@EnableWebFlux
@EnableWebFluxSecurity
@Import(UserDetailsConfig.class)
static class AuthorizeExchangeCustomizerBeanConfig {
@Bean
SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
return http.build();
}
@Bean
static Customizer<AuthorizeExchangeSpec> authz() {
return mock(Customizer.class);
}
}
@Configuration(proxyBeanMethods = false)
@Import(AuthorizeExchangeCustomizerBeanConfig.class)
static class MultiAuthorizeExchangeCustomizerBeanConfig {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
Customizer<AuthorizeExchangeSpec> authz0() {
return mock(Customizer.class);
}
}
@Configuration(proxyBeanMethods = false)
@EnableWebFlux
@EnableWebFluxSecurity
@Import(UserDetailsConfig.class)
static class ServerHttpSecurityCustomizerConfig {
@Bean
SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
return http.build();
}
@Bean
static Customizer<ServerHttpSecurity> httpSecurityCustomizer() {
return mock(Customizer.class);
}
}
@Configuration(proxyBeanMethods = false)
@Import(ServerHttpSecurityCustomizerConfig.class)
static class MultiServerHttpSecurityCustomizerConfig {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
static Customizer<ServerHttpSecurity> httpSecurityCustomizer0() {
return mock(Customizer.class);
}
}
} }

View File

@ -68,7 +68,7 @@ public class UserServiceBeanDefinitionParserTests {
System.setProperty("principal.pass", "joespassword"); System.setProperty("principal.pass", "joespassword");
System.setProperty("principal.authorities", "ROLE_A,ROLE_B"); System.setProperty("principal.authorities", "ROLE_A,ROLE_B");
// @formatter:off // @formatter:off
setContext("<b:bean class='org.springframework.beans.factory.config.PropertyPlaceholderConfigurer'/>" setContext("<b:bean class='org.springframework.context.support.PropertySourcesPlaceholderConfigurer'/>"
+ "<user-service id='service'>" + "<user-service id='service'>"
+ " <user name='${principal.name}' password='${principal.pass}' authorities='${principal.authorities}'/>" + " <user name='${principal.name}' password='${principal.pass}' authorities='${principal.authorities}'/>"
+ "</user-service>"); + "</user-service>");

View File

@ -92,7 +92,7 @@ public class FilterSecurityMetadataSourceBeanDefinitionParserTests {
public void interceptUrlsSupportPropertyPlaceholders() { public void interceptUrlsSupportPropertyPlaceholders() {
System.setProperty("secure.url", "/secure"); System.setProperty("secure.url", "/secure");
System.setProperty("secure.role", "ROLE_A"); System.setProperty("secure.role", "ROLE_A");
setContext("<b:bean class='org.springframework.beans.factory.config.PropertyPlaceholderConfigurer'/>" setContext("<b:bean class='org.springframework.context.support.PropertySourcesPlaceholderConfigurer'/>"
+ "<filter-security-metadata-source id='fids' use-expressions='false'>" + "<filter-security-metadata-source id='fids' use-expressions='false'>"
+ " <intercept-url pattern='${secure.url}' access='${secure.role}'/>" + " <intercept-url pattern='${secure.url}' access='${secure.role}'/>"
+ "</filter-security-metadata-source>"); + "</filter-security-metadata-source>");

View File

@ -24,11 +24,11 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.mock.web.MockFilterChain; import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpServletResponse;
@ -125,11 +125,11 @@ public class CustomHttpSecurityConfigurerTests {
} }
@Bean @Bean
static PropertyPlaceholderConfigurer propertyPlaceholderConfigurer() { static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
// Typically externalize this as a properties file // Typically externalize this as a properties file
Properties properties = new Properties(); Properties properties = new Properties();
properties.setProperty("permitAllPattern", "/public/**"); properties.setProperty("permitAllPattern", "/public/**");
PropertyPlaceholderConfigurer propertyPlaceholderConfigurer = new PropertyPlaceholderConfigurer(); PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
propertyPlaceholderConfigurer.setProperties(properties); propertyPlaceholderConfigurer.setProperties(properties);
return propertyPlaceholderConfigurer; return propertyPlaceholderConfigurer;
} }
@ -153,11 +153,11 @@ public class CustomHttpSecurityConfigurerTests {
} }
@Bean @Bean
static PropertyPlaceholderConfigurer propertyPlaceholderConfigurer() { static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
// Typically externalize this as a properties file // Typically externalize this as a properties file
Properties properties = new Properties(); Properties properties = new Properties();
properties.setProperty("permitAllPattern", "/public/**"); properties.setProperty("permitAllPattern", "/public/**");
PropertyPlaceholderConfigurer propertyPlaceholderConfigurer = new PropertyPlaceholderConfigurer(); PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
propertyPlaceholderConfigurer.setProperties(properties); propertyPlaceholderConfigurer.setProperties(properties);
return propertyPlaceholderConfigurer; return propertyPlaceholderConfigurer;
} }

View File

@ -464,7 +464,9 @@ public class MethodSecurityBeanDefinitionParserTests {
static class MyAuthorizationManager implements AuthorizationManager<MethodInvocation> { static class MyAuthorizationManager implements AuthorizationManager<MethodInvocation> {
@Override @Override
public AuthorizationResult authorize(Supplier<Authentication> authentication, MethodInvocation object) { public AuthorizationResult authorize(
Supplier<? extends @org.jspecify.annotations.Nullable Authentication> authentication,
MethodInvocation object) {
return new AuthorizationDecision("bob".equals(authentication.get().getName())); return new AuthorizationDecision("bob".equals(authentication.get().getName()));
} }

View File

@ -30,8 +30,11 @@ import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostP
import org.springframework.mock.web.MockServletConfig; import org.springframework.mock.web.MockServletConfig;
import org.springframework.security.config.BeanIds; import org.springframework.security.config.BeanIds;
import org.springframework.security.config.util.InMemoryXmlWebApplicationContext; import org.springframework.security.config.util.InMemoryXmlWebApplicationContext;
import org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers;
import org.springframework.security.test.web.reactive.server.WebTestClientBuilder;
import org.springframework.security.web.servlet.MockServletContext; import org.springframework.security.web.servlet.MockServletContext;
import org.springframework.test.context.web.GenericXmlWebContextLoader; import org.springframework.test.context.web.GenericXmlWebContextLoader;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.RequestPostProcessor; import org.springframework.test.web.servlet.request.RequestPostProcessor;
import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder; import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder;
@ -42,6 +45,7 @@ import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.context.support.XmlWebApplicationContext; import org.springframework.web.context.support.XmlWebApplicationContext;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.server.WebFilter;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
@ -156,6 +160,18 @@ public class SpringTestContext implements Closeable {
// @formatter:on // @formatter:on
this.context.getBeanFactory().registerResolvableDependency(MockMvc.class, mockMvc); this.context.getBeanFactory().registerResolvableDependency(MockMvc.class, mockMvc);
} }
String webFluxSecurityBean = "org.springframework.security.config.annotation.web.reactive.WebFluxSecurityConfiguration.WebFilterChainFilter";
if (this.context.containsBean(webFluxSecurityBean)) {
WebFilter springSecurityFilter = this.context.getBean(webFluxSecurityBean, WebFilter.class);
// @formatter:off
WebTestClient webTest = WebTestClient
.bindToController(new WebTestClientBuilder.Http200RestController())
.webFilter(springSecurityFilter)
.apply(SecurityMockServerConfigurers.springSecurity())
.build();
// @formatter:on
this.context.getBeanFactory().registerResolvableDependency(WebTestClient.class, webTest);
}
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
bpp.setBeanFactory(this.context.getBeanFactory()); bpp.setBeanFactory(this.context.getBeanFactory());
bpp.processInjection(this.test); bpp.processInjection(this.test);

View File

@ -16,18 +16,27 @@
package org.springframework.security.config.web.server; package org.springframework.security.config.web.server;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebDriver;
import reactor.core.publisher.Mono;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.web.reactive.ServerHttpSecurityConfigurationBuilder; import org.springframework.security.config.annotation.web.reactive.ServerHttpSecurityConfigurationBuilder;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.htmlunit.server.WebTestClientHtmlUnitDriverBuilder; import org.springframework.security.htmlunit.server.WebTestClientHtmlUnitDriverBuilder;
import org.springframework.security.test.web.reactive.server.WebTestClientBuilder; import org.springframework.security.test.web.reactive.server.WebTestClientBuilder;
import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.authentication.logout.ServerLogoutHandler;
import org.springframework.security.web.server.context.ServerSecurityContextRepository;
import org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository; import org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers; import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
import org.springframework.test.web.reactive.server.WebTestClient; 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.GetMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ServerWebExchange;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.config.Customizer.withDefaults;
@ -210,6 +219,84 @@ public class LogoutSpecTests {
FormLoginTests.HomePage.to(driver, FormLoginTests.DefaultLoginPage.class).assertAt(); FormLoginTests.HomePage.to(driver, FormLoginTests.DefaultLoginPage.class).assertAt();
} }
@Test
public void multipleLogoutHandlers() {
InMemorySecurityContextRepository repository = new InMemorySecurityContextRepository();
MultiValueMap<String, String> logoutData = new LinkedMultiValueMap<>();
ServerLogoutHandler handler1 = (exchange, authentication) -> {
logoutData.add("handler-header", "value1");
return Mono.empty();
};
ServerLogoutHandler handler2 = (exchange, authentication) -> {
logoutData.add("handler-header", "value2");
return Mono.empty();
};
// @formatter:off
SecurityWebFilterChain securityWebFilter = this.http
.securityContextRepository(repository)
.authorizeExchange((authorize) -> authorize
.anyExchange().authenticated())
.formLogin(withDefaults())
.logout((logoutSpec) -> logoutSpec.logoutHandler((handlers) -> {
handlers.add(handler1);
handlers.add(0, handler2);
}))
.build();
WebTestClient webTestClient = WebTestClientBuilder
.bindToWebFilters(securityWebFilter)
.build();
WebDriver driver = WebTestClientHtmlUnitDriverBuilder
.webTestClientSetup(webTestClient)
.build();
// @formatter:on
FormLoginTests.DefaultLoginPage loginPage = FormLoginTests.HomePage
.to(driver, FormLoginTests.DefaultLoginPage.class)
.assertAt();
// @formatter:off
loginPage = loginPage.loginForm()
.username("user")
.password("invalid")
.submit(FormLoginTests.DefaultLoginPage.class)
.assertError();
FormLoginTests.HomePage homePage = loginPage.loginForm()
.username("user")
.password("password")
.submit(FormLoginTests.HomePage.class);
// @formatter:on
homePage.assertAt();
SecurityContext savedContext = repository.getSavedContext();
assertThat(savedContext).isNotNull();
assertThat(savedContext.getAuthentication()).isInstanceOf(UsernamePasswordAuthenticationToken.class);
loginPage = FormLoginTests.DefaultLogoutPage.to(driver).assertAt().logout();
loginPage.assertAt().assertLogout();
assertThat(logoutData).hasSize(1);
assertThat(logoutData.get("handler-header")).containsExactly("value2", "value1");
savedContext = repository.getSavedContext();
assertThat(savedContext).isNull();
}
private static class InMemorySecurityContextRepository implements ServerSecurityContextRepository {
@Nullable private SecurityContext savedContext;
@Override
public Mono<Void> save(ServerWebExchange exchange, SecurityContext context) {
this.savedContext = context;
return Mono.empty();
}
@Override
public Mono<SecurityContext> load(ServerWebExchange exchange) {
return Mono.justOrEmpty(this.savedContext);
}
@Nullable private SecurityContext getSavedContext() {
return this.savedContext;
}
}
@RestController @RestController
public static class HomeController { public static class HomeController {

View File

@ -26,6 +26,7 @@ import java.util.Map;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.assertj.core.api.ThrowableAssert; import org.assertj.core.api.ThrowableAssert;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
@ -735,7 +736,7 @@ public class WebSocketMessageBrokerConfigTests {
} }
@Override @Override
public EvaluationContext createEvaluationContext(Supplier<Authentication> authentication, public EvaluationContext createEvaluationContext(Supplier<? extends @Nullable Authentication> authentication,
Message<Object> message) { Message<Object> message) {
return new StandardEvaluationContext(new MessageSecurityExpressionRoot(authentication, message) { return new StandardEvaluationContext(new MessageSecurityExpressionRoot(authentication, message) {
public boolean denyNile() { public boolean denyNile() {

View File

@ -193,7 +193,7 @@ class AuthorizeHttpRequestsDslTests {
open class MvcMatcherPathVariablesConfig { open class MvcMatcherPathVariablesConfig {
@Bean @Bean
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
val access = AuthorizationManager { _: Supplier<Authentication>, context: RequestAuthorizationContext -> val access = AuthorizationManager { _: Supplier<out Authentication>, context: RequestAuthorizationContext ->
AuthorizationDecision(context.variables["userName"] == "user") AuthorizationDecision(context.variables["userName"] == "user")
} }
http { http {

View File

@ -367,7 +367,7 @@ class HttpSecurityDslTests {
this.spring.register(CustomFilterConfig::class.java).autowire() this.spring.register(CustomFilterConfig::class.java).autowire()
val filterChain = spring.context.getBean(FilterChainProxy::class.java) val filterChain = spring.context.getBean(FilterChainProxy::class.java)
val filters: List<Filter> = filterChain.getFilters("/") val filters: List<Filter>? = filterChain.getFilters("/")
assertThat(filters).anyMatch { it is CustomFilter } assertThat(filters).anyMatch { it is CustomFilter }
} }
@ -390,7 +390,7 @@ class HttpSecurityDslTests {
this.spring.register(CustomFilterConfigReified::class.java).autowire() this.spring.register(CustomFilterConfigReified::class.java).autowire()
val filterChain = spring.context.getBean(FilterChainProxy::class.java) val filterChain = spring.context.getBean(FilterChainProxy::class.java)
val filters: List<Filter> = filterChain.getFilters("/") val filters: List<Filter>? = filterChain.getFilters("/")
assertThat(filters).anyMatch { it is CustomFilter } assertThat(filters).anyMatch { it is CustomFilter }
} }
@ -413,7 +413,7 @@ class HttpSecurityDslTests {
this.spring.register(CustomFilterAfterConfig::class.java).autowire() this.spring.register(CustomFilterAfterConfig::class.java).autowire()
val filterChain = spring.context.getBean(FilterChainProxy::class.java) val filterChain = spring.context.getBean(FilterChainProxy::class.java)
val filters: List<Class<out Filter>> = filterChain.getFilters("/").map { it.javaClass } val filters: List<Class<out Filter>> = filterChain.getFilters("/")!!.map { it.javaClass }
assertThat(filters).containsSubsequence( assertThat(filters).containsSubsequence(
UsernamePasswordAuthenticationFilter::class.java, UsernamePasswordAuthenticationFilter::class.java,
@ -440,7 +440,7 @@ class HttpSecurityDslTests {
this.spring.register(CustomFilterAfterConfigReified::class.java).autowire() this.spring.register(CustomFilterAfterConfigReified::class.java).autowire()
val filterChain = spring.context.getBean(FilterChainProxy::class.java) val filterChain = spring.context.getBean(FilterChainProxy::class.java)
val filterClasses: List<Class<out Filter>> = filterChain.getFilters("/").map { it.javaClass } val filterClasses: List<Class<out Filter>> = filterChain.getFilters("/")!!.map { it.javaClass }
assertThat(filterClasses).containsSubsequence( assertThat(filterClasses).containsSubsequence(
UsernamePasswordAuthenticationFilter::class.java, UsernamePasswordAuthenticationFilter::class.java,
@ -467,7 +467,7 @@ class HttpSecurityDslTests {
this.spring.register(CustomFilterBeforeConfig::class.java).autowire() this.spring.register(CustomFilterBeforeConfig::class.java).autowire()
val filterChain = spring.context.getBean(FilterChainProxy::class.java) val filterChain = spring.context.getBean(FilterChainProxy::class.java)
val filters: List<Class<out Filter>> = filterChain.getFilters("/").map { it.javaClass } val filters: List<Class<out Filter>> = filterChain.getFilters("/")!!.map { it.javaClass }
assertThat(filters).containsSubsequence( assertThat(filters).containsSubsequence(
CustomFilter::class.java, CustomFilter::class.java,
@ -494,7 +494,7 @@ class HttpSecurityDslTests {
this.spring.register(CustomFilterBeforeConfigReified::class.java).autowire() this.spring.register(CustomFilterBeforeConfigReified::class.java).autowire()
val filterChain = spring.context.getBean(FilterChainProxy::class.java) val filterChain = spring.context.getBean(FilterChainProxy::class.java)
val filterClasses: List<Class<out Filter>> = filterChain.getFilters("/").map { it.javaClass } val filterClasses: List<Class<out Filter>> = filterChain.getFilters("/")!!.map { it.javaClass }
assertThat(filterClasses).containsSubsequence( assertThat(filterClasses).containsSubsequence(
CustomFilter::class.java, CustomFilter::class.java,
@ -523,7 +523,7 @@ class HttpSecurityDslTests {
this.spring.register(CustomSecurityConfigurerConfig::class.java).autowire() this.spring.register(CustomSecurityConfigurerConfig::class.java).autowire()
val filterChain = spring.context.getBean(FilterChainProxy::class.java) val filterChain = spring.context.getBean(FilterChainProxy::class.java)
val filterClasses: List<Class<out Filter>> = filterChain.getFilters("/").map { it.javaClass } val filterClasses: List<Class<out Filter>> = filterChain.getFilters("/")!!.map { it.javaClass }
assertThat(filterClasses).contains( assertThat(filterClasses).contains(
CustomFilter::class.java CustomFilter::class.java
@ -535,7 +535,7 @@ class HttpSecurityDslTests {
this.spring.register(CustomSecurityConfigurerConfig::class.java).autowire() this.spring.register(CustomSecurityConfigurerConfig::class.java).autowire()
val filterChain = spring.context.getBean(FilterChainProxy::class.java) val filterChain = spring.context.getBean(FilterChainProxy::class.java)
val filterClasses: List<Class<out Filter>> = filterChain.getFilters("/").map { it.javaClass } val filterClasses: List<Class<out Filter>> = filterChain.getFilters("/")!!.map { it.javaClass }
assertThat(filterClasses).contains( assertThat(filterClasses).contains(
CustomFilter::class.java CustomFilter::class.java
@ -588,7 +588,7 @@ class HttpSecurityDslTests {
this.spring.register(CustomDslUsingWithConfig::class.java).autowire() this.spring.register(CustomDslUsingWithConfig::class.java).autowire()
val filterChain = spring.context.getBean(FilterChainProxy::class.java) val filterChain = spring.context.getBean(FilterChainProxy::class.java)
val filterClasses: List<Class<out Filter>> = filterChain.getFilters("/").map { it.javaClass } val filterClasses: List<Class<out Filter>> = filterChain.getFilters("/")!!.map { it.javaClass }
assertThat(filterClasses).contains( assertThat(filterClasses).contains(
UsernamePasswordAuthenticationFilter::class.java UsernamePasswordAuthenticationFilter::class.java
@ -623,5 +623,38 @@ class HttpSecurityDslTests {
} }
@Test
fun `HTTP security when Dsl Bean`() {
this.spring.register(DslBeanConfig::class.java).autowire()
this.mockMvc.get("/")
.andExpect {
header {
string("Content-Security-Policy", "object-src 'none'")
}
}
}
@Configuration
@EnableWebSecurity
@EnableWebMvc
open class DslBeanConfig {
@Bean
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
httpBasic { }
}
return http.build()
}
@Bean
open fun headersDsl(): HeadersDsl.() -> Unit {
return {
contentSecurityPolicy {
policyDirectives = "object-src 'none'"
}
}
}
}
} }

View File

@ -350,8 +350,8 @@ class LogoutDslTests {
class NoopLogoutHandler: LogoutHandler { class NoopLogoutHandler: LogoutHandler {
override fun logout( override fun logout(
request: HttpServletRequest?, request: HttpServletRequest,
response: HttpServletResponse?, response: HttpServletResponse,
authentication: Authentication? authentication: Authentication?
) { } ) { }

View File

@ -22,7 +22,6 @@ import io.mockk.mockk
import io.mockk.verify import io.mockk.verify
import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse import jakarta.servlet.http.HttpServletResponse
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
@ -44,7 +43,6 @@ import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequ
import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers
import org.springframework.security.web.SecurityFilterChain import org.springframework.security.web.SecurityFilterChain
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler
import org.springframework.security.web.authentication.ott.DefaultGenerateOneTimeTokenRequestResolver
import org.springframework.security.web.authentication.ott.GenerateOneTimeTokenRequestResolver import org.springframework.security.web.authentication.ott.GenerateOneTimeTokenRequestResolver
import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler
import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenGenerationSuccessHandler import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenGenerationSuccessHandler
@ -53,7 +51,6 @@ import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
import org.springframework.test.web.servlet.result.MockMvcResultMatchers import org.springframework.test.web.servlet.result.MockMvcResultMatchers
import java.time.Duration import java.time.Duration
import java.time.Instant import java.time.Instant
import java.time.ZoneOffset
/** /**
* Tests for [OneTimeTokenLoginDsl] * Tests for [OneTimeTokenLoginDsl]
@ -267,7 +264,7 @@ class OneTimeTokenLoginDslTests {
) )
} }
constructor(redirectUrl: String?) { constructor(redirectUrl: String) {
this.delegate = this.delegate =
RedirectOneTimeTokenGenerationSuccessHandler( RedirectOneTimeTokenGenerationSuccessHandler(
redirectUrl redirectUrl

View File

@ -132,8 +132,8 @@ class RequiresChannelDslTests {
companion object { companion object {
val CHANNEL_PROCESSOR: ChannelProcessor = object : ChannelProcessor { val CHANNEL_PROCESSOR: ChannelProcessor = object : ChannelProcessor {
override fun decide(invocation: FilterInvocation?, config: MutableCollection<ConfigAttribute>?) {} override fun decide(invocation: FilterInvocation, config: MutableCollection<ConfigAttribute>) {}
override fun supports(attribute: ConfigAttribute?): Boolean = true override fun supports(attribute: ConfigAttribute): Boolean = true
} }
} }

View File

@ -93,7 +93,7 @@ class SecurityContextDslTests {
testContext.autowire() testContext.autowire()
val filterChainProxy = testContext.context.getBean(FilterChainProxy::class.java) val filterChainProxy = testContext.context.getBean(FilterChainProxy::class.java)
// @formatter:off // @formatter:off
val filterTypes = filterChainProxy.getFilters("/").toList() val filterTypes = filterChainProxy.getFilters("/")!!.toList()
assertThat(filterTypes) assertThat(filterTypes)
.anyMatch { it is SecurityContextHolderFilter } .anyMatch { it is SecurityContextHolderFilter }

View File

@ -1,4 +1,5 @@
/* /*
* Copyright 2004-present the original author or authors. * Copyright 2004-present the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -125,6 +126,8 @@ class WebAuthnDslTests {
http{ http{
formLogin { } formLogin { }
webAuthn { webAuthn {
rpId = "spring.io"
rpName = "spring"
disableDefaultRegistrationPage = true disableDefaultRegistrationPage = true
} }
} }
@ -144,7 +147,10 @@ class WebAuthnDslTests {
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http{ http{
formLogin { } formLogin { }
webAuthn { } webAuthn {
rpId = "spring.io"
rpName = "spring"
}
} }
return http.build() return http.build()
} }

View File

@ -270,8 +270,8 @@ class ServerHttpBasicDslTests {
open class MockServerAuthenticationFailureHandler: ServerAuthenticationFailureHandler { open class MockServerAuthenticationFailureHandler: ServerAuthenticationFailureHandler {
override fun onAuthenticationFailure( override fun onAuthenticationFailure(
webFilterExchange: WebFilterExchange?, webFilterExchange: WebFilterExchange,
exception: AuthenticationException? exception: AuthenticationException
): Mono<Void> { ): Mono<Void> {
return Mono.empty() return Mono.empty()
} }

View File

@ -175,8 +175,8 @@ class ServerOAuth2ResourceServerDslTests {
open class MockServerAuthenticationFailureHandler: ServerAuthenticationFailureHandler { open class MockServerAuthenticationFailureHandler: ServerAuthenticationFailureHandler {
override fun onAuthenticationFailure( override fun onAuthenticationFailure(
webFilterExchange: WebFilterExchange?, webFilterExchange: WebFilterExchange,
exception: AuthenticationException? exception: AuthenticationException
): Mono<Void> { ): Mono<Void> {
return Mono.empty() return Mono.empty()
} }

View File

@ -280,11 +280,11 @@ class ServerOneTimeTokenLoginDslTests {
this.delegate = ServerRedirectOneTimeTokenGenerationSuccessHandler("/login/ott") this.delegate = ServerRedirectOneTimeTokenGenerationSuccessHandler("/login/ott")
} }
constructor(redirectUrl: String?) { constructor(redirectUrl: String) {
this.delegate = ServerRedirectOneTimeTokenGenerationSuccessHandler(redirectUrl) this.delegate = ServerRedirectOneTimeTokenGenerationSuccessHandler(redirectUrl)
} }
override fun handle(exchange: ServerWebExchange?, oneTimeToken: OneTimeToken?): Mono<Void> { override fun handle(exchange: ServerWebExchange, oneTimeToken: OneTimeToken): Mono<Void> {
lastToken = oneTimeToken lastToken = oneTimeToken
return delegate!!.handle(exchange, oneTimeToken) return delegate!!.handle(exchange, oneTimeToken)
} }

View File

@ -29,7 +29,7 @@
<intercept-url pattern="/**" access="permitAll"/> <intercept-url pattern="/**" access="permitAll"/>
</http> </http>
<b:bean name="propertyPlaceholderConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"/> <b:bean name="propertyPlaceholderConfigurer" class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"/>
<b:bean name="simple" class="org.springframework.security.config.http.HttpHeadersConfigTests.SimpleController"/> <b:bean name="simple" class="org.springframework.security.config.http.HttpHeadersConfigTests.SimpleController"/>

View File

@ -29,7 +29,7 @@
<intercept-url pattern="/**" access="permitAll"/> <intercept-url pattern="/**" access="permitAll"/>
</http> </http>
<b:bean name="propertyPlaceholderConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"/> <b:bean name="propertyPlaceholderConfigurer" class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"/>
<b:bean name="simple" class="org.springframework.security.config.http.HttpHeadersConfigTests.SimpleController"/> <b:bean name="simple" class="org.springframework.security.config.http.HttpHeadersConfigTests.SimpleController"/>

View File

@ -31,7 +31,7 @@
<custom-filter ref="userFilter" after="LOGOUT_FILTER"/> <custom-filter ref="userFilter" after="LOGOUT_FILTER"/>
</http> </http>
<b:bean name="propertyPlaceholderConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"/> <b:bean name="propertyPlaceholderConfigurer" class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"/>
<b:bean name="userFilter" class="org.mockito.Mockito" factory-method="mock"> <b:bean name="userFilter" class="org.mockito.Mockito" factory-method="mock">
<b:constructor-arg value="jakarta.servlet.Filter" type="java.lang.Class"/> <b:constructor-arg value="jakarta.servlet.Filter" type="java.lang.Class"/>

View File

@ -29,7 +29,7 @@
<intercept-url pattern="/**" access="authenticated"/> <intercept-url pattern="/**" access="authenticated"/>
</http> </http>
<b:bean name="propertyPlaceholderConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"/> <b:bean name="propertyPlaceholderConfigurer" class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"/>
<b:import resource="MiscHttpConfigTests-controllers.xml"/> <b:import resource="MiscHttpConfigTests-controllers.xml"/>
<b:import resource="userservice.xml"/> <b:import resource="userservice.xml"/>

View File

@ -35,7 +35,7 @@
</b:constructor-arg> </b:constructor-arg>
</b:bean> </b:bean>
<b:bean name="propertyPlaceholderConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"/> <b:bean name="propertyPlaceholderConfigurer" class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"/>
<b:import resource="MiscHttpConfigTests-controllers.xml"/> <b:import resource="MiscHttpConfigTests-controllers.xml"/>
<b:import resource="userservice.xml"/> <b:import resource="userservice.xml"/>

View File

@ -29,7 +29,7 @@
<access-denied-handler error-page="${accessDenied}"/> <access-denied-handler error-page="${accessDenied}"/>
</http> </http>
<b:bean name="propertyPlaceholderConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"/> <b:bean name="propertyPlaceholderConfigurer" class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"/>
<b:bean name="sc" class="org.springframework.security.config.http.PlaceHolderAndELConfigTests.SimpleController"/> <b:bean name="sc" class="org.springframework.security.config.http.PlaceHolderAndELConfigTests.SimpleController"/>

View File

@ -29,7 +29,7 @@
<access-denied-handler error-page="#{'/go' + '-away'}"/> <access-denied-handler error-page="#{'/go' + '-away'}"/>
</http> </http>
<b:bean name="propertyPlaceholderConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"/> <b:bean name="propertyPlaceholderConfigurer" class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"/>
<b:bean name="sc" class="org.springframework.security.config.http.PlaceHolderAndELConfigTests.SimpleController"/> <b:bean name="sc" class="org.springframework.security.config.http.PlaceHolderAndELConfigTests.SimpleController"/>

View File

@ -34,7 +34,7 @@
<csrf disabled="true"/> <csrf disabled="true"/>
</http> </http>
<b:bean name="propertyPlaceholderConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"/> <b:bean name="propertyPlaceholderConfigurer" class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"/>
<b:bean name="unsecured" class="org.springframework.security.config.http.PlaceHolderAndELConfigTests.SimpleController"/> <b:bean name="unsecured" class="org.springframework.security.config.http.PlaceHolderAndELConfigTests.SimpleController"/>

View File

@ -37,7 +37,7 @@
<csrf disabled="true"/> <csrf disabled="true"/>
</http> </http>
<b:bean name="propertyPlaceholderConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"/> <b:bean name="propertyPlaceholderConfigurer" class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"/>
<b:bean name="sc" class="org.springframework.security.config.http.PlaceHolderAndELConfigTests.SimpleController"/> <b:bean name="sc" class="org.springframework.security.config.http.PlaceHolderAndELConfigTests.SimpleController"/>

View File

@ -33,7 +33,7 @@
</port-mappings> </port-mappings>
</http> </http>
<b:bean name="propertyPlaceholderConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"/> <b:bean name="propertyPlaceholderConfigurer" class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"/>
<b:bean name="sc" class="org.springframework.security.config.http.PlaceHolderAndELConfigTests.SimpleController"/> <b:bean name="sc" class="org.springframework.security.config.http.PlaceHolderAndELConfigTests.SimpleController"/>

View File

@ -28,7 +28,7 @@
<intercept-url pattern="${secure.url}" access="ROLE_USER" requires-channel="${required.channel}"/> <intercept-url pattern="${secure.url}" access="ROLE_USER" requires-channel="${required.channel}"/>
</http> </http>
<b:bean name="propertyPlaceholderConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"/> <b:bean name="propertyPlaceholderConfigurer" class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"/>
<b:bean name="sc" class="org.springframework.security.config.http.PlaceHolderAndELConfigTests.SimpleController"/> <b:bean name="sc" class="org.springframework.security.config.http.PlaceHolderAndELConfigTests.SimpleController"/>

View File

@ -30,7 +30,7 @@
<intercept-url pattern="/**" access="ROLE_NUNYA"/> <intercept-url pattern="/**" access="ROLE_NUNYA"/>
</http> </http>
<b:bean name="propertyPlaceholderConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"/> <b:bean name="propertyPlaceholderConfigurer" class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"/>
<b:bean name="unsecured" class="org.springframework.security.config.http.PlaceHolderAndELConfigTests.SimpleController"/> <b:bean name="unsecured" class="org.springframework.security.config.http.PlaceHolderAndELConfigTests.SimpleController"/>

View File

@ -31,7 +31,7 @@
token-validity-seconds="${security.rememberme.ttl}"/> token-validity-seconds="${security.rememberme.ttl}"/>
</http> </http>
<b:bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <b:bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<b:property name="properties" value="security.rememberme.ttl=30"/> <b:property name="properties" value="security.rememberme.ttl=30"/>
</b:bean> </b:bean>

View File

@ -28,7 +28,7 @@
<b:bean name="transactionManager" class="org.springframework.security.config.MockTransactionManager" /> <b:bean name="transactionManager" class="org.springframework.security.config.MockTransactionManager" />
<b:bean class='org.springframework.beans.factory.config.PropertyPlaceholderConfigurer'/> <b:bean class='org.springframework.context.support.PropertySourcesPlaceholderConfigurer'/>
<b:bean id="transactionalTarget" class="org.springframework.security.config.TransactionalTestBusinessBean"> <b:bean id="transactionalTarget" class="org.springframework.security.config.TransactionalTestBusinessBean">
<intercept-methods use-authorization-manager="false"> <intercept-methods use-authorization-manager="false">

View File

@ -110,7 +110,7 @@
</sec:filter-chain-map> </sec:filter-chain-map>
</bean> </bean>
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" /> <bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer" />
<bean id="sec1235FilterChainProxy" class="org.springframework.security.web.FilterChainProxy"> <bean id="sec1235FilterChainProxy" class="org.springframework.security.web.FilterChainProxy">
<constructor-arg> <constructor-arg>

View File

@ -18,6 +18,8 @@ package org.springframework.security.access;
import java.io.Serializable; import java.io.Serializable;
import org.jspecify.annotations.NullUnmarked;
import org.springframework.security.access.intercept.RunAsManager; import org.springframework.security.access.intercept.RunAsManager;
import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.annotation.SecurityAnnotationScanner; import org.springframework.security.core.annotation.SecurityAnnotationScanner;
@ -45,6 +47,7 @@ import org.springframework.security.core.annotation.SecurityAnnotationScanner;
* {@link AuthorizationManager}. * {@link AuthorizationManager}.
*/ */
@Deprecated @Deprecated
@NullUnmarked
public interface ConfigAttribute extends Serializable { public interface ConfigAttribute extends Serializable {
/** /**

View File

@ -70,7 +70,7 @@ public abstract class AbstractSecurityExpressionHandler<T>
* suitable root object. * suitable root object.
*/ */
@Override @Override
public final EvaluationContext createEvaluationContext(Authentication authentication, T invocation) { public final EvaluationContext createEvaluationContext(@Nullable Authentication authentication, T invocation) {
SecurityExpressionOperations root = createSecurityExpressionRoot(authentication, invocation); SecurityExpressionOperations root = createSecurityExpressionRoot(authentication, invocation);
StandardEvaluationContext ctx = createEvaluationContextInternal(authentication, invocation); StandardEvaluationContext ctx = createEvaluationContextInternal(authentication, invocation);
if (this.beanResolver != null) { if (this.beanResolver != null) {
@ -91,7 +91,8 @@ public abstract class AbstractSecurityExpressionHandler<T>
* @return A {@code StandardEvaluationContext} or potentially a custom subclass if * @return A {@code StandardEvaluationContext} or potentially a custom subclass if
* overridden. * overridden.
*/ */
protected StandardEvaluationContext createEvaluationContextInternal(Authentication authentication, T invocation) { protected StandardEvaluationContext createEvaluationContextInternal(@Nullable Authentication authentication,
T invocation) {
return new StandardEvaluationContext(); return new StandardEvaluationContext();
} }
@ -102,8 +103,8 @@ public abstract class AbstractSecurityExpressionHandler<T>
* @param invocation the invocation (filter, method, channel) * @param invocation the invocation (filter, method, channel)
* @return the object * @return the object
*/ */
protected abstract SecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, protected abstract SecurityExpressionOperations createSecurityExpressionRoot(
T invocation); @Nullable Authentication authentication, T invocation);
protected @Nullable RoleHierarchy getRoleHierarchy() { protected @Nullable RoleHierarchy getRoleHierarchy() {
return this.roleHierarchy; return this.roleHierarchy;

View File

@ -18,6 +18,8 @@ package org.springframework.security.access.expression;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.jspecify.annotations.Nullable;
import org.springframework.aop.framework.AopInfrastructureBean; import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.expression.EvaluationContext; import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser; import org.springframework.expression.ExpressionParser;
@ -42,7 +44,7 @@ public interface SecurityExpressionHandler<T> extends AopInfrastructureBean {
* Provides an evaluation context in which to evaluate security expressions for the * Provides an evaluation context in which to evaluate security expressions for the
* invocation type. * invocation type.
*/ */
EvaluationContext createEvaluationContext(Authentication authentication, T invocation); EvaluationContext createEvaluationContext(@Nullable Authentication authentication, T invocation);
/** /**
* Provides an evaluation context in which to evaluate security expressions for the * Provides an evaluation context in which to evaluate security expressions for the
@ -55,7 +57,8 @@ public interface SecurityExpressionHandler<T> extends AopInfrastructureBean {
* @return the {@link EvaluationContext} to use * @return the {@link EvaluationContext} to use
* @since 5.8 * @since 5.8
*/ */
default EvaluationContext createEvaluationContext(Supplier<Authentication> authentication, T invocation) { default EvaluationContext createEvaluationContext(Supplier<? extends @Nullable Authentication> authentication,
T invocation) {
return createEvaluationContext(authentication.get(), invocation); return createEvaluationContext(authentication.get(), invocation);
} }

View File

@ -78,7 +78,7 @@ public abstract class SecurityExpressionRoot implements SecurityExpressionOperat
* Creates a new instance * Creates a new instance
* @param authentication the {@link Authentication} to use. Cannot be null. * @param authentication the {@link Authentication} to use. Cannot be null.
*/ */
public SecurityExpressionRoot(Authentication authentication) { public SecurityExpressionRoot(@Nullable Authentication authentication) {
this(() -> authentication); this(() -> authentication);
} }
@ -89,7 +89,7 @@ public abstract class SecurityExpressionRoot implements SecurityExpressionOperat
* Cannot be null. * Cannot be null.
* @since 5.8 * @since 5.8
*/ */
public SecurityExpressionRoot(Supplier<Authentication> authentication) { public SecurityExpressionRoot(Supplier<? extends @Nullable Authentication> authentication) {
this.authentication = SingletonSupplier.of(() -> { this.authentication = SingletonSupplier.of(() -> {
Authentication value = authentication.get(); Authentication value = authentication.get();
Assert.notNull(value, "Authentication object cannot be null"); Assert.notNull(value, "Authentication object cannot be null");
@ -177,7 +177,7 @@ public abstract class SecurityExpressionRoot implements SecurityExpressionOperat
this.trustResolver = trustResolver; this.trustResolver = trustResolver;
} }
public void setRoleHierarchy(RoleHierarchy roleHierarchy) { public void setRoleHierarchy(@Nullable RoleHierarchy roleHierarchy) {
this.roleHierarchy = roleHierarchy; this.roleHierarchy = roleHierarchy;
} }

View File

@ -79,12 +79,15 @@ public class DefaultMethodSecurityExpressionHandler extends AbstractSecurityExpr
* implementation. * implementation.
*/ */
@Override @Override
public StandardEvaluationContext createEvaluationContextInternal(Authentication auth, MethodInvocation mi) { public StandardEvaluationContext createEvaluationContextInternal(@Nullable Authentication auth,
MethodInvocation mi) {
return new MethodSecurityEvaluationContext(auth, mi, getParameterNameDiscoverer()); return new MethodSecurityEvaluationContext(auth, mi, getParameterNameDiscoverer());
} }
@Override @Override
public EvaluationContext createEvaluationContext(Supplier<Authentication> authentication, MethodInvocation mi) { @SuppressWarnings("NullAway") // FIXME: Dataflow analysis limitation
public EvaluationContext createEvaluationContext(Supplier<? extends @Nullable Authentication> authentication,
MethodInvocation mi) {
MethodSecurityExpressionOperations root = createSecurityExpressionRoot(authentication, mi); MethodSecurityExpressionOperations root = createSecurityExpressionRoot(authentication, mi);
MethodSecurityEvaluationContext ctx = new MethodSecurityEvaluationContext(root, mi, MethodSecurityEvaluationContext ctx = new MethodSecurityEvaluationContext(root, mi,
getParameterNameDiscoverer()); getParameterNameDiscoverer());
@ -96,13 +99,13 @@ public class DefaultMethodSecurityExpressionHandler extends AbstractSecurityExpr
* Creates the root object for expression evaluation. * Creates the root object for expression evaluation.
*/ */
@Override @Override
protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, protected MethodSecurityExpressionOperations createSecurityExpressionRoot(@Nullable Authentication authentication,
MethodInvocation invocation) { MethodInvocation invocation) {
return createSecurityExpressionRoot(() -> authentication, invocation); return createSecurityExpressionRoot(() -> authentication, invocation);
} }
private MethodSecurityExpressionOperations createSecurityExpressionRoot(Supplier<Authentication> authentication, private MethodSecurityExpressionOperations createSecurityExpressionRoot(
MethodInvocation invocation) { Supplier<? extends @Nullable Authentication> authentication, MethodInvocation invocation) {
MethodSecurityExpressionRoot root = new MethodSecurityExpressionRoot(authentication); MethodSecurityExpressionRoot root = new MethodSecurityExpressionRoot(authentication);
root.setThis(invocation.getThis()); root.setThis(invocation.getThis());
root.setPermissionEvaluator(getPermissionEvaluator()); root.setPermissionEvaluator(getPermissionEvaluator());

View File

@ -38,11 +38,11 @@ class MethodSecurityExpressionRoot extends SecurityExpressionRoot implements Met
private @Nullable Object target; private @Nullable Object target;
MethodSecurityExpressionRoot(Authentication a) { MethodSecurityExpressionRoot(@Nullable Authentication a) {
super(a); super(a);
} }
MethodSecurityExpressionRoot(Supplier<Authentication> authentication) { MethodSecurityExpressionRoot(Supplier<? extends @Nullable Authentication> authentication) {
super(authentication); super(authentication);
} }

View File

@ -18,6 +18,8 @@ package org.springframework.security.access.vote;
import java.util.Collection; import java.util.Collection;
import org.jspecify.annotations.NullUnmarked;
import org.springframework.security.access.AccessDecisionVoter; import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
@ -53,6 +55,7 @@ import org.springframework.security.core.GrantedAuthority;
* instead * instead
*/ */
@Deprecated @Deprecated
@NullUnmarked
public class RoleVoter implements AccessDecisionVoter<Object> { public class RoleVoter implements AccessDecisionVoter<Object> {
private String rolePrefix = "ROLE_"; private String rolePrefix = "ROLE_";

View File

@ -16,6 +16,9 @@
package org.springframework.security.authentication; package org.springframework.security.authentication;
import org.jspecify.annotations.Nullable;
import org.springframework.lang.Contract;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
/** /**
@ -37,7 +40,7 @@ public interface AuthenticationTrustResolver {
* @return <code>true</code> the passed authentication token represented an anonymous * @return <code>true</code> the passed authentication token represented an anonymous
* principal, <code>false</code> otherwise * principal, <code>false</code> otherwise
*/ */
boolean isAnonymous(Authentication authentication); boolean isAnonymous(@Nullable Authentication authentication);
/** /**
* Indicates whether the passed <code>Authentication</code> token represents user that * Indicates whether the passed <code>Authentication</code> token represents user that
@ -51,7 +54,7 @@ public interface AuthenticationTrustResolver {
* @return <code>true</code> the passed authentication token represented a principal * @return <code>true</code> the passed authentication token represented a principal
* authenticated using a remember-me token, <code>false</code> otherwise * authenticated using a remember-me token, <code>false</code> otherwise
*/ */
boolean isRememberMe(Authentication authentication); boolean isRememberMe(@Nullable Authentication authentication);
/** /**
* Indicates whether the passed <code>Authentication</code> token represents a fully * Indicates whether the passed <code>Authentication</code> token represents a fully
@ -66,7 +69,7 @@ public interface AuthenticationTrustResolver {
* {@link #isRememberMe(Authentication)}, <code>false</code> otherwise * {@link #isRememberMe(Authentication)}, <code>false</code> otherwise
* @since 6.1 * @since 6.1
*/ */
default boolean isFullyAuthenticated(Authentication authentication) { default boolean isFullyAuthenticated(@Nullable Authentication authentication) {
return isAuthenticated(authentication) && !isRememberMe(authentication); return isAuthenticated(authentication) && !isRememberMe(authentication);
} }
@ -78,7 +81,8 @@ public interface AuthenticationTrustResolver {
* {@link Authentication#isAuthenticated()} is true. * {@link Authentication#isAuthenticated()} is true.
* @since 6.1.7 * @since 6.1.7
*/ */
default boolean isAuthenticated(Authentication authentication) { @Contract("null -> false")
default boolean isAuthenticated(@Nullable Authentication authentication) {
return authentication != null && authentication.isAuthenticated() && !isAnonymous(authentication); return authentication != null && authentication.isAuthenticated() && !isAnonymous(authentication);
} }

View File

@ -16,6 +16,8 @@
package org.springframework.security.authentication; package org.springframework.security.authentication;
import org.jspecify.annotations.Nullable;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
/** /**
@ -44,7 +46,7 @@ public class AuthenticationTrustResolverImpl implements AuthenticationTrustResol
} }
@Override @Override
public boolean isAnonymous(Authentication authentication) { public boolean isAnonymous(@Nullable Authentication authentication) {
if ((this.anonymousClass == null) || (authentication == null)) { if ((this.anonymousClass == null) || (authentication == null)) {
return false; return false;
} }
@ -52,7 +54,7 @@ public class AuthenticationTrustResolverImpl implements AuthenticationTrustResol
} }
@Override @Override
public boolean isRememberMe(Authentication authentication) { public boolean isRememberMe(@Nullable Authentication authentication) {
if ((this.rememberMeClass == null) || (authentication == null)) { if ((this.rememberMeClass == null) || (authentication == null)) {
return false; return false;
} }

View File

@ -18,6 +18,8 @@ package org.springframework.security.authentication;
import java.io.Serial; import java.io.Serial;
import org.jspecify.annotations.Nullable;
import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.AuthenticationException;
/** /**
@ -35,7 +37,7 @@ public class BadCredentialsException extends AuthenticationException {
* Constructs a <code>BadCredentialsException</code> with the specified message. * Constructs a <code>BadCredentialsException</code> with the specified message.
* @param msg the detail message * @param msg the detail message
*/ */
public BadCredentialsException(String msg) { public BadCredentialsException(@Nullable String msg) {
super(msg); super(msg);
} }
@ -45,7 +47,7 @@ public class BadCredentialsException extends AuthenticationException {
* @param msg the detail message * @param msg the detail message
* @param cause root cause * @param cause root cause
*/ */
public BadCredentialsException(String msg, Throwable cause) { public BadCredentialsException(@Nullable String msg, Throwable cause) {
super(msg, cause); super(msg, cause);
} }

View File

@ -39,7 +39,7 @@ public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationT
private static final long serialVersionUID = 620L; private static final long serialVersionUID = 620L;
private final Object principal; private final @Nullable Object principal;
private @Nullable Object credentials; private @Nullable Object credentials;
@ -49,7 +49,7 @@ public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationT
* will return <code>false</code>. * will return <code>false</code>.
* *
*/ */
public UsernamePasswordAuthenticationToken(Object principal, @Nullable Object credentials) { public UsernamePasswordAuthenticationToken(@Nullable Object principal, @Nullable Object credentials) {
super(null); super(null);
this.principal = principal; this.principal = principal;
this.credentials = credentials; this.credentials = credentials;
@ -82,7 +82,8 @@ public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationT
* *
* @since 5.7 * @since 5.7
*/ */
public static UsernamePasswordAuthenticationToken unauthenticated(Object principal, @Nullable Object credentials) { public static UsernamePasswordAuthenticationToken unauthenticated(@Nullable Object principal,
@Nullable Object credentials) {
return new UsernamePasswordAuthenticationToken(principal, credentials); return new UsernamePasswordAuthenticationToken(principal, credentials);
} }
@ -106,7 +107,7 @@ public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationT
} }
@Override @Override
public Object getPrincipal() { public @Nullable Object getPrincipal() {
return this.principal; return this.principal;
} }

View File

@ -178,8 +178,10 @@ public abstract class AbstractJaasAuthenticationProvider implements Authenticati
// applied. // applied.
authorities = getAuthorities(principals); authorities = getAuthorities(principals);
// Convert the authorities set back to an array and apply it to the token. // Convert the authorities set back to an array and apply it to the token.
JaasAuthenticationToken result = new JaasAuthenticationToken(request.getPrincipal(), Object principal = request.getPrincipal();
request.getCredentials(), new ArrayList<>(authorities), loginContext); Assert.notNull(principal, "The principal cannot be null");
JaasAuthenticationToken result = new JaasAuthenticationToken(principal, request.getCredentials(),
new ArrayList<>(authorities), loginContext);
// Publish the success event // Publish the success event
publishSuccessEvent(result); publishSuccessEvent(result);
// we're done, return the token. // we're done, return the token.

View File

@ -0,0 +1,56 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.authentication.ott;
import java.io.Serial;
import java.util.Collection;
import org.jspecify.annotations.Nullable;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
/**
* The result of a successful one-time-token authentication
*
* @author Josh Cummings
* @since 7.0
*/
public class OneTimeTokenAuthentication extends AbstractAuthenticationToken {
@Serial
private static final long serialVersionUID = 1195893764725073959L;
private final Object principal;
public OneTimeTokenAuthentication(Object principal, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
setAuthenticated(true);
}
@Override
public Object getPrincipal() {
return this.principal;
}
@Override
public @Nullable Object getCredentials() {
return null;
}
}

View File

@ -56,8 +56,7 @@ public final class OneTimeTokenAuthenticationProvider implements AuthenticationP
} }
try { try {
UserDetails user = this.userDetailsService.loadUserByUsername(consumed.getUsername()); UserDetails user = this.userDetailsService.loadUserByUsername(consumed.getUsername());
OneTimeTokenAuthenticationToken authenticated = OneTimeTokenAuthenticationToken.authenticated(user, OneTimeTokenAuthentication authenticated = new OneTimeTokenAuthentication(user, user.getAuthorities());
user.getAuthorities());
authenticated.setDetails(otpAuthenticationToken.getDetails()); authenticated.setDetails(otpAuthenticationToken.getDetails());
return authenticated; return authenticated;
} }

View File

@ -40,6 +40,10 @@ public class OneTimeTokenAuthenticationToken extends AbstractAuthenticationToken
private @Nullable String tokenValue; private @Nullable String tokenValue;
/**
* @deprecated Please use constructor that takes a {@link String} instead
*/
@Deprecated(forRemoval = true, since = "7.0")
public OneTimeTokenAuthenticationToken(@Nullable Object principal, String tokenValue) { public OneTimeTokenAuthenticationToken(@Nullable Object principal, String tokenValue) {
super(Collections.emptyList()); super(Collections.emptyList());
this.tokenValue = tokenValue; this.tokenValue = tokenValue;
@ -50,6 +54,10 @@ public class OneTimeTokenAuthenticationToken extends AbstractAuthenticationToken
this(null, tokenValue); this(null, tokenValue);
} }
/**
* @deprecated Please use {@link OneTimeTokenAuthentication} instead
*/
@Deprecated(forRemoval = true, since = "7.0")
public OneTimeTokenAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) { public OneTimeTokenAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
super(authorities); super(authorities);
this.principal = principal; this.principal = principal;
@ -60,9 +68,11 @@ public class OneTimeTokenAuthenticationToken extends AbstractAuthenticationToken
* Creates an unauthenticated token * Creates an unauthenticated token
* @param tokenValue the one-time token value * @param tokenValue the one-time token value
* @return an unauthenticated {@link OneTimeTokenAuthenticationToken} * @return an unauthenticated {@link OneTimeTokenAuthenticationToken}
* @deprecated Please use constructor that takes a {@link String} instead
*/ */
public static OneTimeTokenAuthenticationToken unauthenticated(String tokenValue) { @Deprecated(forRemoval = true, since = "7.0")
return new OneTimeTokenAuthenticationToken(null, tokenValue); public static OneTimeTokenAuthenticationToken unauthenticated(@Nullable String tokenValue) {
return new OneTimeTokenAuthenticationToken(null, (tokenValue != null) ? tokenValue : "");
} }
/** /**
@ -70,7 +80,9 @@ public class OneTimeTokenAuthenticationToken extends AbstractAuthenticationToken
* @param principal the principal * @param principal the principal
* @param tokenValue the one-time token value * @param tokenValue the one-time token value
* @return an unauthenticated {@link OneTimeTokenAuthenticationToken} * @return an unauthenticated {@link OneTimeTokenAuthenticationToken}
* @deprecated Please use constructor that takes a {@link String} instead
*/ */
@Deprecated(forRemoval = true, since = "7.0")
public static OneTimeTokenAuthenticationToken unauthenticated(Object principal, String tokenValue) { public static OneTimeTokenAuthenticationToken unauthenticated(Object principal, String tokenValue) {
return new OneTimeTokenAuthenticationToken(principal, tokenValue); return new OneTimeTokenAuthenticationToken(principal, tokenValue);
} }
@ -80,7 +92,9 @@ public class OneTimeTokenAuthenticationToken extends AbstractAuthenticationToken
* @param principal the principal * @param principal the principal
* @param authorities the principal authorities * @param authorities the principal authorities
* @return an authenticated {@link OneTimeTokenAuthenticationToken} * @return an authenticated {@link OneTimeTokenAuthenticationToken}
* @deprecated Please use {@link OneTimeTokenAuthentication} instead
*/ */
@Deprecated(forRemoval = true, since = "7.0")
public static OneTimeTokenAuthenticationToken authenticated(Object principal, public static OneTimeTokenAuthenticationToken authenticated(Object principal,
Collection<? extends GrantedAuthority> authorities) { Collection<? extends GrantedAuthority> authorities) {
return new OneTimeTokenAuthenticationToken(principal, authorities); return new OneTimeTokenAuthenticationToken(principal, authorities);

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