1
0
mirror of synced 2026-08-05 17:57:15 +00:00

Compare commits

..

78 Commits

Author SHA1 Message Date
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
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
466 changed files with 13913 additions and 1292 deletions
+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";
@@ -57,8 +57,8 @@ public @interface EnableGlobalMultiFactorAuthentication {
/**
* 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 })
* @see org.springframework.security.core.authority.FactorGrantedAuthority
*/
String[] authorities();
@@ -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();
}
@@ -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
@@ -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);
}
@@ -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,203 @@
/*
* 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 OAuth2AuthorizationServerJacksonModule} and
* {@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 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 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);
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 (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 {
/**
@@ -39,7 +39,11 @@ import org.springframework.security.core.userdetails.User;
* @author Jitendra Singh
* @since 4.2
* @see UserMixin
* @deprecated as of 7.0 in favor of
* {@code org.springframework.security.jackson.UserDeserializer} based on Jackson 3
*/
@SuppressWarnings("removal")
@Deprecated(forRemoval = true)
class UserDeserializer extends JsonDeserializer<User> {
private static final TypeReference<Set<SimpleGrantedAuthority>> SIMPLE_GRANTED_AUTHORITY_SET = new TypeReference<>() {
@@ -41,12 +41,16 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
* @see UserDeserializer
* @see CoreJackson2Module
* @see SecurityJackson2Modules
* @deprecated as of 7.0 in favor of
* {@code org.springframework.security.jackson.UserMixin} based on Jackson 3
*/
@SuppressWarnings("removal")
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
@JsonDeserialize(using = UserDeserializer.class)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
@JsonIgnoreProperties(ignoreUnknown = true)
@Deprecated(forRemoval = true)
abstract class UserMixin {
}
@@ -48,7 +48,12 @@ import org.springframework.security.core.GrantedAuthority;
* @author Onur Kagan Ozcan
* @since 4.2
* @see UsernamePasswordAuthenticationTokenMixin
* @deprecated as of 7.0 in favor of
* {@code org.springframework.security.jackson.UsernamePasswordAuthenticationTokenDeserializer}
* based on Jackson 3
*/
@SuppressWarnings("removal")
@Deprecated(forRemoval = true)
class UsernamePasswordAuthenticationTokenDeserializer extends JsonDeserializer<UsernamePasswordAuthenticationToken> {
private static final TypeReference<List<GrantedAuthority>> GRANTED_AUTHORITY_LIST = new TypeReference<>() {
@@ -71,7 +76,7 @@ class UsernamePasswordAuthenticationTokenDeserializer extends JsonDeserializer<U
throws IOException, JsonProcessingException {
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
JsonNode jsonNode = mapper.readTree(jp);
Boolean authenticated = readJsonNode(jsonNode, "authenticated").asBoolean();
boolean authenticated = readJsonNode(jsonNode, "authenticated").asBoolean();
JsonNode principalNode = readJsonNode(jsonNode, "principal");
Object principal = getPrincipal(mapper, principalNode);
JsonNode credentialsNode = readJsonNode(jsonNode, "credentials");
@@ -42,11 +42,16 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
* @since 4.2
* @see CoreJackson2Module
* @see SecurityJackson2Modules
* @deprecated as of 7.0 in favor of
* {@code org.springframework.security.jackson.UsernamePasswordAuthenticationTokenDeserializer}
* based on Jackson 3
*/
@SuppressWarnings("removal")
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class")
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
@JsonDeserialize(using = UsernamePasswordAuthenticationTokenDeserializer.class)
@Deprecated(forRemoval = true)
abstract class UsernamePasswordAuthenticationTokenMixin {
}
@@ -15,10 +15,7 @@
*/
/**
* Mix-in classes to add Jackson serialization support.
*
* @author Jitendra Singh
* @since 4.2
* Jackson 2 serialization support.
*/
@NullMarked
package org.springframework.security.jackson2;
@@ -0,0 +1,25 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.authentication;
public class NonBuildableAuthenticationToken extends TestingAuthenticationToken {
public NonBuildableAuthenticationToken(String user, String password, String... authorities) {
super(user, password, authorities);
}
}
@@ -34,9 +34,9 @@ import java.util.TreeSet;
import java.util.function.Supplier;
import java.util.stream.Stream;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.aop.Pointcut;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
@@ -340,13 +340,15 @@ public class AuthorizationAdvisorProxyFactoryTests {
assertThat(factory.proxy(35)).isEqualTo(35);
}
// TODO Find why callbacks property is serialized with Jackson 3, not with Jackson 2
// FIXME: https://github.com/spring-projects/spring-security/issues/18077
@Disabled("callbacks property is serialized with Jackson 3, not with Jackson 2")
@Test
public void serializeWhenAuthorizationProxyObjectThenOnlyIncludesProxiedProperties()
throws JsonProcessingException {
public void serializeWhenAuthorizationProxyObjectThenOnlyIncludesProxiedProperties() {
SecurityContextHolder.getContext().setAuthentication(this.admin);
AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
User user = proxy(factory, this.alan);
ObjectMapper mapper = new ObjectMapper();
JsonMapper mapper = new JsonMapper();
String serialized = mapper.writeValueAsString(user);
Map<String, Object> properties = mapper.readValue(serialized, Map.class);
assertThat(properties).hasSize(3).containsKeys("id", "firstName", "lastName");
@@ -0,0 +1,51 @@
/*
* 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 org.junit.jupiter.api.BeforeEach;
import tools.jackson.databind.json.JsonMapper;
import tools.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
/**
* @author Jitenra Singh
* @since 4.2
*/
public abstract class AbstractMixinTests {
protected JsonMapper mapper;
@BeforeEach
public void setup() {
ClassLoader loader = getClass().getClassLoader();
BasicPolymorphicTypeValidator.Builder builder = BasicPolymorphicTypeValidator.builder()
.allowIfSubType(
"org.springframework.security.jackson.UsernamePasswordAuthenticationTokenMixinTests$NonUserPrincipal");
this.mapper = JsonMapper.builder().addModules(SecurityJacksonModules.getModules(loader, builder)).build();
}
User createDefaultUser() {
return createUser("admin", "1234", "ROLE_USER");
}
User createUser(String username, String password, String authority) {
return new User(username, password, AuthorityUtils.createAuthorityList(authority));
}
}
@@ -0,0 +1,83 @@
/*
* 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 org.json.JSONException;
import org.junit.jupiter.api.Test;
import org.skyscreamer.jsonassert.JSONAssert;
import tools.jackson.databind.exc.ValueInstantiationException;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* @author Jitendra Singh
* @since 4.2
*/
public class AnonymousAuthenticationTokenMixinTests extends AbstractMixinTests {
private static final String HASH_KEY = "key";
// @formatter:off
private static final String ANONYMOUS_JSON = "{"
+ "\"@class\": \"org.springframework.security.authentication.AnonymousAuthenticationToken\", "
+ "\"details\": null,"
+ "\"principal\": " + UserDeserializerTests.USER_JSON + ","
+ "\"authenticated\": true, "
+ "\"keyHash\": " + HASH_KEY.hashCode() + ","
+ "\"authorities\": " + SimpleGrantedAuthorityMixinTests.AUTHORITIES_ARRAYLIST_JSON
+ "}";
// @formatter:on
@Test
public void serializeAnonymousAuthenticationTokenTest() throws JSONException {
User user = createDefaultUser();
AnonymousAuthenticationToken token = new AnonymousAuthenticationToken(HASH_KEY, user, user.getAuthorities());
String actualJson = this.mapper.writeValueAsString(token);
JSONAssert.assertEquals(ANONYMOUS_JSON, actualJson, true);
}
@Test
public void deserializeAnonymousAuthenticationTokenTest() {
AnonymousAuthenticationToken token = this.mapper.readValue(ANONYMOUS_JSON, AnonymousAuthenticationToken.class);
assertThat(token).isNotNull();
assertThat(token.getKeyHash()).isEqualTo(HASH_KEY.hashCode());
assertThat(token.getAuthorities()).isNotNull().hasSize(1).contains(new SimpleGrantedAuthority("ROLE_USER"));
}
@Test
public void deserializeAnonymousAuthenticationTokenWithoutAuthoritiesTest() {
String jsonString = "{\"@class\": \"org.springframework.security.authentication.AnonymousAuthenticationToken\", \"details\": null,"
+ "\"principal\": \"user\", \"authenticated\": true, \"keyHash\": " + HASH_KEY.hashCode() + ","
+ "\"authorities\": [\"java.util.ArrayList\", []]}";
assertThatExceptionOfType(ValueInstantiationException.class)
.isThrownBy(() -> this.mapper.readValue(jsonString, AnonymousAuthenticationToken.class));
}
@Test
public void serializeAnonymousAuthenticationTokenMixinAfterEraseCredentialTest() throws JSONException {
User user = createDefaultUser();
AnonymousAuthenticationToken token = new AnonymousAuthenticationToken(HASH_KEY, user, user.getAuthorities());
token.eraseCredentials();
String actualJson = this.mapper.writeValueAsString(token);
JSONAssert.assertEquals(ANONYMOUS_JSON.replace(UserDeserializerTests.USER_PASSWORD, "null"), actualJson, true);
}
}
@@ -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.io.IOException;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.json.JSONException;
import org.junit.jupiter.api.Test;
import org.skyscreamer.jsonassert.JSONAssert;
import org.springframework.security.authentication.BadCredentialsException;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Yannick Lombardi
* @since 5.0
*/
public class BadCredentialsExceptionMixinTests extends AbstractMixinTests {
// @formatter:off
private static final String EXCEPTION_JSON = "{"
+ "\"@class\": \"org.springframework.security.authentication.BadCredentialsException\","
+ "\"localizedMessage\": \"message\", "
+ "\"message\": \"message\", "
+ "\"suppressed\": [\"[Ljava.lang.Throwable;\",[]]"
+ "}";
// @formatter:on
@Test
public void serializeBadCredentialsExceptionMixinTest() throws JsonProcessingException, JSONException {
BadCredentialsException exception = new BadCredentialsException("message");
String serializedJson = this.mapper.writeValueAsString(exception);
JSONAssert.assertEquals(EXCEPTION_JSON, serializedJson, true);
}
@Test
public void deserializeBadCredentialsExceptionMixinTest() throws IOException {
BadCredentialsException exception = this.mapper.readValue(EXCEPTION_JSON, BadCredentialsException.class);
assertThat(exception).isNotNull();
assertThat(exception.getCause()).isNull();
assertThat(exception.getMessage()).isEqualTo("message");
assertThat(exception.getLocalizedMessage()).isEqualTo("message");
}
}
@@ -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.time.Instant;
import org.json.JSONException;
import org.junit.jupiter.api.Test;
import org.skyscreamer.jsonassert.JSONAssert;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.FactorGrantedAuthority;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Rob Winch
* @since 7.0
*/
class FactorGrantedAuthorityMixinTests extends AbstractMixinTests {
// @formatter:off
public static final String AUTHORITY_JSON = "{\"@class\": \"org.springframework.security.core.authority.FactorGrantedAuthority\", \"authority\": \"FACTOR_PASSWORD\", \"issuedAt\": 1759177143.043000000 }";
private Instant issuedAt = Instant.ofEpochMilli(1759177143043L);
// @formatter:on
@Test
void serializeSimpleGrantedAuthorityTest() throws JSONException {
GrantedAuthority authority = FactorGrantedAuthority.withAuthority("FACTOR_PASSWORD")
.issuedAt(this.issuedAt)
.build();
String serializeJson = this.mapper.writeValueAsString(authority);
JSONAssert.assertEquals(AUTHORITY_JSON, serializeJson, true);
}
@Test
void deserializeGrantedAuthorityTest() {
FactorGrantedAuthority authority = (FactorGrantedAuthority) this.mapper.readValue(AUTHORITY_JSON, Object.class);
assertThat(authority).isNotNull();
assertThat(authority.getAuthority()).isEqualTo("FACTOR_PASSWORD");
assertThat(authority.getIssuedAt()).isEqualTo(this.issuedAt);
}
}

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