Compare commits

...

186 Commits

Author SHA1 Message Date
dependabot[bot]
6d6552a602 Bump org-aspectj from 1.9.25 to 1.9.25.1
Bumps `org-aspectj` from 1.9.25 to 1.9.25.1.

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-19 17:13:40 -06:00
dependabot[bot]
a259e49380 Bump org.apache.maven:maven-resolver-provider from 3.9.11 to 3.9.12
Bumps org.apache.maven:maven-resolver-provider from 3.9.11 to 3.9.12.

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-19 17:00:24 -06:00
dependabot[bot]
d5b135ad0f Bump org.springframework.ldap:spring-ldap-core from 4.0.0 to 4.0.1
Bumps [org.springframework.ldap:spring-ldap-core](https://github.com/spring-projects/spring-ldap) from 4.0.0 to 4.0.1.
- [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/4.0.0...4.0.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-19 16:58:30 -06:00
dependabot[bot]
5ca0d8027d Bump org-apache-maven-resolver from 1.9.24 to 1.9.25
Bumps `org-apache-maven-resolver` from 1.9.24 to 1.9.25.

Updates `org.apache.maven.resolver:maven-resolver-connector-basic` from 1.9.24 to 1.9.25
- [Release notes](https://github.com/apache/maven-resolver/releases)
- [Commits](https://github.com/apache/maven-resolver/compare/maven-resolver-1.9.24...maven-resolver-1.9.25)

Updates `org.apache.maven.resolver:maven-resolver-impl` from 1.9.24 to 1.9.25
- [Release notes](https://github.com/apache/maven-resolver/releases)
- [Commits](https://github.com/apache/maven-resolver/compare/maven-resolver-1.9.24...maven-resolver-1.9.25)

Updates `org.apache.maven.resolver:maven-resolver-transport-http` from 1.9.24 to 1.9.25

---
updated-dependencies:
- dependency-name: org.apache.maven.resolver:maven-resolver-connector-basic
  dependency-version: 1.9.25
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.apache.maven.resolver:maven-resolver-impl
  dependency-version: 1.9.25
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.apache.maven.resolver:maven-resolver-transport-http
  dependency-version: 1.9.25
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-19 16:57:59 -06:00
github-actions[bot]
ac9c0a4313 Update Antora Spring UI to v0.4.25 2025-12-19 16:57:20 -06:00
Robert Winch
8a3e6a8fda
Merge branch '6.5.x' 2025-12-19 16:53:27 -06:00
Robert Winch
811be0a927
Bump org.springframework.data:spring-data-bom from 2024.1.12 to 2024.1.13 2025-12-19 16:52:57 -06:00
Robert Winch
40e11752e0
Bump ch.qos.logback:logback-classic from 1.5.21 to 1.5.22 2025-12-19 16:51:50 -06:00
Robert Winch
bc8d630bbc
Bump org.springframework.ldap:spring-ldap-core from 3.2.15 to 3.2.16 2025-12-19 16:51:46 -06:00
Robert Winch
861c60a28f
Bump org.springframework:spring-framework-bom from 6.2.14 to 6.2.15 2025-12-19 16:51:39 -06:00
dependabot[bot]
0514ee4cc5
Bump org-aspectj from 1.9.25 to 1.9.25.1
Bumps `org-aspectj` from 1.9.25 to 1.9.25.1.

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-17 03:15:05 +00:00
dependabot[bot]
9fd6d54268
Bump org.springframework:spring-framework-bom from 6.2.14 to 6.2.15
Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 6.2.14 to 6.2.15.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v6.2.14...v6.2.15)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-16 03:16:05 +00:00
dependabot[bot]
3f04f42abb
Bump org.springframework.ldap:spring-ldap-core from 3.2.15 to 3.2.16
Bumps [org.springframework.ldap:spring-ldap-core](https://github.com/spring-projects/spring-ldap) from 3.2.15 to 3.2.16.
- [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.15...3.2.16)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-16 03:15:51 +00:00
dependabot[bot]
f585461427
Bump ch.qos.logback:logback-classic from 1.5.21 to 1.5.22
Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.5.21 to 1.5.22.
- [Release notes](https://github.com/qos-ch/logback/releases)
- [Commits](https://github.com/qos-ch/logback/compare/v_1.5.21...v_1.5.22)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-16 03:15:45 +00:00
dependabot[bot]
a155c035e1
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.12 to 2024.1.13.
- [Release notes](https://github.com/spring-projects/spring-data-bom/releases)
- [Commits](https://github.com/spring-projects/spring-data-bom/compare/2024.1.12...2024.1.13)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-16 03:15:13 +00:00
github-actions[bot]
9095a1bffd Next development version 2025-12-15 20:58:49 +00:00
github-actions[bot]
9d08114c58 Release 7.0.2 2025-12-15 20:23:38 +00:00
Josh Cummings
0155d4a345 Restore Check for DispatcherServlet on Classpath
Closes gh-18315
2025-12-15 12:18:22 -07:00
github-actions[bot]
29ad1e6b07 Next development version 2025-12-15 18:22:29 +00:00
github-actions[bot]
8651868708 Release 7.0.1 2025-12-15 17:52:40 +00:00
dependabot[bot]
5732f39da7 Bump ch.qos.logback:logback-classic from 1.5.21 to 1.5.22
Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.5.21 to 1.5.22.
- [Release notes](https://github.com/qos-ch/logback/releases)
- [Commits](https://github.com/qos-ch/logback/compare/v_1.5.21...v_1.5.22)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 11:46:42 -06:00
dependabot[bot]
8bfa849a9d Bump org.springframework.data:spring-data-bom from 2025.1.0 to 2025.1.1
Bumps [org.springframework.data:spring-data-bom](https://github.com/spring-projects/spring-data-bom) from 2025.1.0 to 2025.1.1.
- [Release notes](https://github.com/spring-projects/spring-data-bom/releases)
- [Commits](https://github.com/spring-projects/spring-data-bom/compare/2025.1.0...2025.1.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 11:32:40 -06:00
dependabot[bot]
e033086ab0 Bump org.springframework:spring-framework-bom from 7.0.1 to 7.0.2
Includes fixes for Breaking Changes in Spring Framework 7.0.2:

- spring-projects/spring-framework#35916
- spring-projects/spring-framework#35947

Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 7.0.1 to 7.0.2.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v7.0.1...v7.0.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 11:25:19 -06:00
Josh Cummings
964fcac086 Polish Tests
Issue gh-18269

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2025-12-15 09:43:07 -07:00
Ziqin Wang
1d1b3ff797 Fix "typ" header value in NimbusJwtEncoder-encoded JWT
Closes gh-18269

Signed-off-by: Ziqin Wang <ziqin@wangziqin.net>
2025-12-15 09:43:07 -07:00
Ziqin Wang
c8898f91fc Test NimbusJwtEncoder & NimbusJwtDecoder symmetrically
This test encodes an JWT with NimbusJwtEncoder, and then decodes it with
NimbusJwtDecoder.

This test will fail when NimbusJwtEncoder emits a JWT with a wrong `typ'
parameter in the header, as NimbusJwtDecoder validates the JWT with
JwtTypeValidator by default.  It may be beneficial for finding out other
similiar bugs too.

Signed-off-by: Ziqin Wang <ziqin@wangziqin.net>
2025-12-15 09:43:07 -07:00
Josh Cummings
dbf93acb05 Check for spring-security-web on Classpath
This commit refines the check for adding AuthorizationWebProxyConfiguration
to the application context. The web-based authorization proxy support is intended
for applying Spring Security Method Security primitives to Spring Web components;
as such, this implies a dependency on Spring Security Web.

Closes gh-18307
2025-12-15 09:18:47 -07:00
Josh Cummings
ae5673b7a8 Merge branch '6.5.x' 2025-12-15 09:05:50 -07:00
Josh Cummings
765abe534e Add Missing Migration Pages to Side Navigation
Closes gh-18313
2025-12-15 09:05:06 -07:00
Josh Cummings
afb0c59875 Add request-matcher XML Migration Steps
Closes gh-18211
2025-12-15 09:05:06 -07:00
dependabot[bot]
d5beb513cd Bump com.unboundid:unboundid-ldapsdk from 7.0.3 to 7.0.4
Bumps [com.unboundid:unboundid-ldapsdk](https://github.com/pingidentity/ldapsdk) from 7.0.3 to 7.0.4.
- [Release notes](https://github.com/pingidentity/ldapsdk/releases)
- [Changelog](https://github.com/pingidentity/ldapsdk/blob/master/docs/release-notes.html)
- [Commits](https://github.com/pingidentity/ldapsdk/commits)

---
updated-dependencies:
- dependency-name: com.unboundid:unboundid-ldapsdk
  dependency-version: 7.0.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 09:58:07 -06:00
Rob Winch
d6a2603e85
Bump io.mockk:mockk from 1.14.6 to 1.14.7 2025-12-15 09:56:57 -06:00
Rob Winch
a4810b7e15
Bump io.micrometer:micrometer-observation from 1.14.13 to 1.14.14 2025-12-15 09:55:21 -06:00
Rob Winch
054f2e9a87
Bump io.projectreactor:reactor-bom from 2025.0.0 to 2025.0.1 2025-12-15 09:55:18 -06:00
Rob Winch
00c7a5b201
Merge branch '6.5.x' 2025-12-15 09:53:06 -06:00
Rob Winch
310f82170f
Bump io.mockk:mockk from 1.14.6 to 1.14.7 2025-12-15 09:52:37 -06:00
Rob Winch
be2f2ec600
Bump io.micrometer:micrometer-observation from 1.14.13 to 1.14.14 2025-12-15 09:50:28 -06:00
Rob Winch
1bc90b5fd0
Bump org-apache-maven-resolver from 1.9.24 to 1.9.25 2025-12-15 09:50:25 -06:00
Rob Winch
d2dd0fe5f6
Bump io.projectreactor:reactor-bom from 2024.0.12 to 2024.0.13 2025-12-15 09:50:23 -06:00
Rob Winch
7a85bf481a
Bump org.hibernate.orm:hibernate-core from 6.6.38.Final to 6.6.39.Final 2025-12-15 09:50:21 -06:00
Rob Winch
af960abe2d
Merge branch '6.4.x' into 6.5.x 2025-12-15 09:49:52 -06:00
Rob Winch
b7b859cd9a
Bump org-apache-maven-resolver from 1.9.24 to 1.9.25 2025-12-15 09:46:59 -06:00
Rob Winch
b83f682154
Bump io.projectreactor:reactor-bom from 2024.0.12 to 2024.0.13 2025-12-15 09:46:56 -06:00
Rob Winch
aca1643284
Bump ch.qos.logback:logback-classic from 1.5.21 to 1.5.22 2025-12-15 09:46:54 -06:00
dependabot[bot]
0c9c152a31
Bump org.hibernate.orm:hibernate-core from 6.6.38.Final to 6.6.39.Final
Bumps [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm) from 6.6.38.Final to 6.6.39.Final.
- [Release notes](https://github.com/hibernate/hibernate-orm/releases)
- [Changelog](https://github.com/hibernate/hibernate-orm/blob/6.6.39/changelog.txt)
- [Commits](https://github.com/hibernate/hibernate-orm/compare/6.6.38...6.6.39)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 03:16:38 +00:00
dependabot[bot]
cf2114e36e
Bump org.springframework:spring-framework-bom from 6.2.14 to 6.2.15
Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 6.2.14 to 6.2.15.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v6.2.14...v6.2.15)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-12 03:15:32 +00:00
dependabot[bot]
ecd17a9ee0
Bump ch.qos.logback:logback-classic from 1.5.21 to 1.5.22
Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.5.21 to 1.5.22.
- [Release notes](https://github.com/qos-ch/logback/releases)
- [Commits](https://github.com/qos-ch/logback/compare/v_1.5.21...v_1.5.22)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-12 03:15:25 +00:00
dependabot[bot]
2a763578f5
Bump io.projectreactor:reactor-bom from 2024.0.12 to 2024.0.13
Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2024.0.12 to 2024.0.13.
- [Release notes](https://github.com/reactor/reactor/releases)
- [Commits](https://github.com/reactor/reactor/compare/2024.0.12...2024.0.13)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-10 03:19:03 +00:00
dependabot[bot]
e978d4bf3d
Bump org-apache-maven-resolver from 1.9.24 to 1.9.25
Bumps `org-apache-maven-resolver` from 1.9.24 to 1.9.25.

Updates `org.apache.maven.resolver:maven-resolver-connector-basic` from 1.9.24 to 1.9.25
- [Release notes](https://github.com/apache/maven-resolver/releases)
- [Commits](https://github.com/apache/maven-resolver/compare/maven-resolver-1.9.24...maven-resolver-1.9.25)

Updates `org.apache.maven.resolver:maven-resolver-impl` from 1.9.24 to 1.9.25
- [Release notes](https://github.com/apache/maven-resolver/releases)
- [Commits](https://github.com/apache/maven-resolver/compare/maven-resolver-1.9.24...maven-resolver-1.9.25)

Updates `org.apache.maven.resolver:maven-resolver-transport-http` from 1.9.24 to 1.9.25

---
updated-dependencies:
- dependency-name: org.apache.maven.resolver:maven-resolver-connector-basic
  dependency-version: 1.9.25
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.apache.maven.resolver:maven-resolver-impl
  dependency-version: 1.9.25
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.apache.maven.resolver:maven-resolver-transport-http
  dependency-version: 1.9.25
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-10 03:18:11 +00:00
dependabot[bot]
ef5cdb50cc
Bump io.projectreactor:reactor-bom from 2024.0.12 to 2024.0.13
Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2024.0.12 to 2024.0.13.
- [Release notes](https://github.com/reactor/reactor/releases)
- [Commits](https://github.com/reactor/reactor/compare/2024.0.12...2024.0.13)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-10 03:14:23 +00:00
dependabot[bot]
b2e2d74cab
Bump org-apache-maven-resolver from 1.9.24 to 1.9.25
Bumps `org-apache-maven-resolver` from 1.9.24 to 1.9.25.

Updates `org.apache.maven.resolver:maven-resolver-connector-basic` from 1.9.24 to 1.9.25
- [Release notes](https://github.com/apache/maven-resolver/releases)
- [Commits](https://github.com/apache/maven-resolver/compare/maven-resolver-1.9.24...maven-resolver-1.9.25)

Updates `org.apache.maven.resolver:maven-resolver-impl` from 1.9.24 to 1.9.25
- [Release notes](https://github.com/apache/maven-resolver/releases)
- [Commits](https://github.com/apache/maven-resolver/compare/maven-resolver-1.9.24...maven-resolver-1.9.25)

Updates `org.apache.maven.resolver:maven-resolver-transport-http` from 1.9.24 to 1.9.25

---
updated-dependencies:
- dependency-name: org.apache.maven.resolver:maven-resolver-connector-basic
  dependency-version: 1.9.25
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.apache.maven.resolver:maven-resolver-impl
  dependency-version: 1.9.25
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.apache.maven.resolver:maven-resolver-transport-http
  dependency-version: 1.9.25
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-10 03:14:06 +00:00
dependabot[bot]
c3a03a4834
Bump io.projectreactor:reactor-bom from 2025.0.0 to 2025.0.1
Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2025.0.0 to 2025.0.1.
- [Release notes](https://github.com/reactor/reactor/releases)
- [Commits](https://github.com/reactor/reactor/compare/2025.0.0...2025.0.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-10 03:08:36 +00:00
Andrey Litvitski
0d5f42f852 Remove requireProofKey warning for non-auth-code flows
The warning is unnecessary since PKCE only applies to authorization_code
flow and the code already corrects this silently.

Closes: gh-18221

Signed-off-by: Andrey Litvitski <andrey1010102008@gmail.com>
2025-12-09 15:29:00 -05:00
Josh Cummings
4d9d40ead8 Update validateType JavaDoc
Closes gh-18227

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2025-12-09 09:07:30 -07:00
dependabot[bot]
568378268e
Bump io.micrometer:micrometer-observation from 1.14.13 to 1.14.14
Bumps [io.micrometer:micrometer-observation](https://github.com/micrometer-metrics/micrometer) from 1.14.13 to 1.14.14.
- [Release notes](https://github.com/micrometer-metrics/micrometer/releases)
- [Commits](https://github.com/micrometer-metrics/micrometer/compare/v1.14.13...v1.14.14)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-09 03:14:47 +00:00
dependabot[bot]
59ffb6f6d1
Bump io.micrometer:micrometer-observation from 1.14.13 to 1.14.14
Bumps [io.micrometer:micrometer-observation](https://github.com/micrometer-metrics/micrometer) from 1.14.13 to 1.14.14.
- [Release notes](https://github.com/micrometer-metrics/micrometer/releases)
- [Commits](https://github.com/micrometer-metrics/micrometer/compare/v1.14.13...v1.14.14)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-09 03:07:04 +00:00
dependabot[bot]
cf8d6a2ee7
Bump io.mockk:mockk from 1.14.6 to 1.14.7
Bumps [io.mockk:mockk](https://github.com/mockk/mockk) from 1.14.6 to 1.14.7.
- [Release notes](https://github.com/mockk/mockk/releases)
- [Commits](https://github.com/mockk/mockk/compare/1.14.6...1.14.7)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-08 03:18:00 +00:00
dependabot[bot]
fd0de94c1b
Bump io.mockk:mockk from 1.14.6 to 1.14.7
Bumps [io.mockk:mockk](https://github.com/mockk/mockk) from 1.14.6 to 1.14.7.
- [Release notes](https://github.com/mockk/mockk/releases)
- [Commits](https://github.com/mockk/mockk/compare/1.14.6...1.14.7)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-08 03:09:21 +00:00
Joe Grandja
29b9dc6f04 Register runtime hints for authorization server
Issue gh-18251
2025-12-04 15:30:39 -05:00
kucoll
10edc14d7e Fix typo in AnnotationTemplateExpressionDefaults
The AnnotationTemplateExpressionDeafults was wrong,and right is  AnnotationTemplateExpressionDefaults

Signed-off-by: kucoll <kucoll@163.com>
2025-12-02 17:26:34 -06:00
kucoll
7503d8018d Fix typo in AnnotationTemplateExpressionDefaults
The AnnotationTemplateExpressionDeafults was wrong,and right is  AnnotationTemplateExpressionDefaults

Signed-off-by: kucoll <kucoll@163.com>
2025-12-02 17:22:12 -06:00
Joe Grandja
c53e66a217 OAuth2AuthorizationEndpointFilter is applied after AuthorizationFilter
Closes gh-18251
2025-12-02 08:49:49 -05:00
Soumik Sarker
244b5a16be Added test scope for NPE in RequestMethod
Signed-off-by: Soumik Sarker <ronodhirsoumik@gmail.com>
2025-12-01 18:06:42 -06:00
Guillaume Husta
1ce73dd45a docs: Fix example in Custom DSLs for http.csrf()
It should use lambda dsl to compile

Signed-off-by: Guillaume Husta <guillaume.husta@gmail.com>
2025-12-01 18:02:41 -06:00
Guillaume Husta
bb7fcb27ef docs: Fix example in MyCustomDsl to remove throws Exception
In `init` and `configure`, throws Exception has been removed in the super interface `SecurityConfigurer`, since Spring Security 7.0.
This change is the consequence of https://github.com/spring-projects/spring-security/issues/17957

Signed-off-by: Guillaume Husta <guillaume.husta@gmail.com>
2025-12-01 17:59:07 -06:00
sach429
19cbd9c570 Update OAuth2 Client to OAuth2 Resource Server
Fix section title to match the corresponding example

Signed-off-by: sach429 <satrajit.acharya@gmail.com>
2025-12-01 17:42:28 -06:00
dependabot[bot]
a20724d30b Bump tools.jackson:jackson-bom from 3.0.2 to 3.0.3
Bumps [tools.jackson:jackson-bom](https://github.com/FasterXML/jackson-bom) from 3.0.2 to 3.0.3.
- [Commits](https://github.com/FasterXML/jackson-bom/compare/jackson-bom-3.0.2...jackson-bom-3.0.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-01 17:30:13 -06:00
dependabot[bot]
3ca59af04f Bump com.fasterxml.jackson:jackson-bom from 2.20.0 to 2.20.1
Bumps [com.fasterxml.jackson:jackson-bom](https://github.com/FasterXML/jackson-bom) from 2.20.0 to 2.20.1.
- [Commits](https://github.com/FasterXML/jackson-bom/compare/jackson-bom-2.20.0...jackson-bom-2.20.1)

---
updated-dependencies:
- dependency-name: com.fasterxml.jackson:jackson-bom
  dependency-version: 2.20.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-01 17:19:27 -06:00
L33gn21
b37c5584f9 Fix broken link to Spring Boot docs
Signed-off-by: L33gn21 <l33gn21@gmail.com>
2025-12-01 16:52:43 -06:00
dependabot[bot]
09e80aafe8 Bump antora from 3.2.0-alpha.10 to 3.2.0-alpha.11 in /docs
Bumps [antora](https://gitlab.com/antora/antora) from 3.2.0-alpha.10 to 3.2.0-alpha.11.
- [Changelog](https://gitlab.com/antora/antora/blob/main/CHANGELOG.adoc)
- [Commits](https://gitlab.com/antora/antora/compare/v3.2.0-alpha.10...v3.2.0-alpha.11)

---
updated-dependencies:
- dependency-name: antora
  dependency-version: 3.2.0-alpha.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-01 14:55:39 -06:00
Rob Winch
43ca71d7b8
Remove 6.3.x from dependabot
6.3.x is no longer supported in OSS

Signed-off-by: Rob Winch <362503+rwinch@users.noreply.github.com>
2025-12-01 14:55:16 -06:00
Rob Winch
3ecd4f3fde
Bump org.springframework:spring-framework-bom from 7.0.0 to 7.0.1 2025-12-01 14:39:26 -06:00
Rob Winch
6cd43d38d5
Bump ch.qos.logback:logback-classic from 1.5.20 to 1.5.21 2025-12-01 14:39:24 -06:00
Rob Winch
3fbe972323
Bump org.apache.kerby:kerb-simplekdc from 2.1.0 to 2.1.1 2025-12-01 14:39:22 -06:00
dependabot[bot]
e582691996 Bump tools.jackson:jackson-bom from 3.0.1 to 3.0.2
Bumps [tools.jackson:jackson-bom](https://github.com/FasterXML/jackson-bom) from 3.0.1 to 3.0.2.
- [Commits](https://github.com/FasterXML/jackson-bom/compare/jackson-bom-3.0.1...jackson-bom-3.0.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-01 14:38:46 -06:00
Rob Winch
0288b5e345
Merge branch '6.5.x' 2025-12-01 14:36:54 -06:00
Rob Winch
1cf75e710e
Bump org.hibernate.orm:hibernate-core from 6.6.36.Final to 6.6.38.Final 2025-12-01 14:36:28 -06:00
Rob Winch
2e55e0cdb3
Merge branch '6.4.x' into 6.5.x 2025-12-01 14:36:17 -06:00
dependabot[bot]
e010d5e689
Bump org.hibernate.orm:hibernate-core from 6.6.36.Final to 6.6.38.Final
Bumps [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm) from 6.6.36.Final to 6.6.38.Final.
- [Release notes](https://github.com/hibernate/hibernate-orm/releases)
- [Changelog](https://github.com/hibernate/hibernate-orm/blob/6.6.38/changelog.txt)
- [Commits](https://github.com/hibernate/hibernate-orm/compare/6.6.36...6.6.38)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-01 03:35:03 +00:00
dependabot[bot]
d3a55291bc
Bump org.hibernate.orm:hibernate-core from 6.6.36.Final to 6.6.38.Final
Bumps [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm) from 6.6.36.Final to 6.6.38.Final.
- [Release notes](https://github.com/hibernate/hibernate-orm/releases)
- [Changelog](https://github.com/hibernate/hibernate-orm/blob/6.6.38/changelog.txt)
- [Commits](https://github.com/hibernate/hibernate-orm/compare/6.6.36...6.6.38)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-01 03:26:20 +00:00
dependabot[bot]
c241ec5f03
Bump org.apache.kerby:kerb-simplekdc from 2.1.0 to 2.1.1
Bumps org.apache.kerby:kerb-simplekdc from 2.1.0 to 2.1.1.

---
updated-dependencies:
- dependency-name: org.apache.kerby:kerb-simplekdc
  dependency-version: 2.1.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-24 03:15:27 +00:00
dependabot[bot]
8d799c3c6e
Bump ch.qos.logback:logback-classic from 1.5.20 to 1.5.21
Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.5.20 to 1.5.21.
- [Release notes](https://github.com/qos-ch/logback/releases)
- [Commits](https://github.com/qos-ch/logback/compare/v_1.5.20...v_1.5.21)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-24 03:15:02 +00:00
dependabot[bot]
9aa729f89a
Bump org.springframework:spring-framework-bom from 7.0.0 to 7.0.1
Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 7.0.0 to 7.0.1.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v7.0.0...v7.0.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-24 03:14:22 +00:00
Rob Winch
9126aaf19b
Merge branch '6.5.x' 2025-11-21 10:36:55 -06:00
Rob Winch
eb5f9e0305
Merge branch '6.4.x' into 6.5.x 2025-11-21 10:36:43 -06:00
Peter Potrowl
d84d0ca22e Fix typo in ldap.adoc
Signed-off-by: Peter Potrowl <peter.potrowl@gmail.com>
2025-11-21 10:33:48 -06:00
Peter Potrowl
f1793f5047 Fix typo in passkeys.adoc
Signed-off-by: Peter Potrowl <peter.potrowl@gmail.com>
2025-11-21 10:33:48 -06:00
Peter Potrowl
4b227649f0 Fix typo in ldap.adoc
Signed-off-by: Peter Potrowl <peter.potrowl@gmail.com>
2025-11-21 10:28:47 -06:00
Peter Potrowl
cfc27f8cc3 Fix typo in passkeys.adoc
Signed-off-by: Peter Potrowl <peter.potrowl@gmail.com>
2025-11-21 10:28:47 -06:00
dependabot[bot]
2f583fc15f Bump js-yaml from 4.1.0 to 4.1.1 in /javascript
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.1.0 to 4.1.1.
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/4.1.0...4.1.1)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 4.1.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-21 10:27:09 -06:00
Rob Winch
e584196c1d
Fix typos in documentation depenendencies->dependencies
Closes gh-18197
2025-11-21 10:17:49 -06:00
Peter Potrowl
5baff27ffb
Fix typo in ldap.adoc
Signed-off-by: Peter Potrowl <peter.potrowl@gmail.com>
2025-11-21 10:12:20 -06:00
Peter Potrowl
39aaf25b60
Fix typo in passkeys.adoc
Signed-off-by: Peter Potrowl <peter.potrowl@gmail.com>
2025-11-21 10:12:20 -06:00
Rob Winch
4327de8667
Bump org-aspectj from 1.9.24 to 1.9.25 2025-11-21 09:57:17 -06:00
Rob Winch
0a7ff3a18a
Bump io.spring.gradle:spring-security-release-plugin from 1.0.10 to 1.0.13 2025-11-21 09:57:15 -06:00
Rob Winch
f77c78b04a
Bump org.junit:junit-bom from 6.0.0 to 6.0.1 2025-11-21 09:57:13 -06:00
Rob Winch
074c1c038f
Bump io.micrometer:micrometer-observation from 1.14.12 to 1.14.13 2025-11-21 09:57:10 -06:00
Rob Winch
7abfcd3963
Merge branch '6.5.x' 2025-11-21 09:57:00 -06:00
Rob Winch
86d630265c
Bump ch.qos.logback:logback-classic from 1.5.20 to 1.5.21 2025-11-21 09:56:32 -06:00
Rob Winch
7b78b0c723
Bump org.hibernate.orm:hibernate-core from 6.6.34.Final to 6.6.36.Final 2025-11-21 09:56:30 -06:00
Rob Winch
e6a4ee03ff
Bump org.springframework:spring-framework-bom from 6.2.13 to 6.2.14 2025-11-21 09:56:29 -06:00
Rob Winch
5cd3f535cf
Merge branch '6.4.x' into 6.5.x 2025-11-21 09:56:08 -06:00
Rob Winch
8ecc4a9157
Bump org.hibernate.orm:hibernate-core from 6.6.34.Final to 6.6.36.Final 2025-11-21 09:55:00 -06:00
Rob Winch
af33ace82f
Bump ch.qos.logback:logback-classic from 1.5.20 to 1.5.21 2025-11-21 09:54:58 -06:00
dependabot[bot]
7a614a535e
Bump org.springframework:spring-framework-bom from 6.2.13 to 6.2.14
Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 6.2.13 to 6.2.14.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v6.2.13...v6.2.14)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-21 03:17:48 +00:00
dependabot[bot]
ddebff043d
Bump org.springframework:spring-framework-bom from 6.2.13 to 6.2.14
Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 6.2.13 to 6.2.14.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v6.2.13...v6.2.14)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-21 03:13:00 +00:00
Josh Cummings
de062c6724
Merge branch '6.5.x' 2025-11-19 15:27:11 -07:00
Josh Cummings
a79354ead9
Merge branch '6.4.x' into 6.5.x 2025-11-19 15:25:48 -07:00
Josh Cummings
29c63bee69
Stop Deploying JavaDoc Outside of Antora
Closes gh-18198
2025-11-19 15:23:50 -07:00
dependabot[bot]
ee7eb68471
Bump org.hibernate.orm:hibernate-core from 6.6.34.Final to 6.6.36.Final
Bumps [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm) from 6.6.34.Final to 6.6.36.Final.
- [Release notes](https://github.com/hibernate/hibernate-orm/releases)
- [Changelog](https://github.com/hibernate/hibernate-orm/blob/6.6.36/changelog.txt)
- [Commits](https://github.com/hibernate/hibernate-orm/compare/6.6.34...6.6.36)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-18 03:20:36 +00:00
dependabot[bot]
f187e9a31d
Bump ch.qos.logback:logback-classic from 1.5.20 to 1.5.21
Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.5.20 to 1.5.21.
- [Release notes](https://github.com/qos-ch/logback/releases)
- [Commits](https://github.com/qos-ch/logback/compare/v_1.5.20...v_1.5.21)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-18 03:20:23 +00:00
dependabot[bot]
944322932a
Bump ch.qos.logback:logback-classic from 1.5.20 to 1.5.21
Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.5.20 to 1.5.21.
- [Release notes](https://github.com/qos-ch/logback/releases)
- [Commits](https://github.com/qos-ch/logback/compare/v_1.5.20...v_1.5.21)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-18 03:15:36 +00:00
dependabot[bot]
affa36b9bb
Bump org.hibernate.orm:hibernate-core from 6.6.34.Final to 6.6.36.Final
Bumps [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm) from 6.6.34.Final to 6.6.36.Final.
- [Release notes](https://github.com/hibernate/hibernate-orm/releases)
- [Changelog](https://github.com/hibernate/hibernate-orm/blob/6.6.36/changelog.txt)
- [Commits](https://github.com/hibernate/hibernate-orm/compare/6.6.34...6.6.36)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-18 03:15:13 +00:00
dependabot[bot]
5cd10088be
Bump io.micrometer:micrometer-observation from 1.14.12 to 1.14.13
Bumps [io.micrometer:micrometer-observation](https://github.com/micrometer-metrics/micrometer) from 1.14.12 to 1.14.13.
- [Release notes](https://github.com/micrometer-metrics/micrometer/releases)
- [Commits](https://github.com/micrometer-metrics/micrometer/compare/v1.14.12...v1.14.13)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-18 03:12:52 +00:00
dependabot[bot]
568ce80d94
Bump org.junit:junit-bom from 6.0.0 to 6.0.1
Bumps [org.junit:junit-bom](https://github.com/junit-team/junit-framework) from 6.0.0 to 6.0.1.
- [Release notes](https://github.com/junit-team/junit-framework/releases)
- [Commits](https://github.com/junit-team/junit-framework/compare/r6.0.0...r6.0.1)

---
updated-dependencies:
- dependency-name: org.junit:junit-bom
  dependency-version: 6.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-18 03:12:41 +00:00
dependabot[bot]
8809bc3782
Bump io.spring.gradle:spring-security-release-plugin
Bumps [io.spring.gradle:spring-security-release-plugin](https://github.com/spring-io/spring-security-release-tools) from 1.0.10 to 1.0.13.
- [Release notes](https://github.com/spring-io/spring-security-release-tools/releases)
- [Commits](https://github.com/spring-io/spring-security-release-tools/compare/v1.0.10...v1.0.13)

---
updated-dependencies:
- dependency-name: io.spring.gradle:spring-security-release-plugin
  dependency-version: 1.0.13
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-18 03:12:30 +00:00
dependabot[bot]
79fc29382f
Bump org-aspectj from 1.9.24 to 1.9.25
Bumps `org-aspectj` from 1.9.24 to 1.9.25.

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-18 03:12:18 +00:00
github-actions[bot]
4825680eb9 Next development version 2025-11-17 17:46:38 +00:00
github-actions[bot]
9ff2d96088 Next development version 2025-11-17 17:36:38 +00:00
github-actions[bot]
ffa89b749b Next development version 2025-11-17 17:36:21 +00:00
github-actions[bot]
644901fed5 Release 6.4.13 2025-11-17 17:05:12 +00:00
github-actions[bot]
1d99a7fb14 Release 7.0.0 2025-11-17 17:04:54 +00:00
github-actions[bot]
e5a379cc91 Release 6.5.7 2025-11-17 17:04:45 +00:00
Rob Winch
0400e29df1
Merge branch '6.5.x' 2025-11-17 10:31:58 -06:00
Rob Winch
7ad28772bc
Update to Spring Data 2024.1.12
Closes gh-18182
2025-11-17 10:31:45 -06:00
Rob Winch
2678925c21
Update to Reactor 2024.0.12
This aligns with Spring Framework's version.

Closes gh-18181
2025-11-17 10:30:45 -06:00
Rob Winch
87472c9ab4
Update to Spring Framework 6.2.13
Closes gh-18180
2025-11-17 10:29:49 -06:00
Rob Winch
ea036702fc
Merge branch '6.4.x' into 6.5.x 2025-11-17 10:27:16 -06:00
Rob Winch
09805317e6
Update Spring Data 2024.1.12
Closes gh-18179
2025-11-17 10:27:01 -06:00
Rob Winch
4ab933803f
Update to Reactor 2024.0.12
This aligns with Spring Framework's version of Reactor

Closes gh-18178
2025-11-17 10:26:09 -06:00
Rob Winch
d2b1cb572f
Update to Spring Framework 6.2.13
Closes gh-18177
2025-11-17 10:25:21 -06:00
Rob Winch
3e936ebe75
Update to Spring LDAP 4.0.0
Closes gh-18175
2025-11-17 09:47:38 -06:00
Rob Winch
ecc2775796
Update to Spring Data 2025.1.0
Closes gh-18174
2025-11-17 09:47:20 -06:00
Rob Winch
cf8e3ee2ab
Update to Reactor 2025.0.0
Closes gh-18173
2025-11-17 09:46:51 -06:00
Rob Winch
12ba56bbf2
Update to Spring Framework 7.0.0
Closes gh-18172
2025-11-17 09:46:29 -06:00
Rob Winch
2f8638d867
Bump org.jetbrains.kotlin:kotlin-bom from 2.2.20 to 2.2.21 2025-11-17 09:35:41 -06:00
Rob Winch
17aad2cea3
Bump org.jetbrains.kotlin:kotlin-gradle-plugin from 2.2.20 to 2.2.21 2025-11-17 09:35:39 -06:00
Rob Winch
9184ad3ad5
Merge branch '6.5.x' 2025-11-17 09:35:29 -06:00
Rob Winch
70076188af
Bump com.fasterxml.jackson:jackson-bom from 2.18.4.1 to 2.18.5 2025-11-17 09:34:44 -06:00
Rob Winch
57c9b1365c
Bump org.hibernate.orm:hibernate-core from 6.6.33.Final to 6.6.34.Final 2025-11-17 09:34:42 -06:00
Rob Winch
1c3d28f14d
Bump io.spring.gradle:spring-security-release-plugin from 1.0.11 to 1.0.13 2025-11-17 09:34:40 -06:00
Rob Winch
aaadb43ef8
Bump org-aspectj from 1.9.24 to 1.9.25 2025-11-17 09:34:38 -06:00
Rob Winch
a6c1a02afa
Bump io.micrometer:micrometer-observation from 1.14.12 to 1.14.13 2025-11-17 09:34:35 -06:00
Rob Winch
baebd04df7
Merge branch '6.4.x' into 6.5.x 2025-11-17 09:34:26 -06:00
Rob Winch
d0166004aa
Bump com.fasterxml.jackson:jackson-bom from 2.18.4.1 to 2.18.5 2025-11-17 09:33:40 -06:00
Rob Winch
9f96fbcda0
Bump org.hibernate.orm:hibernate-core from 6.6.33.Final to 6.6.34.Final 2025-11-17 09:33:37 -06:00
Rob Winch
ccffb48fd1
Bump org-aspectj from 1.9.24 to 1.9.25 2025-11-17 09:33:35 -06:00
Rob Winch
d0fcdebe88
Bump io.spring.gradle:spring-security-release-plugin from 1.0.11 to 1.0.13 2025-11-17 09:33:33 -06:00
Rob Winch
26991bbe5f AuthenticationRequest uses rawId.getBytes()
Previously id.getBytes() was used which was problemantic because
the id is base64 encoded and this did not match the expected ids.

Closes gh-18158
2025-11-14 15:21:20 -06:00
Rob Winch
e4106ecf68 Add Webauthn4JRelyingPartyOperations.setObjectConverter
Simplifies testing of Webauthn4JRelyingPartyOperations

Issue gh-18158
2025-11-14 15:21:20 -06:00
Daniel Garnier-Moiroux
7cb57ab940 Improve webauthn webdriver tests
Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
2025-11-14 15:21:20 -06:00
Joe Grandja
b130e728b7 Polish gh-18153
Issue gh-18144
2025-11-11 14:27:50 -05:00
Andrey Litvitski
e6db56ab4f Add a minimal authorization server configuration
Closes gh-18144

Signed-off-by: Andrey Litvitski <andrey1010102008@gmail.com>
2025-11-11 14:27:36 -05:00
dependabot[bot]
af47cc2abe
Bump io.micrometer:micrometer-observation from 1.14.12 to 1.14.13
Bumps [io.micrometer:micrometer-observation](https://github.com/micrometer-metrics/micrometer) from 1.14.12 to 1.14.13.
- [Release notes](https://github.com/micrometer-metrics/micrometer/releases)
- [Commits](https://github.com/micrometer-metrics/micrometer/compare/v1.14.12...v1.14.13)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-07 03:20:24 +00:00
dependabot[bot]
f997e22d9d
Bump io.micrometer:micrometer-observation from 1.14.12 to 1.14.13
Bumps [io.micrometer:micrometer-observation](https://github.com/micrometer-metrics/micrometer) from 1.14.12 to 1.14.13.
- [Release notes](https://github.com/micrometer-metrics/micrometer/releases)
- [Commits](https://github.com/micrometer-metrics/micrometer/compare/v1.14.12...v1.14.13)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-07 03:14:59 +00:00
github-actions[bot]
b7fb2892ed Next development version 2025-11-06 17:35:33 +00:00
github-actions[bot]
608b1484e4 Release 7.0.0-RC3 2025-11-06 17:07:27 +00:00
Joe Grandja
5fb2875f47 AOT hints for authorization server Jackson 3 types should be registered
Closes gh-18146
2025-11-06 10:14:00 -05:00
Joe Grandja
27ae318992 JdbcRegisteredClientRepository should support Jackson 3
Issue gh-17832

Closes gh-18143
2025-11-05 15:27:14 -05:00
Joe Grandja
73840663b9 Polish JdbcOAuth2AuthorizationService 2025-11-05 06:41:41 -05:00
dependabot[bot]
c85cb2d1ef
Bump org-aspectj from 1.9.24 to 1.9.25
Bumps `org-aspectj` from 1.9.24 to 1.9.25.

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-05 03:19:48 +00:00
dependabot[bot]
bf26dd9b33
Bump io.spring.gradle:spring-security-release-plugin
Bumps [io.spring.gradle:spring-security-release-plugin](https://github.com/spring-io/spring-security-release-tools) from 1.0.11 to 1.0.13.
- [Release notes](https://github.com/spring-io/spring-security-release-tools/releases)
- [Commits](https://github.com/spring-io/spring-security-release-tools/compare/v1.0.11...v1.0.13)

---
updated-dependencies:
- dependency-name: io.spring.gradle:spring-security-release-plugin
  dependency-version: 1.0.13
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-05 03:19:25 +00:00
dependabot[bot]
ff908c4d7c
Bump io.spring.gradle:spring-security-release-plugin
Bumps [io.spring.gradle:spring-security-release-plugin](https://github.com/spring-io/spring-security-release-tools) from 1.0.11 to 1.0.13.
- [Release notes](https://github.com/spring-io/spring-security-release-tools/releases)
- [Commits](https://github.com/spring-io/spring-security-release-tools/compare/v1.0.11...v1.0.13)

---
updated-dependencies:
- dependency-name: io.spring.gradle:spring-security-release-plugin
  dependency-version: 1.0.13
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-05 03:15:17 +00:00
dependabot[bot]
521f533fc4
Bump org-aspectj from 1.9.24 to 1.9.25
Bumps `org-aspectj` from 1.9.24 to 1.9.25.

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-05 03:14:41 +00:00
Josh Cummings
5662e17370 Add Nullable Annotations
Added Nullable to methods that may return a null value

Closes gh-18046
2025-11-04 15:08:12 -07:00
Josh Cummings
63f28a7e1f Merge branch '6.5.x' 2025-11-04 14:04:56 -07:00
Josh Cummings
f988272fff Merge branch '6.4.x' into 6.5.x 2025-11-04 14:04:29 -07:00
Josh Cummings
532d0bef14 Add Test to Confirm 72-byte BCrypt Password Limit
Closes gh-18133
2025-11-04 14:04:02 -07:00
Joe Grandja
571bd60d82 Document OAuth 2.0 Protected Resource Metadata support
Issue gh-17244
2025-11-04 14:37:19 -05:00
Rob Winch
6471a32d66
Merge branch '6.5.x'
Closes gh-18132
2025-11-04 11:37:11 -06:00
Rob Winch
c1e9e10bf0
Merge branch '6.4.x' into 6.5.x
Closes gh-18131
2025-11-04 11:28:40 -06:00
Daniel Garnier-Moiroux
fed6df5167 Default WebAuthnConfigurer#rpName to rpId
In WebAuthn L3 spec, PublicKeyCredentialEntity.name is deprecated:

> This member is deprecated because many clients do not display it,
> but it remains a required dictionary member for backwards compatibility.
> Relying Parties MAY, as a safe default, set this equal to the RP ID.

Source: https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialentity

Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
2025-11-04 11:16:22 -06:00
Josh Cummings
20ae9dc6bc Remove Stray Needs Declaration 2025-11-04 10:08:45 -07:00
Josh Cummings
03eadb846c Add Workflow to Finalize a Release 2025-11-04 10:07:22 -07:00
Rob Winch
0928a60cd2
Post Process WebAuthnAuthenticationFilter
This commit ensures that WebAuthnAuthenticationFilter is
post processed by BeanPostProcessors and
ObjectPostProcessor.

Closes gh-18128
2025-11-04 10:54:45 -06:00
Rob Winch
322634ca6a
Next Development Version 2025-11-04 10:39:24 -06:00
Rob Winch
5213cc44fc
Merge branch '6.5.x' 2025-11-04 10:24:32 -06:00
Rob Winch
8fa2fc0e1e
Merge branch '6.4.x' into 6.5.x 2025-11-04 10:24:15 -06:00
Daniel Garnier-Moiroux
4feeb0f843 Docs: document effects of disabling CORS configurer
Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
2025-11-04 10:23:46 -06:00
Daniel Garnier-Moiroux
ea88671f4c Update webauthn4j usage, use non-deprecated methods
Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
2025-11-04 10:21:23 -06:00
dependabot[bot]
ee49c18ce2
Bump org.hibernate.orm:hibernate-core from 6.6.33.Final to 6.6.34.Final
Bumps [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm) from 6.6.33.Final to 6.6.34.Final.
- [Release notes](https://github.com/hibernate/hibernate-orm/releases)
- [Changelog](https://github.com/hibernate/hibernate-orm/blob/6.6.34/changelog.txt)
- [Commits](https://github.com/hibernate/hibernate-orm/compare/6.6.33...6.6.34)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-28 03:20:26 +00:00
dependabot[bot]
f0afca7610
Bump com.fasterxml.jackson:jackson-bom from 2.18.4.1 to 2.18.5
Bumps [com.fasterxml.jackson:jackson-bom](https://github.com/FasterXML/jackson-bom) from 2.18.4.1 to 2.18.5.
- [Commits](https://github.com/FasterXML/jackson-bom/compare/jackson-bom-2.18.4.1...jackson-bom-2.18.5)

---
updated-dependencies:
- dependency-name: com.fasterxml.jackson:jackson-bom
  dependency-version: 2.18.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-28 03:19:44 +00:00
dependabot[bot]
8b0689cbb8
Bump org.hibernate.orm:hibernate-core from 6.6.33.Final to 6.6.34.Final
Bumps [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm) from 6.6.33.Final to 6.6.34.Final.
- [Release notes](https://github.com/hibernate/hibernate-orm/releases)
- [Changelog](https://github.com/hibernate/hibernate-orm/blob/6.6.34/changelog.txt)
- [Commits](https://github.com/hibernate/hibernate-orm/compare/6.6.33...6.6.34)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-28 03:15:58 +00:00
dependabot[bot]
28e158d1cb
Bump com.fasterxml.jackson:jackson-bom from 2.18.4.1 to 2.18.5
Bumps [com.fasterxml.jackson:jackson-bom](https://github.com/FasterXML/jackson-bom) from 2.18.4.1 to 2.18.5.
- [Commits](https://github.com/FasterXML/jackson-bom/compare/jackson-bom-2.18.4.1...jackson-bom-2.18.5)

---
updated-dependencies:
- dependency-name: com.fasterxml.jackson:jackson-bom
  dependency-version: 2.18.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-28 03:15:38 +00:00
dependabot[bot]
36f1f2ca4f
Bump org.jetbrains.kotlin:kotlin-gradle-plugin from 2.2.20 to 2.2.21
Bumps [org.jetbrains.kotlin:kotlin-gradle-plugin](https://github.com/JetBrains/kotlin) from 2.2.20 to 2.2.21.
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/v2.2.21/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v2.2.20...v2.2.21)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlin:kotlin-gradle-plugin
  dependency-version: 2.2.21
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-24 03:11:20 +00:00
dependabot[bot]
46b6744b42
Bump org.jetbrains.kotlin:kotlin-bom from 2.2.20 to 2.2.21
Bumps [org.jetbrains.kotlin:kotlin-bom](https://github.com/JetBrains/kotlin) from 2.2.20 to 2.2.21.
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/v2.2.21/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v2.2.20...v2.2.21)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlin:kotlin-bom
  dependency-version: 2.2.21
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-24 03:09:22 +00:00
54 changed files with 1304 additions and 501 deletions

View File

@ -111,11 +111,3 @@ updates:
labels:
- 'type: task'
- 'in: build'
- package-ecosystem: npm
target-branch: 6.3.x
directory: /docs
schedule:
interval: weekly
labels:
- 'type: task'
- 'in: build'

View File

@ -35,13 +35,6 @@ jobs:
should-deploy-artifacts: ${{ needs.build.outputs.should-deploy-artifacts }}
default-publish-milestones-central: true
secrets: inherit
deploy-docs:
name: Deploy Docs
needs: [ build ]
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-docs.yml@v1
with:
should-deploy-docs: ${{ needs.build.outputs.should-deploy-artifacts }}
secrets: inherit
deploy-schema:
name: Deploy Schema
needs: [ build ]
@ -51,7 +44,7 @@ jobs:
secrets: inherit
perform-release:
name: Perform Release
needs: [ deploy-artifacts, deploy-docs, deploy-schema ]
needs: [ deploy-artifacts, deploy-schema ]
uses: spring-io/spring-security-release-tools/.github/workflows/perform-release.yml@v1
with:
should-perform-release: ${{ needs.deploy-artifacts.outputs.artifacts-deployed }}

27
.github/workflows/finalize-release.yml vendored Normal file
View File

@ -0,0 +1,27 @@
name: Finalize Release
on:
workflow_dispatch: # Manual trigger
inputs:
version:
description: The Spring Security release to finalize (e.g. 7.0.0-RC2)
required: true
env:
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
permissions:
contents: read
jobs:
perform-release:
name: Perform Release
uses: spring-io/spring-security-release-tools/.github/workflows/perform-release.yml@v1
with:
should-perform-release: true
project-version: ${{ inputs.version }}
milestone-repo-url: https://repo1.maven.org/maven2
release-repo-url: https://repo1.maven.org/maven2
artifact-path: org/springframework/security/spring-security-core
slack-announcing-id: spring-security-announcing
secrets: inherit

View File

@ -42,7 +42,7 @@ springRelease {
weekOfMonth = 3
dayOfWeek = 1
referenceDocUrl = "https://docs.spring.io/spring-security/reference/{version}/index.html"
apiDocUrl = "https://docs.spring.io/spring-security/site/docs/{version}/api/"
apiDocUrl = "https://docs.spring.io/spring-security/reference/{version}/api/java/index.html"
replaceSnapshotVersionInReferenceDocUrl = true
}

View File

@ -1,82 +0,0 @@
/*
* 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 io.spring.gradle.convention
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.tasks.bundling.Zip
import org.gradle.api.Plugin
import org.gradle.api.Project
public class DeployDocsPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
project.getPluginManager().apply('org.hidetake.ssh')
project.ssh.settings {
knownHosts = allowAnyHosts
}
project.remotes {
docs {
role 'docs'
if (project.hasProperty('deployDocsHost')) {
host = project.findProperty('deployDocsHost')
} else {
host = 'docs.af.pivotal.io'
}
retryCount = 5 // retry 5 times (default is 0)
retryWaitSec = 10 // wait 10 seconds between retries (default is 0)
user = project.findProperty('deployDocsSshUsername')
if (project.hasProperty('deployDocsSshKeyPath')) {
identity = project.file(project.findProperty('deployDocsSshKeyPath'))
} else if (project.hasProperty('deployDocsSshKey')) {
identity = project.findProperty('deployDocsSshKey')
}
if(project.hasProperty('deployDocsSshPassphrase')) {
passphrase = project.findProperty('deployDocsSshPassphrase')
}
}
}
project.task('deployDocs') {
dependsOn 'docsZip'
doFirst {
project.ssh.run {
session(project.remotes.docs) {
def now = System.currentTimeMillis()
def name = project.rootProject.name
def version = project.rootProject.version
def tempPath = "/tmp/${name}-${now}-docs/".replaceAll(' ', '_')
execute "mkdir -p $tempPath"
project.tasks.docsZip.outputs.each { o ->
put from: o.files, into: tempPath
}
execute "unzip $tempPath*.zip -d $tempPath"
def extractPath = "/var/www/domains/spring.io/docs/htdocs/autorepo/docs/${name}/${version}/"
execute "rm -rf $extractPath"
execute "mkdir -p $extractPath"
execute "mv $tempPath/docs/* $extractPath"
execute "chmod -R g+w $extractPath"
}
}
}
}
}
}

View File

@ -17,7 +17,6 @@ public class DocsPlugin implements Plugin<Project> {
PluginManager pluginManager = project.getPluginManager();
pluginManager.apply(BasePlugin);
pluginManager.apply(DeployDocsPlugin);
pluginManager.apply(JavadocApiPlugin);
Task docsZip = project.tasks.create('docsZip', Zip) {

View File

@ -30,16 +30,6 @@ ossrh: {
}
}
},
docs: {
stage('Deploy Docs') {
node {
checkout scm
withCredentials([file(credentialsId: 'docs.spring.io-jenkins_private_ssh_key', variable: 'DEPLOY_SSH_KEY')]) {
sh "./gradlew deployDocs -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME --refresh-dependencies --no-daemon --stacktrace"
}
}
}
},
schema: {
stage('Deploy Schema') {
node {
@ -49,4 +39,4 @@ schema: {
}
}
}
}
}

View File

@ -31,6 +31,7 @@ import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriverException;
@ -55,6 +56,7 @@ import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.util.StringUtils;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.filter.DelegatingFilterProxy;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
@ -67,7 +69,7 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Daniel Garnier-Moiroux
*/
@org.junit.jupiter.api.Disabled
@Disabled
class WebAuthnWebDriverTests {
private String baseUrl;
@ -82,6 +84,8 @@ class WebAuthnWebDriverTests {
private static final String PASSWORD = "password";
private String authenticatorId = null;
@BeforeAll
static void startChromeDriverService() throws Exception {
driverService = new ChromeDriverService.Builder().usingAnyFreePort().build();
@ -144,7 +148,7 @@ class WebAuthnWebDriverTests {
@Test
void loginWhenNoValidAuthenticatorCredentialsThenRejects() {
createVirtualAuthenticator(true);
this.driver.get(this.baseUrl);
this.getAndWait("/", "/login");
this.driver.findElement(signinWithPasskeyButton()).click();
await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/login?error"));
}
@ -153,7 +157,7 @@ class WebAuthnWebDriverTests {
void registerWhenNoLabelThenRejects() {
login();
this.driver.get(this.baseUrl + "/webauthn/register");
this.getAndWait("/webauthn/register");
this.driver.findElement(registerPasskeyButton()).click();
assertHasAlertStartingWith("error", "Error: Passkey Label is required");
@ -163,7 +167,7 @@ class WebAuthnWebDriverTests {
void registerWhenAuthenticatorNoUserVerificationThenRejects() {
createVirtualAuthenticator(false);
login();
this.driver.get(this.baseUrl + "/webauthn/register");
this.getAndWait("/webauthn/register");
this.driver.findElement(passkeyLabel()).sendKeys("Virtual authenticator");
this.driver.findElement(registerPasskeyButton()).click();
@ -178,7 +182,8 @@ class WebAuthnWebDriverTests {
* <li>Step 1: Log in with username / password</li>
* <li>Step 2: Register a credential from the virtual authenticator</li>
* <li>Step 3: Log out</li>
* <li>Step 4: Log in with the authenticator</li>
* <li>Step 4: Log in with the authenticator (no allowCredentials)</li>
* <li>Step 5: Log in again with the same authenticator (with allowCredentials)</li>
* </ul>
*/
@Test
@ -190,7 +195,7 @@ class WebAuthnWebDriverTests {
login();
// Step 2: register a credential from the virtual authenticator
this.driver.get(this.baseUrl + "/webauthn/register");
this.getAndWait("/webauthn/register");
this.driver.findElement(passkeyLabel()).sendKeys("Virtual authenticator");
this.driver.findElement(registerPasskeyButton()).click();
@ -212,9 +217,58 @@ class WebAuthnWebDriverTests {
logout();
// Step 4: log in with the virtual authenticator
this.driver.get(this.baseUrl + "/webauthn/register");
this.getAndWait("/webauthn/register", "/login");
this.driver.findElement(signinWithPasskeyButton()).click();
await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/webauthn/register?continue"));
// Step 5: authenticate while being already logged in
// This simulates some use-cases with MFA. Since the user is already logged in,
// the "allowCredentials" property is populated
this.getAndWait("/login");
this.driver.findElement(signinWithPasskeyButton()).click();
await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/"));
}
@Test
void registerWhenAuthenticatorAlreadyRegisteredThenRejects() {
createVirtualAuthenticator(true);
login();
registerAuthenticator("Virtual authenticator");
// Cannot re-register the same authenticator because excludeCredentials
// is not empty and contains the given authenticator
this.driver.findElement(passkeyLabel()).sendKeys("Same authenticator");
this.driver.findElement(registerPasskeyButton()).click();
await(() -> assertHasAlertStartingWith("error", "Registration failed"));
}
@Test
void registerSecondAuthenticatorThenSucceeds() {
createVirtualAuthenticator(true);
login();
registerAuthenticator("Virtual authenticator");
this.getAndWait("/webauthn/register");
List<WebElement> passkeyRows = this.driver.findElements(passkeyTableRows());
assertThat(passkeyRows).hasSize(1)
.first()
.extracting((row) -> row.findElement(firstCell()))
.extracting(WebElement::getText)
.isEqualTo("Virtual authenticator");
// Create second authenticator and register
removeAuthenticator();
createVirtualAuthenticator(true);
registerAuthenticator("Second virtual authenticator");
this.getAndWait("/webauthn/register");
passkeyRows = this.driver.findElements(passkeyTableRows());
assertThat(passkeyRows).hasSize(2)
.extracting((row) -> row.findElement(firstCell()))
.extracting(WebElement::getText)
.contains("Second virtual authenticator");
}
/**
@ -231,11 +285,14 @@ class WebAuthnWebDriverTests {
* "https://chromedevtools.github.io/devtools-protocol/tot/WebAuthn/">https://chromedevtools.github.io/devtools-protocol/tot/WebAuthn/</a>
*/
private void createVirtualAuthenticator(boolean userIsVerified) {
if (StringUtils.hasText(this.authenticatorId)) {
throw new IllegalStateException("Authenticator already exists, please remove it before re-creating one");
}
HasCdp cdpDriver = (HasCdp) this.driver;
cdpDriver.executeCdpCommand("WebAuthn.enable", Map.of("enableUI", false));
// this.driver.addVirtualAuthenticator(createVirtualAuthenticatorOptions());
//@formatter:off
cdpDriver.executeCdpCommand("WebAuthn.addVirtualAuthenticator",
Map<String, Object> cmdResponse = cdpDriver.executeCdpCommand("WebAuthn.addVirtualAuthenticator",
Map.of(
"options",
Map.of(
@ -248,21 +305,38 @@ class WebAuthnWebDriverTests {
)
));
//@formatter:on
this.authenticatorId = cmdResponse.get("authenticatorId").toString();
}
private void removeAuthenticator() {
HasCdp cdpDriver = (HasCdp) this.driver;
cdpDriver.executeCdpCommand("WebAuthn.removeVirtualAuthenticator",
Map.of("authenticatorId", this.authenticatorId));
this.authenticatorId = null;
}
private void login() {
this.driver.get(this.baseUrl);
this.getAndWait("/", "/login");
this.driver.findElement(usernameField()).sendKeys(USERNAME);
this.driver.findElement(passwordField()).sendKeys(PASSWORD);
this.driver.findElement(signinWithUsernamePasswordButton()).click();
// Ensure login has completed
await(() -> assertThat(this.driver.getCurrentUrl()).doesNotContain("/login"));
}
private void logout() {
this.driver.get(this.baseUrl + "/logout");
this.getAndWait("/logout");
this.driver.findElement(logoutButton()).click();
await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/login?logout"));
}
private void registerAuthenticator(String passkeyName) {
this.getAndWait("/webauthn/register");
this.driver.findElement(passkeyLabel()).sendKeys(passkeyName);
this.driver.findElement(registerPasskeyButton()).click();
await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/webauthn/register?success"));
}
private AbstractStringAssert<?> assertHasAlertStartingWith(String alertType, String alertMessage) {
WebElement alert = this.driver.findElement(new By.ById(alertType));
assertThat(alert.isDisplayed())
@ -289,6 +363,15 @@ class WebAuthnWebDriverTests {
});
}
private void getAndWait(String endpoint) {
this.getAndWait(endpoint, endpoint);
}
private void getAndWait(String endpoint, String redirectUrl) {
this.driver.get(this.baseUrl + endpoint);
this.await(() -> assertThat(this.driver.getCurrentUrl()).endsWith(redirectUrl));
}
private static By.ById passkeyLabel() {
return new By.ById("label");
}
@ -325,6 +408,10 @@ class WebAuthnWebDriverTests {
return new By.ByCssSelector("button");
}
private static By.ByCssSelector deletePasskeyButton() {
return new By.ByCssSelector("table > tbody > tr > button");
}
/**
* The configuration for WebAuthN tests. It accesses the Server's current port, so we
* can configurer WebAuthnConfigurer#allowedOrigin

View File

@ -42,7 +42,8 @@ final class MethodSecuritySelector implements ImportSelector {
.isPresent("org.springframework.security.data.aot.hint.AuthorizeReturnObjectDataHintsRegistrar", null);
private static final boolean isWebPresent = ClassUtils
.isPresent("org.springframework.web.servlet.DispatcherServlet", null);
.isPresent("org.springframework.web.servlet.DispatcherServlet", null)
&& ClassUtils.isPresent("org.springframework.security.web.util.ThrowableAnalyzer", null);
private static final boolean isObservabilityPresent = ClassUtils
.isPresent("io.micrometer.observation.ObservationRegistry", null);

View File

@ -2052,7 +2052,6 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
* http
* // ...
* .webAuthn((webAuthn) -&gt; webAuthn
* .rpName("Spring Security Relying Party")
* .rpId("example.com")
* .allowedOrigins("https://example.com")
* );

View File

@ -177,6 +177,7 @@ public class WebAuthnConfigurer<H extends HttpSecurityBuilder<H>>
WebAuthnAuthenticationFilter webAuthnAuthnFilter = new WebAuthnAuthenticationFilter();
webAuthnAuthnFilter.setAuthenticationManager(
new ProviderManager(new WebAuthnAuthenticationProvider(rpOperations, userDetailsService)));
webAuthnAuthnFilter = postProcess(webAuthnAuthnFilter);
WebAuthnRegistrationFilter webAuthnRegistrationFilter = new WebAuthnRegistrationFilter(userCredentials,
rpOperations);
PublicKeyCredentialCreationOptionsFilter creationOptionsFilter = new PublicKeyCredentialCreationOptionsFilter(
@ -256,9 +257,10 @@ public class WebAuthnConfigurer<H extends HttpSecurityBuilder<H>>
PublicKeyCredentialUserEntityRepository userEntities, UserCredentialRepository userCredentials) {
Optional<WebAuthnRelyingPartyOperations> webauthnOperationsBean = getBeanOrNull(
WebAuthnRelyingPartyOperations.class);
return webauthnOperationsBean.orElseGet(() -> new Webauthn4JRelyingPartyOperations(userEntities,
userCredentials, PublicKeyCredentialRpEntity.builder().id(this.rpId).name(this.rpName).build(),
this.allowedOrigins));
String rpName = (this.rpName != null) ? this.rpName : this.rpId;
return webauthnOperationsBean
.orElseGet(() -> new Webauthn4JRelyingPartyOperations(userEntities, userCredentials,
PublicKeyCredentialRpEntity.builder().id(this.rpId).name(rpName).build(), this.allowedOrigins));
}
}

View File

@ -16,10 +16,12 @@
package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import jakarta.servlet.Filter;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpMethod;
@ -36,10 +38,12 @@ import org.springframework.security.oauth2.server.authorization.authentication.O
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationValidator;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeRequestAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationConsentAuthenticationConverter;
import org.springframework.security.web.access.intercept.AuthorizationFilter;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
@ -50,6 +54,7 @@ import org.springframework.security.web.servlet.util.matcher.PathPatternRequestM
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
@ -83,6 +88,8 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C
private Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authorizationCodeRequestAuthenticationValidator;
private Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authorizationCodeRequestAuthenticationValidatorComposite;
private SessionAuthenticationStrategy sessionAuthenticationStrategy;
/**
@ -248,8 +255,16 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C
authenticationProviders.addAll(0, this.authenticationProviders);
}
this.authenticationProvidersConsumer.accept(authenticationProviders);
authenticationProviders.forEach(
(authenticationProvider) -> httpSecurity.authenticationProvider(postProcess(authenticationProvider)));
authenticationProviders.forEach((authenticationProvider) -> {
httpSecurity.authenticationProvider(postProcess(authenticationProvider));
if (authenticationProvider instanceof OAuth2AuthorizationCodeRequestAuthenticationProvider) {
Method method = ReflectionUtils.findMethod(OAuth2AuthorizationCodeRequestAuthenticationProvider.class,
"getAuthenticationValidatorComposite");
ReflectionUtils.makeAccessible(method);
this.authorizationCodeRequestAuthenticationValidatorComposite = (Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext>) ReflectionUtils
.invokeMethod(method, authenticationProvider);
}
});
}
@Override
@ -282,7 +297,18 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C
if (this.sessionAuthenticationStrategy != null) {
authorizationEndpointFilter.setSessionAuthenticationStrategy(this.sessionAuthenticationStrategy);
}
httpSecurity.addFilterBefore(postProcess(authorizationEndpointFilter),
httpSecurity.addFilterAfter(postProcess(authorizationEndpointFilter), AuthorizationFilter.class);
// Create and add
// OAuth2AuthorizationEndpointFilter.OAuth2AuthorizationCodeRequestValidatingFilter
Method method = ReflectionUtils.findMethod(OAuth2AuthorizationEndpointFilter.class,
"createAuthorizationCodeRequestValidatingFilter", RegisteredClientRepository.class, Consumer.class);
ReflectionUtils.makeAccessible(method);
RegisteredClientRepository registeredClientRepository = OAuth2ConfigurerUtils
.getRegisteredClientRepository(httpSecurity);
Filter authorizationCodeRequestValidatingFilter = (Filter) ReflectionUtils.invokeMethod(method,
authorizationEndpointFilter, registeredClientRepository,
this.authorizationCodeRequestAuthenticationValidatorComposite);
httpSecurity.addFilterBefore(postProcess(authorizationCodeRequestValidatingFilter),
AbstractPreAuthenticatedProcessingFilter.class);
}

View File

@ -19,10 +19,13 @@ package org.springframework.security.config.annotation.web.configurers;
import java.nio.charset.StandardCharsets;
import java.util.List;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpOutputMessage;
@ -42,6 +45,7 @@ import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.ui.DefaultResourcesFilter;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions;
import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialCreationOptions;
import org.springframework.security.web.webauthn.authentication.WebAuthnAuthenticationFilter;
import org.springframework.security.web.webauthn.management.WebAuthnRelyingPartyOperations;
import org.springframework.security.web.webauthn.registration.HttpSessionPublicKeyCredentialCreationOptionsRepository;
import org.springframework.test.web.servlet.MockMvc;
@ -52,6 +56,8 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.willAnswer;
import static org.mockito.Mockito.mock;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
@ -88,6 +94,14 @@ public class WebAuthnConfigurerTests {
.andExpect(content().string(containsString("body {")));
}
// gh-18128
@Test
public void webAuthnAuthenticationFilterIsPostProcessed() throws Exception {
this.spring.register(DefaultWebauthnConfiguration.class, PostProcessorConfiguration.class).autowire();
PostProcessorConfiguration postProcess = this.spring.getContext().getBean(PostProcessorConfiguration.class);
assertThat(postProcess.webauthnFilter).isNotNull();
}
@Test
public void webauthnWhenNoFormLoginAndDefaultRegistrationPageConfiguredThenServesJavascript() throws Exception {
this.spring.register(NoFormLoginAndDefaultRegistrationPageConfiguration.class).autowire();
@ -127,6 +141,42 @@ public class WebAuthnConfigurerTests {
.hasSize(1);
}
@Test
void webauthnWhenConfiguredDefaultsRpNameToRpId() throws Exception {
ObjectMapper mapper = new ObjectMapper();
this.spring.register(DefaultWebauthnConfiguration.class).autowire();
String response = this.mvc
.perform(post("/webauthn/register/options").with(csrf())
.with(authentication(new TestingAuthenticationToken("test", "ignored", "ROLE_user"))))
.andExpect(status().is2xxSuccessful())
.andReturn()
.getResponse()
.getContentAsString();
JsonNode parsedResponse = mapper.readTree(response);
assertThat(parsedResponse.get("rp").get("id").asText()).isEqualTo("example.com");
assertThat(parsedResponse.get("rp").get("name").asText()).isEqualTo("example.com");
}
@Test
void webauthnWhenRpNameConfiguredUsesRpName() throws Exception {
ObjectMapper mapper = new ObjectMapper();
this.spring.register(CustomRpNameWebauthnConfiguration.class).autowire();
String response = this.mvc
.perform(post("/webauthn/register/options").with(csrf())
.with(authentication(new TestingAuthenticationToken("test", "ignored", "ROLE_user"))))
.andExpect(status().is2xxSuccessful())
.andReturn()
.getResponse()
.getContentAsString();
JsonNode parsedResponse = mapper.readTree(response);
assertThat(parsedResponse.get("rp").get("id").asText()).isEqualTo("example.com");
assertThat(parsedResponse.get("rp").get("name").asText()).isEqualTo("Test RP Name");
}
@Test
public void webauthnWhenConfiguredAndFormLoginThenDoesServesJavascript() throws Exception {
this.spring.register(FormLoginAndNoDefaultRegistrationPageConfiguration.class).autowire();
@ -289,6 +339,26 @@ public class WebAuthnConfigurerTests {
}
@Configuration(proxyBeanMethods = false)
static class PostProcessorConfiguration {
WebAuthnAuthenticationFilter webauthnFilter;
@Bean
BeanPostProcessor beanPostProcessor() {
return new BeanPostProcessor() {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean instanceof WebAuthnAuthenticationFilter filter) {
PostProcessorConfiguration.this.webauthnFilter = filter;
}
return bean;
}
};
}
}
@Configuration
@EnableWebSecurity
static class DefaultWebauthnConfiguration {
@ -304,8 +374,7 @@ public class WebAuthnConfigurerTests {
http
.formLogin(Customizer.withDefaults())
.webAuthn((authn) -> authn
.rpId("spring.io")
.rpName("spring")
.rpId("example.com")
);
// @formatter:on
return http.build();
@ -313,6 +382,24 @@ public class WebAuthnConfigurerTests {
}
@Configuration
@EnableWebSecurity
static class CustomRpNameWebauthnConfiguration {
@Bean
UserDetailsService userDetailsService() {
return new InMemoryUserDetailsManager();
}
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http.formLogin(Customizer.withDefaults())
.webAuthn((webauthn) -> webauthn.rpId("example.com").rpName("Test RP Name"))
.build();
}
}
@Configuration
@EnableWebSecurity
static class NoFormLoginAndDefaultRegistrationPageConfiguration {

View File

@ -307,8 +307,8 @@ public class OAuth2AuthorizationCodeGrantTests {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
this.mvc
.perform(
get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).params(getAuthorizationRequestParameters(registeredClient)))
.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI)
.queryParams(getAuthorizationRequestParameters(registeredClient)))
.andExpect(status().isBadRequest())
.andReturn();
}
@ -851,21 +851,31 @@ public class OAuth2AuthorizationCodeGrantTests {
this.spring.register(AuthorizationServerConfigurationCustomAuthorizationEndpoint.class).autowire();
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
this.registeredClientRepository.save(registeredClient);
TestingAuthenticationToken principal = new TestingAuthenticationToken("principalName", "password");
Map<String, Object> additionalParameters = new HashMap<>();
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, S256_CODE_CHALLENGE);
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256");
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication = new OAuth2AuthorizationCodeRequestAuthenticationToken(
"https://provider.com/oauth2/authorize", registeredClient.getClientId(), principal,
registeredClient.getRedirectUris().iterator().next(), STATE_URL_UNENCODED, registeredClient.getScopes(),
additionalParameters);
OAuth2AuthorizationCode authorizationCode = new OAuth2AuthorizationCode("code", Instant.now(),
Instant.now().plus(5, ChronoUnit.MINUTES));
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult = new OAuth2AuthorizationCodeRequestAuthenticationToken(
"https://provider.com/oauth2/authorize", registeredClient.getClientId(), principal, authorizationCode,
registeredClient.getRedirectUris().iterator().next(), STATE_URL_UNENCODED,
registeredClient.getScopes());
given(authorizationRequestConverter.convert(any())).willReturn(authorizationCodeRequestAuthenticationResult);
given(authorizationRequestConverter.convert(any())).willReturn(authorizationCodeRequestAuthentication);
given(authorizationRequestAuthenticationProvider
.supports(eq(OAuth2AuthorizationCodeRequestAuthenticationToken.class))).willReturn(true);
given(authorizationRequestAuthenticationProvider.authenticate(any()))
.willReturn(authorizationCodeRequestAuthenticationResult);
this.mvc
.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).params(getAuthorizationRequestParameters(registeredClient))
.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI)
.queryParams(getAuthorizationRequestParameters(registeredClient))
.with(user("user")))
.andExpect(status().isOk());
@ -880,8 +890,7 @@ public class OAuth2AuthorizationCodeGrantTests {
|| converter instanceof OAuth2AuthorizationCodeRequestAuthenticationConverter
|| converter instanceof OAuth2AuthorizationConsentAuthenticationConverter);
verify(authorizationRequestAuthenticationProvider)
.authenticate(eq(authorizationCodeRequestAuthenticationResult));
verify(authorizationRequestAuthenticationProvider).authenticate(eq(authorizationCodeRequestAuthentication));
@SuppressWarnings("unchecked")
ArgumentCaptor<List<AuthenticationProvider>> authenticationProvidersCaptor = ArgumentCaptor

View File

@ -275,7 +275,7 @@ class ServerJwtDslTests {
}
class NullConverter: Converter<Jwt, Mono<AbstractAuthenticationToken>> {
override fun convert(source: Jwt): Mono<AbstractAuthenticationToken>? {
override fun convert(source: Jwt): Mono<AbstractAuthenticationToken> {
return Mono.empty()
}

View File

@ -16,6 +16,7 @@
package org.springframework.security.crypto.bcrypt;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import org.junit.jupiter.api.BeforeEach;
@ -25,6 +26,7 @@ import org.springframework.security.crypto.password.AbstractPasswordEncoderValid
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatNoException;
/**
* @author Dave Syer
@ -236,4 +238,23 @@ public class BCryptPasswordEncoderTests extends AbstractPasswordEncoderValidatio
assertThat(getEncoder().matches(password73chars, encodedPassword73chars)).isTrue();
}
/**
* Fixes gh-18133
* @author StringManolo
*/
@Test
void passwordLargerThan72BytesShouldThrowIllegalArgumentException() {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String singleByteChars = "a".repeat(68);
String password72Bytes = singleByteChars + "😀";
assertThat(password72Bytes.length()).isEqualTo(70);
assertThat(password72Bytes.getBytes(StandardCharsets.UTF_8).length).isEqualTo(72);
assertThatNoException().isThrownBy(() -> encoder.encode(password72Bytes));
String singleByteCharsTooLong = "a".repeat(69);
String password73Bytes = singleByteCharsTooLong + "😀";
assertThat(password73Bytes.getBytes(StandardCharsets.UTF_8).length).isEqualTo(73);
assertThatIllegalArgumentException().isThrownBy(() -> encoder.encode(password73Bytes))
.withMessageContaining("password cannot be more than 72 bytes");
}
}

View File

@ -31,7 +31,7 @@ urls:
redirect_facility: httpd
ui:
bundle:
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.18/ui-bundle.zip
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.25/ui-bundle.zip
snapshot: true
runtime:
log:

View File

@ -90,6 +90,7 @@
**** xref:servlet/oauth2/resource-server/multitenancy.adoc[Multitenancy]
**** xref:servlet/oauth2/resource-server/bearer-tokens.adoc[Bearer Tokens]
**** xref:servlet/oauth2/resource-server/dpop-tokens.adoc[DPoP-bound Access Tokens]
**** xref:servlet/oauth2/resource-server/protected-resource-metadata.adoc[Protected Resource Metadata]
*** xref:servlet/oauth2/authorization-server/index.adoc[OAuth2 Authorization Server]
**** xref:servlet/oauth2/authorization-server/getting-started.adoc[Getting Started]
**** xref:servlet/oauth2/authorization-server/configuration-model.adoc[Configuration Model]

View File

@ -1,4 +1,3 @@
[[webflux-cors]]
= CORS
@ -75,3 +74,11 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
}
----
======
[WARNING]
====
CORS is a browser-based security feature.
By disabling CORS in Spring Security, you are not removing CORS protection from your browser.
Instead, you are removing CORS support from Spring Security, and users will not be able to interact with your Spring backend from a cross-origin browser application.
To fix CORS errors in your application, you must enable CORS support, and provide an appropriate configuration source.
====

View File

@ -39,7 +39,7 @@ Gradle::
+
[source,groovy,role="secondary",subs="verbatim,attributes"]
----
depenendencies {
dependencies {
implementation "org.springframework.security:spring-security-web"
implementation "com.webauthn4j:webauthn4j-core:{webauthn4j-core-version}"
}
@ -65,7 +65,6 @@ SecurityFilterChain filterChain(HttpSecurity http) {
// ...
.formLogin(withDefaults())
.webAuthn((webAuthn) -> webAuthn
.rpName("Spring Security Relying Party")
.rpId("example.com")
.allowedOrigins("https://example.com")
// optional properties
@ -96,7 +95,6 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain {
// ...
http {
webAuthn {
rpName = "Spring Security Relying Party"
rpId = "example.com"
allowedOrigins = setOf("https://example.com")
// optional properties

View File

@ -38,7 +38,7 @@ Gradle::
+
[source,groovy,role="secondary"]
----
depenendencies {
dependencies {
implementation "org.springframework.boot:spring-boot-starter-data-ldap"
implementation "org.springframework.security:spring-security-ldap"
}
@ -150,7 +150,7 @@ Gradle::
+
[source,groovy,role="secondary",subs="verbatim,attributes"]
----
depenendencies {
dependencies {
runtimeOnly "com.unboundid:unboundid-ldapsdk:{unboundid-ldapsdk-version}"
}
----

View File

@ -639,7 +639,7 @@ This is because Spring Security requires all URIs to be absolute (minus the cont
[TIP]
=====
There are several other components that create request matchers for you like {spring-boot-api-url}org/springframework/boot/autoconfigure/security/servlet/PathRequest.html[`PathRequest#toStaticResources#atCommonLocations`]
There are several other components that create request matchers for you like {spring-boot-api-url}org/springframework/boot/security/autoconfigure/web/servlet/PathRequest.html[`PathRequest#toStaticResources#atCommonLocations`]
=====
[[match-by-custom]]

View File

@ -493,14 +493,14 @@ public class MyCustomDsl extends AbstractHttpConfigurer<MyCustomDsl, HttpSecurit
private boolean flag;
@Override
public void init(HttpSecurity http) throws Exception {
public void init(HttpSecurity http) {
// any method that adds another configurer
// must be done in the init method
http.csrf().disable();
http.csrf(csrf -> csrf.disable());
}
@Override
public void configure(HttpSecurity http) throws Exception {
public void configure(HttpSecurity http) {
ApplicationContext context = http.getSharedObject(ApplicationContext.class);
// here we lookup from the ApplicationContext. You can also just create a new instance.

View File

@ -118,7 +118,7 @@ The default arrangement of Spring Boot and Spring Security affords the following
* Publishes xref:servlet/authentication/events.adoc[authentication success and failure events]
It can be helpful to understand how Spring Boot is coordinating with Spring Security to achieve this.
Taking a look at {spring-boot-api-url}org/springframework/boot/autoconfigure/security/servlet/SecurityAutoConfiguration.html[Boot's security auto configuration], it does the following (simplified for illustration):
Taking a look at {spring-boot-api-url}org/springframework/boot/security/autoconfigure/SecurityAutoConfiguration.html[Boot's security auto configuration], it does the following (simplified for illustration):
.Spring Boot Security Auto Configuration
[source,java]

View File

@ -183,3 +183,11 @@ fun corsConfigurationSource(): UrlBasedCorsConfigurationSource {
}
----
======
[WARNING]
====
CORS is a browser-based security feature.
By disabling CORS in Spring Security with `.cors(CorsConfigurer::disable)`, you are not removing CORS protection from your browser.
Instead, you are removing CORS support from Spring Security, and users will not be able to interact with your Spring backend from a cross-origin browser application.
To fix CORS errors in your application, you must enable CORS support, and provide an appropriate configuration source.
====

View File

@ -370,7 +370,7 @@ Java::
----
@Bean
public AnnotationTemplateExpressionDefaults templateDefaults() {
return new AnnotationTemplateExpressionDeafults();
return new AnnotationTemplateExpressionDefaults();
}
----
@ -380,7 +380,7 @@ Kotlin::
----
@Bean
fun templateDefaults(): AnnotationTemplateExpressionDefaults {
return AnnotationTemplateExpressionDeafults()
return AnnotationTemplateExpressionDefaults()
}
----

View File

@ -108,6 +108,34 @@ spring:
require-authorization-consent: true
----
If you want to customize the default `HttpSecurity` configuration, you may override Spring Boot's auto-configuration with the following example:
[[oauth2AuthorizationServer-minimal-sample-gettingstarted]]
.SecurityConfig.java
[source,java]
----
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) {
http
.authorizeHttpRequests((authorize) ->
authorize
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults())
.oauth2AuthorizationServer((authorizationServer) ->
authorizationServer
.oidc(Customizer.withDefaults()) // Enable OpenID Connect 1.0
);
return http.build();
}
}
----
TIP: Beyond the Getting Started experience, most users will want to customize the default configuration. The xref:servlet/oauth2/authorization-server/getting-started.adoc#oauth2AuthorizationServer-defining-required-components[next section] demonstrates providing all of the necessary beans yourself.
[[oauth2AuthorizationServer-defining-required-components]]

View File

@ -41,7 +41,7 @@ See xref:servlet/oauth2/resource-server/index.adoc[OAuth 2.0 Resource Server] fo
To get started, add the `spring-security-oauth2-resource-server` dependency to your project.
When using Spring Boot, add the following starter:
.OAuth2 Client with Spring Boot
.OAuth2 Resource Server with Spring Boot
[tabs]
======
Gradle::

View File

@ -0,0 +1,28 @@
[[oauth2resourceserver-protected-resource-metadata]]
= OAuth 2.0 Protected Resource Metadata
`OAuth2ResourceServerConfigurer.ProtectedResourceMetadataConfigurer` provides the ability to customize the https://www.rfc-editor.org/rfc/rfc9728.html#section-3[OAuth 2.0 Protected Resource Metadata endpoint].
It defines an extension point that lets you customize the https://www.rfc-editor.org/rfc/rfc9728.html#section-3.2[OAuth 2.0 Protected Resource Metadata response].
`OAuth2ResourceServerConfigurer.ProtectedResourceMetadataConfigurer` provides the following configuration option:
[source,java]
----
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.oauth2ResourceServer((resourceServer) ->
resourceServer
.protectedResourceMetadata(protectedResourceMetadata ->
protectedResourceMetadata
.protectedResourceMetadataCustomizer(protectedResourceMetadataCustomizer) <1>
)
);
return http.build();
}
----
<1> `protectedResourceMetadataCustomizer()`: The `Consumer` providing access to the `OAuth2ProtectedResourceMetadata.Builder` allowing the ability to customize the claims of the Resource Server's configuration.
`OAuth2ResourceServerConfigurer.ProtectedResourceMetadataConfigurer` configures the `OAuth2ProtectedResourceMetadataFilter` and registers it with the Resource Server `SecurityFilterChain` `@Bean`.
`OAuth2ProtectedResourceMetadataFilter` is the `Filter` that returns the https://www.rfc-editor.org/rfc/rfc9728.html#section-3.2[OAuth2ProtectedResourceMetadata response].

View File

@ -1,6 +1,6 @@
{
"dependencies": {
"antora": "3.2.0-alpha.10",
"antora": "3.2.0-alpha.11",
"@antora/atlas-extension": "1.0.0-alpha.5",
"@antora/collector-extension": "1.0.2",
"@asciidoctor/tabs": "1.0.0-beta.6",

View File

@ -14,7 +14,7 @@
# limitations under the License.
#
springBootVersion=4.0.0-SNAPSHOT
version=7.0.0-RC2
version=7.0.3-SNAPSHOT
samplesBranch=main
org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError
org.gradle.parallel=true

View File

@ -4,39 +4,39 @@ io-rsocket = "1.1.5"
io-spring-javaformat = "0.0.47"
io-spring-nohttp = "0.0.11"
jakarta-websocket = "2.2.0"
org-apache-maven-resolver = "1.9.24"
org-aspectj = "1.9.24"
org-apache-maven-resolver = "1.9.25"
org-aspectj = "1.9.25.1"
org-bouncycastle = "1.80"
org-eclipse-jetty = "11.0.26"
org-jetbrains-kotlin = "2.2.20"
org-jetbrains-kotlin = "2.2.21"
org-jetbrains-kotlinx = "1.10.2"
org-mockito = "5.17.0"
org-opensaml5 = "5.1.6"
org-springframework = "7.0.0-RC1"
org-springframework = "7.0.2"
com-password4j = "1.8.4"
[libraries]
ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.5.20"
com-fasterxml-jackson-jackson-bom = "com.fasterxml.jackson:jackson-bom:2.20.0"
ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.5.22"
com-fasterxml-jackson-jackson-bom = "com.fasterxml.jackson:jackson-bom:2.20.1"
com-google-inject-guice = "com.google.inject:guice:3.0"
com-netflix-nebula-nebula-project-plugin = "com.netflix.nebula:nebula-project-plugin:8.2.0"
com-nimbusds-nimbus-jose-jwt = "com.nimbusds:nimbus-jose-jwt:10.4"
com-nimbusds-oauth2-oidc-sdk = "com.nimbusds:oauth2-oidc-sdk:11.26.1"
com-squareup-okhttp3-mockwebserver = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "com-squareup-okhttp3" }
com-squareup-okhttp3-okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "com-squareup-okhttp3" }
com-unboundid-unboundid-ldapsdk = "com.unboundid:unboundid-ldapsdk:7.0.3"
com-unboundid-unboundid-ldapsdk = "com.unboundid:unboundid-ldapsdk:7.0.4"
com-jayway-jsonpath-json-path = "com.jayway.jsonpath:json-path:2.9.0"
commons-collections = "commons-collections:commons-collections:3.2.2"
io-micrometer-context-propagation = "io.micrometer:context-propagation:1.1.3"
io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.14.12"
io-mockk = "io.mockk:mockk:1.14.6"
io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2025.0.0-RC1"
io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.14.14"
io-mockk = "io.mockk:mockk:1.14.7"
io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2025.0.1"
io-rsocket-rsocket-bom = { module = "io.rsocket:rsocket-bom", version.ref = "io-rsocket" }
io-spring-javaformat-spring-javaformat-checkstyle = { module = "io.spring.javaformat:spring-javaformat-checkstyle", version.ref = "io-spring-javaformat" }
io-spring-javaformat-spring-javaformat-gradle-plugin = { module = "io.spring.javaformat:spring-javaformat-gradle-plugin", version.ref = "io-spring-javaformat" }
io-spring-nohttp-nohttp-checkstyle = { module = "io.spring.nohttp:nohttp-checkstyle", version.ref = "io-spring-nohttp" }
io-spring-nohttp-nohttp-gradle = { module = "io.spring.nohttp:nohttp-gradle", version.ref = "io-spring-nohttp" }
io-spring-security-release-plugin = "io.spring.gradle:spring-security-release-plugin:1.0.10"
io-spring-security-release-plugin = "io.spring.gradle:spring-security-release-plugin:1.0.13"
jakarta-annotation-jakarta-annotation-api = "jakarta.annotation:jakarta.annotation-api:3.0.0"
jakarta-inject-jakarta-inject-api = "jakarta.inject:jakarta.inject-api:2.0.1"
jakarta-persistence-jakarta-persistence-api = "jakarta.persistence:jakarta.persistence-api:3.2.0"
@ -50,8 +50,8 @@ ldapsdk = "ldapsdk:ldapsdk:4.1"
net-sourceforge-htmlunit = "net.sourceforge.htmlunit:htmlunit:2.70.0"
org-htmlunit-htmlunit = "org.htmlunit:htmlunit:4.11.1"
org-apache-httpcomponents-httpclient = "org.apache.httpcomponents.client5:httpclient5:5.5.1"
org-apache-kerby-simplekdc='org.apache.kerby:kerb-simplekdc:2.1.0'
org-apache-maven-maven-resolver-provider = "org.apache.maven:maven-resolver-provider:3.9.11"
org-apache-kerby-simplekdc='org.apache.kerby:kerb-simplekdc:2.1.1'
org-apache-maven-maven-resolver-provider = "org.apache.maven:maven-resolver-provider:3.9.12"
org-apache-maven-resolver-maven-resolver-connector-basic = { module = "org.apache.maven.resolver:maven-resolver-connector-basic", version.ref = "org-apache-maven-resolver" }
org-apache-maven-resolver-maven-resolver-impl = { module = "org.apache.maven.resolver:maven-resolver-impl", version.ref = "org-apache-maven-resolver" }
org-apache-maven-resolver-maven-resolver-transport-http = { module = "org.apache.maven.resolver:maven-resolver-transport-http", version.ref = "org-apache-maven-resolver" }
@ -68,9 +68,9 @@ org-hamcrest = "org.hamcrest:hamcrest:2.2"
org-hibernate-orm-hibernate-core = "org.hibernate.orm:hibernate-core:7.0.10.Final"
org-hsqldb = "org.hsqldb:hsqldb:2.7.4"
org-jetbrains-kotlin-kotlin-bom = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "org-jetbrains-kotlin" }
org-jetbrains-kotlin-kotlin-gradle-plugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:2.2.20"
org-jetbrains-kotlin-kotlin-gradle-plugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:2.2.21"
org-jetbrains-kotlinx-kotlinx-coroutines-bom = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-bom", version.ref = "org-jetbrains-kotlinx" }
org-junit-junit-bom = "org.junit:junit-bom:6.0.0"
org-junit-junit-bom = "org.junit:junit-bom:6.0.1"
org-mockito-mockito-bom = { module = "org.mockito:mockito-bom", version.ref = "org-mockito" }
org-opensaml-opensaml5-saml-api = { module = "org.opensaml:opensaml-saml-api", version.ref = "org-opensaml5" }
org-opensaml-opensaml5-saml-impl = { module = "org.opensaml:opensaml-saml-impl", version.ref = "org-opensaml5" }
@ -81,11 +81,11 @@ org-seleniumhq-selenium-selenium-support = "org.seleniumhq.selenium:selenium-sup
org-skyscreamer-jsonassert = "org.skyscreamer:jsonassert:1.5.3"
org-slf4j-log4j-over-slf4j = "org.slf4j:log4j-over-slf4j:1.7.36"
org-slf4j-slf4j-api = "org.slf4j:slf4j-api:2.0.17"
org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2025.1.0-RC1"
org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:4.0.0-RC1"
org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2025.1.1"
org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:4.0.1"
org-springframework-spring-framework-bom = { module = "org.springframework:spring-framework-bom", version.ref = "org-springframework" }
org-synchronoss-cloud-nio-multipart-parser = "org.synchronoss.cloud:nio-multipart-parser:1.1.0"
tools-jackson-jackson-bom = "tools.jackson:jackson-bom:3.0.1"
tools-jackson-jackson-bom = "tools.jackson:jackson-bom:3.0.3"
com-google-code-gson-gson = "com.google.code.gson:gson:2.13.2"
com-thaiopensource-trag = "com.thaiopensource:trang:20091111"

View File

@ -1944,9 +1944,9 @@
"dev": true
},
"node_modules/js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"dev": true,
"dependencies": {
"argparse": "^2.0.1"
@ -4524,9 +4524,9 @@
"dev": true
},
"js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"dev": true,
"requires": {
"argparse": "^2.0.1"

View File

@ -466,7 +466,7 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
/**
* The default {@link RowMapper} that maps the current row in
* {@code java.sql.ResultSet} to {@link OAuth2Authorization} using Jackson 3's
* {@link JsonMapper} to read all {@code Map<String,Object>} within the result.
* {@link JsonMapper}.
*
* @author Rob Winch
* @since 7.0
@ -482,6 +482,7 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
public JsonMapperOAuth2AuthorizationRowMapper(RegisteredClientRepository registeredClientRepository,
JsonMapper jsonMapper) {
super(registeredClientRepository);
Assert.notNull(jsonMapper, "jsonMapper cannot be null");
this.jsonMapper = jsonMapper;
}
@ -544,7 +545,7 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
private LobHandler lobHandler = new DefaultLobHandler();
AbstractOAuth2AuthorizationRowMapper(RegisteredClientRepository registeredClientRepository) {
private AbstractOAuth2AuthorizationRowMapper(RegisteredClientRepository registeredClientRepository) {
Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null");
this.registeredClientRepository = registeredClientRepository;
}
@ -713,42 +714,36 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
}
/**
* Nested class to protect from getting {@link NoClassDefFoundError} when Jackson 2 is
* not on the classpath.
* The default {@code Function} that maps {@link OAuth2Authorization} to a
* {@code List} of {@link SqlParameterValue} using an instance of Jackson 3's
* {@link JsonMapper}.
*/
public static class JsonMapperOAuth2AuthorizationParametersMapper
extends AbstractOAuth2AuthorizationParametersMapper {
private final JsonMapper jsonMapper;
public JsonMapperOAuth2AuthorizationParametersMapper() {
this(Jackson3.createJsonMapper());
}
public JsonMapperOAuth2AuthorizationParametersMapper(JsonMapper jsonMapper) {
Assert.notNull(jsonMapper, "jsonMapper cannot be null");
this.jsonMapper = jsonMapper;
}
@Override
String writeValueAsString(Map<String, Object> data) throws Exception {
return this.jsonMapper.writeValueAsString(data);
}
}
/**
* A {@code Function} that maps {@link OAuth2Authorization} to a {@code List} of
* {@link SqlParameterValue} using an instance of Jackson 2's {@link ObjectMapper}.
*
* @deprecated This is used to allow transition to Jackson 3. Use {@link Jackson3}
* instead.
*/
@Deprecated(forRemoval = true, since = "7.0")
private static final class Jackson2 {
static ObjectMapper createObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
ClassLoader classLoader = Jackson2.class.getClassLoader();
List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
objectMapper.registerModules(securityModules);
objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
return objectMapper;
}
}
/**
* Nested class used to get a common default instance of {@link JsonMapper}. It is in
* a nested class to protect from getting {@link NoClassDefFoundError} when Jackson 3
* is not on the classpath.
*/
private static final class Jackson3 {
static JsonMapper createJsonMapper() {
List<JacksonModule> modules = SecurityJacksonModules.getModules(Jackson3.class.getClassLoader());
return JsonMapper.builder().addModules(modules).build();
}
}
/**
* @deprecated Use {@link JsonMapperOAuth2AuthorizationParametersMapper} to migrate to
* @deprecated Use {@link JsonMapperOAuth2AuthorizationParametersMapper} to switch to
* Jackson 3.
*/
@Deprecated(forRemoval = true, since = "7.0")
@ -772,32 +767,6 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
}
/**
* The default {@code Function} that maps {@link OAuth2Authorization} to a
* {@code List} of {@link SqlParameterValue} using an instance of Jackson 3's
* {@link JsonMapper}.
*/
public static final class JsonMapperOAuth2AuthorizationParametersMapper
extends AbstractOAuth2AuthorizationParametersMapper {
private final JsonMapper mapper;
public JsonMapperOAuth2AuthorizationParametersMapper() {
this(Jackson3.createJsonMapper());
}
public JsonMapperOAuth2AuthorizationParametersMapper(JsonMapper mapper) {
Assert.notNull(mapper, "mapper cannot be null");
this.mapper = mapper;
}
@Override
String writeValueAsString(Map<String, Object> data) throws Exception {
return this.mapper.writeValueAsString(data);
}
}
/**
* The base {@code Function} that maps {@link OAuth2Authorization} to a {@code List}
* of {@link SqlParameterValue}.
@ -805,7 +774,7 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
private abstract static class AbstractOAuth2AuthorizationParametersMapper
implements Function<OAuth2Authorization, List<SqlParameterValue>> {
protected AbstractOAuth2AuthorizationParametersMapper() {
private AbstractOAuth2AuthorizationParametersMapper() {
}
@Override
@ -916,6 +885,41 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
}
/**
* Nested class to protect from getting {@link NoClassDefFoundError} when Jackson 2 is
* not on the classpath.
*
* @deprecated This is used to allow transition to Jackson 3. Use {@link Jackson3}
* instead.
*/
@Deprecated(forRemoval = true, since = "7.0")
private static final class Jackson2 {
private static ObjectMapper createObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
ClassLoader classLoader = Jackson2.class.getClassLoader();
List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
objectMapper.registerModules(securityModules);
objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
return objectMapper;
}
}
/**
* Nested class used to get a common default instance of {@link JsonMapper}. It is in
* a nested class to protect from getting {@link NoClassDefFoundError} when Jackson 3
* is not on the classpath.
*/
private static final class Jackson3 {
private static JsonMapper createJsonMapper() {
List<JacksonModule> modules = SecurityJacksonModules.getModules(Jackson3.class.getClassLoader());
return JsonMapper.builder().addModules(modules).build();
}
}
private static final class LobCreatorArgumentPreparedStatementSetter extends ArgumentPreparedStatementSetter {
private final LobCreator lobCreator;

View File

@ -33,6 +33,7 @@ import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.jackson.CoreJacksonModule;
import org.springframework.security.jackson2.CoreJackson2Module;
import org.springframework.security.oauth2.core.AbstractOAuth2Token;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
@ -48,9 +49,11 @@ import org.springframework.security.oauth2.server.authorization.JdbcOAuth2Author
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeActor;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeCompositeAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.jackson.OAuth2AuthorizationServerJacksonModule;
import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module;
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
import org.springframework.security.web.authentication.WebAuthenticationDetails;
import org.springframework.security.web.jackson.WebServletJacksonModule;
import org.springframework.security.web.jackson2.WebServletJackson2Module;
import org.springframework.security.web.savedrequest.DefaultSavedRequest;
import org.springframework.util.ClassUtils;
@ -67,7 +70,18 @@ import org.springframework.util.ClassUtils;
*/
class OAuth2AuthorizationServerBeanRegistrationAotProcessor implements BeanRegistrationAotProcessor {
private boolean jackson2Contributed;
private static final boolean jackson2Present;
private static final boolean jackson3Present;
static {
ClassLoader classLoader = ClassUtils.getDefaultClassLoader();
jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader)
&& ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
jackson3Present = ClassUtils.isPresent("tools.jackson.databind.json.JsonMapper", classLoader);
}
private boolean jacksonContributed;
@Override
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
@ -79,17 +93,17 @@ class OAuth2AuthorizationServerBeanRegistrationAotProcessor implements BeanRegis
// @formatter:off
if ((isJdbcBasedOAuth2AuthorizationService || isJdbcBasedRegisteredClientRepository)
&& !this.jackson2Contributed) {
Jackson2ConfigurationBeanRegistrationAotContribution jackson2Contribution =
new Jackson2ConfigurationBeanRegistrationAotContribution();
this.jackson2Contributed = true;
return jackson2Contribution;
&& !this.jacksonContributed) {
JacksonConfigurationBeanRegistrationAotContribution jacksonContribution =
new JacksonConfigurationBeanRegistrationAotContribution();
this.jacksonContributed = true;
return jacksonContribution;
}
// @formatter:on
return null;
}
private static class Jackson2ConfigurationBeanRegistrationAotContribution
private static class JacksonConfigurationBeanRegistrationAotContribution
implements BeanRegistrationAotContribution {
private final BindingReflectionHintsRegistrar reflectionHintsRegistrar = new BindingReflectionHintsRegistrar();
@ -109,7 +123,6 @@ class OAuth2AuthorizationServerBeanRegistrationAotProcessor implements BeanRegis
.registerType(HashSet.class, MemberCategory.DECLARED_FIELDS,
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS);
// Spring Security and Spring Authorization Server
hints.reflection()
.registerTypes(Arrays.asList(TypeReference.of(AbstractAuthenticationToken.class),
TypeReference.of(DefaultSavedRequest.Builder.class),
@ -128,75 +141,143 @@ class OAuth2AuthorizationServerBeanRegistrationAotProcessor implements BeanRegis
(builder) -> builder.withMembers(MemberCategory.DECLARED_FIELDS,
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS));
// Jackson Modules - Spring Security and Spring Authorization Server
hints.reflection()
.registerTypes(
Arrays.asList(TypeReference.of(CoreJackson2Module.class),
TypeReference.of(WebServletJackson2Module.class),
TypeReference.of(OAuth2AuthorizationServerJackson2Module.class)),
(builder) -> builder.withMembers(MemberCategory.DECLARED_FIELDS,
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS));
// Jackson Modules
if (jackson2Present) {
hints.reflection()
.registerTypes(
Arrays.asList(TypeReference.of(CoreJackson2Module.class),
TypeReference.of(WebServletJackson2Module.class),
TypeReference.of(OAuth2AuthorizationServerJackson2Module.class)),
(builder) -> builder.withMembers(MemberCategory.DECLARED_FIELDS,
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_DECLARED_METHODS));
}
if (jackson3Present) {
hints.reflection()
.registerTypes(
Arrays.asList(TypeReference.of(CoreJacksonModule.class),
TypeReference.of(WebServletJacksonModule.class),
TypeReference.of(OAuth2AuthorizationServerJacksonModule.class)),
(builder) -> builder.withMembers(MemberCategory.DECLARED_FIELDS,
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_DECLARED_METHODS));
}
// Jackson Mixins - Spring Security and Spring Authorization Server
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.jackson2.UnmodifiableSetMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.jackson2.UnmodifiableListMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.jackson2.UnmodifiableMapMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
"org.springframework.security.oauth2.server.authorization.jackson2.UnmodifiableMapMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.oauth2.server.authorization.jackson2.HashSetMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.web.jackson2.DefaultSavedRequestMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.web.jackson2.WebAuthenticationDetailsMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.jackson2.UsernamePasswordAuthenticationTokenMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.jackson2.UserMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.jackson2.SimpleGrantedAuthorityMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
"org.springframework.security.oauth2.server.authorization.jackson2.OAuth2TokenExchangeActorMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
"org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationRequestMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
"org.springframework.security.oauth2.server.authorization.jackson2.OAuth2TokenExchangeCompositeAuthenticationTokenMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
"org.springframework.security.oauth2.server.authorization.jackson2.OAuth2TokenFormatMixin"));
// Jackson Mixins
if (jackson2Present) {
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.jackson2.UnmodifiableSetMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.jackson2.UnmodifiableListMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.jackson2.UnmodifiableMapMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
"org.springframework.security.oauth2.server.authorization.jackson2.UnmodifiableMapMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.oauth2.server.authorization.jackson2.HashSetMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.web.jackson2.DefaultSavedRequestMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.web.jackson2.WebAuthenticationDetailsMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.jackson2.UsernamePasswordAuthenticationTokenMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.jackson2.UserMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.jackson2.SimpleGrantedAuthorityMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
"org.springframework.security.oauth2.server.authorization.jackson2.OAuth2TokenExchangeActorMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
"org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationRequestMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
"org.springframework.security.oauth2.server.authorization.jackson2.OAuth2TokenExchangeCompositeAuthenticationTokenMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
"org.springframework.security.oauth2.server.authorization.jackson2.OAuth2TokenFormatMixin"));
}
if (jackson3Present) {
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.web.jackson.DefaultSavedRequestMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.web.jackson.WebAuthenticationDetailsMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.jackson.UsernamePasswordAuthenticationTokenMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.jackson.UserMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.jackson.SimpleGrantedAuthorityMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
"org.springframework.security.oauth2.server.authorization.jackson.OAuth2TokenExchangeActorMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
"org.springframework.security.oauth2.server.authorization.jackson.OAuth2AuthorizationRequestMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
"org.springframework.security.oauth2.server.authorization.jackson.OAuth2TokenExchangeCompositeAuthenticationTokenMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
"org.springframework.security.oauth2.server.authorization.jackson.OAuth2TokenFormatMixin"));
}
// Check if Spring Security OAuth2 Client is on classpath
// Check if OAuth2 Client is on classpath
if (ClassUtils.isPresent("org.springframework.security.oauth2.client.registration.ClientRegistration",
ClassUtils.getDefaultClassLoader())) {
// Jackson Module (and required types) - Spring Security OAuth2 Client
hints.reflection()
.registerTypes(Arrays.asList(
TypeReference
.of("org.springframework.security.oauth2.client.jackson2.OAuth2ClientJackson2Module"),
TypeReference
.of("org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken")),
.registerType(TypeReference
.of("org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken"),
(builder) -> builder.withMembers(MemberCategory.DECLARED_FIELDS,
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_DECLARED_METHODS));
// Jackson Mixins - Spring Security OAuth2 Client
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
"org.springframework.security.oauth2.client.jackson2.OAuth2AuthenticationTokenMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.oauth2.client.jackson2.DefaultOidcUserMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.oauth2.client.jackson2.DefaultOAuth2UserMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.oauth2.client.jackson2.OidcUserAuthorityMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.oauth2.client.jackson2.OAuth2UserAuthorityMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.oauth2.client.jackson2.OidcIdTokenMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.oauth2.client.jackson2.OidcUserInfoMixin"));
// Jackson Module
if (jackson2Present) {
hints.reflection()
.registerType(TypeReference
.of("org.springframework.security.oauth2.client.jackson2.OAuth2ClientJackson2Module"),
(builder) -> builder.withMembers(MemberCategory.DECLARED_FIELDS,
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_DECLARED_METHODS));
}
if (jackson3Present) {
hints.reflection()
.registerType(
TypeReference
.of("org.springframework.security.oauth2.client.jackson.OAuth2ClientJacksonModule"),
(builder) -> builder.withMembers(MemberCategory.DECLARED_FIELDS,
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_DECLARED_METHODS));
}
// Jackson Mixins
if (jackson2Present) {
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
"org.springframework.security.oauth2.client.jackson2.OAuth2AuthenticationTokenMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.oauth2.client.jackson2.DefaultOidcUserMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.oauth2.client.jackson2.DefaultOAuth2UserMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.oauth2.client.jackson2.OidcUserAuthorityMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.oauth2.client.jackson2.OAuth2UserAuthorityMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.oauth2.client.jackson2.OidcIdTokenMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.oauth2.client.jackson2.OidcUserInfoMixin"));
}
if (jackson3Present) {
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
"org.springframework.security.oauth2.client.jackson.OAuth2AuthenticationTokenMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.oauth2.client.jackson.DefaultOidcUserMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.oauth2.client.jackson.DefaultOAuth2UserMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.oauth2.client.jackson.OidcUserAuthorityMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.oauth2.client.jackson.OAuth2UserAuthorityMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.oauth2.client.jackson.OidcIdTokenMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.oauth2.client.jackson.OidcUserInfoMixin"));
}
}
}

View File

@ -0,0 +1,53 @@
/*
* 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.oauth2.server.authorization.aot.hint;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.TypeReference;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter;
/**
* {@link RuntimeHintsRegistrar} that contributes the required {@link RuntimeHints} for
* OAuth 2.1 Authorization Server. Statically registered via
* META-INF/spring/aot.factories.
*
* @author Joe Grandja
* @since 7.0
*/
class OAuth2AuthorizationServerRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
hints.reflection()
.registerType(OAuth2AuthorizationCodeRequestAuthenticationProvider.class,
MemberCategory.INVOKE_DECLARED_METHODS);
hints.reflection()
.registerType(OAuth2AuthorizationEndpointFilter.class, MemberCategory.INVOKE_DECLARED_METHODS);
hints.reflection()
.registerType(TypeReference
.of("org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter$OAuth2AuthorizationCodeRequestValidatingFilter"),
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
hints.reflection()
.registerType(OAuth2AuthorizationCodeRequestAuthenticationToken.class, MemberCategory.DECLARED_FIELDS);
}
}

View File

@ -190,51 +190,55 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen
OAuth2AuthorizationCodeRequestAuthenticationContext.Builder authenticationContextBuilder = OAuth2AuthorizationCodeRequestAuthenticationContext
.with(authorizationCodeRequestAuthentication)
.registeredClient(registeredClient);
OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext = authenticationContextBuilder
.build();
// grant_type
OAuth2AuthorizationCodeRequestAuthenticationValidator.DEFAULT_AUTHORIZATION_GRANT_TYPE_VALIDATOR
.accept(authenticationContext);
if (!authorizationCodeRequestAuthentication.isValidated()) {
OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext = authenticationContextBuilder
.build();
// redirect_uri and scope
this.authenticationValidator.accept(authenticationContext);
// grant_type
OAuth2AuthorizationCodeRequestAuthenticationValidator.DEFAULT_AUTHORIZATION_GRANT_TYPE_VALIDATOR
.accept(authenticationContext);
// code_challenge (REQUIRED for public clients) - RFC 7636 (PKCE)
OAuth2AuthorizationCodeRequestAuthenticationValidator.DEFAULT_CODE_CHALLENGE_VALIDATOR
.accept(authenticationContext);
// redirect_uri and scope
this.authenticationValidator.accept(authenticationContext);
// prompt (OPTIONAL for OpenID Connect 1.0 Authentication Request)
Set<String> promptValues = Collections.emptySet();
if (authorizationCodeRequestAuthentication.getScopes().contains(OidcScopes.OPENID)) {
String prompt = (String) authorizationCodeRequestAuthentication.getAdditionalParameters().get("prompt");
if (StringUtils.hasText(prompt)) {
OAuth2AuthorizationCodeRequestAuthenticationValidator.DEFAULT_PROMPT_VALIDATOR
.accept(authenticationContext);
promptValues = new HashSet<>(Arrays.asList(StringUtils.delimitedListToStringArray(prompt, " ")));
// code_challenge (REQUIRED for public clients) - RFC 7636 (PKCE)
OAuth2AuthorizationCodeRequestAuthenticationValidator.DEFAULT_CODE_CHALLENGE_VALIDATOR
.accept(authenticationContext);
// prompt (OPTIONAL for OpenID Connect 1.0 Authentication Request)
OAuth2AuthorizationCodeRequestAuthenticationValidator.DEFAULT_PROMPT_VALIDATOR
.accept(authenticationContext);
authorizationCodeRequestAuthentication.setValidated(true);
if (this.logger.isTraceEnabled()) {
this.logger.trace("Validated authorization code request parameters");
}
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Validated authorization code request parameters");
}
// ---------------
// The request is valid - ensure the resource owner is authenticated
// ---------------
Authentication principal = (Authentication) authorizationCodeRequestAuthentication.getPrincipal();
Set<String> promptValues = Collections.emptySet();
if (authorizationCodeRequestAuthentication.getScopes().contains(OidcScopes.OPENID)) {
String prompt = (String) authorizationCodeRequestAuthentication.getAdditionalParameters().get("prompt");
if (StringUtils.hasText(prompt)) {
promptValues = new HashSet<>(Arrays.asList(StringUtils.delimitedListToStringArray(prompt, " ")));
}
}
if (!isPrincipalAuthenticated(principal)) {
if (promptValues.contains(OidcPrompt.NONE)) {
// Return an error instead of displaying the login page (via the
// configured AuthenticationEntryPoint)
throwError("login_required", "prompt", authorizationCodeRequestAuthentication, registeredClient);
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Did not authenticate authorization code request since principal not authenticated");
else {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, "principal", authorizationCodeRequestAuthentication,
registeredClient);
}
// Return the authorization request as-is where isAuthenticated() is false
return authorizationCodeRequestAuthentication;
}
OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
@ -400,6 +404,13 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen
this.authorizationConsentRequired = authorizationConsentRequired;
}
Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> getAuthenticationValidatorComposite() {
return OAuth2AuthorizationCodeRequestAuthenticationValidator.DEFAULT_AUTHORIZATION_GRANT_TYPE_VALIDATOR
.andThen(this.authenticationValidator)
.andThen(OAuth2AuthorizationCodeRequestAuthenticationValidator.DEFAULT_CODE_CHALLENGE_VALIDATOR)
.andThen(OAuth2AuthorizationCodeRequestAuthenticationValidator.DEFAULT_PROMPT_VALIDATOR);
}
private static boolean isAuthorizationConsentRequired(
OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext) {
if (!authenticationContext.getRegisteredClient().getClientSettings().isRequireAuthorizationConsent()) {

View File

@ -42,6 +42,8 @@ public class OAuth2AuthorizationCodeRequestAuthenticationToken
private final OAuth2AuthorizationCode authorizationCode;
private boolean validated;
/**
* Constructs an {@code OAuth2AuthorizationCodeRequestAuthenticationToken} using the
* provided parameters.
@ -89,4 +91,12 @@ public class OAuth2AuthorizationCodeRequestAuthenticationToken
return this.authorizationCode;
}
final boolean isValidated() {
return this.validated;
}
final void setValidated(boolean validated) {
this.validated = validated;
}
}

View File

@ -28,19 +28,23 @@ import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import tools.jackson.databind.JacksonModule;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.context.annotation.ImportRuntimeHints;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.io.ClassPathResource;
import org.springframework.jdbc.core.ArgumentPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.PreparedStatementSetter;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.SqlParameterValue;
import org.springframework.security.jackson.SecurityJacksonModules;
import org.springframework.security.jackson2.SecurityJackson2Modules;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
@ -134,8 +138,8 @@ public class JdbcRegisteredClientRepository implements RegisteredClientRepositor
public JdbcRegisteredClientRepository(JdbcOperations jdbcOperations) {
Assert.notNull(jdbcOperations, "jdbcOperations cannot be null");
this.jdbcOperations = jdbcOperations;
this.registeredClientRowMapper = new RegisteredClientRowMapper();
this.registeredClientParametersMapper = new RegisteredClientParametersMapper();
this.registeredClientRowMapper = new JsonMapperRegisteredClientRowMapper();
this.registeredClientParametersMapper = new JsonMapperRegisteredClientParametersMapper();
}
@Override
@ -206,7 +210,7 @@ public class JdbcRegisteredClientRepository implements RegisteredClientRepositor
/**
* Sets the {@link RowMapper} used for mapping the current row in
* {@code java.sql.ResultSet} to {@link RegisteredClient}. The default is
* {@link RegisteredClientRowMapper}.
* {@link JsonMapperRegisteredClientRowMapper}.
* @param registeredClientRowMapper the {@link RowMapper} used for mapping the current
* row in {@code ResultSet} to {@link RegisteredClient}
*/
@ -218,7 +222,7 @@ public class JdbcRegisteredClientRepository implements RegisteredClientRepositor
/**
* Sets the {@code Function} used for mapping {@link RegisteredClient} to a
* {@code List} of {@link SqlParameterValue}. The default is
* {@link RegisteredClientParametersMapper}.
* {@link JsonMapperRegisteredClientParametersMapper}.
* @param registeredClientParametersMapper the {@code Function} used for mapping
* {@link RegisteredClient} to a {@code List} of {@link SqlParameterValue}
*/
@ -242,17 +246,76 @@ public class JdbcRegisteredClientRepository implements RegisteredClientRepositor
/**
* The default {@link RowMapper} that maps the current row in
* {@code java.sql.ResultSet} to {@link RegisteredClient}.
* {@code java.sql.ResultSet} to {@link RegisteredClient} using Jackson 3's
* {@link JsonMapper}.
*
* @author Joe Grandja
* @since 7.0
*/
public static class RegisteredClientRowMapper implements RowMapper<RegisteredClient> {
public static class JsonMapperRegisteredClientRowMapper extends AbstractRegisteredClientRowMapper {
private ObjectMapper objectMapper = new ObjectMapper();
private final JsonMapper jsonMapper;
public RegisteredClientRowMapper() {
ClassLoader classLoader = JdbcRegisteredClientRepository.class.getClassLoader();
List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
this.objectMapper.registerModules(securityModules);
this.objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
public JsonMapperRegisteredClientRowMapper() {
this(Jackson3.createJsonMapper());
}
public JsonMapperRegisteredClientRowMapper(JsonMapper jsonMapper) {
Assert.notNull(jsonMapper, "jsonMapper cannot be null");
this.jsonMapper = jsonMapper;
}
@Override
Map<String, Object> readValue(String data) {
final ParameterizedTypeReference<Map<String, Object>> typeReference = new ParameterizedTypeReference<>() {
};
tools.jackson.databind.JavaType javaType = this.jsonMapper.getTypeFactory()
.constructType(typeReference.getType());
return this.jsonMapper.readValue(data, javaType);
}
}
/**
* A {@link RowMapper} that maps the current row in {@code java.sql.ResultSet} to
* {@link RegisteredClient} using Jackson 2's {@link ObjectMapper}.
*
* @deprecated Use {@link JsonMapperRegisteredClientRowMapper} to switch to Jackson 3.
*/
@Deprecated(forRemoval = true, since = "7.0")
public static class RegisteredClientRowMapper extends AbstractRegisteredClientRowMapper {
private ObjectMapper objectMapper = Jackson2.createObjectMapper();
public final void setObjectMapper(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "objectMapper cannot be null");
this.objectMapper = objectMapper;
}
protected final ObjectMapper getObjectMapper() {
return this.objectMapper;
}
@Override
Map<String, Object> readValue(String data) throws JsonProcessingException {
final ParameterizedTypeReference<Map<String, Object>> typeReference = new ParameterizedTypeReference<>() {
};
com.fasterxml.jackson.databind.JavaType javaType = this.objectMapper.getTypeFactory()
.constructType(typeReference.getType());
return this.objectMapper.readValue(data, javaType);
}
}
/**
* The base {@link RowMapper} that maps the current row in {@code java.sql.ResultSet}
* to {@link RegisteredClient}. This is extracted to a distinct class so that
* {@link RegisteredClientRowMapper} can be deprecated in favor of
* {@link JsonMapperRegisteredClientRowMapper}.
*/
private abstract static class AbstractRegisteredClientRowMapper implements RowMapper<RegisteredClient> {
private AbstractRegisteredClientRowMapper() {
}
@Override
@ -299,25 +362,17 @@ public class JdbcRegisteredClientRepository implements RegisteredClientRepositor
return builder.build();
}
public final void setObjectMapper(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "objectMapper cannot be null");
this.objectMapper = objectMapper;
}
protected final ObjectMapper getObjectMapper() {
return this.objectMapper;
}
private Map<String, Object> parseMap(String data) {
try {
return this.objectMapper.readValue(data, new TypeReference<>() {
});
return readValue(data);
}
catch (Exception ex) {
throw new IllegalArgumentException(ex.getMessage(), ex);
}
}
abstract Map<String, Object> readValue(String data) throws Exception;
private static AuthorizationGrantType resolveAuthorizationGrantType(String authorizationGrantType) {
if (AuthorizationGrantType.AUTHORIZATION_CODE.getValue().equals(authorizationGrantType)) {
return AuthorizationGrantType.AUTHORIZATION_CODE;
@ -350,18 +405,64 @@ public class JdbcRegisteredClientRepository implements RegisteredClientRepositor
/**
* The default {@code Function} that maps {@link RegisteredClient} to a {@code List}
* of {@link SqlParameterValue}.
* of {@link SqlParameterValue} using an instance of Jackson 3's {@link JsonMapper}.
*/
public static class RegisteredClientParametersMapper
public static class JsonMapperRegisteredClientParametersMapper extends AbstractRegisteredClientParametersMapper {
private final JsonMapper jsonMapper;
public JsonMapperRegisteredClientParametersMapper() {
this(Jackson3.createJsonMapper());
}
public JsonMapperRegisteredClientParametersMapper(JsonMapper jsonMapper) {
Assert.notNull(jsonMapper, "jsonMapper cannot be null");
this.jsonMapper = jsonMapper;
}
@Override
String writeValueAsString(Map<String, Object> data) throws Exception {
return this.jsonMapper.writeValueAsString(data);
}
}
/**
* A {@code Function} that maps {@link RegisteredClient} to a {@code List} of
* {@link SqlParameterValue} using an instance of Jackson 2's {@link ObjectMapper}.
*
* @deprecated Use {@link JsonMapperRegisteredClientParametersMapper} to switch to
* Jackson 3.
*/
@Deprecated(forRemoval = true, since = "7.0")
public static class RegisteredClientParametersMapper extends AbstractRegisteredClientParametersMapper {
private ObjectMapper objectMapper = Jackson2.createObjectMapper();
public final void setObjectMapper(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "objectMapper cannot be null");
this.objectMapper = objectMapper;
}
protected final ObjectMapper getObjectMapper() {
return this.objectMapper;
}
@Override
String writeValueAsString(Map<String, Object> data) throws JsonProcessingException {
return this.objectMapper.writeValueAsString(data);
}
}
/**
* The base {@code Function} that maps {@link RegisteredClient} to a {@code List} of
* {@link SqlParameterValue}.
*/
private abstract static class AbstractRegisteredClientParametersMapper
implements Function<RegisteredClient, List<SqlParameterValue>> {
private ObjectMapper objectMapper = new ObjectMapper();
public RegisteredClientParametersMapper() {
ClassLoader classLoader = JdbcRegisteredClientRepository.class.getClassLoader();
List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
this.objectMapper.registerModules(securityModules);
this.objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
private AbstractRegisteredClientParametersMapper() {
}
@Override
@ -403,24 +504,52 @@ public class JdbcRegisteredClientRepository implements RegisteredClientRepositor
new SqlParameterValue(Types.VARCHAR, writeMap(registeredClient.getTokenSettings().getSettings())));
}
public final void setObjectMapper(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "objectMapper cannot be null");
this.objectMapper = objectMapper;
}
protected final ObjectMapper getObjectMapper() {
return this.objectMapper;
}
private String writeMap(Map<String, Object> data) {
try {
return this.objectMapper.writeValueAsString(data);
return writeValueAsString(data);
}
catch (Exception ex) {
throw new IllegalArgumentException(ex.getMessage(), ex);
}
}
abstract String writeValueAsString(Map<String, Object> data) throws Exception;
}
/**
* Nested class to protect from getting {@link NoClassDefFoundError} when Jackson 2 is
* not on the classpath.
*
* @deprecated This is used to allow transition to Jackson 3. Use {@link Jackson3}
* instead.
*/
@Deprecated(forRemoval = true, since = "7.0")
private static final class Jackson2 {
private static ObjectMapper createObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
ClassLoader classLoader = Jackson2.class.getClassLoader();
List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
objectMapper.registerModules(securityModules);
objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
return objectMapper;
}
}
/**
* Nested class used to get a common default instance of {@link JsonMapper}. It is in
* a nested class to protect from getting {@link NoClassDefFoundError} when Jackson 3
* is not on the classpath.
*/
private static final class Jackson3 {
private static JsonMapper createJsonMapper() {
List<JacksonModule> modules = SecurityJacksonModules.getModules(Jackson3.class.getClassLoader());
return JsonMapper.builder().addModules(modules).build();
}
}
static class JdbcRegisteredClientRepositoryRuntimeHintsRegistrar implements RuntimeHintsRegistrar {

View File

@ -17,11 +17,14 @@
package org.springframework.security.oauth2.server.authorization.web;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.Set;
import java.util.function.Consumer;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
@ -38,14 +41,18 @@ import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationContext;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationException;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationToken;
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.web.authentication.OAuth2AuthorizationCodeRequestAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationConsentAuthenticationConverter;
import org.springframework.security.web.DefaultRedirectStrategy;
@ -64,6 +71,7 @@ import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.UriComponentsBuilder;
@ -180,21 +188,18 @@ public final class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilte
}
try {
Authentication authentication = this.authenticationConverter.convert(request);
if (authentication instanceof AbstractAuthenticationToken authenticationToken) {
authenticationToken.setDetails(this.authenticationDetailsSource.buildDetails(request));
// Get the pre-validated authorization code request (if available),
// which was set by OAuth2AuthorizationCodeRequestValidatingFilter
Authentication authentication = (Authentication) request
.getAttribute(OAuth2AuthorizationCodeRequestAuthenticationToken.class.getName());
if (authentication == null) {
authentication = this.authenticationConverter.convert(request);
if (authentication instanceof AbstractAuthenticationToken authenticationToken) {
authenticationToken.setDetails(this.authenticationDetailsSource.buildDetails(request));
}
}
Authentication authenticationResult = this.authenticationManager.authenticate(authentication);
if (!authenticationResult.isAuthenticated()) {
// If the Principal (Resource Owner) is not authenticated then pass
// through the chain
// with the expectation that the authentication process will commence via
// AuthenticationEntryPoint
filterChain.doFilter(request, response);
return;
}
if (authenticationResult instanceof OAuth2AuthorizationConsentAuthenticationToken authorizationConsentAuthenticationToken) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Authorization consent is required");
@ -401,4 +406,109 @@ public final class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilte
this.redirectStrategy.sendRedirect(request, response, redirectUri);
}
Filter createAuthorizationCodeRequestValidatingFilter(RegisteredClientRepository registeredClientRepository,
Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authenticationValidator) {
return new OAuth2AuthorizationCodeRequestValidatingFilter(registeredClientRepository, authenticationValidator);
}
/**
* A {@code Filter} that is applied before {@code OAuth2AuthorizationEndpointFilter}
* and handles the pre-validation of an OAuth 2.0 Authorization Code Request.
*/
private final class OAuth2AuthorizationCodeRequestValidatingFilter extends OncePerRequestFilter {
private final RegisteredClientRepository registeredClientRepository;
private final Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authenticationValidator;
private final Field setValidatedField;
private OAuth2AuthorizationCodeRequestValidatingFilter(RegisteredClientRepository registeredClientRepository,
Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authenticationValidator) {
Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null");
Assert.notNull(authenticationValidator, "authenticationValidator cannot be null");
this.registeredClientRepository = registeredClientRepository;
this.authenticationValidator = authenticationValidator;
this.setValidatedField = ReflectionUtils.findField(OAuth2AuthorizationCodeRequestAuthenticationToken.class,
"validated");
ReflectionUtils.makeAccessible(this.setValidatedField);
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
if (!OAuth2AuthorizationEndpointFilter.this.authorizationEndpointMatcher.matches(request)) {
filterChain.doFilter(request, response);
return;
}
try {
Authentication authentication = OAuth2AuthorizationEndpointFilter.this.authenticationConverter
.convert(request);
if (!(authentication instanceof OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication)) {
filterChain.doFilter(request, response);
return;
}
String requestUri = (String) authorizationCodeRequestAuthentication.getAdditionalParameters()
.get(OAuth2ParameterNames.REQUEST_URI);
if (StringUtils.hasText(requestUri)) {
filterChain.doFilter(request, response);
return;
}
authorizationCodeRequestAuthentication.setDetails(
OAuth2AuthorizationEndpointFilter.this.authenticationDetailsSource.buildDetails(request));
RegisteredClient registeredClient = this.registeredClientRepository
.findByClientId(authorizationCodeRequestAuthentication.getClientId());
if (registeredClient == null) {
String redirectUri = null; // Prevent redirect
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult = new OAuth2AuthorizationCodeRequestAuthenticationToken(
authorizationCodeRequestAuthentication.getAuthorizationUri(),
authorizationCodeRequestAuthentication.getClientId(),
(Authentication) authorizationCodeRequestAuthentication.getPrincipal(), redirectUri,
authorizationCodeRequestAuthentication.getState(),
authorizationCodeRequestAuthentication.getScopes(),
authorizationCodeRequestAuthentication.getAdditionalParameters());
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST,
"OAuth 2.0 Parameter: " + OAuth2ParameterNames.CLIENT_ID,
"https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1");
throw new OAuth2AuthorizationCodeRequestAuthenticationException(error,
authorizationCodeRequestAuthenticationResult);
}
OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext = OAuth2AuthorizationCodeRequestAuthenticationContext
.with(authorizationCodeRequestAuthentication)
.registeredClient(registeredClient)
.build();
this.authenticationValidator.accept(authenticationContext);
ReflectionUtils.setField(this.setValidatedField, authorizationCodeRequestAuthentication, true);
// Set the validated authorization code request as a request
// attribute
// to be used upstream by OAuth2AuthorizationEndpointFilter
request.setAttribute(OAuth2AuthorizationCodeRequestAuthenticationToken.class.getName(),
authorizationCodeRequestAuthentication);
filterChain.doFilter(request, response);
}
catch (OAuth2AuthenticationException ex) {
if (this.logger.isTraceEnabled()) {
this.logger.trace(LogMessage.format("Authorization request failed: %s", ex.getError()), ex);
}
OAuth2AuthorizationEndpointFilter.this.authenticationFailureHandler.onAuthenticationFailure(request,
response, ex);
}
finally {
request.removeAttribute(OAuth2AuthorizationCodeRequestAuthenticationToken.class.getName());
}
}
}
}

View File

@ -1,2 +1,5 @@
org.springframework.beans.factory.aot.BeanRegistrationAotProcessor=\
org.springframework.security.oauth2.server.authorization.aot.hint.OAuth2AuthorizationServerBeanRegistrationAotProcessor
org.springframework.aot.hint.RuntimeHintsRegistrar=\
org.springframework.security.oauth2.server.authorization.aot.hint.OAuth2AuthorizationServerRuntimeHints

View File

@ -428,7 +428,7 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
}
@Test
public void authenticateWhenPrincipalNotAuthenticatedThenReturnAuthorizationCodeRequest() {
public void authenticateWhenPrincipalNotAuthenticatedThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
.willReturn(registeredClient);
@ -438,12 +438,10 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken(
AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, redirectUri, STATE,
registeredClient.getScopes(), createPkceParameters());
OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult = (OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider
.authenticate(authentication);
assertThat(authenticationResult).isSameAs(authentication);
assertThat(authenticationResult.isAuthenticated()).isFalse();
assertThatExceptionOfType(OAuth2AuthorizationCodeRequestAuthenticationException.class)
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.satisfies((ex) -> assertAuthenticationException(ex, OAuth2ErrorCodes.INVALID_REQUEST, "principal",
authentication.getRedirectUri()));
}
@Test

View File

@ -25,13 +25,13 @@ import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import tools.jackson.databind.JacksonModule;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.jdbc.core.ArgumentPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.JdbcTemplate;
@ -41,13 +41,12 @@ import org.springframework.jdbc.core.SqlParameterValue;
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.jackson2.SecurityJackson2Modules;
import org.springframework.security.jackson.SecurityJacksonModules;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientParametersMapper;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientRowMapper;
import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.JsonMapperRegisteredClientParametersMapper;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.JsonMapperRegisteredClientRowMapper;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
import org.springframework.util.StringUtils;
@ -222,9 +221,9 @@ public class JdbcRegisteredClientRepositoryTests {
@Test
public void saveLoadRegisteredClientWhenCustomStrategiesSetThenCalled() throws Exception {
RowMapper<RegisteredClient> registeredClientRowMapper = spy(new RegisteredClientRowMapper());
RowMapper<RegisteredClient> registeredClientRowMapper = spy(new JsonMapperRegisteredClientRowMapper());
this.registeredClientRepository.setRegisteredClientRowMapper(registeredClientRowMapper);
RegisteredClientParametersMapper clientParametersMapper = new RegisteredClientParametersMapper();
JsonMapperRegisteredClientParametersMapper clientParametersMapper = new JsonMapperRegisteredClientParametersMapper();
Function<RegisteredClient, List<SqlParameterValue>> registeredClientParametersMapper = spy(
clientParametersMapper);
this.registeredClientRepository.setRegisteredClientParametersMapper(registeredClientParametersMapper);
@ -365,16 +364,14 @@ public class JdbcRegisteredClientRepositoryTests {
return !result.isEmpty() ? result.get(0) : null;
}
@SuppressWarnings("removal")
private static final class CustomRegisteredClientRowMapper implements RowMapper<RegisteredClient> {
private final ObjectMapper objectMapper = new ObjectMapper();
private final JsonMapper jsonMapper;
private CustomRegisteredClientRowMapper() {
ClassLoader classLoader = CustomJdbcRegisteredClientRepository.class.getClassLoader();
List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
this.objectMapper.registerModules(securityModules);
this.objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
List<JacksonModule> modules = SecurityJacksonModules
.getModules(CustomRegisteredClientRowMapper.class.getClassLoader());
this.jsonMapper = JsonMapper.builder().addModules(modules).build();
}
@Override
@ -418,9 +415,12 @@ public class JdbcRegisteredClientRepositoryTests {
}
private Map<String, Object> parseMap(String data) {
final ParameterizedTypeReference<Map<String, Object>> typeReference = new ParameterizedTypeReference<>() {
};
try {
return this.objectMapper.readValue(data, new TypeReference<>() {
});
tools.jackson.databind.JavaType javaType = this.jsonMapper.getTypeFactory()
.constructType(typeReference.getType());
return this.jsonMapper.readValue(data, javaType);
}
catch (Exception ex) {
throw new IllegalArgumentException(ex.getMessage(), ex);

View File

@ -372,7 +372,11 @@ public class OAuth2AuthorizationEndpointFilterTests {
given(authenticationConverter.convert(any())).willReturn(authorizationCodeRequestAuthentication);
this.filter.setAuthenticationConverter(authenticationConverter);
given(this.authenticationManager.authenticate(any())).willReturn(authorizationCodeRequestAuthentication);
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult = new OAuth2AuthorizationCodeRequestAuthenticationToken(
AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, this.authorizationCode,
registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes());
authorizationCodeRequestAuthenticationResult.setAuthenticated(true);
given(this.authenticationManager.authenticate(any())).willReturn(authorizationCodeRequestAuthenticationResult);
MockHttpServletRequest request = createAuthorizationRequest(registeredClient);
MockHttpServletResponse response = new MockHttpServletResponse();
@ -382,7 +386,7 @@ public class OAuth2AuthorizationEndpointFilterTests {
verify(authenticationConverter).convert(any());
verify(this.authenticationManager).authenticate(any());
verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
verifyNoInteractions(filterChain);
}
@Test
@ -461,9 +465,6 @@ public class OAuth2AuthorizationEndpointFilterTests {
@Test
public void doFilterWhenCustomAuthenticationDetailsSourceThenUsed() throws Exception {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication = new OAuth2AuthorizationCodeRequestAuthenticationToken(
AUTHORIZATION_URI, registeredClient.getClientId(), this.principal,
registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), null);
MockHttpServletRequest request = createAuthorizationRequest(registeredClient);
AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> authenticationDetailsSource = mock(
@ -472,7 +473,11 @@ public class OAuth2AuthorizationEndpointFilterTests {
given(authenticationDetailsSource.buildDetails(request)).willReturn(webAuthenticationDetails);
this.filter.setAuthenticationDetailsSource(authenticationDetailsSource);
given(this.authenticationManager.authenticate(any())).willReturn(authorizationCodeRequestAuthentication);
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult = new OAuth2AuthorizationCodeRequestAuthenticationToken(
AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, this.authorizationCode,
registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes());
authorizationCodeRequestAuthenticationResult.setAuthenticated(true);
given(this.authenticationManager.authenticate(any())).willReturn(authorizationCodeRequestAuthenticationResult);
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain filterChain = mock(FilterChain.class);
@ -481,27 +486,7 @@ public class OAuth2AuthorizationEndpointFilterTests {
verify(authenticationDetailsSource).buildDetails(any());
verify(this.authenticationManager).authenticate(any());
verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
}
@Test
public void doFilterWhenAuthorizationRequestPrincipalNotAuthenticatedThenCommenceAuthentication() throws Exception {
this.principal.setAuthenticated(false);
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult = new OAuth2AuthorizationCodeRequestAuthenticationToken(
AUTHORIZATION_URI, registeredClient.getClientId(), this.principal,
registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), null);
authorizationCodeRequestAuthenticationResult.setAuthenticated(false);
given(this.authenticationManager.authenticate(any())).willReturn(authorizationCodeRequestAuthenticationResult);
MockHttpServletRequest request = createAuthorizationRequest(registeredClient);
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain filterChain = mock(FilterChain.class);
this.filter.doFilter(request, response, filterChain);
verify(this.authenticationManager).authenticate(any());
verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
verifyNoInteractions(filterChain);
}
@Test

View File

@ -705,9 +705,6 @@ public final class ClientRegistration implements Serializable {
if (!AuthorizationGrantType.AUTHORIZATION_CODE.equals(this.authorizationGrantType)
&& this.clientSettings.isRequireProofKey()) {
this.clientSettings = ClientSettings.builder().requireProofKey(false).build();
logger.warn(LogMessage.format(
"clientSettings.isRequireProofKey=true is only valid with authorizationGrantType=%s. Got authorizationGrantType=%s. Resetting to clientSettings.isRequireProofKey=false",
AuthorizationGrantType.AUTHORIZATION_CODE, this.authorizationGrantType));
}
}

View File

@ -319,21 +319,17 @@ public final class NimbusJwtDecoder implements JwtDecoder {
}
/**
* Whether to use Nimbus's typ header verification. This is {@code true} by
* default, however it may change to {@code false} in a future major release.
* Whether to use Nimbus's {@code typ} header verification. This is {@code false}
* by default.
*
* <p>
* By turning off this feature, {@link NimbusJwtDecoder} expects applications to
* check the {@code typ} header themselves in order to determine what kind of
* validation is needed
* By turning on this feature, {@link NimbusJwtDecoder} will delegate checking the
* {@code typ} header to Nimbus by using Nimbus's default
* {@link JOSEObjectTypeVerifier}.
* </p>
*
* <p>
* This is done for you when you use {@link JwtValidators} to construct a
* validator.
*
* <p>
* That means that this: <code>
* When this is set to {@code false}, this: <code>
* NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuer).build();
* jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(issuer);
* </code>
@ -600,21 +596,17 @@ public final class NimbusJwtDecoder implements JwtDecoder {
}
/**
* Whether to use Nimbus's typ header verification. This is {@code true} by
* default, however it may change to {@code false} in a future major release.
* Whether to use Nimbus's {@code typ} header verification. This is {@code false}
* by default.
*
* <p>
* By turning off this feature, {@link NimbusJwtDecoder} expects applications to
* check the {@code typ} header themselves in order to determine what kind of
* validation is needed
* By turning on this feature, {@link NimbusJwtDecoder} will delegate checking the
* {@code typ} header to Nimbus by using Nimbus's default
* {@link JOSEObjectTypeVerifier}.
* </p>
*
* <p>
* This is done for you when you use {@link JwtValidators} to construct a
* validator.
*
* <p>
* That means that this: <code>
* When this is set to {@code false}, this: <code>
* NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuer).build();
* jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(issuer);
* </code>
@ -729,21 +721,17 @@ public final class NimbusJwtDecoder implements JwtDecoder {
}
/**
* Whether to use Nimbus's typ header verification. This is {@code true} by
* default, however it may change to {@code false} in a future major release.
* Whether to use Nimbus's {@code typ} header verification. This is {@code false}
* by default.
*
* <p>
* By turning off this feature, {@link NimbusJwtDecoder} expects applications to
* check the {@code typ} header themselves in order to determine what kind of
* validation is needed
* By turning on this feature, {@link NimbusJwtDecoder} will delegate checking the
* {@code typ} header to Nimbus by using Nimbus's default
* {@link JOSEObjectTypeVerifier}.
* </p>
*
* <p>
* This is done for you when you use {@link JwtValidators} to construct a
* validator.
*
* <p>
* That means that this: <code>
* When this is set to {@code false}, this: <code>
* NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuer).build();
* jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(issuer);
* </code>

View File

@ -136,7 +136,7 @@ public final class NimbusJwtEncoder implements JwtEncoder {
algorithm = MacAlgorithm.from(jwk.getAlgorithm().getName());
}
Assert.notNull(algorithm, "Failed to derive supported algorithm from " + jwk.getAlgorithm());
JwsHeader.Builder builder = JwsHeader.with(algorithm).type(jwk.getKeyType().getValue()).keyId(jwk.getKeyID());
JwsHeader.Builder builder = JwsHeader.with(algorithm).type("JWT").keyId(jwk.getKeyID());
URI x509Url = jwk.getX509CertURL();
if (x509Url != null) {
builder.x509Url(jwk.getX509CertURL().toASCIIString());

View File

@ -0,0 +1,114 @@
/*
* 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.oauth2.jwt;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.ECGenParameterSpec;
import java.util.Objects;
import java.util.Set;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.crypto.impl.ECDSA;
import com.nimbusds.jose.jwk.Curve;
import com.nimbusds.jose.jwk.ECKey;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.KeyOperation;
import com.nimbusds.jose.jwk.KeyUse;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import org.junit.jupiter.api.Test;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Use {@link NimbusJwtDecoder} to decode JWT's encoded with {@link NimbusJwtEncoder}
*
* @author Ziqin Wang
*/
class NimbusJwtEncoderDecoderTests {
@Test
void encodeAndDecodeHS256() throws GeneralSecurityException {
KeyGenerator keyGenerator = KeyGenerator.getInstance("HmacSHA256");
SecretKey secretKey = keyGenerator.generateKey();
NimbusJwtEncoder jwtEncoder = NimbusJwtEncoder.withSecretKey(secretKey).build();
JwtClaimsSet claims = TestJwtClaimsSets.jwtClaimsSet().build();
String jwt = jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withSecretKey(secretKey).build();
Jwt decodedJwt = jwtDecoder.decode(jwt);
assertThat(decodedJwt.getSubject()).isEqualTo("subject");
}
@Test
void encodeAndDecodeRS256() throws GeneralSecurityException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
NimbusJwtEncoder jwtEncoder = NimbusJwtEncoder.withKeyPair(publicKey, privateKey).build();
JwtClaimsSet claims = TestJwtClaimsSets.jwtClaimsSet().build();
String jwt = jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withPublicKey(publicKey).build();
Jwt decodedJwt = jwtDecoder.decode(jwt);
assertThat(decodedJwt.getSubject()).isEqualTo("subject");
}
@Test
void encodeAndDecodeES256() throws GeneralSecurityException, JOSEException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC");
keyPairGenerator.initialize(new ECGenParameterSpec("secp256r1"));
KeyPair keyPair = keyPairGenerator.generateKeyPair();
ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic();
ECPrivateKey privateKey = (ECPrivateKey) keyPair.getPrivate();
NimbusJwtEncoder jwtEncoder = NimbusJwtEncoder.withKeyPair(publicKey, privateKey).build();
JwtClaimsSet claims = TestJwtClaimsSets.jwtClaimsSet().build();
String jwt = jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();
Curve curve = Curve.forECParameterSpec(publicKey.getParams());
JWK jwk = new ECKey.Builder(curve, publicKey).keyOperations(Set.of(KeyOperation.VERIFY))
.keyUse(KeyUse.SIGNATURE)
.algorithm(ECDSA.resolveAlgorithm(curve))
.keyIDFromThumbprint()
.build();
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withJwkSource(new ImmutableJWKSet<>(new JWKSet(jwk)))
.jwsAlgorithm(Objects.requireNonNull(SignatureAlgorithm.from(jwk.getAlgorithm().getName())))
.build();
Jwt decodedJwt = jwtDecoder.decode(jwt);
assertThat(decodedJwt.getSubject()).isEqualTo("subject");
}
}

View File

@ -58,7 +58,7 @@ public class RequestAttributeAuthenticationFilter extends AbstractPreAuthenticat
* missing and {@code exceptionIfVariableMissing} is set to {@code true}.
*/
@Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
protected @Nullable Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
String principal = (String) request.getAttribute(this.principalEnvironmentVariable);
if (principal == null && this.exceptionIfVariableMissing) {
throw new PreAuthenticatedCredentialsNotFoundException(
@ -73,7 +73,7 @@ public class RequestAttributeAuthenticationFilter extends AbstractPreAuthenticat
* credentials value. Otherwise a dummy value will be used.
*/
@Override
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
protected @Nullable Object getPreAuthenticatedCredentials(HttpServletRequest request) {
if (this.credentialsEnvironmentVariable != null) {
return request.getAttribute(this.credentialsEnvironmentVariable);
}

View File

@ -59,7 +59,7 @@ public class RequestHeaderAuthenticationFilter extends AbstractPreAuthenticatedP
* {@code exceptionIfHeaderMissing} is set to {@code true}.
*/
@Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
protected @Nullable Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
String principal = request.getHeader(this.principalRequestHeader);
if (principal == null && this.exceptionIfHeaderMissing) {
throw new PreAuthenticatedCredentialsNotFoundException(
@ -74,7 +74,7 @@ public class RequestHeaderAuthenticationFilter extends AbstractPreAuthenticatedP
* will be used.
*/
@Override
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
protected @Nullable Object getPreAuthenticatedCredentials(HttpServletRequest request) {
if (this.credentialsRequestHeader != null) {
return request.getHeader(this.credentialsRequestHeader);
}

View File

@ -146,6 +146,14 @@ public class PathPatternRequestMatcherTests {
assertThat(matcher.matches(mock)).isTrue();
}
@Test
void matcherWhenRequestMethodIsNullThenNoNullPointerException() {
RequestMatcher matcher = pathPattern(HttpMethod.GET, "/");
MockHttpServletRequest mock = new MockHttpServletRequest(null, "/");
ServletRequestPathUtils.parseAndCache(mock);
assertThat(matcher.matches(mock)).isFalse();
}
MockHttpServletRequest request(String uri) {
MockHttpServletRequest request = new MockHttpServletRequest("GET", uri);
ServletRequestPathUtils.parseAndCache(request);

View File

@ -174,6 +174,12 @@ public class WebAuthnAuthenticationFilter extends AbstractAuthenticationProcessi
this.delegate = delegate;
}
@Override
public boolean canRead(ResolvableType type, @Nullable MediaType mediaType) {
Class<?> clazz = type.resolve();
return (clazz != null) ? canRead(clazz, mediaType) : canRead(mediaType);
}
@Override
public boolean canRead(Class<?> clazz, @Nullable MediaType mediaType) {
return this.delegate.canRead(clazz, mediaType);
@ -206,6 +212,11 @@ public class WebAuthnAuthenticationFilter extends AbstractAuthenticationProcessi
return this.delegate.read(type.getType(), null, inputMessage);
}
@Override
public boolean canWrite(ResolvableType targetType, Class<?> valueClass, @Nullable MediaType mediaType) {
return this.delegate.canWrite(targetType.getType(), valueClass, mediaType);
}
}
}

View File

@ -22,15 +22,15 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import com.webauthn4j.WebAuthnManager;
import com.webauthn4j.authenticator.Authenticator;
import com.webauthn4j.authenticator.AuthenticatorImpl;
import com.webauthn4j.converter.util.CborConverter;
import com.webauthn4j.converter.util.ObjectConverter;
import com.webauthn4j.credential.CredentialRecordImpl;
import com.webauthn4j.data.AuthenticationData;
import com.webauthn4j.data.AuthenticationParameters;
import com.webauthn4j.data.RegistrationData;
@ -95,7 +95,7 @@ public class Webauthn4JRelyingPartyOperations implements WebAuthnRelyingPartyOpe
private final PublicKeyCredentialRpEntity rp;
private final ObjectConverter objectConverter = new ObjectConverter();
private ObjectConverter objectConverter = new ObjectConverter();
private final AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
@ -137,6 +137,15 @@ public class Webauthn4JRelyingPartyOperations implements WebAuthnRelyingPartyOpe
this.webAuthnManager = webAuthnManager;
}
/**
* Sets the {@link ObjectConverter} to use.
* @param objectConverter the {@link ObjectConverter} to use. Cannot be null.
*/
void setObjectConverter(ObjectConverter objectConverter) {
Assert.notNull(objectConverter, "objectConverter cannot be null");
this.objectConverter = objectConverter;
}
/**
* Sets a {@link Consumer} used to customize the
* {@link PublicKeyCredentialCreationOptionsBuilder} for
@ -248,9 +257,7 @@ public class Webauthn4JRelyingPartyOperations implements WebAuthnRelyingPartyOpe
byte[] attestationObject = response.getAttestationObject().getBytes();
byte[] clientDataJSON = response.getClientDataJSON().getBytes();
Challenge challenge = new DefaultChallenge(base64Challenge);
byte[] tokenBindingId = null /* set tokenBindingId */; // FIXME:
// https://www.w3.org/TR/webauthn-1/#dom-collectedclientdata-tokenbinding
ServerProperty serverProperty = new ServerProperty(origins, rpId, challenge, tokenBindingId);
ServerProperty serverProperty = new ServerProperty(origins, rpId, challenge);
boolean userVerificationRequired = creationOptions.getAuthenticatorSelection()
.getUserVerification() == UserVerificationRequirement.REQUIRED;
// requireUserPresence The constant Boolean value true
@ -263,7 +270,7 @@ public class Webauthn4JRelyingPartyOperations implements WebAuthnRelyingPartyOpe
transports);
RegistrationParameters registrationParameters = new RegistrationParameters(serverProperty, pubKeyCredParams,
userVerificationRequired, userPresenceRequired);
RegistrationData wa4jRegistrationData = this.webAuthnManager.validate(webauthn4jRegistrationRequest,
RegistrationData wa4jRegistrationData = this.webAuthnManager.verify(webauthn4jRegistrationRequest,
registrationParameters);
AttestationObject wa4jAttestationObject = wa4jRegistrationData.getAttestationObject();
Assert.notNull(wa4jAttestationObject, "attestationObject cannot be null");
@ -306,7 +313,7 @@ public class Webauthn4JRelyingPartyOperations implements WebAuthnRelyingPartyOpe
private List<com.webauthn4j.data.PublicKeyCredentialParameters> convertCredentialParamsToWebauthn4j(
List<PublicKeyCredentialParameters> parameters) {
return parameters.stream().map(this::convertParamToWebauthn4j).collect(Collectors.toUnmodifiableList());
return parameters.stream().map(this::convertParamToWebauthn4j).toList();
}
private com.webauthn4j.data.PublicKeyCredentialParameters convertParamToWebauthn4j(
@ -382,28 +389,29 @@ public class Webauthn4JRelyingPartyOperations implements WebAuthnRelyingPartyOpe
.getAuthenticatorData();
AttestedCredentialData wa4jCredData = wa4jAuthData.getAttestedCredentialData();
Assert.notNull(wa4jCredData, "attestedCredentialData cannot be null");
AttestedCredentialData data = new AttestedCredentialData(wa4jCredData.getAaguid(), keyId.getBytes(),
wa4jCredData.getCOSEKey());
Authenticator authenticator = new AuthenticatorImpl(data, wa4jAttestationObject.getAttestationStatement(),
credentialRecord.getSignatureCount());
Set<Origin> origins = toOrigins();
Challenge challenge = new DefaultChallenge(requestOptions.getChallenge().getBytes());
// FIXME: should populate this
byte[] tokenBindingId = null /* set tokenBindingId */;
String rpId = requestOptions.getRpId();
Assert.notNull(rpId, "rpId cannot be null");
ServerProperty serverProperty = new ServerProperty(origins, rpId, challenge, tokenBindingId);
ServerProperty serverProperty = new ServerProperty(origins, rpId, challenge);
boolean userVerificationRequired = request.getRequestOptions()
.getUserVerification() == UserVerificationRequirement.REQUIRED;
com.webauthn4j.data.AuthenticationRequest authenticationRequest = new com.webauthn4j.data.AuthenticationRequest(
request.getPublicKey().getId().getBytes(), assertionResponse.getAuthenticatorData().getBytes(),
request.getPublicKey().getRawId().getBytes(), assertionResponse.getAuthenticatorData().getBytes(),
assertionResponse.getClientDataJSON().getBytes(), assertionResponse.getSignature().getBytes());
AuthenticationParameters authenticationParameters = new AuthenticationParameters(serverProperty, authenticator,
userVerificationRequired);
AuthenticationData wa4jAuthenticationData = this.webAuthnManager.validate(authenticationRequest,
// CollectedClientData and ExtensionsClientOutputs is registration data, and can
// be null at authentication time.
com.webauthn4j.credential.CredentialRecord wa4jCredentialRecord = new CredentialRecordImpl(
wa4jAttestationObject, null, null, convertTransportsToWebauthn4j(credentialRecord.getTransports()));
List<byte[]> allowCredentials = convertAllowedCredentialsToWebauthn4j(
request.getRequestOptions().getAllowCredentials());
AuthenticationParameters authenticationParameters = new AuthenticationParameters(serverProperty,
wa4jCredentialRecord, allowCredentials.isEmpty() ? null : allowCredentials, userVerificationRequired);
AuthenticationData wa4jAuthenticationData = this.webAuthnManager.verify(authenticationRequest,
authenticationParameters);
AuthenticatorData<AuthenticationExtensionAuthenticatorOutput> wa4jValidatedAuthData = wa4jAuthenticationData
@ -424,4 +432,21 @@ public class Webauthn4JRelyingPartyOperations implements WebAuthnRelyingPartyOpe
return userEntity;
}
private static Set<com.webauthn4j.data.AuthenticatorTransport> convertTransportsToWebauthn4j(
Set<AuthenticatorTransport> transports) {
return transports.stream()
.map(AuthenticatorTransport::getValue)
.map(com.webauthn4j.data.AuthenticatorTransport::create)
.collect(Collectors.toSet());
}
private static List<byte[]> convertAllowedCredentialsToWebauthn4j(
List<PublicKeyCredentialDescriptor> allowedCredentials) {
return allowedCredentials.stream()
.map(PublicKeyCredentialDescriptor::getId)
.filter(Objects::nonNull)
.map(Bytes::getBytes)
.collect(Collectors.toList());
}
}

View File

@ -27,15 +27,20 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.dataformat.cbor.CBORFactory;
import com.webauthn4j.WebAuthnManager;
import com.webauthn4j.converter.AttestationObjectConverter;
import com.webauthn4j.converter.util.ObjectConverter;
import com.webauthn4j.data.AuthenticationData;
import com.webauthn4j.data.AuthenticationRequest;
import com.webauthn4j.data.attestation.AttestationObject;
import com.webauthn4j.data.attestation.authenticator.AttestedCredentialData;
import com.webauthn4j.data.attestation.authenticator.AuthenticatorData;
import com.webauthn4j.data.extension.authenticator.RegistrationExtensionAuthenticatorOutput;
import org.assertj.core.api.recursive.comparison.RecursiveComparisonConfiguration;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@ -44,12 +49,14 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.PasswordEncodedUser;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.webauthn.api.AuthenticatorAssertionResponse;
import org.springframework.security.web.webauthn.api.AuthenticatorAttestationResponse;
import org.springframework.security.web.webauthn.api.AuthenticatorAttestationResponse.AuthenticatorAttestationResponseBuilder;
import org.springframework.security.web.webauthn.api.AuthenticatorSelectionCriteria;
import org.springframework.security.web.webauthn.api.AuthenticatorTransport;
import org.springframework.security.web.webauthn.api.Bytes;
import org.springframework.security.web.webauthn.api.CredentialRecord;
import org.springframework.security.web.webauthn.api.ImmutableCredentialRecord;
import org.springframework.security.web.webauthn.api.PublicKeyCredential;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialDescriptor;
@ -57,9 +64,11 @@ import org.springframework.security.web.webauthn.api.PublicKeyCredentialParamete
import org.springframework.security.web.webauthn.api.PublicKeyCredentialRequestOptions;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialRpEntity;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity;
import org.springframework.security.web.webauthn.api.TestAuthenticationAssertionResponses;
import org.springframework.security.web.webauthn.api.TestAuthenticatorAttestationResponses;
import org.springframework.security.web.webauthn.api.TestCredentialRecords;
import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialCreationOptions;
import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialRequestOptions;
import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialUserEntities;
import org.springframework.security.web.webauthn.api.TestPublicKeyCredentials;
import org.springframework.security.web.webauthn.api.UserVerificationRequirement;
@ -67,7 +76,9 @@ import org.springframework.security.web.webauthn.api.UserVerificationRequirement
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatRuntimeException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyNoInteractions;
@ExtendWith(MockitoExtension.class)
@ -587,6 +598,50 @@ class Webauthn4jRelyingPartyOperationsTests {
.containsExactly(credentialRecord.getCredentialId());
}
// gh-18158
@Test
void authenticateThenWa4jRequestCredentialIdIsRawIdBytes() throws Exception {
PublicKeyCredentialRequestOptions options = TestPublicKeyCredentialRequestOptions.create().build();
AuthenticatorAssertionResponse response = TestAuthenticationAssertionResponses
.createAuthenticatorAssertionResponse()
.build();
PublicKeyCredential<AuthenticatorAssertionResponse> credentials = TestPublicKeyCredentials
.createPublicKeyCredential(response)
.build();
RelyingPartyAuthenticationRequest request = new RelyingPartyAuthenticationRequest(options, credentials);
PublicKeyCredential<AuthenticatorAssertionResponse> publicKey = request.getPublicKey();
ImmutableCredentialRecord credentialRecord = TestCredentialRecords.fullUserCredential().build();
given(this.userCredentials.findByCredentialId(publicKey.getRawId())).willReturn(credentialRecord);
ObjectMapper json = mock(ObjectMapper.class);
ObjectMapper cbor = mock(ObjectMapper.class);
given(cbor.getFactory()).willReturn(mock(CBORFactory.class));
AttestationObject attestationObject = mock(AttestationObject.class);
AuthenticatorData wa4jAuthData = mock(AuthenticatorData.class);
given(attestationObject.getAuthenticatorData()).willReturn(wa4jAuthData);
given(wa4jAuthData.getAttestedCredentialData()).willReturn(mock(AttestedCredentialData.class));
given(cbor.readValue(credentialRecord.getAttestationObject().getBytes(), AttestationObject.class))
.willReturn(attestationObject);
this.rpOperations.setObjectConverter(new ObjectConverter(json, cbor));
WebAuthnManager manager = mock(WebAuthnManager.class);
ArgumentCaptor<AuthenticationRequest> wa4jRequest = ArgumentCaptor.forClass(AuthenticationRequest.class);
AuthenticationData wa4jData = mock(AuthenticationData.class);
given(wa4jData.getAuthenticatorData()).willReturn(mock(AuthenticatorData.class));
given(manager.verify(wa4jRequest.capture(), any())).willReturn(wa4jData);
given(this.userEntities.findById(any())).willReturn(TestPublicKeyCredentialUserEntities.userEntity().build());
this.rpOperations.setWebAuthnManager(manager);
this.rpOperations.authenticate(request);
// this ensures that our next assertion is valid (we want the rawId bytes, not the
// id bytes to be used)
assertThat(publicKey.getRawId().getBytes()).isNotEqualTo(publicKey.getId().getBytes());
// ensure that the raw id bytes are passed into webauthn4j (not the id bytes which
// are base64 encoded)
assertThat(wa4jRequest.getValue().getCredentialId()).isEqualTo(publicKey.getRawId().getBytes());
}
private static AuthenticatorAttestationResponse setFlag(byte... flags) throws Exception {
AuthenticatorAttestationResponseBuilder authAttResponseBldr = TestAuthenticatorAttestationResponses
.createAuthenticatorAttestationResponse();