1
0
mirror of synced 2026-07-19 09:35:12 +00:00

Compare commits

..

112 Commits

Author SHA1 Message Date
Josh Cummings e3e7d76b70 Add Workflow to Finalize a Release 2025-11-04 09:32:41 -07:00
github-actions[bot] 9dde69746f Release 7.0.0-RC2 2025-11-04 05:32:57 +00:00
Rob Winch 884cf0d62e EnableGlobalMultiFactorAuthentication->EnableMultiFactorAuthentication
Closes gh-18127
2025-11-03 22:42:28 -06:00
Rob Winch aaf738f7ac MFA is now Opt In
This commit ensures that MFA is only performed when users opt in. By
doing so, we allow users to decide if they will opt into the semantics
of merging two Authentication instances.

Closes gh-18126
2025-11-03 22:42:27 -06:00
Rob Winch ccd39a23c9 Only perform MFA if Authentication.getName() is the same
Closes gh-18112
2025-11-03 22:42:27 -06:00
Josh Cummings 793820acfa Remove Authority Copying From Reactive
We will re-address this when adding factors to
ReactiveAuthenticationManager implementations.

Issue gh-2603
2025-11-03 13:31:30 -07:00
Joe Grandja b6ed037c39 Document device_code grant disabled by default
Issue gh-17998
2025-10-31 06:38:09 -04:00
Joe Grandja 5da0cbea4b Document OAuth 2.0 Dynamic Client Registration support
Issue gh-17964
2025-10-30 16:01:51 -04:00
Joe Grandja e6b4d461e7 Fix OAuth2AuthorizationServerJacksonModule type validator configuration
Closes gh-18102
2025-10-30 07:19:45 -04:00
Josh Cummings 4daf089e46 Merge remote-tracking branch 'origin/6.5.x' 2025-10-28 12:08:53 -06:00
namest504 6501e97ece Fix sensitive case in JwtTypeValidator
Closes gh-18092

Signed-off-by: namest504 <namest504@gmail.com>
2025-10-28 12:08:29 -06:00
Josh Cummings 3a84894bf4 Revert "Add AuthorizationProxyMixin"
This reverts commit 743817fc15.
2025-10-27 17:30:44 -06:00
Joe Grandja 90855aa128 Missing response_type in POST authorization request returns invalid_request
Issue https://github.com/spring-projects/spring-authorization-server/issues/2226
2025-10-24 05:55:45 -04:00
dependabot[bot] 9f7e92d6f2 Bump tools.jackson:jackson-bom from 3.0.0 to 3.0.1
Bumps [tools.jackson:jackson-bom](https://github.com/FasterXML/jackson-bom) from 3.0.0 to 3.0.1.
- [Commits](https://github.com/FasterXML/jackson-bom/compare/jackson-bom-3.0.0...jackson-bom-3.0.1)

---
updated-dependencies:
- dependency-name: tools.jackson:jackson-bom
  dependency-version: 3.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-23 09:33:13 -05:00
Josh Cummings 727f0e27d6 Merge branch '6.5.x' 2025-10-20 17:42:52 -06:00
Josh Cummings f548aaf5c5 Merge branch '6.4.x' into 6.5.x 2025-10-20 17:42:25 -06:00
Josh Cummings 743817fc15 Add AuthorizationProxyMixin
This commit adds Jackson configuration specific to
authorization proxies created by Spring Security

Closes gh-18077
2025-10-20 17:16:21 -06:00
Josh Cummings fb701e4615 Merge remote-tracking branch 'origin/6.5.x' 2025-10-20 17:10:05 -06:00
Josh Cummings 1c112005fa Don't Attempt to Generate Token Without Valid Token Request
Closes gh-18088

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2025-10-20 17:09:43 -06:00
Marcus Hert da Coregio e0a71eb00e Fix GenerateOneTimeTokenRequestResolver ignored if username param not present
Signed-off-by: Marcus Hert da Coregio <marcusdacoregio@gmail.com>
2025-10-20 17:09:43 -06:00
Josh Cummings 69d28dc35b Merge branch '6.5.x' 2025-10-20 17:07:34 -06:00
Josh Cummings 42ddaba870 Next Development Version 2025-10-20 17:07:18 -06:00
Josh Cummings da46ba2619 Update Password Samples for Nullability
Issue gh-16226
2025-10-20 17:04:22 -06:00
Josh Cummings a406f5fe2d Merge remote-tracking branch 'origin/6.5.x' 2025-10-20 16:46:49 -06:00
Himanshu Pareek dcb4e47cd5 Add Include-Code to the Password Storage page
References gh-16226

Signed-off-by: Himanshu Pareek <himanshupareekiit01@gmail.com>
2025-10-20 16:35:23 -06:00
Rob Winch 82f87cf2b6 Next Development Version 2025-10-20 16:55:17 -05:00
Josh Cummings 0a2f55d485 Clarify Nullability in Granted Authority Lambda
Issue gh-17999
2025-10-20 15:22:24 -06:00
Andrey Litvitski 9b61533db2 Mark GrantedAuthority#getAuthority as @Nullable
Closes: gh-17999

Signed-off-by: Andrey Litvitski <andrey1010102008@gmail.com>
2025-10-20 15:22:24 -06:00
Josh Cummings eb43830260 Polish JavaDoc
1. Removed comment about not changing field name in a
serialized object as this is true for all fields in a
Java-serialize POJO
2. Added example value for the constructor that demonstrates
the relationship between a role and an authority

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2025-10-20 15:18:11 -06:00
Yanming Zhou b55c28cf25 Polish SimpleGrantedAuthority
1. Add Javadoc to state that role is prefixed.
2. Rename constructor argument from `role` to `authority` for better readability.

Signed-off-by: Yanming Zhou <zhouyanming@gmail.com>
2025-10-20 15:18:11 -06:00
Simon Von 0927bed66a 📔 Documentation
1. Correct the org.springframework.security.config.annotation.web.LogoutDsl's property description

Signed-off-by: Simon Von <g1672943850@gmail.com>
2025-10-20 15:17:32 -06:00
Josh Cummings 9ed446e6f5 Next Development Version 2025-10-20 15:15:57 -06:00
github-actions[bot] d5e6da5aba Release 7.0.0-RC1 2025-10-20 17:32:34 +00:00
Rob Winch 4d2bd30c75 Update to Reactor 2025.0.0-RC1
Closes gh-18087
2025-10-20 12:31:09 -05:00
Rob Winch 5acad99852 Revert "Release 7.0.0-RC1"
This reverts commit e616688f56.
2025-10-20 12:29:58 -05:00
github-actions[bot] e616688f56 Release 7.0.0-RC1 2025-10-20 17:26:08 +00:00
github-actions[bot] 56a23d9ddc Release 6.5.6 2025-10-20 17:17:40 +00:00
github-actions[bot] dc5aed9b5f Release 6.4.12 2025-10-20 17:17:37 +00:00
Josh Cummings 9c7b34a48b Favor Relative Redirects by Default
Closes gh-16300
2025-10-20 10:25:17 -06:00
Josh Cummings d5d7fd414d Update What's New 2025-10-20 10:25:17 -06:00
Rob Winch 491a3e8f68 Update to Spring LDAP 4.0.0-RC1
Closes gh-18086
2025-10-20 09:35:15 -05:00
Rob Winch 43d20ea91f Update to Spring Data 2025.1.0-RC1
Closes gh-18085
2025-10-20 09:35:14 -05:00
Rob Winch 24241d0384 Update to Spring Framework 7.0.0-RC1
Closes gh-18084
2025-10-20 09:35:14 -05:00
dependabot[bot] cb8c2b090c Bump ch.qos.logback:logback-classic from 1.5.19 to 1.5.20
Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.5.19 to 1.5.20.
- [Release notes](https://github.com/qos-ch/logback/releases)
- [Commits](https://github.com/qos-ch/logback/compare/v_1.5.19...v_1.5.20)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-20 09:17:01 -05:00
Rob Winch e94de4d0e3 Merge branch '6.5.x' 2025-10-20 09:16:23 -05:00
Rob Winch cb994aad6c Bump ch.qos.logback:logback-classic from 1.5.19 to 1.5.20 2025-10-20 09:15:32 -05:00
Rob Winch 6f6ee0c060 Bump org.springframework.data:spring-data-bom from 2024.1.10 to 2024.1.11 2025-10-20 09:15:30 -05:00
Rob Winch 9cecc2cf09 Merge branch '6.4.x' into 6.5.x 2025-10-20 09:15:18 -05:00
Rob Winch f19c9c8625 Bump ch.qos.logback:logback-classic from 1.5.19 to 1.5.20 2025-10-20 09:14:31 -05:00
Rob Winch 95abf61c88 Refine Jackson 3 format description 2025-10-20 09:11:22 -05:00
Joe Grandja 22cbb13f7d Add comments to SQL-scripts to ensure robust timezone handling
Issue https://github.com/spring-projects/spring-authorization-server/pull/2217
2025-10-20 07:12:50 -04:00
Joe Grandja fc8b6b5863 Return PAR endpoint metadata only when enabled
Issue https://github.com/spring-projects/spring-authorization-server/issues/2219
2025-10-20 06:06:24 -04:00
dependabot[bot] 8b89e31e3d Bump org.springframework.data:spring-data-bom
Bumps [org.springframework.data:spring-data-bom](https://github.com/spring-projects/spring-data-bom) from 2024.1.10 to 2024.1.11.
- [Release notes](https://github.com/spring-projects/spring-data-bom/releases)
- [Commits](https://github.com/spring-projects/spring-data-bom/compare/2024.1.10...2024.1.11)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-20 03:18:26 +00:00
dependabot[bot] 67b15be917 Bump ch.qos.logback:logback-classic from 1.5.19 to 1.5.20
Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.5.19 to 1.5.20.
- [Release notes](https://github.com/qos-ch/logback/releases)
- [Commits](https://github.com/qos-ch/logback/compare/v_1.5.19...v_1.5.20)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-20 03:18:21 +00:00
dependabot[bot] 217a29e6ba Bump org.springframework.data:spring-data-bom
Bumps [org.springframework.data:spring-data-bom](https://github.com/spring-projects/spring-data-bom) from 2024.1.10 to 2024.1.11.
- [Release notes](https://github.com/spring-projects/spring-data-bom/releases)
- [Commits](https://github.com/spring-projects/spring-data-bom/compare/2024.1.10...2024.1.11)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-20 03:12:54 +00:00
dependabot[bot] b2d6380633 Bump ch.qos.logback:logback-classic from 1.5.19 to 1.5.20
Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.5.19 to 1.5.20.
- [Release notes](https://github.com/qos-ch/logback/releases)
- [Commits](https://github.com/qos-ch/logback/compare/v_1.5.19...v_1.5.20)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-20 03:12:45 +00:00
Rob Winch 9dc27bee03 Link to gh-18077 2025-10-19 17:03:19 -05:00
Rob Winch a181733365 Encapsulate GenericHttpMessageConverterAdapter
This will allow its removal in gh-18073
2025-10-19 17:03:19 -05:00
Rob Winch 51e8f8f1c6 Deprecate WebAuthnAuthenticationFilter.setConverter(GenericHttpMessageConverter)
This makes sense given that Framework's new Jackson support is a
SmartHttpMessageConverter. Additionally,
GenericHttpMessageConverterAdapter is now package private to encapsulate
it.

Issue gh-18073
2025-10-19 17:03:19 -05:00
Rob Winch d309f1887e Remove Extra Blank Line from CoreJacksonModule 2025-10-19 17:03:19 -05:00
Rob Winch 5e851e0b26 Remove JdbcOAuth2AuthorizationService.Mapper
- We should not introduce an unnecessary public API
  - It would need to be removed when Jackson 2 support was removed, but
    was required to configure Jackson 3 support
  - There are already existing interfaces that could be used
- OAuth2AuthorizationRowMapper & OAuth2AuthorizationParametersMapper had
  unnecessary breaking changes by removing getter/setter for ObjectMapper
- To prevent NoClassDefFoundErrors all optional (Jackson) dependencies
  need to be on different classes & we wish to preserve the existing
  accessors for ObjectMapper which is this uses subclasses
- With added TestAuthenticationTokenMixin support, no need to explicitly
  add it in tests
2025-10-19 17:03:19 -05:00
Rob Winch 803936cfbe JacksonDelegate uses SecurityJacksonModules 2025-10-19 17:03:19 -05:00
Rob Winch 50568da1e5 Add Jackson 3 TestingAuthenticationToken Support
Without this many of the tests fail when using Jackson 3
2025-10-19 17:03:19 -05:00
Sébastien Deleuze 8f8a25533a Refine documentation for Jackson 3
This commit refines the documentation by:
 - Updating Jackson documentation for Jackson 3
 - Removing the outdated documentation in servlet
 - Adding migration guidelines

Closes gh-17832
Signed-off-by: Sébastien Deleuze <sdeleuze@users.noreply.github.com>
2025-10-19 17:03:19 -05:00
Sébastien Deleuze 137f8fd670 Add support for JacksonJsonHttpMessageConverter
This commit introduces classpath checks and instantiation of
JacksonJsonHttpMessageConverter (based on Jackson 3) leveraging
a new GenericHttpMessageConverterAdapter which allows to adapt
SmartHttpMessageConverter to GenericHttpMessageConverter.

See gh-17832
Signed-off-by: Sébastien Deleuze <sdeleuze@users.noreply.github.com>
2025-10-19 17:03:19 -05:00
Sébastien Deleuze 702a177e25 Add webauthn Jackson 3 support and deprecate Jackson 2 one
Since this module was already using the jackson sub-package for Jackson 2
support, both Jackson 2 and Jackson 3 support lives in the same subpackage
and the former package-private classes has been renamed with a Jackson2
qualifier.

See gh-17832
Signed-off-by: Sébastien Deleuze <sdeleuze@users.noreply.github.com>
2025-10-19 17:03:19 -05:00
Sébastien Deleuze 48854c3ac9 Deprecate Jackson 2 support
This commit does not cover webauthn which is a special case (uses
jackson sub-package for Jackson 2 support) which will be handled in
a distinct commit.

See gh-17832
Signed-off-by: Sébastien Deleuze <sdeleuze@users.noreply.github.com>
2025-10-19 17:03:19 -05:00
Sébastien Deleuze 65a14d6c6d Add Jackson 3 support
This commit adds support for Jackson 3 which has the following
major differences with the Jackson 2 one:
 - jackson subpackage instead of jackson2
 - Jackson type prefix instead of Jackson2
 - JsonMapper instead of ObjectMapper
 - For configuration, JsonMapper.Builder instead of ObjectMapper
   since the latter is now immutable
 - Remove custom support for unmodifiable collections
 - Use safe default typing via a PolymorphicTypeValidator

Jackson 3 changes compared to Jackson 2 are documented in
https://cowtowncoder.medium.com/jackson-3-0-0-ga-released-1f669cda529a
and
https://github.com/FasterXML/jackson/blob/main/jackson3/MIGRATING_TO_JACKSON_3.md.

This commit does not cover webauthn which is a special case (uses
jackson sub-package for Jackson 2 support) which will be handled in
a distinct commit.

See gh-17832
Signed-off-by: Sébastien Deleuze <sdeleuze@users.noreply.github.com>
2025-10-19 17:03:19 -05:00
Sébastien Deleuze 916a687b29 Add Jackson 3 BOM
See gh-17832
Signed-off-by: Sébastien Deleuze <sdeleuze@users.noreply.github.com>
2025-10-19 17:03:19 -05:00
Sébastien Deleuze 762fcbb516 Add .kotlin/ to .gitignore
Signed-off-by: Sébastien Deleuze <sdeleuze@users.noreply.github.com>
2025-10-19 17:03:19 -05:00
Joe Grandja fc795a81d4 PAR uses requested scopes on consent
Issue https://github.com/spring-projects/spring-authorization-server/pull/2182
2025-10-17 16:14:31 -04:00
Josh Cummings 4bc319883b Address Nullability 2025-10-17 14:03:15 -06:00
dependabot[bot] cb7a6292b7 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.5 to 0.0.6.
- [Release notes](https://github.com/spring-gradle-plugins/nullability-plugin/releases)
- [Commits](https://github.com/spring-gradle-plugins/nullability-plugin/compare/v0.0.5...v0.0.6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-17 14:03:15 -06:00
Josh Cummings bbf6a4e786 Merge branch '6.5.x' 2025-10-17 13:50:05 -06:00
Josh Cummings ba2619cb8a Merge remote-tracking branch 'origin/6.4.x' into 6.5.x 2025-10-17 13:49:54 -06:00
dependabot[bot] 43c53c3b78 Bump org.springframework:spring-framework-bom from 6.2.11 to 6.2.12
Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 6.2.11 to 6.2.12.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v6.2.11...v6.2.12)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-17 13:48:50 -06:00
dependabot[bot] b1e16cd147 Bump org.springframework.ldap:spring-ldap-core from 3.2.14 to 3.2.15
Bumps [org.springframework.ldap:spring-ldap-core](https://github.com/spring-projects/spring-ldap) from 3.2.14 to 3.2.15.
- [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.14...3.2.15)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-17 13:48:30 -06:00
dependabot[bot] 9961e6d56c Bump org.springframework:spring-framework-bom from 6.2.11 to 6.2.12
Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 6.2.11 to 6.2.12.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v6.2.11...v6.2.12)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-17 13:48:13 -06:00
dependabot[bot] cbad2ff5ca Bump org.springframework.ldap:spring-ldap-core from 3.2.14 to 3.2.15
Bumps [org.springframework.ldap:spring-ldap-core](https://github.com/spring-projects/spring-ldap) from 3.2.14 to 3.2.15.
- [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.14...3.2.15)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-17 13:47:56 -06:00
dependabot[bot] 63c8b0faa3 Bump org.springframework.ldap:spring-ldap-core from 3.2.13 to 3.2.15
Bumps [org.springframework.ldap:spring-ldap-core](https://github.com/spring-projects/spring-ldap) from 3.2.13 to 3.2.15.
- [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.15)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-17 13:46:16 -06:00
Josh Cummings a435175723 Clean Up Generic Typing in Builder
Issue gh-17997
2025-10-17 11:13:00 -06:00
Joe Grandja 4b810a8971 Disallow usage of the openid scope in device authorization requests
Issue https://github.com/spring-projects/spring-authorization-server/pull/2177
2025-10-17 11:41:30 -04:00
Joe Grandja 0d261e9c32 Remove setOidcUserMapper() in OidcUserService and OidcReactiveOAuth2UserService
Closes gh-18060
2025-10-16 16:29:52 -04:00
Josh Cummings c5e141ad07 Change JavaDoc to FactorGrantedAuthority
Issue gh-18030
2025-10-16 14:00:43 -06:00
Josh Cummings ba42b9c4cc Update Documentation for All-Factor Propagation
Issue gh-18000
2025-10-16 13:41:46 -06:00
Josh Cummings b1a50a25b6 Check If toBuilder Is Implemented
Since RC1 is right around the corner, let's change the API
footprint as little as possible by using reflection to check
if a class has declared toBuilder themselves. If they have, we
can assume that that class's builder will produce that class.

Issue gh-18052
2025-10-16 13:41:45 -06:00
Josh Cummings 4281f6b00b Prevent Duplicate Authorities
Issue gh-17981
2025-10-16 13:41:45 -06:00
Josh Cummings 0fcef6dca2 Add Missing Mock Configuration 2025-10-16 13:41:45 -06:00
Josh Cummings 2e7cdd7b14 Revert "Merge branch 'builder-enhancements'"
This reverts commit 95644fb73c, reversing
changes made to fbf7bb3be1.

Reverting this commit will allow us more time to
consider the ideal way to add this support to the public API.
2025-10-16 13:41:45 -06:00
Josh Cummings cefc0cddec Propagate All Missing Factors
Closes gh-18000
2025-10-16 13:41:45 -06:00
Joe Grandja af1de950ae Align setRetrieveUserInfo() between OidcUserService and OidcReactiveOAuth2UserService
Closes gh-18057
2025-10-16 15:12:10 -04:00
Joe Grandja 7f29585df4 Remove OidcUserService.setAccessibleScopes()
Closes gh-18056
2025-10-16 15:12:10 -04:00
Rob Winch 2eb5da3764 Deprecate CacheControlServerHttpHeadersWriter.CACHE_CONTRTOL_VALUE
The member is public, so we need to deprecate it rather than remove it.

Issue gh-18035

Closes gh-18058
2025-10-16 14:03:19 -05:00
Tran Ngoc Nhan f5d33457dc Fix-typos
Signed-off-by: Tran Ngoc Nhan <ngocnhan.tran1996@gmail.com>
2025-10-16 14:03:19 -05:00
parthokr 938a5a7c77 Fix typo in AuthenticationProvider Javadoc
Signed-off-by: parthokr <partho.kr@proton.me>
2025-10-16 13:54:00 -05:00
dependabot[bot] f03213383e Bump io.micrometer:micrometer-observation from 1.14.11 to 1.14.12
Bumps [io.micrometer:micrometer-observation](https://github.com/micrometer-metrics/micrometer) from 1.14.11 to 1.14.12.
- [Release notes](https://github.com/micrometer-metrics/micrometer/releases)
- [Commits](https://github.com/micrometer-metrics/micrometer/compare/v1.14.11...v1.14.12)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-16 12:54:46 -05:00
Rob Winch fc2b1f9923 Merge branch '6.5.x' 2025-10-16 12:53:33 -05:00
Rob Winch dee33b5337 Bump org.hibernate.orm:hibernate-core from 6.6.31.Final to 6.6.33.Final 2025-10-16 12:52:50 -05:00
Rob Winch 9f936015ff Bump io.micrometer:micrometer-observation from 1.14.11 to 1.14.12 2025-10-16 12:52:46 -05:00
Rob Winch 79dfbe14c2 Merge branch '6.4.x' into 6.5.x 2025-10-16 12:52:34 -05:00
Rob Winch b75f2582c4 Bump org.hibernate.orm:hibernate-core from 6.6.31.Final to 6.6.33.Final 2025-10-16 12:51:41 -05:00
Joe Grandja 67c3ceb611 Fix NullAway error
Related https://github.com/spring-projects/spring-framework/pull/35629
2025-10-15 14:53:06 -04:00
Josh Cummings 95644fb73c Merge branch 'builder-enhancements'
Issue gh-18052
Issue gh-18053
2025-10-15 12:02:41 -06:00
Josh Cummings 21ff7688cc Move Builder to Authentication
Leaving the Builder in Authentication allows
authentication implementations to implement Builder
without needing to implement BuildableAuthentication.

Issue gh-18052
2025-10-15 12:01:11 -06:00
Josh Cummings 4102007119 Add Builder#authentication
This commit consolidates logic common to applying one
authenticaiton to another. Specifically, it will copy the
authorities in one authentication into the builder instance
of another.

Closes gh-18053
2025-10-15 12:01:11 -06:00
Josh Cummings e535e61c8b Move toBuilder to BuildableAuthentication
Closes gh-18052
2025-10-15 12:01:11 -06:00
Joe Grandja fbf7bb3be1 Allow OAuth2AuthorizationRequest to be extended
Closes gh-18049
2025-10-14 16:34:59 -04:00
Ivan Golovko 979ac7c336 Remove cache from (Reactive)OidcIdTokenDecoderFactory
Closes gh-16647

Signed-off-by: iigolovko <iigolovko@ginc-it.ru>
2025-10-14 11:24:54 -04:00
dependabot[bot] 90a1c2c15d Bump io.micrometer:micrometer-observation from 1.14.11 to 1.14.12
Bumps [io.micrometer:micrometer-observation](https://github.com/micrometer-metrics/micrometer) from 1.14.11 to 1.14.12.
- [Release notes](https://github.com/micrometer-metrics/micrometer/releases)
- [Commits](https://github.com/micrometer-metrics/micrometer/compare/v1.14.11...v1.14.12)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-14 03:20:40 +00:00
dependabot[bot] 978459bd1d Bump io.micrometer:micrometer-observation from 1.14.11 to 1.14.12
Bumps [io.micrometer:micrometer-observation](https://github.com/micrometer-metrics/micrometer) from 1.14.11 to 1.14.12.
- [Release notes](https://github.com/micrometer-metrics/micrometer/releases)
- [Commits](https://github.com/micrometer-metrics/micrometer/compare/v1.14.11...v1.14.12)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-14 03:15:43 +00:00
dependabot[bot] 73690a928b Bump org.hibernate.orm:hibernate-core from 6.6.31.Final to 6.6.33.Final
Bumps [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm) from 6.6.31.Final to 6.6.33.Final.
- [Release notes](https://github.com/hibernate/hibernate-orm/releases)
- [Changelog](https://github.com/hibernate/hibernate-orm/blob/6.6.33/changelog.txt)
- [Commits](https://github.com/hibernate/hibernate-orm/compare/6.6.31...6.6.33)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-13 03:27:33 +00:00
dependabot[bot] 7cc9d2849e Bump org.hibernate.orm:hibernate-core from 6.6.31.Final to 6.6.33.Final
Bumps [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm) from 6.6.31.Final to 6.6.33.Final.
- [Release notes](https://github.com/hibernate/hibernate-orm/releases)
- [Changelog](https://github.com/hibernate/hibernate-orm/blob/6.6.33/changelog.txt)
- [Commits](https://github.com/hibernate/hibernate-orm/compare/6.6.31...6.6.33)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-13 03:16:24 +00:00
508 changed files with 15199 additions and 1755 deletions
+41
View File
@@ -0,0 +1,41 @@
name: Finalize Release
on:
workflow_dispatch: # Manual trigger
env:
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
permissions:
contents: read
jobs:
project-version:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.project-version.outputs.version }}
steps:
- id: project-version
run: echo "version=$(grep '^version=' gradle.properties | cut -d'=' -f2)" >> $GITHUB_OUTPUT
perform-release:
name: Perform Release
needs: [ project-version ]
uses: spring-io/spring-security-release-tools/.github/workflows/perform-release.yml@v1
with:
should-perform-release: true
project-version: ${{ needs.project-version.outputs.version }}
milestone-repo-url: https://repo1.maven.org/maven2
release-repo-url: https://repo1.maven.org/maven2
artifact-path: org/springframework/security/spring-security-core
slack-announcing-id: spring-security-announcing
secrets: inherit
send-notification:
name: Send Notification
needs: [ perform-release ]
if: ${{ !success() }}
runs-on: ubuntu-latest
steps:
- name: Send Notification
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@v1
with:
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
+1
View File
@@ -25,6 +25,7 @@ atlassian-ide-plugin.xml
s101plugin.state
.attach_pid*
.~lock.*#
.kotlin/
!.idea/checkstyle-idea.xml
!.idea/externalDependencies.xml
@@ -71,7 +71,7 @@ import org.springframework.util.StringUtils;
* <tt>AclEntryVoter</tt>:
* <ul>
* <li>Process domain object class <code>BankAccount</code>, configuration attribute
* <code>VOTE_ACL_BANK_ACCONT_READ</code>, require permission
* <code>VOTE_ACL_BANK_ACCOUNT_READ</code>, require permission
* <code>BasePermission.READ</code></li>
* <li>Process domain object class <code>BankAccount</code>, configuration attribute
* <code>VOTE_ACL_BANK_ACCOUNT_WRITE</code>, require permission list
@@ -65,10 +65,11 @@ import org.springframework.util.Assert;
* NB: This implementation does attempt to provide reasonably optimised lookups - within
* the constraints of a normalised database and standard ANSI SQL features. If you are
* willing to sacrifice either of these constraints (e.g. use a particular database
* feature such as hierarchical queries or materalized views, or reduce normalisation) you
* are likely to achieve better performance. In such situations you will need to provide
* your own custom <code>LookupStrategy</code>. This class does not support subclassing,
* as it is likely to change in future releases and therefore subclassing is unsupported.
* feature such as hierarchical queries or materialized views, or reduce normalisation)
* you are likely to achieve better performance. In such situations you will need to
* provide your own custom <code>LookupStrategy</code>. This class does not support
* subclassing, as it is likely to change in future releases and therefore subclassing is
* unsupported.
* <p>
* There are two SQL queries executed, one in the <tt>lookupPrimaryKeys</tt> method and
* one in <tt>lookupObjectIdentities</tt>. These are built from the same select and "order
+1
View File
@@ -15,6 +15,7 @@ dependencies {
api 'org.springframework:spring-web'
optional 'com.fasterxml.jackson.core:jackson-databind'
optional 'tools.jackson.core:jackson-databind'
provided 'jakarta.servlet:jakarta.servlet-api'
@@ -0,0 +1,60 @@
/*
* 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.cas.jackson;
import java.util.Date;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.apereo.cas.client.authentication.AttributePrincipal;
/**
* Helps in jackson deserialization of class
* {@link org.apereo.cas.client.validation.AssertionImpl}, which is used with
* {@link org.springframework.security.cas.authentication.CasAuthenticationToken}.
*
* @author Sebastien Deleuze
* @author Jitendra Singh
* @since 7.0
* @see CasJacksonModule
* @see org.springframework.security.jackson.SecurityJacksonModules
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
class AssertionImplMixin {
/**
* Mixin Constructor helps in deserialize
* {@link org.apereo.cas.client.validation.AssertionImpl}
* @param principal the Principal to associate with the Assertion.
* @param validFromDate when the assertion is valid from.
* @param validUntilDate when the assertion is valid to.
* @param authenticationDate when the assertion is authenticated.
* @param attributes the key/value pairs for this attribute.
*/
@JsonCreator
AssertionImplMixin(@JsonProperty("principal") AttributePrincipal principal,
@JsonProperty("validFromDate") Date validFromDate, @JsonProperty("validUntilDate") Date validUntilDate,
@JsonProperty("authenticationDate") Date authenticationDate,
@JsonProperty("attributes") Map<String, Object> attributes) {
}
}
@@ -0,0 +1,59 @@
/*
* 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.cas.jackson;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.apereo.cas.client.proxy.ProxyRetriever;
/**
* Helps in deserialize
* {@link org.apereo.cas.client.authentication.AttributePrincipalImpl} which is used with
* {@link org.springframework.security.cas.authentication.CasAuthenticationToken}.
*
* @author Sebastien Deleuze
* @author Jitendra Singh
* @since 7.0
* @see CasJacksonModule
* @see org.springframework.security.jackson.SecurityJacksonModules
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
class AttributePrincipalImplMixin {
/**
* Mixin Constructor helps in deserialize
* {@link org.apereo.cas.client.authentication.AttributePrincipalImpl}
* @param name the unique identifier for the principal.
* @param attributes the key/value pairs for this principal.
* @param proxyGrantingTicket the ticket associated with this principal.
* @param proxyRetriever the ProxyRetriever implementation to call back to the CAS
* server.
*/
@JsonCreator
AttributePrincipalImplMixin(@JsonProperty("name") String name,
@JsonProperty("attributes") Map<String, Object> attributes,
@JsonProperty("proxyGrantingTicket") String proxyGrantingTicket,
@JsonProperty("proxyRetriever") ProxyRetriever proxyRetriever) {
}
}
@@ -0,0 +1,69 @@
/*
* 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.cas.jackson;
import java.util.Collection;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.apereo.cas.client.validation.Assertion;
import org.springframework.security.cas.authentication.CasAuthenticationProvider;
import org.springframework.security.cas.authentication.CasAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
/**
* Mixin class which helps in deserialize {@link CasAuthenticationToken} using jackson.
*
* @author Sebastien Deleuze
* @author Jitendra Singh
* @since 7.0
* @see CasJacksonModule
* @see org.springframework.security.jackson.SecurityJacksonModules
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, isGetterVisibility = JsonAutoDetect.Visibility.NONE,
getterVisibility = JsonAutoDetect.Visibility.NONE, creatorVisibility = JsonAutoDetect.Visibility.ANY)
class CasAuthenticationTokenMixin {
/**
* Mixin Constructor helps in deserialize {@link CasAuthenticationToken}
* @param keyHash hashCode of provided key to identify if this object made by a given
* {@link CasAuthenticationProvider}
* @param principal typically the UserDetails object (cannot be <code>null</code>)
* @param credentials the service/proxy ticket ID from CAS (cannot be
* <code>null</code>)
* @param authorities the authorities granted to the user (from the
* {@link org.springframework.security.core.userdetails.UserDetailsService}) (cannot
* be <code>null</code>)
* @param userDetails the user details (from the
* {@link org.springframework.security.core.userdetails.UserDetailsService}) (cannot
* be <code>null</code>)
* @param assertion the assertion returned from the CAS servers. It contains the
* principal and how to obtain a proxy ticket for the user.
*/
@JsonCreator
CasAuthenticationTokenMixin(@JsonProperty("keyHash") Integer keyHash, @JsonProperty("principal") Object principal,
@JsonProperty("credentials") Object credentials,
@JsonProperty("authorities") Collection<? extends GrantedAuthority> authorities,
@JsonProperty("userDetails") UserDetails userDetails, @JsonProperty("assertion") Assertion assertion) {
}
}
@@ -0,0 +1,71 @@
/*
* 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.cas.jackson;
import org.apereo.cas.client.authentication.AttributePrincipalImpl;
import org.apereo.cas.client.validation.AssertionImpl;
import tools.jackson.core.Version;
import tools.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
import org.springframework.security.cas.authentication.CasAuthenticationToken;
import org.springframework.security.jackson.SecurityJacksonModule;
import org.springframework.security.jackson.SecurityJacksonModules;
/**
* Jackson module for spring-security-cas. This module register
* {@link AssertionImplMixin}, {@link AttributePrincipalImplMixin} and
* {@link CasAuthenticationTokenMixin}. If no default typing enabled by default then it'll
* enable it because typing info is needed to properly serialize/deserialize objects. In
* order to use this module just add this module into your JsonMapper configuration.
*
* <p>
* The recommended way to configure it is to use {@link SecurityJacksonModules} in order
* to enable properly automatic inclusion of type information with related validation.
*
* <pre>
* ClassLoader loader = getClass().getClassLoader();
* JsonMapper mapper = JsonMapper.builder()
* .addModules(SecurityJacksonModules.getModules(loader))
* .build();
* </pre>
*
* @author Sebastien Deleuze
* @author Jitendra Singh
* @since 7.0
* @see SecurityJacksonModules
*/
public class CasJacksonModule extends SecurityJacksonModule {
public CasJacksonModule() {
super(CasJacksonModule.class.getName(), new Version(1, 0, 0, null, null, null));
}
@Override
public void configurePolymorphicTypeValidator(BasicPolymorphicTypeValidator.Builder builder) {
builder.allowIfSubType(AssertionImpl.class)
.allowIfSubType(AttributePrincipalImpl.class)
.allowIfSubType(CasAuthenticationToken.class);
}
@Override
public void setupModule(SetupContext context) {
context.setMixIn(AssertionImpl.class, AssertionImplMixin.class);
context.setMixIn(AttributePrincipalImpl.class, AttributePrincipalImplMixin.class);
context.setMixIn(CasAuthenticationToken.class, CasAuthenticationTokenMixin.class);
}
}
@@ -0,0 +1,20 @@
/*
* 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 3+ serialization support for CAS.
*/
package org.springframework.security.cas.jackson;
@@ -33,6 +33,7 @@ import org.apereo.cas.client.authentication.AttributePrincipal;
* this class we need to register with
* {@link com.fasterxml.jackson.databind.ObjectMapper}. Type information will be stored
* in @class property.
*
* <p>
* <pre>
* ObjectMapper mapper = new ObjectMapper();
@@ -43,7 +44,11 @@ import org.apereo.cas.client.authentication.AttributePrincipal;
* @since 4.2
* @see CasJackson2Module
* @see org.springframework.security.jackson2.SecurityJackson2Modules
* @deprecated as of 7.0 in favor of
* {@code org.springframework.security.cas.jackson.AssertionImplMixin} based on Jackson 3
*/
@SuppressWarnings("removal")
@Deprecated(forRemoval = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
@@ -30,6 +30,7 @@ import org.apereo.cas.client.proxy.ProxyRetriever;
* {@link org.apereo.cas.client.authentication.AttributePrincipalImpl} which is used with
* {@link org.springframework.security.cas.authentication.CasAuthenticationToken}. Type
* information will be stored in property named @class.
*
* <p>
* <pre>
* ObjectMapper mapper = new ObjectMapper();
@@ -40,7 +41,12 @@ import org.apereo.cas.client.proxy.ProxyRetriever;
* @since 4.2
* @see CasJackson2Module
* @see org.springframework.security.jackson2.SecurityJackson2Modules
* @deprecated as of 7.0 in favor of
* {@code org.springframework.security.cas.jackson.AttributePrincipalImplMixin} based on
* Jackson 3
*/
@SuppressWarnings("removal")
@Deprecated(forRemoval = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
@@ -40,7 +40,6 @@ import org.springframework.security.core.userdetails.UserDetails;
* </ol>
*
* <p>
*
* <pre>
* ObjectMapper mapper = new ObjectMapper();
* mapper.registerModule(new CasJackson2Module());
@@ -50,7 +49,12 @@ import org.springframework.security.core.userdetails.UserDetails;
* @since 4.2
* @see CasJackson2Module
* @see org.springframework.security.jackson2.SecurityJackson2Modules
* @deprecated as of 7.0 in favor of
* {@code org.springframework.security.cas.jackson.CasAuthenticationTokenMixin} based on
* Jackson 3
*/
@SuppressWarnings("removal")
@Deprecated(forRemoval = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, isGetterVisibility = JsonAutoDetect.Visibility.NONE,
getterVisibility = JsonAutoDetect.Visibility.NONE, creatorVisibility = JsonAutoDetect.Visibility.ANY)
@@ -37,11 +37,14 @@ import org.springframework.security.jackson2.SecurityJackson2Modules;
* </pre> <b>Note: use {@link SecurityJackson2Modules#getModules(ClassLoader)} to get list
* of all security modules on the classpath.</b>
*
* @author Jitendra Singh.
* @author Jitendra Singh
* @since 4.2
* @see org.springframework.security.jackson2.SecurityJackson2Modules
* @deprecated as of 7.0 in favor of
* {@link org.springframework.security.cas.jackson.CasJacksonModule} based on Jackson 3
*/
@SuppressWarnings("serial")
@Deprecated(forRemoval = true)
@SuppressWarnings({ "serial", "removal" })
public class CasJackson2Module extends SimpleModule {
public CasJackson2Module() {
@@ -15,7 +15,7 @@
*/
/**
* Jackson support for CAS.
* Jackson 2 support for CAS.
*/
@NullMarked
package org.springframework.security.cas.jackson2;
@@ -0,0 +1,151 @@
/*
* 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.cas.jackson;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import org.apereo.cas.client.authentication.AttributePrincipalImpl;
import org.apereo.cas.client.validation.Assertion;
import org.apereo.cas.client.validation.AssertionImpl;
import org.json.JSONException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.skyscreamer.jsonassert.JSONAssert;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.security.cas.authentication.CasAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.jackson.SecurityJacksonModules;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Jitendra Singh
* @since 4.2
*/
public class CasAuthenticationTokenMixinTests {
private static final String KEY = "casKey";
private static final String PASSWORD = "\"1234\"";
private static final Date START_DATE = new Date();
private static final Date END_DATE = new Date();
public static final String AUTHORITY_JSON = "{\"@class\": \"org.springframework.security.core.authority.SimpleGrantedAuthority\", \"authority\": \"ROLE_USER\"}";
public static final String AUTHORITIES_SET_JSON = "[\"java.util.Collections$UnmodifiableSet\", [" + AUTHORITY_JSON
+ "]]";
public static final String AUTHORITIES_ARRAYLIST_JSON = "[\"java.util.Collections$UnmodifiableRandomAccessList\", ["
+ AUTHORITY_JSON + "]]";
// @formatter:off
public static final String USER_JSON = "{"
+ "\"@class\": \"org.springframework.security.core.userdetails.User\", "
+ "\"username\": \"admin\","
+ " \"password\": " + PASSWORD + ", "
+ "\"accountNonExpired\": true, "
+ "\"accountNonLocked\": true, "
+ "\"credentialsNonExpired\": true, "
+ "\"enabled\": true, "
+ "\"authorities\": " + AUTHORITIES_SET_JSON
+ "}";
// @formatter:on
private static final String CAS_TOKEN_JSON = "{"
+ "\"@class\": \"org.springframework.security.cas.authentication.CasAuthenticationToken\", "
+ "\"keyHash\": " + KEY.hashCode() + "," + "\"principal\": " + USER_JSON + ", " + "\"credentials\": "
+ PASSWORD + ", " + "\"authorities\": " + AUTHORITIES_ARRAYLIST_JSON + "," + "\"userDetails\": " + USER_JSON
+ "," + "\"authenticated\": true, " + "\"details\": null," + "\"assertion\": {"
+ "\"@class\": \"org.apereo.cas.client.validation.AssertionImpl\", " + "\"principal\": {"
+ "\"@class\": \"org.apereo.cas.client.authentication.AttributePrincipalImpl\", "
+ "\"name\": \"assertName\", " + "\"attributes\": {\"@class\": \"java.util.Collections$EmptyMap\"}, "
+ "\"proxyGrantingTicket\": null, " + "\"proxyRetriever\": null" + "}, "
+ "\"validFromDate\": [\"java.util.Date\", " + START_DATE.getTime() + "], "
+ "\"validUntilDate\": [\"java.util.Date\", " + END_DATE.getTime() + "],"
+ "\"authenticationDate\": [\"java.util.Date\", " + START_DATE.getTime() + "], "
+ "\"attributes\": {\"@class\": \"java.util.Collections$EmptyMap\"},"
+ "\"context\": {\"@class\":\"java.util.HashMap\"}" + "}" + "}";
private static final String CAS_TOKEN_CLEARED_JSON = CAS_TOKEN_JSON.replaceFirst(PASSWORD, "null");
protected JsonMapper mapper;
@BeforeEach
public void setup() {
ClassLoader loader = getClass().getClassLoader();
this.mapper = JsonMapper.builder().addModules(SecurityJacksonModules.getModules(loader)).build();
}
@Test
public void serializeCasAuthenticationTest() throws JSONException {
CasAuthenticationToken token = createCasAuthenticationToken();
String actualJson = this.mapper.writeValueAsString(token);
JSONAssert.assertEquals(CAS_TOKEN_JSON, actualJson, true);
}
@Test
public void serializeCasAuthenticationTestAfterEraseCredentialInvoked() throws JSONException {
CasAuthenticationToken token = createCasAuthenticationToken();
token.eraseCredentials();
String actualJson = this.mapper.writeValueAsString(token);
JSONAssert.assertEquals(CAS_TOKEN_CLEARED_JSON, actualJson, true);
}
@Test
public void deserializeCasAuthenticationTestAfterEraseCredentialInvoked() {
CasAuthenticationToken token = this.mapper.readValue(CAS_TOKEN_CLEARED_JSON, CasAuthenticationToken.class);
assertThat(((UserDetails) token.getPrincipal()).getPassword()).isNull();
}
@Test
public void deserializeCasAuthenticationTest() throws IOException {
CasAuthenticationToken token = this.mapper.readValue(CAS_TOKEN_JSON, CasAuthenticationToken.class);
assertThat(token).isNotNull();
assertThat(token.getPrincipal()).isNotNull().isInstanceOf(User.class);
assertThat(((User) token.getPrincipal()).getUsername()).isEqualTo("admin");
assertThat(((User) token.getPrincipal()).getPassword()).isEqualTo("1234");
assertThat(token.getUserDetails()).isNotNull().isInstanceOf(User.class);
assertThat(token.getAssertion()).isNotNull().isInstanceOf(AssertionImpl.class);
assertThat(token.getKeyHash()).isEqualTo(KEY.hashCode());
assertThat(token.getUserDetails().getAuthorities()).extracting(GrantedAuthority::getAuthority)
.containsOnly("ROLE_USER");
assertThat(token.getAssertion().getAuthenticationDate()).isEqualTo(START_DATE);
assertThat(token.getAssertion().getValidFromDate()).isEqualTo(START_DATE);
assertThat(token.getAssertion().getValidUntilDate()).isEqualTo(END_DATE);
assertThat(token.getAssertion().getPrincipal().getName()).isEqualTo("assertName");
assertThat(token.getAssertion().getAttributes()).hasSize(0);
}
private CasAuthenticationToken createCasAuthenticationToken() {
User principal = new User("admin", "1234", Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")));
Collection<? extends GrantedAuthority> authorities = Collections
.singletonList(new SimpleGrantedAuthority("ROLE_USER"));
Assertion assertion = new AssertionImpl(new AttributePrincipalImpl("assertName"), START_DATE, END_DATE,
START_DATE, Collections.<String, Object>emptyMap());
return new CasAuthenticationToken(KEY, principal, principal.getPassword(), authorities,
new User("admin", "1234", authorities), assertion);
}
}
@@ -44,6 +44,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Jitendra Singh
* @since 4.2
*/
@SuppressWarnings("removal")
public class CasAuthenticationTokenMixinTests {
private static final String KEY = "casKey";
@@ -27,14 +27,14 @@ import org.springframework.security.authorization.AuthorizationManagerFactories;
import org.springframework.security.authorization.DefaultAuthorizationManagerFactory;
/**
* Uses {@link EnableGlobalMultiFactorAuthentication} to configure a
* Uses {@link EnableMultiFactorAuthentication} to configure a
* {@link DefaultAuthorizationManagerFactory}.
*
* @author Rob Winch
* @since 7.0
* @see EnableGlobalMultiFactorAuthentication
* @see EnableMultiFactorAuthentication
*/
class GlobalMultiFactorAuthenticationConfiguration implements ImportAware {
class AuthorizationManagerFactoryConfiguration implements ImportAware {
private String[] authorities;
@@ -49,9 +49,9 @@ class GlobalMultiFactorAuthenticationConfiguration implements ImportAware {
@Override
public void setImportMetadata(AnnotationMetadata importMetadata) {
Map<String, Object> multiFactorAuthenticationAttrs = importMetadata
.getAnnotationAttributes(EnableGlobalMultiFactorAuthentication.class.getName());
.getAnnotationAttributes(EnableMultiFactorAuthentication.class.getName());
this.authorities = (String[]) multiFactorAuthenticationAttrs.get("authorities");
this.authorities = (String[]) multiFactorAuthenticationAttrs.getOrDefault("authorities", new String[0]);
}
}
@@ -0,0 +1,65 @@
/*
* Copyright 2002-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.authorization;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.AuthenticationFilter;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
@Configuration(proxyBeanMethods = false)
class EnableMfaFiltersConfiguration {
@Bean
BeanPostProcessor mfaBeanPostProcessor() {
return new EnableMfaFiltersPostProcessor();
}
/**
* A {@link BeanPostProcessor} that enables MFA on authentication filters.
*
* @author Rob Winch
* @since 7.0
*/
private static class EnableMfaFiltersPostProcessor implements BeanPostProcessor {
@Override
public @Nullable Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof AbstractAuthenticationProcessingFilter filter) {
filter.setMfaEnabled(true);
}
if (bean instanceof AuthenticationFilter filter) {
filter.setMfaEnabled(true);
}
if (bean instanceof AbstractPreAuthenticatedProcessingFilter filter) {
filter.setMfaEnabled(true);
}
if (bean instanceof BasicAuthenticationFilter filter) {
filter.setMfaEnabled(true);
}
return bean;
}
}
}
@@ -26,9 +26,12 @@ import org.springframework.context.annotation.Import;
import org.springframework.security.authorization.DefaultAuthorizationManagerFactory;
/**
* Exposes a {@link DefaultAuthorizationManagerFactory} as a Bean with the
* {@link #authorities()} specified as additional required authorities. The configuration
* will be picked up by both
* Enables Multi-Factor Authentication (MFA) support within Spring Security.
*
* When {@link #authorities()} is specified creates a
* {@link DefaultAuthorizationManagerFactory} as a Bean with the {@link #authorities()}
* specified as additional required authorities. The configuration will be picked up by
* both
* {@link org.springframework.security.config.annotation.web.configuration.EnableWebSecurity}
* and
* {@link org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity}.
@@ -36,7 +39,7 @@ import org.springframework.security.authorization.DefaultAuthorizationManagerFac
* <pre>
* &#64;Configuration
* &#64;EnableGlobalMultiFactorAuthentication(authorities = { GrantedAuthorities.FACTOR_OTT, GrantedAuthorities.FACTOR_PASSWORD })
* &#64;EnableMultiFactorAuthentication(authorities = { GrantedAuthorities.FACTOR_OTT, GrantedAuthorities.FACTOR_PASSWORD })
* public class MyConfiguration {
* // ...
* }
@@ -51,14 +54,16 @@ import org.springframework.security.authorization.DefaultAuthorizationManagerFac
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(GlobalMultiFactorAuthenticationConfiguration.class)
public @interface EnableGlobalMultiFactorAuthentication {
@Import(MultiFactorAuthenticationSelector.class)
public @interface EnableMultiFactorAuthentication {
/**
* The additional authorities that are required.
* @return the additional authorities that are required (e.g. {
* GrantedAuthorities.FACTOR_OTT, GrantedAuthorities.FACTOR_PASSWORD })
* @see org.springframework.security.core.GrantedAuthorities
* FactorGrantedAuthority.FACTOR_OTT, FactorGrantedAuthority.FACTOR_PASSWORD }). Can
* be null or an empty array if no additional authorities are required (if
* authorization rules are not globally requiring MFA).
* @see org.springframework.security.core.authority.FactorGrantedAuthority
*/
String[] authorities();
@@ -0,0 +1,50 @@
/*
* 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.authorization;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.security.authorization.DefaultAuthorizationManagerFactory;
/**
* Uses {@link EnableMultiFactorAuthentication} to configure a
* {@link DefaultAuthorizationManagerFactory}.
*
* @author Rob Winch
* @since 7.0
* @see EnableMultiFactorAuthentication
*/
class MultiFactorAuthenticationSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata metadata) {
Map<String, Object> multiFactorAuthenticationAttrs = metadata
.getAnnotationAttributes(EnableMultiFactorAuthentication.class.getName());
String[] authorities = (String[]) multiFactorAuthenticationAttrs.getOrDefault("authorities", new String[0]);
List<String> imports = new ArrayList<>(2);
if (authorities.length > 0) {
imports.add(AuthorizationManagerFactoryConfiguration.class.getName());
}
imports.add(EnableMfaFiltersConfiguration.class.getName());
return imports.toArray(new String[imports.size()]);
}
}
@@ -482,6 +482,27 @@ public final class OAuth2AuthorizationServerConfigurer
});
}
OAuth2PushedAuthorizationRequestEndpointConfigurer pushedAuthorizationRequestEndpointConfigurer = getConfigurer(
OAuth2PushedAuthorizationRequestEndpointConfigurer.class);
if (pushedAuthorizationRequestEndpointConfigurer != null) {
OAuth2AuthorizationServerMetadataEndpointConfigurer authorizationServerMetadataEndpointConfigurer = getConfigurer(
OAuth2AuthorizationServerMetadataEndpointConfigurer.class);
authorizationServerMetadataEndpointConfigurer.addDefaultAuthorizationServerMetadataCustomizer((builder) -> {
AuthorizationServerContext authorizationServerContext = AuthorizationServerContextHolder.getContext();
String issuer = authorizationServerContext.getIssuer();
AuthorizationServerSettings authorizationServerSettings = authorizationServerContext
.getAuthorizationServerSettings();
String pushedAuthorizationRequestEndpoint = UriComponentsBuilder.fromUriString(issuer)
.path(authorizationServerSettings.getPushedAuthorizationRequestEndpoint())
.build()
.toUriString();
builder.pushedAuthorizationRequestEndpoint(pushedAuthorizationRequestEndpoint);
});
}
this.configurers.values().forEach((configurer) -> configurer.configure(httpSecurity));
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils
@@ -171,6 +171,28 @@ public final class OidcConfigurer extends AbstractOAuth2Configurer {
});
}
OAuth2PushedAuthorizationRequestEndpointConfigurer pushedAuthorizationRequestEndpointConfigurer = httpSecurity
.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
.getConfigurer(OAuth2PushedAuthorizationRequestEndpointConfigurer.class);
if (pushedAuthorizationRequestEndpointConfigurer != null) {
OidcProviderConfigurationEndpointConfigurer providerConfigurationEndpointConfigurer = getConfigurer(
OidcProviderConfigurationEndpointConfigurer.class);
providerConfigurationEndpointConfigurer.addDefaultProviderConfigurationCustomizer((builder) -> {
AuthorizationServerContext authorizationServerContext = AuthorizationServerContextHolder.getContext();
String issuer = authorizationServerContext.getIssuer();
AuthorizationServerSettings authorizationServerSettings = authorizationServerContext
.getAuthorizationServerSettings();
String pushedAuthorizationRequestEndpoint = UriComponentsBuilder.fromUriString(issuer)
.path(authorizationServerSettings.getPushedAuthorizationRequestEndpoint())
.build()
.toUriString();
builder.pushedAuthorizationRequestEndpoint(pushedAuthorizationRequestEndpoint);
});
}
this.configurers.values().forEach((configurer) -> configurer.configure(httpSecurity));
}
@@ -0,0 +1,99 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.web.server;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.List;
import org.jspecify.annotations.Nullable;
import org.springframework.core.ResolvableType;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.GenericHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.http.converter.SmartHttpMessageConverter;
/**
* {@link GenericHttpMessageConverter} implementation that delegates to a
* {@link SmartHttpMessageConverter}.
*
* @param <T> the converted object type
* @author Sebastien Deleuze
* @since 7.0
*/
final class GenericHttpMessageConverterAdapter<T> implements GenericHttpMessageConverter<T> {
private final SmartHttpMessageConverter<T> smartConverter;
GenericHttpMessageConverterAdapter(SmartHttpMessageConverter<T> smartConverter) {
this.smartConverter = smartConverter;
}
@Override
public boolean canRead(Type type, @Nullable Class<?> contextClass, @Nullable MediaType mediaType) {
return this.smartConverter.canRead(ResolvableType.forType(type), mediaType);
}
@Override
public T read(Type type, @Nullable Class<?> contextClass, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
return this.smartConverter.read(ResolvableType.forType(type), inputMessage, null);
}
@Override
public boolean canWrite(@Nullable Type type, Class<?> clazz, @Nullable MediaType mediaType) {
return this.smartConverter.canWrite(ResolvableType.forType(type), clazz, mediaType);
}
@Override
public void write(T t, @Nullable Type type, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
this.smartConverter.write(t, ResolvableType.forType(type), contentType, outputMessage, null);
}
@Override
public boolean canRead(Class<?> clazz, @Nullable MediaType mediaType) {
return this.smartConverter.canRead(ResolvableType.forClass(clazz), mediaType);
}
@Override
public boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType) {
return this.smartConverter.canWrite(clazz, mediaType);
}
@Override
public List<MediaType> getSupportedMediaTypes() {
return this.smartConverter.getSupportedMediaTypes();
}
@Override
public T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
return this.smartConverter.read(clazz, inputMessage);
}
@Override
public void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
this.smartConverter.write(t, contentType, outputMessage);
}
}
@@ -19,6 +19,7 @@ package org.springframework.security.config.web.server;
import org.springframework.http.converter.GenericHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.GsonHttpMessageConverter;
import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
import org.springframework.http.converter.json.JsonbHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.util.ClassUtils;
@@ -32,6 +33,8 @@ import org.springframework.util.ClassUtils;
*/
final class HttpMessageConverters {
private static final boolean jacksonPresent;
private static final boolean jackson2Present;
private static final boolean gsonPresent;
@@ -40,6 +43,7 @@ final class HttpMessageConverters {
static {
ClassLoader classLoader = HttpMessageConverters.class.getClassLoader();
jacksonPresent = ClassUtils.isPresent("tools.jackson.databind.json.JsonMapper", classLoader);
jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader)
&& ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
@@ -49,7 +53,11 @@ final class HttpMessageConverters {
private HttpMessageConverters() {
}
@SuppressWarnings("removal")
static GenericHttpMessageConverter<Object> getJsonMessageConverter() {
if (jacksonPresent) {
return new GenericHttpMessageConverterAdapter<>(new JacksonJsonHttpMessageConverter());
}
if (jackson2Present) {
return new MappingJackson2HttpMessageConverter();
}
@@ -35,7 +35,7 @@ import jakarta.servlet.http.HttpSession
* @since 5.3
* @property clearAuthentication whether the [SecurityContextLogoutHandler] should clear
* the [Authentication] at the time of logout.
* @property clearAuthentication whether to invalidate the [HttpSession] at the time of logout.
* @property invalidateHttpSession whether to invalidate the [HttpSession] at the time of logout.
* @property logoutUrl the URL that triggers log out to occur.
* @property logoutRequestMatcher the [RequestMatcher] that triggers log out to occur.
* @property logoutSuccessUrl the URL to redirect to after logout has occurred.
@@ -0,0 +1,158 @@
/*
* 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.authorization;
import jakarta.servlet.Filter;
import jakarta.servlet.http.HttpServletRequest;
import org.jspecify.annotations.Nullable;
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.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockServletContext;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.FactorGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.AuthenticationFilter;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import org.springframework.security.web.authentication.www.BasicAuthenticationConverter;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link EnableMultiFactorAuthentication}.
*
* @author Rob Winch
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@WithMockUser(authorities = FactorGrantedAuthority.PASSWORD_AUTHORITY)
public class EnableMultiFactorAuthenticationFiltersSetTests {
@Autowired
private AuthenticationManager manager;
private TestingAuthenticationToken newAuthn = new TestingAuthenticationToken("user", "password", "ROLE_USER",
FactorGrantedAuthority.OTT_AUTHORITY);
@Test
void preAuthenticationFilter(@Autowired AbstractAuthenticationProcessingFilter filter) throws Exception {
assertMfaEnabled(filter);
}
@Test
void authenticationFilter(@Autowired AuthenticationFilter filter) throws Exception {
assertMfaEnabled(filter);
}
@Test
void preAuthnFilter(@Autowired AbstractPreAuthenticatedProcessingFilter filter) throws Exception {
assertMfaEnabled(filter);
}
@Test
void basicAuthnFilter(@Autowired BasicAuthenticationFilter filter) throws Exception {
assertMfaEnabled(filter);
}
private void assertMfaEnabled(Filter filter) throws Exception {
given(this.manager.authenticate(any())).willReturn(this.newAuthn);
MockHttpServletRequest request = MockMvcRequestBuilders.get("/")
.headers((headers) -> headers.setBasicAuth("u", "p"))
.buildRequest(new MockServletContext());
MockHttpServletResponse response = new MockHttpServletResponse();
MockFilterChain chain = new MockFilterChain();
filter.doFilter(request, response, chain);
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
assertThat(authentication).isNotNull();
assertThat(authentication.getAuthorities()).extracting(GrantedAuthority::getAuthority)
.containsExactlyInAnyOrder(FactorGrantedAuthority.OTT_AUTHORITY, FactorGrantedAuthority.PASSWORD_AUTHORITY,
"ROLE_USER");
}
@EnableWebSecurity
@Configuration
@EnableMultiFactorAuthentication(
authorities = { FactorGrantedAuthority.OTT_AUTHORITY, FactorGrantedAuthority.PASSWORD_AUTHORITY })
static class Config {
@Bean
AuthenticationManager authenticationManager() {
return mock(AuthenticationManager.class);
}
@Bean
static AbstractAuthenticationProcessingFilter authnProcessingFilter(
AuthenticationManager authenticationManager) {
AbstractAuthenticationProcessingFilter result = new AbstractAuthenticationProcessingFilter(
AnyRequestMatcher.INSTANCE, authenticationManager) {
};
result.setAuthenticationConverter(new BasicAuthenticationConverter());
return result;
}
@Bean
static AuthenticationFilter authenticationFilter(AuthenticationManager authenticationManager) {
return new AuthenticationFilter(authenticationManager, new BasicAuthenticationConverter());
}
@Bean
static AbstractPreAuthenticatedProcessingFilter preAuthenticatedProcessingFilter(
AuthenticationManager authenticationManager) {
AbstractPreAuthenticatedProcessingFilter result = new AbstractPreAuthenticatedProcessingFilter() {
@Override
protected @Nullable Object getPreAuthenticatedCredentials(HttpServletRequest request) {
return "password";
}
@Override
protected @Nullable Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
return "user";
}
};
result.setRequiresAuthenticationRequestMatcher(AnyRequestMatcher.INSTANCE);
result.setAuthenticationManager(authenticationManager);
return result;
}
@Bean
static BasicAuthenticationFilter basicAuthenticationFilter(AuthenticationManager authenticationManager) {
return new BasicAuthenticationFilter(authenticationManager);
}
}
}
@@ -16,6 +16,13 @@
package org.springframework.security.config.annotation.authorization;
import java.io.IOException;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -25,10 +32,18 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.authority.FactorGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.security.web.context.SecurityContextHolderFilter;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
@@ -37,18 +52,22 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests for {@link EnableGlobalMultiFactorAuthentication}.
* Tests for {@link EnableMultiFactorAuthentication}.
*
* @author Rob Winch
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
public class EnableGlobalMultiFactorAuthenticationTests {
public class EnableMultiFactorAuthenticationTests {
private static final String ATTR_NAME = "org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors$SecurityContextRequestPostProcessorSupport$TestSecurityContextRepository.REPO";
@Autowired
MockMvc mvc;
@@ -56,6 +75,14 @@ public class EnableGlobalMultiFactorAuthenticationTests {
@Autowired
Service service;
@Test
@WithMockUser(authorities = { "ROLE_USER", FactorGrantedAuthority.OTT_AUTHORITY })
public void formLoginWhenAuthenticatedThenMergedAuthorities() throws Exception {
this.mvc.perform(formLogin())
.andExpect(authenticated().withAuthorities("ROLE_USER", FactorGrantedAuthority.OTT_AUTHORITY,
FactorGrantedAuthority.PASSWORD_AUTHORITY));
}
@Test
@WithMockUser(authorities = { FactorGrantedAuthority.PASSWORD_AUTHORITY, FactorGrantedAuthority.OTT_AUTHORITY })
void webWhenAuthorized() throws Exception {
@@ -84,7 +111,7 @@ public class EnableGlobalMultiFactorAuthenticationTests {
@EnableWebSecurity
@EnableMethodSecurity
@Configuration
@EnableGlobalMultiFactorAuthentication(
@EnableMultiFactorAuthentication(
authorities = { FactorGrantedAuthority.OTT_AUTHORITY, FactorGrantedAuthority.PASSWORD_AUTHORITY })
static class Config {
@@ -98,6 +125,33 @@ public class EnableGlobalMultiFactorAuthenticationTests {
return MockMvcBuilders.webAppContextSetup(context).apply(springSecurity()).build();
}
@Bean
static Customizer<HttpSecurity> captureAuthn() {
return (http) -> http.addFilterAfter(new Filter() {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
try {
filterChain.doFilter(servletRequest, servletResponse);
}
finally {
servletRequest.setAttribute(ATTR_NAME, SecurityContextHolder.getContext());
}
}
}, SecurityContextHolderFilter.class);
}
@Bean
@SuppressWarnings("deprecation")
UserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
@RestController
static class OkController {
@@ -87,7 +87,7 @@ import static org.springframework.security.web.servlet.util.matcher.PathPatternR
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrlPattern;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
@@ -175,7 +175,7 @@ public class NamespaceHttpTests {
// @formatter:off
this.mockMvc.perform(get("/"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrlPattern("**/entry-point"));
.andExpect(redirectedUrl("/entry-point"));
// @formatter:on
}
@@ -78,7 +78,7 @@ public class DefaultLoginPageConfigurerTests {
@Test
public void getWhenFormLoginEnabledThenRedirectsToLoginPage() throws Exception {
this.spring.register(DefaultLoginPageConfig.class).autowire();
this.mvc.perform(get("/")).andExpect(redirectedUrl("http://localhost/login"));
this.mvc.perform(get("/")).andExpect(redirectedUrl("/login"));
}
@Test
@@ -214,8 +214,7 @@ public class ExceptionHandlingConfigurerTests {
@Test
public void getWhenUsingDefaultsAndUnauthenticatedThenRedirectsToLogin() throws Exception {
this.spring.register(DefaultHttpConfig.class).autowire();
this.mvc.perform(get("/").header(HttpHeaders.ACCEPT, "bogus/type"))
.andExpect(redirectedUrl("http://localhost/login"));
this.mvc.perform(get("/").header(HttpHeaders.ACCEPT, "bogus/type")).andExpect(redirectedUrl("/login"));
}
@Test
@@ -181,7 +181,7 @@ public class FormLoginConfigurerTests {
// @formatter:off
this.mockMvc.perform(get("/private"))
.andExpect(status().isFound())
.andExpect(redirectedUrl("http://localhost/login"));
.andExpect(redirectedUrl("/login"));
// @formatter:on
}
@@ -236,7 +236,7 @@ public class FormLoginConfigurerTests {
// @formatter:off
this.mockMvc.perform(get("/private"))
.andExpect(status().isFound())
.andExpect(redirectedUrl("http://localhost/login"));
.andExpect(redirectedUrl("/login"));
// @formatter:on
}
@@ -350,7 +350,7 @@ public class FormLoginConfigurerTests {
// @formatter:off
this.mockMvc.perform(get("/login?error"))
.andExpect(status().isFound())
.andExpect(redirectedUrl("http://localhost/login"));
.andExpect(redirectedUrl("/login"));
// @formatter:on
}
@@ -403,7 +403,8 @@ public class FormLoginConfigurerTests {
UserDetails user = PasswordEncodedUser.user();
this.mockMvc.perform(get("/profile").with(user(user)))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("http://localhost/login?factor.type=password&factor.reason=missing"));
.andExpect(redirectedUrl(
"/login?factor.type=password&factor.type=ott&factor.reason=missing&factor.reason=missing"));
this.mockMvc
.perform(post("/ott/generate").param("username", "rod")
.with(user(user))
@@ -421,13 +422,13 @@ public class FormLoginConfigurerTests {
.build();
this.mockMvc.perform(get("/profile").with(user(user)))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("http://localhost/login?factor.type=password&factor.reason=missing"));
.andExpect(redirectedUrl("/login?factor.type=password&factor.reason=missing"));
user = PasswordEncodedUser.withUserDetails(user)
.authorities("profile:read", FactorGrantedAuthority.PASSWORD_AUTHORITY)
.build();
this.mockMvc.perform(get("/profile").with(user(user)))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("http://localhost/login?factor.type=ott&factor.reason=missing"));
.andExpect(redirectedUrl("/login?factor.type=ott&factor.reason=missing"));
user = PasswordEncodedUser.withUserDetails(user)
.authorities("profile:read", FactorGrantedAuthority.PASSWORD_AUTHORITY,
FactorGrantedAuthority.OTT_AUTHORITY)
@@ -444,7 +445,7 @@ public class FormLoginConfigurerTests {
this.mockMvc.perform(get("/login")).andExpect(status().isOk());
this.mockMvc.perform(get("/profile").with(SecurityMockMvcRequestPostProcessors.x509("rod.cer")))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("http://localhost/login?factor.type=password&factor.reason=missing"));
.andExpect(redirectedUrl("/login?factor.type=password&factor.reason=missing"));
this.mockMvc
.perform(post("/login").param("username", "rod")
.param("password", "password")
@@ -66,7 +66,7 @@ public class NamespaceHttpFormLoginTests {
@Test
public void formLoginWhenDefaultConfigurationThenMatchesNamespace() throws Exception {
this.spring.register(FormLoginConfig.class, UserDetailsServiceConfig.class).autowire();
this.mvc.perform(get("/")).andExpect(redirectedUrl("http://localhost/login"));
this.mvc.perform(get("/")).andExpect(redirectedUrl("/login"));
this.mvc.perform(post("/login").with(csrf())).andExpect(redirectedUrl("/login?error"));
// @formatter:off
MockHttpServletRequestBuilder loginRequest = post("/login")
@@ -80,7 +80,7 @@ public class NamespaceHttpFormLoginTests {
@Test
public void formLoginWithCustomEndpointsThenBehaviorMatchesNamespace() throws Exception {
this.spring.register(FormLoginCustomConfig.class, UserDetailsServiceConfig.class).autowire();
this.mvc.perform(get("/")).andExpect(redirectedUrl("http://localhost/authentication/login"));
this.mvc.perform(get("/")).andExpect(redirectedUrl("/authentication/login"));
this.mvc.perform(post("/authentication/login/process").with(csrf()))
.andExpect(redirectedUrl("/authentication/login?failed"));
// @formatter:off
@@ -95,7 +95,7 @@ public class NamespaceHttpFormLoginTests {
@Test
public void formLoginWithCustomHandlersThenBehaviorMatchesNamespace() throws Exception {
this.spring.register(FormLoginCustomRefsConfig.class, UserDetailsServiceConfig.class).autowire();
this.mvc.perform(get("/")).andExpect(redirectedUrl("http://localhost/login"));
this.mvc.perform(get("/")).andExpect(redirectedUrl("/login"));
this.mvc.perform(post("/login").with(csrf())).andExpect(redirectedUrl("/custom/failure"));
verifyBean(WebAuthenticationDetailsSource.class).buildDetails(any(HttpServletRequest.class));
// @formatter:off
@@ -104,7 +104,7 @@ public class NamespaceRememberMeTests {
.with(csrf())
.cookie(rememberMe);
this.mvc.perform(authenticationClassRequest)
.andExpect(redirectedUrl("http://localhost/login"))
.andExpect(redirectedUrl("/login"))
.andReturn();
// @formatter:on
}
@@ -150,7 +150,7 @@ public class NamespaceRememberMeTests {
// @formatter:off
this.mvc.perform(somewhereRequest)
.andExpect(status().isFound())
.andExpect(redirectedUrl("http://localhost/login"));
.andExpect(redirectedUrl("/login"));
MockHttpServletRequestBuilder loginWithRememberme = post("/login").with(rememberMeLogin());
Cookie withKey = this.mvc.perform(loginWithRememberme)
.andReturn()
@@ -240,7 +240,7 @@ public class RememberMeConfigurerTests {
.with(csrf())
.cookie(expiredRememberMeCookie);
// @formatter:on
this.mvc.perform(expiredRequest).andExpect(redirectedUrl("http://localhost/login"));
this.mvc.perform(expiredRequest).andExpect(redirectedUrl("/login"));
}
@Test
@@ -90,7 +90,7 @@ public class RequestCacheConfigurerTests {
this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire();
// @formatter:off
MockHttpSession session = (MockHttpSession) this.mvc.perform(get("/favicon.ico"))
.andExpect(redirectedUrl("http://localhost/login"))
.andExpect(redirectedUrl("/login"))
.andReturn()
.getRequest()
.getSession();
@@ -104,7 +104,7 @@ public class RequestCacheConfigurerTests {
this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire();
// @formatter:off
MockHttpSession session = (MockHttpSession) this.mvc.perform(get("/favicon.png"))
.andExpect(redirectedUrl("http://localhost/login"))
.andExpect(redirectedUrl("/login"))
.andReturn()
.getRequest()
.getSession();
@@ -120,7 +120,7 @@ public class RequestCacheConfigurerTests {
MockHttpServletRequestBuilder request = get("/messages").header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON);
// @formatter:off
MockHttpSession session = (MockHttpSession) this.mvc.perform(request)
.andExpect(redirectedUrl("http://localhost/login"))
.andExpect(redirectedUrl("/login"))
.andReturn()
.getRequest()
.getSession();
@@ -140,7 +140,7 @@ public class RequestCacheConfigurerTests {
.header("X-Requested-With", "XMLHttpRequest");
MockHttpSession session = (MockHttpSession) this.mvc
.perform(xRequestedWith)
.andExpect(redirectedUrl("http://localhost/login"))
.andExpect(redirectedUrl("/login"))
.andReturn()
.getRequest()
.getSession();
@@ -157,7 +157,7 @@ public class RequestCacheConfigurerTests {
MediaType.TEXT_EVENT_STREAM);
// @formatter:off
MockHttpSession session = (MockHttpSession) this.mvc.perform(request)
.andExpect(redirectedUrl("http://localhost/login"))
.andExpect(redirectedUrl("/login"))
.andReturn()
.getRequest()
.getSession();
@@ -174,7 +174,7 @@ public class RequestCacheConfigurerTests {
MockHttpServletRequestBuilder request = get("/messages").header("Upgrade", "websocket");
// @formatter:off
MockHttpSession session = (MockHttpSession) this.mvc.perform(request)
.andExpect(redirectedUrl("http://localhost/login"))
.andExpect(redirectedUrl("/login"))
.andReturn()
.getRequest()
.getSession();
@@ -191,7 +191,7 @@ public class RequestCacheConfigurerTests {
MockHttpServletRequestBuilder request = get("/messages").header(HttpHeaders.ACCEPT, MediaType.ALL);
// @formatter:off
MockHttpSession session = (MockHttpSession) this.mvc.perform(request)
.andExpect(redirectedUrl("http://localhost/login"))
.andExpect(redirectedUrl("/login"))
.andReturn()
.getRequest()
.getSession();
@@ -205,7 +205,7 @@ public class RequestCacheConfigurerTests {
MockHttpServletRequestBuilder request = get("/messages").header(HttpHeaders.ACCEPT, MediaType.TEXT_HTML);
// @formatter:off
MockHttpSession session = (MockHttpSession) this.mvc.perform(request)
.andExpect(redirectedUrl("http://localhost/login"))
.andExpect(redirectedUrl("/login"))
.andReturn()
.getRequest()
.getSession();
@@ -220,7 +220,7 @@ public class RequestCacheConfigurerTests {
MockHttpServletRequestBuilder request = get("/messages")
.header(HttpHeaders.ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
MockHttpSession session = (MockHttpSession) this.mvc.perform(request)
.andExpect(redirectedUrl("http://localhost/login"))
.andExpect(redirectedUrl("/login"))
.andReturn()
.getRequest()
.getSession();
@@ -235,7 +235,7 @@ public class RequestCacheConfigurerTests {
MockHttpServletRequestBuilder request = get("/messages")
.header("X-Requested-With", "com.android");
MockHttpSession session = (MockHttpSession) this.mvc.perform(request)
.andExpect(redirectedUrl("http://localhost/login"))
.andExpect(redirectedUrl("/login"))
.andReturn()
.getRequest()
.getSession();
@@ -315,7 +315,7 @@ public class RequestCacheConfigurerTests {
.autowire();
// @formatter:off
MockHttpSession session = (MockHttpSession) this.mvc.perform(get("/favicon.ico"))
.andExpect(redirectedUrl("http://localhost/login"))
.andExpect(redirectedUrl("/login"))
.andReturn()
.getRequest()
.getSession();
@@ -447,7 +447,7 @@ public class OAuth2LoginConfigurerTests {
String requestUri = "/";
this.request = get(requestUri).build();
this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain);
assertThat(this.response.getRedirectedUrl()).matches("http://localhost/oauth2/authorization/google");
assertThat(this.response.getRedirectedUrl()).matches("/oauth2/authorization/google");
}
// gh-6802
@@ -457,7 +457,7 @@ public class OAuth2LoginConfigurerTests {
String requestUri = "/";
this.request = get(requestUri).build();
this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain);
assertThat(this.response.getRedirectedUrl()).matches("http://localhost/login");
assertThat(this.response.getRedirectedUrl()).matches("/login");
}
// gh-5347
@@ -469,7 +469,7 @@ public class OAuth2LoginConfigurerTests {
this.request = get(requestUri).build();
this.request.addHeader(HttpHeaders.ACCEPT, new MediaType("image", "*").toString());
this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain);
assertThat(this.response.getRedirectedUrl()).matches("http://localhost/login");
assertThat(this.response.getRedirectedUrl()).matches("/login");
}
// gh-5347
@@ -479,7 +479,7 @@ public class OAuth2LoginConfigurerTests {
String requestUri = "/";
this.request = get(requestUri).build();
this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain);
assertThat(this.response.getRedirectedUrl()).matches("http://localhost/login");
assertThat(this.response.getRedirectedUrl()).matches("/login");
}
// gh-6812
@@ -524,7 +524,7 @@ public class OAuth2LoginConfigurerTests {
String requestUri = "/";
this.request = get(requestUri).build();
this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain);
assertThat(this.response.getRedirectedUrl()).matches("http://localhost/oauth2/authorization/google");
assertThat(this.response.getRedirectedUrl()).matches("/oauth2/authorization/google");
}
@Test
@@ -533,7 +533,7 @@ public class OAuth2LoginConfigurerTests {
String requestUri = "/";
this.request = get(requestUri).build();
this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain);
assertThat(this.response.getRedirectedUrl()).matches("http://localhost/custom-login");
assertThat(this.response.getRedirectedUrl()).matches("/custom-login");
}
@Test
@@ -542,7 +542,7 @@ public class OAuth2LoginConfigurerTests {
String requestUri = "/";
this.request = get(requestUri).build();
this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain);
assertThat(this.response.getRedirectedUrl()).matches("http://localhost/custom-login");
assertThat(this.response.getRedirectedUrl()).matches("/custom-login");
}
@Test
@@ -34,7 +34,6 @@ import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.config.test.SpringTestContext;
@@ -44,7 +43,6 @@ import org.springframework.security.oauth2.server.authorization.JdbcOAuth2Author
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.jackson2.TestingAuthenticationTokenMixin;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.test.web.servlet.MockMvc;
@@ -141,11 +139,7 @@ public class JwkSetTests {
@Bean
OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations,
RegisteredClientRepository registeredClientRepository) {
JdbcOAuth2AuthorizationService authorizationService = new JdbcOAuth2AuthorizationService(jdbcOperations,
registeredClientRepository);
authorizationService.setAuthorizationRowMapper(new RowMapper(registeredClientRepository));
authorizationService.setAuthorizationParametersMapper(new ParametersMapper());
return authorizationService;
return new JdbcOAuth2AuthorizationService(jdbcOperations, registeredClientRepository);
}
@Bean
@@ -163,24 +157,6 @@ public class JwkSetTests {
return jwkSource;
}
static class RowMapper extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper {
RowMapper(RegisteredClientRepository registeredClientRepository) {
super(registeredClientRepository);
getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class);
}
}
static class ParametersMapper extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationParametersMapper {
ParametersMapper() {
super();
getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class);
}
}
}
@EnableWebSecurity
@@ -116,7 +116,6 @@ import org.springframework.security.oauth2.server.authorization.client.JdbcRegis
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
import org.springframework.security.oauth2.server.authorization.jackson2.TestingAuthenticationTokenMixin;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator;
@@ -1071,6 +1070,94 @@ public class OAuth2AuthorizationCodeGrantTests {
.isEqualTo(true);
}
// gh-2182
@Test
public void requestWhenPushedAuthorizationRequestAndRequiresConsentThenDisplaysConsentPage() throws Exception {
this.spring.register(AuthorizationServerConfigurationWithPushedAuthorizationRequests.class).autowire();
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scopes((scopes) -> {
scopes.clear();
scopes.add("message.read");
scopes.add("message.write");
}).clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()).build();
this.registeredClientRepository.save(registeredClient);
MvcResult mvcResult = this.mvc
.perform(post("/oauth2/par").params(getAuthorizationRequestParameters(registeredClient))
.header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(registeredClient)))
.andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store")))
.andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache")))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.request_uri").isNotEmpty())
.andExpect(jsonPath("$.expires_in").isNotEmpty())
.andReturn();
String requestUri = JsonPath.read(mvcResult.getResponse().getContentAsString(), "$.request_uri");
String consentPage = this.mvc
.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI)
.queryParam(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId())
.queryParam(OAuth2ParameterNames.REQUEST_URI, requestUri)
.with(user("user")))
.andExpect(status().is2xxSuccessful())
.andReturn()
.getResponse()
.getContentAsString();
assertThat(consentPage).contains("Consent required");
assertThat(consentPage).contains(scopeCheckbox("message.read"));
assertThat(consentPage).contains(scopeCheckbox("message.write"));
}
// gh-2182
@Test
public void requestWhenPushedAuthorizationRequestAndCustomConsentPageConfiguredThenRedirect() throws Exception {
this.spring.register(AuthorizationServerConfigurationWithPushedAuthorizationRequestsAndCustomConsentPage.class)
.autowire();
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scopes((scopes) -> {
scopes.clear();
scopes.add("message.read");
scopes.add("message.write");
}).clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()).build();
this.registeredClientRepository.save(registeredClient);
MvcResult mvcResult = this.mvc
.perform(post("/oauth2/par").params(getAuthorizationRequestParameters(registeredClient))
.header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(registeredClient)))
.andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store")))
.andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache")))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.request_uri").isNotEmpty())
.andExpect(jsonPath("$.expires_in").isNotEmpty())
.andReturn();
String requestUri = JsonPath.read(mvcResult.getResponse().getContentAsString(), "$.request_uri");
mvcResult = this.mvc
.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI)
.queryParam(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId())
.queryParam(OAuth2ParameterNames.REQUEST_URI, requestUri)
.with(user("user")))
.andExpect(status().is3xxRedirection())
.andReturn();
String redirectedUrl = mvcResult.getResponse().getRedirectedUrl();
assertThat(redirectedUrl).matches("http://localhost/oauth2/consent\\?scope=.+&client_id=.+&state=.+");
String locationHeader = URLDecoder.decode(redirectedUrl, StandardCharsets.UTF_8.name());
UriComponents uriComponents = UriComponentsBuilder.fromUriString(locationHeader).build();
MultiValueMap<String, String> redirectQueryParams = uriComponents.getQueryParams();
assertThat(uriComponents.getPath()).isEqualTo(consentPage);
assertThat(redirectQueryParams.getFirst(OAuth2ParameterNames.SCOPE)).isEqualTo("message.read message.write");
assertThat(redirectQueryParams.getFirst(OAuth2ParameterNames.CLIENT_ID))
.isEqualTo(registeredClient.getClientId());
String state = extractParameterFromRedirectUri(redirectedUrl, "state");
OAuth2Authorization authorization = this.authorizationService.findByToken(state, STATE_TOKEN_TYPE);
assertThat(authorization).isNotNull();
}
private static OAuth2Authorization createAuthorization(RegisteredClient registeredClient) {
Map<String, Object> additionalParameters = new HashMap<>();
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, S256_CODE_CHALLENGE);
@@ -1125,8 +1212,8 @@ public class OAuth2AuthorizationCodeGrantTests {
private static String getAuthorizationHeader(RegisteredClient registeredClient) throws Exception {
String clientId = registeredClient.getClientId();
String clientSecret = registeredClient.getClientSecret();
clientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8.name());
clientSecret = URLEncoder.encode(clientSecret, StandardCharsets.UTF_8.name());
clientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8);
clientSecret = URLEncoder.encode(clientSecret, StandardCharsets.UTF_8);
String credentialsString = clientId + ":" + clientSecret;
byte[] encodedBytes = Base64.getEncoder().encode(credentialsString.getBytes(StandardCharsets.UTF_8));
return "Basic " + new String(encodedBytes, StandardCharsets.UTF_8);
@@ -1151,11 +1238,7 @@ public class OAuth2AuthorizationCodeGrantTests {
@Bean
OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations,
RegisteredClientRepository registeredClientRepository) {
JdbcOAuth2AuthorizationService authorizationService = new JdbcOAuth2AuthorizationService(jdbcOperations,
registeredClientRepository);
authorizationService.setAuthorizationRowMapper(new RowMapper(registeredClientRepository));
authorizationService.setAuthorizationParametersMapper(new ParametersMapper());
return authorizationService;
return new JdbcOAuth2AuthorizationService(jdbcOperations, registeredClientRepository);
}
@Bean
@@ -1208,24 +1291,6 @@ public class OAuth2AuthorizationCodeGrantTests {
return NoOpPasswordEncoder.getInstance();
}
static class RowMapper extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper {
RowMapper(RegisteredClientRepository registeredClientRepository) {
super(registeredClientRepository);
getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class);
}
}
static class ParametersMapper extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationParametersMapper {
ParametersMapper() {
super();
getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class);
}
}
}
@EnableWebSecurity
@@ -1496,4 +1561,28 @@ public class OAuth2AuthorizationCodeGrantTests {
}
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
static class AuthorizationServerConfigurationWithPushedAuthorizationRequestsAndCustomConsentPage
extends AuthorizationServerConfiguration {
// @formatter:off
@Bean
SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
http
.oauth2AuthorizationServer((authorizationServer) ->
authorizationServer
.pushedAuthorizationRequestEndpoint(Customizer.withDefaults())
.authorizationEndpoint((authorizationEndpoint) ->
authorizationEndpoint.consentPage(consentPage))
)
.authorizeHttpRequests((authorize) ->
authorize.anyRequest().authenticated()
);
return http.build();
}
// @formatter:on
}
}
@@ -185,6 +185,17 @@ public class OAuth2AuthorizationServerMetadataTests {
.andExpect(jsonPath("$.grant_types_supported[4]").value(AuthorizationGrantType.DEVICE_CODE.getValue()));
}
@Test
public void requestWhenAuthorizationServerMetadataRequestAndPushedAuthorizationRequestEnabledThenMetadataResponseIncludesPushedAuthorizationRequestEndpoint()
throws Exception {
this.spring.register(AuthorizationServerConfigurationWithPushedAuthorizationRequestEnabled.class).autowire();
this.mvc.perform(get(ISSUER.concat(DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI)))
.andExpect(status().is2xxSuccessful())
.andExpect(jsonPath("$.pushed_authorization_request_endpoint")
.value(ISSUER.concat(this.authorizationServerSettings.getPushedAuthorizationRequestEndpoint())));
}
@EnableWebSecurity
@Import(OAuth2AuthorizationServerConfiguration.class)
static class AuthorizationServerConfiguration {
@@ -301,4 +312,26 @@ public class OAuth2AuthorizationServerMetadataTests {
}
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
static class AuthorizationServerConfigurationWithPushedAuthorizationRequestEnabled
extends AuthorizationServerConfiguration {
// @formatter:off
@Bean
SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
http
.oauth2AuthorizationServer((authorizationServer) ->
authorizationServer
.pushedAuthorizationRequestEndpoint(Customizer.withDefaults())
)
.authorizeHttpRequests((authorize) ->
authorize.anyRequest().authenticated()
);
return http.build();
}
// @formatter:on
}
}
@@ -52,7 +52,6 @@ import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.TestingAuthenticationToken;
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.OAuth2AuthorizationServerConfiguration;
@@ -93,7 +92,6 @@ import org.springframework.security.oauth2.server.authorization.client.JdbcRegis
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
import org.springframework.security.oauth2.server.authorization.jackson2.TestingAuthenticationTokenMixin;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
@@ -533,11 +531,7 @@ public class OAuth2ClientCredentialsGrantTests {
@Bean
OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations,
RegisteredClientRepository registeredClientRepository) {
JdbcOAuth2AuthorizationService authorizationService = new JdbcOAuth2AuthorizationService(jdbcOperations,
registeredClientRepository);
authorizationService.setAuthorizationRowMapper(new RowMapper(registeredClientRepository));
authorizationService.setAuthorizationParametersMapper(new ParametersMapper());
return authorizationService;
return new JdbcOAuth2AuthorizationService(jdbcOperations, registeredClientRepository);
}
@Bean
@@ -569,24 +563,6 @@ public class OAuth2ClientCredentialsGrantTests {
return NoOpPasswordEncoder.getInstance();
}
static class RowMapper extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper {
RowMapper(RegisteredClientRepository registeredClientRepository) {
super(registeredClientRepository);
getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class);
}
}
static class ParametersMapper extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationParametersMapper {
ParametersMapper() {
super();
getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class);
}
}
}
@EnableWebSecurity
@@ -56,7 +56,6 @@ import org.springframework.lang.Nullable;
import org.springframework.mock.http.client.MockClientHttpResponse;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.TestingAuthenticationToken;
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.OAuth2AuthorizationServerConfiguration;
@@ -98,7 +97,6 @@ import org.springframework.security.oauth2.server.authorization.client.JdbcRegis
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
import org.springframework.security.oauth2.server.authorization.jackson2.TestingAuthenticationTokenMixin;
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
import org.springframework.security.web.SecurityFilterChain;
@@ -467,11 +465,7 @@ public class OAuth2RefreshTokenGrantTests {
@Bean
OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations,
RegisteredClientRepository registeredClientRepository) {
JdbcOAuth2AuthorizationService authorizationService = new JdbcOAuth2AuthorizationService(jdbcOperations,
registeredClientRepository);
authorizationService.setAuthorizationRowMapper(new RowMapper(registeredClientRepository));
authorizationService.setAuthorizationParametersMapper(new ParametersMapper());
return authorizationService;
return new JdbcOAuth2AuthorizationService(jdbcOperations, registeredClientRepository);
}
@Bean
@@ -512,24 +506,6 @@ public class OAuth2RefreshTokenGrantTests {
return NoOpPasswordEncoder.getInstance();
}
static class RowMapper extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper {
RowMapper(RegisteredClientRepository registeredClientRepository) {
super(registeredClientRepository);
getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class);
}
}
static class ParametersMapper extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationParametersMapper {
ParametersMapper() {
super();
getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class);
}
}
}
@EnableWebSecurity
@@ -51,7 +51,6 @@ import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.mock.http.client.MockClientHttpResponse;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.TestingAuthenticationToken;
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.OAuth2AuthorizationServerConfiguration;
@@ -87,7 +86,6 @@ import org.springframework.security.oauth2.server.authorization.client.Registere
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
import org.springframework.security.oauth2.server.authorization.http.converter.OAuth2TokenIntrospectionHttpMessageConverter;
import org.springframework.security.oauth2.server.authorization.jackson2.TestingAuthenticationTokenMixin;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
@@ -507,11 +505,7 @@ public class OAuth2TokenIntrospectionTests {
@Bean
OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations,
RegisteredClientRepository registeredClientRepository) {
JdbcOAuth2AuthorizationService authorizationService = new JdbcOAuth2AuthorizationService(jdbcOperations,
registeredClientRepository);
authorizationService.setAuthorizationRowMapper(new RowMapper(registeredClientRepository));
authorizationService.setAuthorizationParametersMapper(new ParametersMapper());
return authorizationService;
return new JdbcOAuth2AuthorizationService(jdbcOperations, registeredClientRepository);
}
@Bean
@@ -549,24 +543,6 @@ public class OAuth2TokenIntrospectionTests {
return NoOpPasswordEncoder.getInstance();
}
static class RowMapper extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper {
RowMapper(RegisteredClientRepository registeredClientRepository) {
super(registeredClientRepository);
getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class);
}
}
static class ParametersMapper extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationParametersMapper {
ParametersMapper() {
super();
getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class);
}
}
}
@EnableWebSecurity
@@ -43,7 +43,6 @@ import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.TestingAuthenticationToken;
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.OAuth2AuthorizationServerConfiguration;
@@ -71,7 +70,6 @@ import org.springframework.security.oauth2.server.authorization.client.JdbcRegis
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
import org.springframework.security.oauth2.server.authorization.jackson2.TestingAuthenticationTokenMixin;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2TokenRevocationAuthenticationConverter;
import org.springframework.security.web.SecurityFilterChain;
@@ -316,11 +314,7 @@ public class OAuth2TokenRevocationTests {
@Bean
OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations,
RegisteredClientRepository registeredClientRepository) {
JdbcOAuth2AuthorizationService authorizationService = new JdbcOAuth2AuthorizationService(jdbcOperations,
registeredClientRepository);
authorizationService.setAuthorizationRowMapper(new RowMapper(registeredClientRepository));
authorizationService.setAuthorizationParametersMapper(new ParametersMapper());
return authorizationService;
return new JdbcOAuth2AuthorizationService(jdbcOperations, registeredClientRepository);
}
@Bean
@@ -347,24 +341,6 @@ public class OAuth2TokenRevocationTests {
return NoOpPasswordEncoder.getInstance();
}
static class RowMapper extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper {
RowMapper(RegisteredClientRepository registeredClientRepository) {
super(registeredClientRepository);
getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class);
}
}
static class ParametersMapper extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationParametersMapper {
ParametersMapper() {
super();
getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class);
}
}
}
@EnableWebSecurity
@@ -159,6 +159,18 @@ public class OidcProviderConfigurationTests {
.andExpect(jsonPath("$.grant_types_supported[4]").value(AuthorizationGrantType.DEVICE_CODE.getValue()));
}
@Test
public void requestWhenConfigurationRequestAndPushedAuthorizationRequestEnabledThenConfigurationResponseIncludesPushedAuthorizationRequestEndpoint()
throws Exception {
this.spring.register(AuthorizationServerConfigurationWithPushedAuthorizationRequestEnabled.class).autowire();
this.mvc.perform(get(ISSUER.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI)))
.andExpect(status().is2xxSuccessful())
.andExpectAll(defaultConfigurationMatchers(ISSUER))
.andExpect(jsonPath("$.pushed_authorization_request_endpoint")
.value(ISSUER.concat(this.authorizationServerSettings.getPushedAuthorizationRequestEndpoint())));
}
private ResultMatcher[] defaultConfigurationMatchers(String issuer) {
// @formatter:off
return new ResultMatcher[] {
@@ -357,6 +369,26 @@ public class OidcProviderConfigurationTests {
}
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
static class AuthorizationServerConfigurationWithPushedAuthorizationRequestEnabled
extends AuthorizationServerConfiguration {
// @formatter:off
@Bean
SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
http
.oauth2AuthorizationServer((authorizationServer) ->
authorizationServer
.pushedAuthorizationRequestEndpoint(Customizer.withDefaults())
.oidc(Customizer.withDefaults())
);
return http.build();
}
// @formatter:on
}
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
static class AuthorizationServerConfigurationWithInvalidIssuerUrl extends AuthorizationServerConfiguration {
@@ -52,7 +52,6 @@ import org.springframework.lang.Nullable;
import org.springframework.mock.http.client.MockClientHttpResponse;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@@ -90,7 +89,6 @@ import org.springframework.security.oauth2.server.authorization.client.JdbcRegis
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
import org.springframework.security.oauth2.server.authorization.jackson2.TestingAuthenticationTokenMixin;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
@@ -631,11 +629,7 @@ public class OidcTests {
@Bean
OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations,
RegisteredClientRepository registeredClientRepository) {
JdbcOAuth2AuthorizationService authorizationService = new JdbcOAuth2AuthorizationService(jdbcOperations,
registeredClientRepository);
authorizationService.setAuthorizationRowMapper(new RowMapper(registeredClientRepository));
authorizationService.setAuthorizationParametersMapper(new ParametersMapper());
return authorizationService;
return new JdbcOAuth2AuthorizationService(jdbcOperations, registeredClientRepository);
}
@Bean
@@ -691,24 +685,6 @@ public class OidcTests {
return sessionRegistry;
}
static class RowMapper extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper {
RowMapper(RegisteredClientRepository registeredClientRepository) {
super(registeredClientRepository);
getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class);
}
}
static class ParametersMapper extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationParametersMapper {
ParametersMapper() {
super();
getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class);
}
}
}
@EnableWebSecurity
@@ -1212,7 +1212,7 @@ public class OAuth2ResourceServerConfigurerTests {
MvcResult result = this.mvc.perform(get("/authenticated")
.header("Accept", "text/html"))
.andExpect(status().isFound())
.andExpect(redirectedUrl("http://localhost/login"))
.andExpect(redirectedUrl("/login"))
.andReturn();
// @formatter:on
assertThat(result.getRequest().getSession(false)).isNotNull();
@@ -191,9 +191,7 @@ public class OneTimeTokenLoginConfigurerTests {
@Test
void oneTimeTokenWhenLoginPageConfiguredThenRedirects() throws Exception {
this.spring.register(OneTimeTokenLoginPageConfig.class).autowire();
this.mvc.perform(get("/login"))
.andExpect(status().isFound())
.andExpect(redirectedUrl("http://localhost/custom-login"));
this.mvc.perform(get("/login")).andExpect(status().isFound()).andExpect(redirectedUrl("/custom-login"));
}
@Test
@@ -356,7 +356,7 @@ public class Saml2LoginConfigurerTests {
MockHttpServletRequestBuilder request = get("/custom/auth/sso");
this.mvc.perform(request)
.andExpect(status().isFound())
.andExpect(redirectedUrl("http://localhost/custom/auth/sso?entityId=registration-id"));
.andExpect(redirectedUrl("/custom/auth/sso?entityId=registration-id"));
request.queryParam("entityId", registration.getRegistrationId());
MvcResult result = this.mvc.perform(request).andExpect(status().isFound()).andReturn();
String redirectedUrl = result.getResponse().getRedirectedUrl();
@@ -407,10 +407,10 @@ public class Saml2LoginConfigurerTests {
this.spring.register(Saml2LoginConfig.class).autowire();
this.mvc.perform(get("/favicon.ico").accept(MediaType.TEXT_HTML))
.andExpect(status().isFound())
.andExpect(redirectedUrl("http://localhost/login"));
.andExpect(redirectedUrl("/login"));
this.mvc.perform(get("/").accept(MediaType.TEXT_HTML))
.andExpect(status().isFound())
.andExpect(header().string("Location", startsWith("http://localhost/saml2/authenticate")));
.andExpect(header().string("Location", startsWith("/saml2/authenticate")));
}
@Test
@@ -379,7 +379,7 @@ public class CsrfConfigTests {
this.spring.configLocations(this.xml("CsrfEnabled")).autowire();
// simulates a request that has no authentication (e.g. session time-out)
MvcResult result = this.mvc.perform(post("/authenticated").with(csrf()))
.andExpect(redirectedUrl("http://localhost/login"))
.andExpect(redirectedUrl("/login"))
.andReturn();
MockHttpSession session = (MockHttpSession) result.getRequest().getSession();
// if the request cache is consulted, then it will redirect back to /some-url,
@@ -400,9 +400,7 @@ public class CsrfConfigTests {
throws Exception {
this.spring.configLocations(this.xml("CsrfEnabled")).autowire();
// simulates a request that has no authentication (e.g. session time-out)
MvcResult result = this.mvc.perform(get("/authenticated"))
.andExpect(redirectedUrl("http://localhost/login"))
.andReturn();
MvcResult result = this.mvc.perform(get("/authenticated")).andExpect(redirectedUrl("/login")).andReturn();
MockHttpSession session = (MockHttpSession) result.getRequest().getSession();
// if the request cache is consulted, then it will redirect back to /some-url,
// which we do want
@@ -73,7 +73,7 @@ public class FormLoginConfigTests {
this.spring.configLocations(this.xml("WithRequestMatcher")).autowire();
// @formatter:off
this.mvc.perform(get("/"))
.andExpect(redirectedUrl("http://localhost/login"));
.andExpect(redirectedUrl("/login"));
// @formatter:on
}
@@ -107,7 +107,7 @@ public class FormLoginConfigTests {
this.mvc.perform(invalidPassword)
.andExpect(redirectedUrl(WebConfigUtilsTests.URL + "/failure"));
this.mvc.perform(get("/"))
.andExpect(redirectedUrl("http://localhost" + WebConfigUtilsTests.URL + "/login"));
.andExpect(redirectedUrl(WebConfigUtilsTests.URL + "/login"));
// @formatter:on
}
@@ -71,7 +71,7 @@ public class HttpConfigTests {
// @formatter:off
this.mvc.perform(get("/"))
.andExpect(status().isFound())
.andExpect(redirectedUrl("http://localhost/login"));
.andExpect(redirectedUrl("/login"));
// @formatter:on
}
@@ -81,7 +81,7 @@ public class HttpConfigTests {
// @formatter:off
this.mvc.perform(get("/"))
.andExpect(status().isFound())
.andExpect(redirectedUrl("http://localhost/login"));
.andExpect(redirectedUrl("/login"));
// @formatter:on
}
@@ -94,7 +94,7 @@ public class HttpConfigTests {
// @formatter:off
this.mvc.perform(get("/"))
.andExpect(status().isFound())
.andExpect(redirectedUrl("http://localhost/login"));
.andExpect(redirectedUrl("/login"));
// @formatter:on
verify(authorizationManager).authorize(any(), any());
}
@@ -108,7 +108,7 @@ public class HttpConfigTests {
proxy.doFilter(request, new EncodeUrlDenyingHttpServletResponseWrapper(response), (req, resp) -> {
});
assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_MOVED_TEMPORARILY);
assertThat(response.getRedirectedUrl()).isEqualTo("http://localhost/login");
assertThat(response.getRedirectedUrl()).isEqualTo("/login");
}
@Test
@@ -612,7 +612,7 @@ public class MiscHttpConfigTests {
proxy.doFilter(request, new EncodeUrlDenyingHttpServletResponseWrapper(response), (req, resp) -> {
});
assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_MOVED_TEMPORARILY);
assertThat(response.getRedirectedUrl()).isEqualTo("http://localhost/login");
assertThat(response.getRedirectedUrl()).isEqualTo("/login");
}
@Test
@@ -841,7 +841,7 @@ public class MiscHttpConfigTests {
this.spring.configLocations(xml("PortsMappedRequiresHttps")).autowire();
// @formatter:off
MockHttpSession session = (MockHttpSession) this.mvc.perform(get("https://localhost:9080/protected"))
.andExpect(redirectedUrl("https://localhost:9443/login"))
.andExpect(redirectedUrl("/login"))
.andReturn()
.getRequest()
.getSession(false);
@@ -60,7 +60,9 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequ
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses;
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationRequests;
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.oauth2.core.oidc.user.TestOidcUsers;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.oauth2.core.user.TestOAuth2Users;
import org.springframework.security.oauth2.jwt.Jwt;
@@ -133,6 +135,9 @@ public class OAuth2LoginBeanDefinitionParserTests {
@Autowired(required = false)
private OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService;
@Autowired(required = false)
private OAuth2UserService<OAuth2UserRequest, OAuth2User> oidcUserService;
@Autowired(required = false)
private JwtDecoderFactory<ClientRegistration> jwtDecoderFactory;
@@ -175,7 +180,7 @@ public class OAuth2LoginBeanDefinitionParserTests {
// @formatter:off
this.mvc.perform(get("/"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("http://localhost/oauth2/authorization/google-login"));
.andExpect(redirectedUrl("/oauth2/authorization/google-login"));
// @formatter:on
verify(this.requestCache).saveRequest(any(), any());
}
@@ -188,7 +193,7 @@ public class OAuth2LoginBeanDefinitionParserTests {
// @formatter:off
this.mvc.perform(get("/favicon.ico").accept(new MediaType("image", "*")))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("http://localhost/login"));
.andExpect(redirectedUrl("/login"));
// @formatter:on
}
@@ -200,7 +205,7 @@ public class OAuth2LoginBeanDefinitionParserTests {
// @formatter:off
this.mvc.perform(get("/").header("X-Requested-With", "XMLHttpRequest"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("http://localhost/login"));
.andExpect(redirectedUrl("/login"));
// @formatter:on
}
@@ -286,6 +291,8 @@ public class OAuth2LoginBeanDefinitionParserTests {
given(this.accessTokenResponseClient.getTokenResponse(any())).willReturn(accessTokenResponse);
Jwt jwt = TestJwts.user();
given(this.jwtDecoderFactory.createDecoder(any())).willReturn((token) -> jwt);
DefaultOidcUser oidcUser = TestOidcUsers.create();
given(this.oidcUserService.loadUser(any())).willReturn(oidcUser);
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("code", "code123");
params.add("state", authorizationRequest.getState());
@@ -339,6 +346,8 @@ public class OAuth2LoginBeanDefinitionParserTests {
given(this.accessTokenResponseClient.getTokenResponse(any())).willReturn(accessTokenResponse);
Jwt jwt = TestJwts.user();
given(this.jwtDecoderFactory.createDecoder(any())).willReturn((token) -> jwt);
DefaultOidcUser oidcUser = TestOidcUsers.create();
given(this.oidcUserService.loadUser(any())).willReturn(oidcUser);
given(this.userAuthoritiesMapper.mapAuthorities(any()))
.willReturn((Collection) AuthorityUtils.createAuthorityList("ROLE_OIDC_USER"));
// @formatter:off
@@ -414,7 +423,7 @@ public class OAuth2LoginBeanDefinitionParserTests {
// @formatter:off
this.mvc.perform(get("/"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("http://localhost/login"));
.andExpect(redirectedUrl("/login"));
// @formatter:on
}
@@ -424,7 +433,7 @@ public class OAuth2LoginBeanDefinitionParserTests {
// @formatter:off
this.mvc.perform(get("/"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("http://localhost/custom-login"));
.andExpect(redirectedUrl("/custom-login"));
// @formatter:on
}
@@ -436,7 +445,7 @@ public class OAuth2LoginBeanDefinitionParserTests {
// @formatter:off
this.mvc.perform(get("/"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("http://localhost/login"));
.andExpect(redirectedUrl("/login"));
// @formatter:on
}
@@ -73,7 +73,7 @@ public class PlaceHolderAndELConfigTests {
// login-page setting
// @formatter:off
this.mvc.perform(get("/secured"))
.andExpect(redirectedUrl("http://localhost/loginPage"));
.andExpect(redirectedUrl("/loginPage"));
// login-processing-url setting
// default-target-url setting
this.mvc.perform(post("/loginPage").param("username", "user").param("password", "password"))
@@ -98,7 +98,7 @@ public class PlaceHolderAndELConfigTests {
// login-page setting
// @formatter:off
this.mvc.perform(get("/secured"))
.andExpect(redirectedUrl("http://localhost/loginPage"));
.andExpect(redirectedUrl("/loginPage"));
// login-processing-url setting
// default-target-url setting
this.mvc.perform(post("/loginPage").param("username", "user").param("password", "password"))
@@ -161,7 +161,7 @@ public class Saml2LoginBeanDefinitionParserTests {
// @formatter:off
this.mvc.perform(get("/"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("http://localhost/saml2/authenticate/one"));
.andExpect(redirectedUrl("/saml2/authenticate/one"));
// @formatter:on
verify(this.requestCache).saveRequest(any(), any());
}
@@ -172,7 +172,7 @@ public class Saml2LoginBeanDefinitionParserTests {
// @formatter:off
this.mvc.perform(get("/"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("http://localhost/login"));
.andExpect(redirectedUrl("/login"));
// @formatter:on
}
@@ -78,7 +78,7 @@ public class SecurityContextHolderAwareRequestConfigTests {
// @formatter:off
this.mvc.perform(get("/authenticate"))
.andExpect(status().isFound())
.andExpect(redirectedUrl("http://localhost/login"));
.andExpect(redirectedUrl("/login"));
// @formatter:on
}
@@ -114,7 +114,7 @@ public class SecurityContextHolderAwareRequestConfigTests {
// @formatter:off
this.mvc.perform(get("/authenticate"))
.andExpect(status().isFound())
.andExpect(redirectedUrl("http://localhost/login"));
.andExpect(redirectedUrl("/login"));
// @formatter:on
}
@@ -137,10 +137,10 @@ public class SecurityContextHolderAwareRequestConfigTests {
// @formatter:off
this.mvc.perform(get("/authenticate"))
.andExpect(status().isFound())
.andExpect(redirectedUrl("http://localhost/login"));
.andExpect(redirectedUrl("/login"));
this.mvc.perform(get("/v2/authenticate"))
.andExpect(status().isFound())
.andExpect(redirectedUrl("http://localhost/login2"));
.andExpect(redirectedUrl("/login2"));
// @formatter:on
}
@@ -177,9 +177,7 @@ public class SecurityContextHolderAwareRequestConfigTests {
@Test
public void servletLogoutWhenUsingCustomLogoutThenUsesSpringSecurity() throws Exception {
this.spring.configLocations(this.xml("Logout")).autowire();
this.mvc.perform(get("/authenticate"))
.andExpect(status().isFound())
.andExpect(redirectedUrl("http://localhost/signin"));
this.mvc.perform(get("/authenticate")).andExpect(status().isFound()).andExpect(redirectedUrl("/signin"));
// @formatter:off
MvcResult result = this.mvc.perform(get("/good-login"))
.andReturn();
@@ -218,7 +218,7 @@ class ExceptionHandlingDslTests {
this.mockMvc.get("/")
.andExpect {
status { isFound() }
redirectedUrl("http://localhost/custom-login")
redirectedUrl("/custom-login")
}
}
@@ -247,13 +247,13 @@ class ExceptionHandlingDslTests {
this.mockMvc.get("/secured1")
.andExpect {
status { isFound() }
redirectedUrl("http://localhost/custom-login1")
redirectedUrl("/custom-login1")
}
this.mockMvc.get("/secured2")
.andExpect {
status { isFound() }
redirectedUrl("http://localhost/custom-login2")
redirectedUrl("/custom-login2")
}
}
@@ -144,7 +144,7 @@ class FormLoginDslTests {
this.mockMvc.get("/")
.andExpect {
status { isFound() }
redirectedUrl("http://localhost/login")
redirectedUrl("/login")
}
}
@@ -170,7 +170,7 @@ class FormLoginDslTests {
this.mockMvc.get("/")
.andExpect {
status { isFound() }
redirectedUrl("http://localhost/log-in")
redirectedUrl("/log-in")
}
}
@@ -148,7 +148,7 @@ internal class RememberMeDslTests {
cookie(expiredRememberMeCookie)
}.andExpect {
status { isFound() }
redirectedUrl("http://localhost/login")
redirectedUrl("/login")
}
}
@@ -224,7 +224,7 @@ internal class RememberMeDslTests {
cookie(withoutKeyRememberMeCookie)
}.andExpect {
status { isFound() }
redirectedUrl("http://localhost/login")
redirectedUrl("/login")
}
val keyMvcResult = mockMvc.post("/login") {
loginRememberMeRequest()
@@ -146,7 +146,7 @@ class Saml2DslTests {
val request = MockMvcRequestBuilders.get("/custom/auth/sso")
this.mockMvc.perform(request)
.andExpect(MockMvcResultMatchers.status().isFound())
.andExpect(MockMvcResultMatchers.redirectedUrl("http://localhost/custom/auth/sso?entityId=simplesamlphp"))
.andExpect(MockMvcResultMatchers.redirectedUrl("/custom/auth/sso?entityId=simplesamlphp"))
request.queryParam("entityId", registration.registrationId)
val result: MvcResult =
this.mockMvc.perform(request).andExpect(MockMvcResultMatchers.status().isFound()).andReturn()
@@ -72,7 +72,7 @@ class TokenEndpointDslTests {
.state("test")
.clientId("clientId")
.authorizationUri("https://test")
.redirectUri("http://localhost/login/oauth2/code/google")
.redirectUri("/login/oauth2/code/google")
.attributes(attributes)
.build()
every {
@@ -77,7 +77,7 @@ class UserInfoEndpointDslTests {
.state("test")
.clientId("clientId")
.authorizationUri("https://test")
.redirectUri("http://localhost/login/oauth2/code/google")
.redirectUri("/login/oauth2/code/google")
.attributes(attributes)
.build()
every {
@@ -28,6 +28,7 @@
<intercept-url pattern="/**" access="authenticated"/>
<oauth2-login access-token-response-client-ref="accessTokenResponseClient"
user-service-ref="oauth2UserService"
oidc-user-service-ref="oidcUserService"
user-authorities-mapper-ref="userAuthoritiesMapper"
jwt-decoder-factory-ref="jwtDecoderFactory"
authorization-request-repository-ref="authorizationRequestRepository"
@@ -39,6 +40,9 @@
</b:bean>
<b:bean id="oauth2UserService" class="org.mockito.Mockito" factory-method="mock">
<b:constructor-arg value="org.springframework.security.oauth2.client.userinfo.OAuth2UserService" type="java.lang.Class"/>
</b:bean>
<b:bean id="oidcUserService" class="org.mockito.Mockito" factory-method="mock">
<b:constructor-arg value="org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService" type="java.lang.Class"/>
</b:bean>
<b:bean id="userAuthoritiesMapper" class="org.mockito.Mockito" factory-method="mock">
<b:constructor-arg value="org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper" type="java.lang.Class"/>
@@ -27,6 +27,7 @@
<http auto-config="true">
<intercept-url pattern="/**" access="authenticated"/>
<oauth2-login access-token-response-client-ref="accessTokenResponseClient"
oidc-user-service-ref="oidcUserService"
jwt-decoder-factory-ref="jwtDecoderFactory"
authorization-request-repository-ref="authorizationRequestRepository"/>
<request-cache ref="requestCache" />
@@ -34,6 +35,9 @@
<b:bean id="accessTokenResponseClient" class="org.mockito.Mockito" factory-method="mock">
<b:constructor-arg value="org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient" type="java.lang.Class"/>
</b:bean>
<b:bean id="oidcUserService" class="org.mockito.Mockito" factory-method="mock">
<b:constructor-arg value="org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService" type="java.lang.Class"/>
</b:bean>
<b:bean id="jwtDecoderFactory" class="org.mockito.Mockito" factory-method="mock">
<b:constructor-arg value="org.springframework.security.oauth2.jwt.JwtDecoderFactory" type="java.lang.Class"/>
+1
View File
@@ -25,6 +25,7 @@ dependencies {
optional 'org.springframework:spring-jdbc'
optional 'org.springframework:spring-tx'
optional 'org.jetbrains.kotlinx:kotlinx-coroutines-reactor'
optional 'tools.jackson.core:jackson-databind'
testImplementation 'commons-collections:commons-collections'
testImplementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
@@ -56,7 +56,7 @@ public interface AuthenticationProvider {
* </p>
* <p>
* Selection of an <code>AuthenticationProvider</code> capable of performing
* authentication is conducted at runtime the <code>ProviderManager</code>.
* authentication is conducted at runtime by the <code>ProviderManager</code>.
* </p>
* @param authentication
* @return <code>true</code> if the implementation can more closely evaluate the
@@ -99,7 +99,7 @@ public final class AllRequiredFactorsAuthorizationManager<T> implements Authoriz
private @Nullable RequiredFactorError requiredFactorError(RequiredFactor requiredFactor,
List<GrantedAuthority> currentFactors) {
Optional<GrantedAuthority> matchingAuthority = currentFactors.stream()
.filter((authority) -> authority.getAuthority().equals(requiredFactor.getAuthority()))
.filter((authority) -> Objects.equals(authority.getAuthority(), requiredFactor.getAuthority()))
.findFirst();
if (!matchingAuthority.isPresent()) {
return RequiredFactorError.createMissing(requiredFactor);
@@ -166,7 +166,7 @@ public final class AllRequiredFactorsAuthorizationManager<T> implements Authoriz
* @param requiredFactor the {@link Consumer} to invoke.
* @return the builder.
*/
public Builder requireFactor(Consumer<RequiredFactor.Builder> requiredFactor) {
public Builder<T> requireFactor(Consumer<RequiredFactor.Builder> requiredFactor) {
Assert.notNull(requiredFactor, "requiredFactor cannot be null");
RequiredFactor.Builder builder = RequiredFactor.builder();
requiredFactor.accept(builder);
@@ -178,7 +178,7 @@ public final class AllRequiredFactorsAuthorizationManager<T> implements Authoriz
* @param requiredFactor the requiredFactor to add. Cannot be null.
* @return the builder.
*/
public Builder requireFactor(RequiredFactor requiredFactor) {
public Builder<T> requireFactor(RequiredFactor requiredFactor) {
Assert.notNull(requiredFactor, "requiredFactor cannot be null");
this.requiredFactors.add(requiredFactor);
return this;
@@ -186,10 +186,9 @@ public final class AllRequiredFactorsAuthorizationManager<T> implements Authoriz
/**
* Builds the {@link AllRequiredFactorsAuthorizationManager}.
* @param <T> the type.
* @return the {@link AllRequiredFactorsAuthorizationManager}
*/
public <T> AllRequiredFactorsAuthorizationManager<T> build() {
public AllRequiredFactorsAuthorizationManager<T> build() {
Assert.state(!this.requiredFactors.isEmpty(), "requiredFactors cannot be empty");
return new AllRequiredFactorsAuthorizationManager<T>(this.requiredFactors);
}
@@ -17,7 +17,10 @@
package org.springframework.security.authorization;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import org.jspecify.annotations.Nullable;
import reactor.core.publisher.Mono;
import org.springframework.security.core.Authentication;
@@ -47,8 +50,8 @@ public class AuthorityReactiveAuthorizationManager<T> implements ReactiveAuthori
// @formatter:off
return authentication.filter(Authentication::isAuthenticated)
.flatMapIterable(Authentication::getAuthorities)
.map(GrantedAuthority::getAuthority)
.any((grantedAuthority) -> this.authorities.stream().anyMatch((authority) -> authority.getAuthority().equals(grantedAuthority)))
.mapNotNull((Function<GrantedAuthority, @Nullable String>) GrantedAuthority::getAuthority)
.any((grantedAuthority) -> this.authorities.stream().anyMatch((authority) -> Objects.equals(authority.getAuthority(), grantedAuthority)))
.map((granted) -> ((AuthorizationResult) new AuthorityAuthorizationDecision(granted, this.authorities)))
.defaultIfEmpty(new AuthorityAuthorizationDecision(false, this.authorities));
// @formatter:on
@@ -18,6 +18,8 @@ package org.springframework.security.core;
import java.io.Serializable;
import org.jspecify.annotations.Nullable;
import org.springframework.security.authorization.AuthorizationManager;
/**
@@ -46,6 +48,6 @@ public interface GrantedAuthority extends Serializable {
* granted authority cannot be expressed as a <code>String</code> with sufficient
* precision).
*/
String getAuthority();
@Nullable String getAuthority();
}
@@ -27,6 +27,7 @@ import org.springframework.util.Assert;
* {@link org.springframework.security.core.Authentication Authentication} object.
*
* @author Luke Taylor
* @author Yanming Zhou
*/
public final class SimpleGrantedAuthority implements GrantedAuthority {
@@ -34,9 +35,14 @@ public final class SimpleGrantedAuthority implements GrantedAuthority {
private final String role;
public SimpleGrantedAuthority(String role) {
Assert.hasText(role, "A granted authority textual representation is required");
this.role = role;
/**
* Constructs a {@code SimpleGrantedAuthority} using the provided authority.
* @param authority The provided authority, including any prefix; for example,
* {@code ROLE_ADMIN}
*/
public SimpleGrantedAuthority(String authority) {
Assert.hasText(authority, "A granted authority textual representation is required");
this.role = authority;
}
@Override
@@ -64,7 +64,10 @@ public final class SimpleAuthorityMapper implements GrantedAuthoritiesMapper, In
public Set<GrantedAuthority> mapAuthorities(Collection<? extends GrantedAuthority> authorities) {
HashSet<GrantedAuthority> mapped = new HashSet<>(authorities.size());
for (GrantedAuthority authority : authorities) {
mapped.add(mapAuthority(authority.getAuthority()));
String authorityStr = authority.getAuthority();
if (authorityStr != null) {
mapped.add(mapAuthority(authorityStr));
}
}
if (this.defaultAuthority != null) {
mapped.add(this.defaultAuthority);
@@ -0,0 +1,57 @@
/*
* 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.jackson;
import java.util.Collection;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.springframework.security.core.GrantedAuthority;
/**
* This is a Jackson mixin class helps in serialize/deserialize
* {@link org.springframework.security.authentication.AnonymousAuthenticationToken} class.
*
* @author Sebastien Deleuze
* @author Jitendra Singh
* @since 7.0
* @see CoreJacksonModule
* @see SecurityJacksonModules
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, isGetterVisibility = JsonAutoDetect.Visibility.NONE,
getterVisibility = JsonAutoDetect.Visibility.NONE, creatorVisibility = JsonAutoDetect.Visibility.ANY)
class AnonymousAuthenticationTokenMixin {
/**
* Constructor used by Jackson to create object of
* {@link org.springframework.security.authentication.AnonymousAuthenticationToken}.
* @param keyHash hashCode of key provided at the time of token creation by using
* {@link org.springframework.security.authentication.AnonymousAuthenticationToken#AnonymousAuthenticationToken(String, Object, Collection)}
* @param principal the principal (typically a <code>UserDetails</code>)
* @param authorities the authorities granted to the principal
*/
@JsonCreator
AnonymousAuthenticationTokenMixin(@JsonProperty("keyHash") Integer keyHash,
@JsonProperty("principal") Object principal,
@JsonProperty("authorities") Collection<? extends GrantedAuthority> authorities) {
}
}
@@ -0,0 +1,46 @@
/*
* 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.jackson;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
/**
* This mixin class helps in serialize/deserialize
* {@link org.springframework.security.authentication.BadCredentialsException} class.
*
* @author Sebastien Deleuze
* @author Yannick Lombardi
* @since 7.0
* @see CoreJacksonModule
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonIgnoreProperties({ "cause", "stackTrace", "authenticationRequest" })
class BadCredentialsExceptionMixin {
/**
* Constructor used by Jackson to create
* {@link org.springframework.security.authentication.BadCredentialsException} object.
* @param message the detail message
*/
@JsonCreator
BadCredentialsExceptionMixin(@JsonProperty("message") String message) {
}
}
@@ -0,0 +1,113 @@
/*
* 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.jackson;
import java.time.Duration;
import java.time.Instant;
import tools.jackson.core.Version;
import tools.jackson.databind.cfg.DateTimeFeature;
import tools.jackson.databind.cfg.MapperBuilder;
import tools.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.RememberMeAuthenticationToken;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.FactorGrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.core.userdetails.User;
/**
* Jackson module for spring-security-core. This module register
* {@link AnonymousAuthenticationTokenMixin}, {@link RememberMeAuthenticationTokenMixin},
* {@link SimpleGrantedAuthorityMixin}, {@link FactorGrantedAuthorityMixin},
* {{@link UserMixin}, {@link UsernamePasswordAuthenticationTokenMixin} and
* {@link UsernamePasswordAuthenticationTokenMixin}.
*
* <p>
* The recommended way to configure it is to use {@link SecurityJacksonModules} in order
* to enable properly automatic inclusion of type information with related validation.
*
* <pre>
* ClassLoader loader = getClass().getClassLoader();
* JsonMapper mapper = JsonMapper.builder()
* .addModules(SecurityJacksonModules.getModules(loader))
* .build();
* </pre>
*
* @author Sebastien Deleuze
* @author Jitendra Singh
* @since 7.O
* @see SecurityJacksonModules
*/
@SuppressWarnings("serial")
public class CoreJacksonModule extends SecurityJacksonModule {
public CoreJacksonModule() {
super(CoreJacksonModule.class.getName(), new Version(1, 0, 0, null, null, null));
}
protected CoreJacksonModule(String name, Version version) {
super(name, version);
}
@Override
public void configurePolymorphicTypeValidator(BasicPolymorphicTypeValidator.Builder builder) {
builder.allowIfSubType(Instant.class)
.allowIfSubType(Duration.class)
.allowIfSubType(SimpleGrantedAuthority.class)
.allowIfSubType(FactorGrantedAuthority.class)
.allowIfSubType(UsernamePasswordAuthenticationToken.class)
.allowIfSubType(RememberMeAuthenticationToken.class)
.allowIfSubType(AnonymousAuthenticationToken.class)
.allowIfSubType(User.class)
.allowIfSubType(BadCredentialsException.class)
.allowIfSubType(SecurityContextImpl.class)
.allowIfSubType(TestingAuthenticationToken.class)
.allowIfSubType("java.util.Collections$UnmodifiableSet")
.allowIfSubType("java.util.Collections$UnmodifiableRandomAccessList")
.allowIfSubType("java.util.Collections$EmptyList")
.allowIfSubType("java.util.ArrayList")
.allowIfSubType("java.util.HashMap")
.allowIfSubType("java.util.Collections$EmptyMap")
.allowIfSubType("java.util.Date")
.allowIfSubType("java.util.Arrays$ArrayList")
.allowIfSubType("java.util.Collections$UnmodifiableMap")
.allowIfSubType("java.util.LinkedHashMap")
.allowIfSubType("java.util.Collections$SingletonList")
.allowIfSubType("java.util.TreeMap")
.allowIfSubType("java.util.HashSet")
.allowIfSubType("java.util.LinkedHashSet");
}
@Override
public void setupModule(SetupContext context) {
((MapperBuilder<?, ?>) context.getOwner()).enable(DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS);
context.setMixIn(AnonymousAuthenticationToken.class, AnonymousAuthenticationTokenMixin.class);
context.setMixIn(RememberMeAuthenticationToken.class, RememberMeAuthenticationTokenMixin.class);
context.setMixIn(SimpleGrantedAuthority.class, SimpleGrantedAuthorityMixin.class);
context.setMixIn(FactorGrantedAuthority.class, FactorGrantedAuthorityMixin.class);
context.setMixIn(User.class, UserMixin.class);
context.setMixIn(UsernamePasswordAuthenticationToken.class, UsernamePasswordAuthenticationTokenMixin.class);
context.setMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class);
context.setMixIn(BadCredentialsException.class, BadCredentialsExceptionMixin.class);
}
}
@@ -0,0 +1,50 @@
/*
* 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.jackson;
import java.time.Instant;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
/**
* Jackson Mixin class helps in serialize/deserialize
* {@link org.springframework.security.core.authority.SimpleGrantedAuthority}.
*
* @author Sebastien Deleuze
* @author Rob Winch
* @since 7.0
* @see CoreJacksonModule
* @see SecurityJacksonModules
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.NONE,
getterVisibility = JsonAutoDetect.Visibility.PUBLIC_ONLY, isGetterVisibility = JsonAutoDetect.Visibility.NONE)
abstract class FactorGrantedAuthorityMixin {
/**
* Mixin Constructor.
* @param authority the authority
*/
@JsonCreator
FactorGrantedAuthorityMixin(@JsonProperty("authority") String authority,
@JsonProperty("issuedAt") Instant issuedAt) {
}
}
@@ -0,0 +1,60 @@
/*
* 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.jackson;
import java.util.Collection;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.springframework.security.core.GrantedAuthority;
/**
* This mixin class helps in serialize/deserialize
* {@link org.springframework.security.authentication.RememberMeAuthenticationToken}
* class.
*
* @author Sebastien Deleuze
* @author Jitendra Singh
* @since 7.0
* @see CoreJacksonModule
* @see SecurityJacksonModules
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE, creatorVisibility = JsonAutoDetect.Visibility.ANY)
@JsonIgnoreProperties(ignoreUnknown = true)
class RememberMeAuthenticationTokenMixin {
/**
* Constructor used by Jackson to create
* {@link org.springframework.security.authentication.RememberMeAuthenticationToken}
* object.
* @param keyHash hashCode of above given key.
* @param principal the principal (typically a <code>UserDetails</code>)
* @param authorities the authorities granted to the principal
*/
@JsonCreator
RememberMeAuthenticationTokenMixin(@JsonProperty("keyHash") Integer keyHash,
@JsonProperty("principal") Object principal,
@JsonProperty("authorities") Collection<? extends GrantedAuthority> authorities) {
}
}
@@ -0,0 +1,42 @@
/*
* 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.jackson;
import tools.jackson.core.Version;
import tools.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
import tools.jackson.databind.jsontype.PolymorphicTypeValidator;
import tools.jackson.databind.module.SimpleModule;
/**
* Jackson module allowing to contribute {@link PolymorphicTypeValidator} configuration.
*
* @author Sebastien Deleuze
* @since 7.0
*/
public abstract class SecurityJacksonModule extends SimpleModule {
public SecurityJacksonModule() {
super();
}
public SecurityJacksonModule(String name, Version version) {
super(name, version, null);
}
public abstract void configurePolymorphicTypeValidator(BasicPolymorphicTypeValidator.Builder builder);
}
@@ -0,0 +1,210 @@
/*
* 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.jackson;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
import tools.jackson.databind.DefaultTyping;
import tools.jackson.databind.JacksonModule;
import tools.jackson.databind.cfg.MapperBuilder;
import tools.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
import tools.jackson.databind.jsontype.PolymorphicTypeValidator;
import tools.jackson.databind.module.SimpleModule;
import org.springframework.core.log.LogMessage;
import org.springframework.util.ClassUtils;
/**
* This utility class will find all the Jackson modules contributed by Spring Security in
* the classpath (except {@code WebauthnJacksonModule}), enable automatic inclusion of
* type information and configure a {@link PolymorphicTypeValidator} that handles the
* validation of class names.
*
* <p>
* <pre>
* ClassLoader loader = getClass().getClassLoader();
* JsonMapper mapper = JsonMapper.builder()
* .addModules(SecurityJacksonModules.getModules(loader))
* .build();
* </pre>
*
* If needed, you can add custom classes to the validation handling.
* <p>
* <pre>
* ClassLoader loader = getClass().getClassLoader();
* BasicPolymorphicTypeValidator.Builder builder = BasicPolymorphicTypeValidator.builder()
* .allowIfSubType(MyCustomType.class);
* JsonMapper mapper = JsonMapper.builder()
* .addModules(SecurityJacksonModules.getModules(loader, builder))
* .build();
* </pre>
*
* @author Sebastien Deleuze
* @author Jitendra Singh
* @since 7.0
*/
public final class SecurityJacksonModules {
private static final Log logger = LogFactory.getLog(SecurityJacksonModules.class);
private static final List<String> securityJacksonModuleClasses = Arrays.asList(
"org.springframework.security.jackson.CoreJacksonModule",
"org.springframework.security.web.jackson.WebJacksonModule",
"org.springframework.security.web.server.jackson.WebServerJacksonModule");
private static final String webServletJacksonModuleClass = "org.springframework.security.web.jackson.WebServletJacksonModule";
private static final String oauth2ClientJacksonModuleClass = "org.springframework.security.oauth2.client.jackson.OAuth2ClientJacksonModule";
private static final String oauth2AuthorizationServerJacksonModuleClass = "org.springframework.security.oauth2.server.authorization.jackson.OAuth2AuthorizationServerJacksonModule";
private static final String ldapJacksonModuleClass = "org.springframework.security.ldap.jackson.LdapJacksonModule";
private static final String saml2JacksonModuleClass = "org.springframework.security.saml2.jackson.Saml2JacksonModule";
private static final String casJacksonModuleClass = "org.springframework.security.cas.jackson.CasJacksonModule";
private static final boolean webServletPresent;
private static final boolean oauth2ClientPresent;
private static final boolean oauth2AuthorizationServerPresent;
private static final boolean ldapJacksonPresent;
private static final boolean saml2JacksonPresent;
private static final boolean casJacksonPresent;
static {
ClassLoader classLoader = SecurityJacksonModules.class.getClassLoader();
webServletPresent = ClassUtils.isPresent("jakarta.servlet.http.Cookie", classLoader);
oauth2ClientPresent = ClassUtils.isPresent("org.springframework.security.oauth2.client.OAuth2AuthorizedClient",
classLoader);
oauth2AuthorizationServerPresent = ClassUtils
.isPresent("org.springframework.security.oauth2.server.authorization.OAuth2Authorization", classLoader);
ldapJacksonPresent = ClassUtils.isPresent(ldapJacksonModuleClass, classLoader);
saml2JacksonPresent = ClassUtils.isPresent(saml2JacksonModuleClass, classLoader);
casJacksonPresent = ClassUtils.isPresent(casJacksonModuleClass, classLoader);
}
private SecurityJacksonModules() {
}
@SuppressWarnings("unchecked")
private static @Nullable SecurityJacksonModule loadAndGetInstance(String className, ClassLoader loader) {
try {
Class<? extends SecurityJacksonModule> securityModule = (Class<? extends SecurityJacksonModule>) ClassUtils
.forName(className, loader);
logger.debug(LogMessage.format("Loaded module %s, now registering", className));
return securityModule.getConstructor().newInstance();
}
catch (Exception ex) {
logger.debug(LogMessage.format("Cannot load module %s", className), ex);
}
return null;
}
/**
* Return the list of available security modules in classpath, enable automatic
* inclusion of type information and configure a default
* {@link PolymorphicTypeValidator} that handles the validation of class names.
* @param loader the ClassLoader to use
* @return List of available security modules in classpath
* @see #getModules(ClassLoader, BasicPolymorphicTypeValidator.Builder)
*/
public static List<JacksonModule> getModules(ClassLoader loader) {
return getModules(loader, null);
}
/**
* Return the list of available security modules in classpath, enable automatic
* inclusion of type information and configure a default
* {@link PolymorphicTypeValidator} customizable with the provided builder that
* handles the validation of class names.
* @param loader the ClassLoader to use
* @param typeValidatorBuilder the builder to configure custom types allowed in
* addition to Spring Security ones
* @return List of available security modules in classpath.
*/
public static List<JacksonModule> getModules(ClassLoader loader,
BasicPolymorphicTypeValidator.@Nullable Builder typeValidatorBuilder) {
List<JacksonModule> modules = new ArrayList<>();
for (String className : securityJacksonModuleClasses) {
addToModulesList(loader, modules, className);
}
if (webServletPresent) {
addToModulesList(loader, modules, webServletJacksonModuleClass);
}
if (oauth2ClientPresent) {
addToModulesList(loader, modules, oauth2ClientJacksonModuleClass);
}
if (oauth2AuthorizationServerPresent) {
addToModulesList(loader, modules, oauth2AuthorizationServerJacksonModuleClass);
}
if (ldapJacksonPresent) {
addToModulesList(loader, modules, ldapJacksonModuleClass);
}
if (saml2JacksonPresent) {
addToModulesList(loader, modules, saml2JacksonModuleClass);
}
if (casJacksonPresent) {
addToModulesList(loader, modules, casJacksonModuleClass);
}
applyPolymorphicTypeValidator(modules, typeValidatorBuilder);
return modules;
}
private static void applyPolymorphicTypeValidator(List<JacksonModule> modules,
BasicPolymorphicTypeValidator.@Nullable Builder typeValidatorBuilder) {
BasicPolymorphicTypeValidator.Builder builder = (typeValidatorBuilder != null) ? typeValidatorBuilder
: BasicPolymorphicTypeValidator.builder();
for (JacksonModule module : modules) {
if (module instanceof SecurityJacksonModule securityModule) {
securityModule.configurePolymorphicTypeValidator(builder);
}
}
modules.add(new SimpleModule() {
@Override
public void setupModule(SetupContext context) {
((MapperBuilder<?, ?>) context.getOwner()).activateDefaultTyping(builder.build(),
DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
}
});
}
/**
* @param loader the ClassLoader to use
* @param modules list of the modules to add
* @param className name of the class to instantiate
*/
private static void addToModulesList(ClassLoader loader, List<JacksonModule> modules, String className) {
SecurityJacksonModule module = loadAndGetInstance(className, loader);
if (module != null) {
modules.add(module);
}
}
}
@@ -0,0 +1,47 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.jackson;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
/**
* Jackson Mixin class helps in serialize/deserialize
* {@link org.springframework.security.core.authority.SimpleGrantedAuthority}.
*
* @author Sebastien Deleuze
* @author Jitendra Singh
* @since 7.0
* @see CoreJacksonModule
* @see SecurityJacksonModules
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.NONE,
getterVisibility = JsonAutoDetect.Visibility.PUBLIC_ONLY, isGetterVisibility = JsonAutoDetect.Visibility.NONE)
public abstract class SimpleGrantedAuthorityMixin {
/**
* Mixin Constructor.
* @param role the role
*/
@JsonCreator
public SimpleGrantedAuthorityMixin(@JsonProperty("authority") String role) {
}
}
@@ -0,0 +1,57 @@
/*
* 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.jackson;
import java.util.Collection;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.springframework.security.core.GrantedAuthority;
/**
* This is a Jackson mixin class helps in serialize/deserialize
* {@link org.springframework.security.authentication.AnonymousAuthenticationToken} class.
*
* @author Sebastien Deleuze
* @author Jitendra Singh
* @since 7.0
* @see CoreJacksonModule
* @see SecurityJacksonModules
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, isGetterVisibility = JsonAutoDetect.Visibility.NONE,
getterVisibility = JsonAutoDetect.Visibility.NONE, creatorVisibility = JsonAutoDetect.Visibility.ANY)
class TestingAuthenticationTokenMixin {
/**
* Constructor used by Jackson to create object of
* {@link org.springframework.security.authentication.AnonymousAuthenticationToken}.
* {@link org.springframework.security.authentication.AnonymousAuthenticationToken#AnonymousAuthenticationToken(String, Object, Collection)}
* @param principal the principal (typically a <code>UserDetails</code>)
* @param credentials the credentials
* @param authorities the authorities granted to the principal
*/
@JsonCreator
TestingAuthenticationTokenMixin(@JsonProperty("principal") Object principal,
@JsonProperty("credentials") Object credentials,
@JsonProperty("authorities") Collection<? extends GrantedAuthority> authorities) {
}
}
@@ -0,0 +1,81 @@
/*
* 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.jackson;
import java.util.Set;
import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonParser;
import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.ValueDeserializer;
import tools.jackson.databind.node.MissingNode;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
/**
* Custom Deserializer for {@link User} class. This is already registered with
* {@link UserMixin}. You can also use it directly with your mixin class.
*
* @author Sebastien Deleuze
* @author Jitendra Singh
* @since 7.0
* @see UserMixin
*/
class UserDeserializer extends ValueDeserializer<User> {
private static final TypeReference<Set<GrantedAuthority>> GRANTED_AUTHORITY_SET = new TypeReference<>() {
};
/**
* This method will create {@link User} object. It will ensure successful object
* creation even if password key is null in serialized json, because credentials may
* be removed from the {@link User} by invoking {@link User#eraseCredentials()}. In
* that case there won't be any password key in serialized json.
* @param jp the JsonParser
* @param ctxt the DeserializationContext
* @return the user
* @throws JacksonException if an error during JSON processing occurs
*/
@Override
public User deserialize(JsonParser jp, DeserializationContext ctxt) throws JacksonException {
JsonNode jsonNode = ctxt.readTree(jp);
JsonNode authoritiesNode = readJsonNode(jsonNode, "authorities");
Set<GrantedAuthority> authorities = ctxt.readTreeAsValue(authoritiesNode,
ctxt.getTypeFactory().constructType(GRANTED_AUTHORITY_SET));
JsonNode passwordNode = readJsonNode(jsonNode, "password");
String username = readJsonNode(jsonNode, "username").asString();
String password = (passwordNode.isMissingNode()) ? null : passwordNode.stringValue();
boolean enabled = readJsonNode(jsonNode, "enabled").asBoolean();
boolean accountNonExpired = readJsonNode(jsonNode, "accountNonExpired").asBoolean();
boolean credentialsNonExpired = readJsonNode(jsonNode, "credentialsNonExpired").asBoolean();
boolean accountNonLocked = readJsonNode(jsonNode, "accountNonLocked").asBoolean();
User result = new User(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked,
authorities);
if (passwordNode.asString(null) == null) {
result.eraseCredentials();
}
return result;
}
private JsonNode readJsonNode(JsonNode jsonNode, String field) {
return jsonNode.has(field) ? jsonNode.get(field) : MissingNode.getInstance();
}
}
@@ -0,0 +1,41 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.jackson;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import tools.jackson.databind.annotation.JsonDeserialize;
/**
* This mixin class helps in serialize/deserialize
* {@link org.springframework.security.core.userdetails.User}. This class also register a
* custom deserializer {@link UserDeserializer} to deserialize User object successfully.
*
* @author Sebastien Deleuze
* @author Jitendra Singh
* @since 7.0
* @see UserDeserializer
* @see CoreJacksonModule
* @see SecurityJacksonModules
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonDeserialize(using = UserDeserializer.class)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
abstract class UserMixin {
}
@@ -0,0 +1,105 @@
/*
* 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.jackson;
import java.util.List;
import org.jspecify.annotations.Nullable;
import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonParser;
import tools.jackson.core.exc.StreamReadException;
import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.DatabindException;
import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.ValueDeserializer;
import tools.jackson.databind.node.MissingNode;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
/**
* Custom deserializer for {@link UsernamePasswordAuthenticationToken}. At the time of
* deserialization it will invoke suitable constructor depending on the value of
* <b>authenticated</b> property. It will ensure that the token's state must not change.
*
* @author Sebastien Deleuze
* @author Jitendra Singh
* @author Greg Turnquist
* @author Onur Kagan Ozcan
* @since 7.0
* @see UsernamePasswordAuthenticationTokenMixin
*/
class UsernamePasswordAuthenticationTokenDeserializer extends ValueDeserializer<UsernamePasswordAuthenticationToken> {
private static final TypeReference<List<GrantedAuthority>> GRANTED_AUTHORITY_LIST = new TypeReference<>() {
};
/**
* This method construct {@link UsernamePasswordAuthenticationToken} object from
* serialized json.
* @param jp the JsonParser
* @param ctxt the DeserializationContext
* @return the user
* @throws JacksonException if an error during JSON processing occurs
*/
@Override
public UsernamePasswordAuthenticationToken deserialize(JsonParser jp, DeserializationContext ctxt)
throws JacksonException {
JsonNode jsonNode = ctxt.readTree(jp);
boolean authenticated = readJsonNode(jsonNode, "authenticated").asBoolean();
JsonNode principalNode = readJsonNode(jsonNode, "principal");
Object principal = getPrincipal(ctxt, principalNode);
JsonNode credentialsNode = readJsonNode(jsonNode, "credentials");
Object credentials = getCredentials(credentialsNode);
JsonNode authoritiesNode = readJsonNode(jsonNode, "authorities");
List<GrantedAuthority> authorities = ctxt.readTreeAsValue(authoritiesNode,
ctxt.getTypeFactory().constructType(GRANTED_AUTHORITY_LIST));
UsernamePasswordAuthenticationToken token = (!authenticated)
? UsernamePasswordAuthenticationToken.unauthenticated(principal, credentials)
: UsernamePasswordAuthenticationToken.authenticated(principal, credentials, authorities);
JsonNode detailsNode = readJsonNode(jsonNode, "details");
if (detailsNode.isNull() || detailsNode.isMissingNode()) {
token.setDetails(null);
}
else {
Object details = ctxt.readTreeAsValue(detailsNode, Object.class);
token.setDetails(details);
}
return token;
}
private @Nullable Object getCredentials(JsonNode credentialsNode) {
if (credentialsNode.isNull() || credentialsNode.isMissingNode()) {
return null;
}
return credentialsNode.asString();
}
private Object getPrincipal(DeserializationContext ctxt, JsonNode principalNode)
throws StreamReadException, DatabindException {
if (principalNode.isObject()) {
return ctxt.readTreeAsValue(principalNode, Object.class);
}
return principalNode.asString();
}
private JsonNode readJsonNode(JsonNode jsonNode, String field) {
return jsonNode.has(field) ? jsonNode.get(field) : MissingNode.getInstance();
}
}
@@ -0,0 +1,41 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.jackson;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import tools.jackson.databind.annotation.JsonDeserialize;
/**
* This mixin class is used to serialize / deserialize
* {@link org.springframework.security.authentication.UsernamePasswordAuthenticationToken}.
* This class register a custom deserializer
* {@link UsernamePasswordAuthenticationTokenDeserializer}.
*
* @author Sebastien Deleuze
* @author Jitendra Singh
* @since 7.0
* @see CoreJacksonModule
* @see SecurityJacksonModules
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
@JsonDeserialize(using = UsernamePasswordAuthenticationTokenDeserializer.class)
abstract class UsernamePasswordAuthenticationTokenMixin {
}
@@ -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 3+ serialization support.
*/
@NullMarked
package org.springframework.security.jackson;
import org.jspecify.annotations.NullMarked;
@@ -38,7 +38,12 @@ import com.fasterxml.jackson.databind.node.ArrayNode;
* @param <T> the type of the unmodifiable collection, such as {@link List} or
* {@link Set}.
* @author Hyunmin Choi
* @deprecated as of 7.0 in favor of
* {@code org.springframework.security.jackson.AbstractUnmodifiableCollectionDeserializer}
* based on Jackson 3
*/
@SuppressWarnings("removal")
@Deprecated(forRemoval = true)
abstract class AbstractUnmodifiableCollectionDeserializer<T> extends JsonDeserializer<T> {
@Override
@@ -43,11 +43,16 @@ import org.springframework.security.core.GrantedAuthority;
* @since 4.2
* @see CoreJackson2Module
* @see SecurityJackson2Modules
* @deprecated as of 7.0 in favor of
* {@code org.springframework.security.jackson.AnonymousAuthenticationTokenMixin} based on
* Jackson 3
*/
@SuppressWarnings("removal")
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, isGetterVisibility = JsonAutoDetect.Visibility.NONE,
getterVisibility = JsonAutoDetect.Visibility.NONE, creatorVisibility = JsonAutoDetect.Visibility.ANY)
@JsonIgnoreProperties(ignoreUnknown = true)
@Deprecated(forRemoval = true)
class AnonymousAuthenticationTokenMixin {
/**
@@ -38,9 +38,14 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
* @author Yannick Lombardi
* @since 5.0
* @see CoreJackson2Module
* @deprecated as of 7.0 in favor of
* {@code org.springframework.security.jackson.BadCredentialsExceptionMixin} based on
* Jackson 3
*/
@SuppressWarnings("removal")
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
@JsonIgnoreProperties(ignoreUnknown = true, value = { "cause", "stackTrace", "authenticationRequest" })
@Deprecated(forRemoval = true)
class BadCredentialsExceptionMixin {
/**
@@ -44,11 +44,14 @@ import org.springframework.security.core.userdetails.User;
* </pre> <b>Note: use {@link SecurityJackson2Modules#getModules(ClassLoader)} to get list
* of all security modules.</b>
*
* @author Jitendra Singh.
* @author Jitendra Singh
* @since 4.2
* @see SecurityJackson2Modules
* @deprecated as of 7.0 in favor of
* {@link org.springframework.security.jackson.CoreJacksonModule} based on Jackson 3
*/
@SuppressWarnings("serial")
@SuppressWarnings({ "serial", "removal" })
@Deprecated(forRemoval = true)
public class CoreJackson2Module extends SimpleModule {
public CoreJackson2Module() {
@@ -37,7 +37,13 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
* @since 7.0
* @see CoreJackson2Module
* @see SecurityJackson2Modules
* @deprecated as of 7.0 in favor of
* {@code org.springframework.security.jackson.FactorGrantedAuthorityMixin} based on
* Jackson 3
*
*/
@SuppressWarnings("removal")
@Deprecated(forRemoval = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.NONE,
getterVisibility = JsonAutoDetect.Visibility.PUBLIC_ONLY, isGetterVisibility = JsonAutoDetect.Visibility.NONE)
@@ -50,11 +50,16 @@ import org.springframework.security.core.GrantedAuthority;
* @since 4.2
* @see CoreJackson2Module
* @see SecurityJackson2Modules
* @deprecated as of 7.0 in favor of
* {@code org.springframework.security.jackson.RememberMeAuthenticationTokenMixin} based
* on Jackson 3
*/
@SuppressWarnings("removal")
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE, creatorVisibility = JsonAutoDetect.Visibility.ANY)
@JsonIgnoreProperties(ignoreUnknown = true)
@Deprecated(forRemoval = true)
class RememberMeAuthenticationTokenMixin {
/**
@@ -67,9 +67,12 @@ import org.springframework.util.ClassUtils;
* mapper.registerModule(new Saml2Jackson2Module());
* </pre>
*
* @author Jitendra Singh.
* @author Jitendra Singh
* @since 4.2
* @deprecated as of 7.0 in favor of
* {@link org.springframework.security.jackson.SecurityJacksonModules} based on Jackson 3
*/
@Deprecated(forRemoval = true)
public final class SecurityJackson2Modules {
private static final Log logger = LogFactory.getLog(SecurityJackson2Modules.class);
@@ -35,11 +35,16 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
* @since 4.2
* @see CoreJackson2Module
* @see SecurityJackson2Modules
* @deprecated as of 7.0 in favor of
* {@code org.springframework.security.jackson.SimpleGrantedAuthorityMixin} based on
* Jackson 3
*/
@SuppressWarnings("removal")
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.NONE,
getterVisibility = JsonAutoDetect.Visibility.PUBLIC_ONLY, isGetterVisibility = JsonAutoDetect.Visibility.NONE)
@JsonIgnoreProperties(ignoreUnknown = true)
@Deprecated(forRemoval = true)
public abstract class SimpleGrantedAuthorityMixin {
/**
@@ -28,7 +28,10 @@ import java.util.List;
* @author Hyunmin Choi
* @since 5.0.2
* @see UnmodifiableListMixin
* @deprecated as of 7.0
*/
@SuppressWarnings("removal")
@Deprecated(forRemoval = true)
class UnmodifiableListDeserializer extends AbstractUnmodifiableCollectionDeserializer<List> {
@Override
@@ -36,9 +36,12 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
* @see UnmodifiableListDeserializer
* @see CoreJackson2Module
* @see SecurityJackson2Modules
* @deprecated as of 7.0
*/
@SuppressWarnings("removal")
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
@JsonDeserialize(using = UnmodifiableListDeserializer.class)
@Deprecated(forRemoval = true)
class UnmodifiableListMixin {
/**
@@ -33,7 +33,10 @@ import com.fasterxml.jackson.databind.ObjectMapper;
* @author Ulrich Grave
* @since 5.7
* @see UnmodifiableMapMixin
* @deprecated as of 7.0
*/
@SuppressWarnings("removal")
@Deprecated(forRemoval = true)
class UnmodifiableMapDeserializer extends JsonDeserializer<Map<?, ?>> {
@Override
@@ -36,9 +36,12 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
* @see UnmodifiableMapDeserializer
* @see CoreJackson2Module
* @see SecurityJackson2Modules
* @deprecated as of 7.0
*/
@SuppressWarnings("removal")
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonDeserialize(using = UnmodifiableMapDeserializer.class)
@Deprecated(forRemoval = true)
class UnmodifiableMapMixin {
@JsonCreator
@@ -28,7 +28,10 @@ import java.util.Set;
* @author Hyunmin Choi
* @since 4.2
* @see UnmodifiableSetMixin
* @deprecated as of 7.0
*/
@SuppressWarnings("removal")
@Deprecated(forRemoval = true)
class UnmodifiableSetDeserializer extends AbstractUnmodifiableCollectionDeserializer<Set> {
@Override
@@ -36,9 +36,12 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
* @see UnmodifiableSetDeserializer
* @see CoreJackson2Module
* @see SecurityJackson2Modules
* @deprecated as of 7.0
*/
@SuppressWarnings("removal")
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
@JsonDeserialize(using = UnmodifiableSetDeserializer.class)
@Deprecated(forRemoval = true)
class UnmodifiableSetMixin {
/**

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