1
0
mirror of synced 2026-08-05 01:36:56 +00:00

Compare commits

...

38 Commits

Author SHA1 Message Date
github-actions[bot] 928ad9600c Release 7.1.0-M2 2026-02-13 16:25:52 +00:00
dependabot[bot] f9b2c86e1d Bump org.springframework.data:spring-data-bom from 2025.1.2 to 2025.1.3
Bumps [org.springframework.data:spring-data-bom](https://github.com/spring-projects/spring-data-bom) from 2025.1.2 to 2025.1.3.
- [Release notes](https://github.com/spring-projects/spring-data-bom/releases)
- [Commits](https://github.com/spring-projects/spring-data-bom/compare/2025.1.2...2025.1.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-13 12:19:41 +00:00
dependabot[bot] d77e48f9ef Bump org.springframework.ldap:spring-ldap-core from 4.0.1 to 4.0.2
Bumps [org.springframework.ldap:spring-ldap-core](https://github.com/spring-projects/spring-ldap) from 4.0.1 to 4.0.2.
- [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.1...4.0.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-13 03:17:06 +00:00
dependabot[bot] 25da472d67 Bump org.springframework:spring-framework-bom from 7.0.3 to 7.0.4
Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 7.0.3 to 7.0.4.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v7.0.3...v7.0.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-13 03:16:43 +00:00
dependabot[bot] 9d3e217b79 Bump spring-io/spring-security-release-tools from 1.0.13 to 1.0.14
Bumps [spring-io/spring-security-release-tools](https://github.com/spring-io/spring-security-release-tools) from 1.0.13 to 1.0.14.
- [Release notes](https://github.com/spring-io/spring-security-release-tools/releases)
- [Changelog](https://github.com/spring-io/spring-security-release-tools/blob/main/RELEASE.adoc)
- [Commits](https://github.com/spring-io/spring-security-release-tools/compare/v1.0.13...729fed56d42122f88583aff1be35c0800b7d77e9)

---
updated-dependencies:
- dependency-name: spring-io/spring-security-release-tools
  dependency-version: 1.0.14
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-13 00:49:00 +00:00
dependabot[bot] 7bbfc09f49 Bump spring-io/spring-security-release-tools/.github/workflows/perform-release.yml
Bumps [spring-io/spring-security-release-tools/.github/workflows/perform-release.yml](https://github.com/spring-io/spring-security-release-tools) from 1.0.13 to 1.0.14.
- [Release notes](https://github.com/spring-io/spring-security-release-tools/releases)
- [Changelog](https://github.com/spring-io/spring-security-release-tools/blob/main/RELEASE.adoc)
- [Commits](https://github.com/spring-io/spring-security-release-tools/compare/v1.0.13...729fed56d42122f88583aff1be35c0800b7d77e9)

---
updated-dependencies:
- dependency-name: spring-io/spring-security-release-tools/.github/workflows/perform-release.yml
  dependency-version: 1.0.14
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-13 00:47:48 +00:00
Josh Cummings 85e2d9298b Merge remote-tracking branch 'origin/7.0.x' 2026-02-12 16:46:14 -07:00
Josh Cummings b804da974d Update Test to Align with webauthn4j
The latest webauthn4j exposes Jackson 3 instead of Jackson 2,
as such this test now uses Jackson 3 where needed.

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-02-12 16:45:13 -07:00
dependabot[bot] b9bb5e0b52 Bump com.webauthn4j:webauthn4j-core
Bumps [com.webauthn4j:webauthn4j-core](https://github.com/webauthn4j/webauthn4j) from 0.29.7.RELEASE to 0.31.0.RELEASE.
- [Release notes](https://github.com/webauthn4j/webauthn4j/releases)
- [Commits](https://github.com/webauthn4j/webauthn4j/compare/0.29.7.RELEASE...0.31.0.RELEASE)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-12 16:45:13 -07:00
Josh Cummings 4fd8e1d596 Remove Trailing Bytes from AttestationStatement
Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-02-12 16:45:13 -07:00
Josh Cummings c59fb0cd35 Add Jackson 2 Databind as Optional Dependency
Since spring-security-webauthn has Jackson 2 Mixins, it would
be clearer to set Jackson 2 explicitly as an optional dependency

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-02-12 16:45:13 -07:00
Josh Cummings bc6ce0d346 Merge branch '7.0.x' 2026-02-12 10:36:20 -07:00
dependabot[bot] 50aba3aaf3 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.13 to 1.0.14.
- [Release notes](https://github.com/spring-io/spring-security-release-tools/releases)
- [Changelog](https://github.com/spring-io/spring-security-release-tools/blob/main/RELEASE.adoc)
- [Commits](https://github.com/spring-io/spring-security-release-tools/compare/v1.0.13...v1.0.14)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-12 10:35:14 -07:00
Josh Cummings 25aec8c5e0 Update Release Steps to JDK 25
Issue gh-18512

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-02-12 10:29:43 -07:00
Josh Cummings 6eb0af9912 Merge branch '7.0.x' 2026-02-12 10:28:00 -07:00
Josh Cummings 6cbbf6c561 Merge branch '6.5.x' into 7.0.x 2026-02-12 10:27:46 -07:00
Josh Cummings 10cb6f7003 Update spring-security-release-tools 1.0.14 2026-02-12 10:25:47 -07:00
Josh Cummings 7fdff6a907 Use spring-github-workflows Auto-Merge 2026-02-12 10:21:32 -07:00
github-actions[bot] 117af3bc2b Merge pull request #18723 from spring-projects/dependabot/gradle/main/io.spring.gradle-spring-security-release-plugin-1.0.14
Bump io.spring.gradle:spring-security-release-plugin from 1.0.13 to 1.0.14
2026-02-12 03:17:42 +00:00
dependabot[bot] c7f781423f 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.13 to 1.0.14.
- [Release notes](https://github.com/spring-io/spring-security-release-tools/releases)
- [Changelog](https://github.com/spring-io/spring-security-release-tools/blob/main/RELEASE.adoc)
- [Commits](https://github.com/spring-io/spring-security-release-tools/compare/v1.0.13...v1.0.14)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-12 03:06:08 +00:00
github-actions[bot] fb2f0d5c38 Merge pull request #18715 from spring-projects/dependabot/gradle/main/io.projectreactor-reactor-bom-2025.0.3
Bump io.projectreactor:reactor-bom from 2025.0.2 to 2025.0.3
2026-02-11 03:18:39 +00:00
github-actions[bot] 10b9cc8c2b Merge pull request #18713 from spring-projects/dependabot/gradle/main/io.micrometer-micrometer-observation-1.16.3
Bump io.micrometer:micrometer-observation from 1.16.2 to 1.16.3
2026-02-11 03:18:24 +00:00
github-actions[bot] 5240878272 Merge pull request #18714 from spring-projects/dependabot/gradle/main/ch.qos.logback-logback-classic-1.5.29
Bump ch.qos.logback:logback-classic from 1.5.28 to 1.5.29
2026-02-11 03:18:04 +00:00
dependabot[bot] ba4bd61c5b Bump io.projectreactor:reactor-bom from 2025.0.2 to 2025.0.3
Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2025.0.2 to 2025.0.3.
- [Release notes](https://github.com/reactor/reactor/releases)
- [Commits](https://github.com/reactor/reactor/compare/2025.0.2...2025.0.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-11 03:06:26 +00:00
dependabot[bot] c25ec70374 Bump ch.qos.logback:logback-classic from 1.5.28 to 1.5.29
Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.5.28 to 1.5.29.
- [Release notes](https://github.com/qos-ch/logback/releases)
- [Commits](https://github.com/qos-ch/logback/compare/v_1.5.28...v_1.5.29)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-11 03:06:19 +00:00
dependabot[bot] 8e1e0ca9d2 Bump io.micrometer:micrometer-observation from 1.16.2 to 1.16.3
Bumps [io.micrometer:micrometer-observation](https://github.com/micrometer-metrics/micrometer) from 1.16.2 to 1.16.3.
- [Release notes](https://github.com/micrometer-metrics/micrometer/releases)
- [Commits](https://github.com/micrometer-metrics/micrometer/compare/v1.16.2...v1.16.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-11 03:05:06 +00:00
Josh Cummings 705fa60a01 Document Method Security hasScope Support
Issue gh-18013

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-02-10 15:23:32 -07:00
Tran Ngoc Nhan f2b7cb2de5 Support hasScope in Method Security
Closes gh-18013

Signed-off-by: Tran Ngoc Nhan <ngocnhan.tran1996@gmail.com>
2026-02-10 15:23:32 -07:00
coehgns 8652950fb2 Fix typos in contributing guide Tidy up wording in CONTRIBUTING.adoc to improve readability.
Signed-off-by: coehgns <modooboiroo@gmail.com>
2026-02-10 13:54:55 -07:00
Josh Cummings 07ba3e623f Merge branch '7.0.x' 2026-02-10 13:41:47 -07:00
Josh Cummings 252c69460e Merge remote-tracking branch 'origin/6.5.x' into 7.0.x 2026-02-10 13:41:29 -07:00
dependabot[bot] 3131642aae Bump io.micrometer:context-propagation from 1.1.3 to 1.1.4
Bumps [io.micrometer:context-propagation](https://github.com/micrometer-metrics/context-propagation) from 1.1.3 to 1.1.4.
- [Release notes](https://github.com/micrometer-metrics/context-propagation/releases)
- [Commits](https://github.com/micrometer-metrics/context-propagation/compare/v1.1.3...v1.1.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-10 13:41:09 -07:00
dependabot[bot] 552d8d1d29 Bump ch.qos.logback:logback-classic from 1.5.28 to 1.5.29
Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.5.28 to 1.5.29.
- [Release notes](https://github.com/qos-ch/logback/releases)
- [Commits](https://github.com/qos-ch/logback/compare/v_1.5.28...v_1.5.29)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-10 13:39:59 -07:00
dependabot[bot] f240f29433 Bump gradle-wrapper from 8.14 to 8.14.4
Bumps gradle-wrapper from 8.14 to 8.14.4.

---
updated-dependencies:
- dependency-name: gradle-wrapper
  dependency-version: 8.14.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-10 13:39:38 -07:00
github-actions[bot] f91b5f33fc Merge pull request #18701 from spring-projects/dependabot/gradle/main/com.nimbusds-oauth2-oidc-sdk-11.33
Bump com.nimbusds:oauth2-oidc-sdk from 11.26.1 to 11.33
2026-02-10 17:51:23 +00:00
Josh Cummings 5418ab2081 Update nimbus-jose-jwt from 10.4 to 10.6
Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-02-10 10:37:44 -07:00
Josh Cummings e8e4110334 Wrap RuntimeException in fromOidcConfiguration
This commit makes so that fromOidcConfiguration throws the same exception
caused by chain as other configuration methods. Specifically, if parsing
throws a RuntimeException, this method will now wrap it in an
IllegalArgumentException as other configuration methods do.

This makes specific sense here since the RuntimeException is almost certainly
caused by a malformed configuration set handed in as a method parameter.

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-02-10 10:37:40 -07:00
dependabot[bot] 17e368435d Bump com.nimbusds:oauth2-oidc-sdk from 11.26.1 to 11.33
Bumps [com.nimbusds:oauth2-oidc-sdk](https://bitbucket.org/connect2id/oauth-2.0-sdk-with-openid-connect-extensions) from 11.26.1 to 11.33.
- [Changelog](https://bitbucket.org/connect2id/oauth-2.0-sdk-with-openid-connect-extensions/src/master/CHANGELOG.txt)
- [Commits](https://bitbucket.org/connect2id/oauth-2.0-sdk-with-openid-connect-extensions/branches/compare/11.33..11.26.1)

---
updated-dependencies:
- dependency-name: com.nimbusds:oauth2-oidc-sdk
  dependency-version: '11.33'
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-09 03:07:15 +00:00
29 changed files with 639 additions and 50 deletions
+10 -19
View File
@@ -1,25 +1,16 @@
name: Dependabot Auto-Merge
name: Merge Dependabot PR
on:
pull_request:
branches:
- 'main'
- main
- '*.x'
permissions:
contents: write
pull-requests: write
run-name: Merge Dependabot PR ${{ github.ref_name }}
jobs:
dependabot:
runs-on: ubuntu-latest
if: github.event.pull_request.user.login == 'dependabot[bot]' && github.repository == 'spring-projects/spring-security'
steps:
- name: Dependabot metadata
id: metadata
uses: dependabot/fetch-metadata@d7267f607e9d3fb96fc2fbe83e0af444713e90b7
with:
github-token: ${{ github.token }}
- name: Enable auto-merge for Dependabot PRs
run: gh pr merge --auto --merge "$PR_URL"
env:
PR_URL: ${{ github.event.pull_request.html_url }}
GH_TOKEN: ${{ github.token }}
merge-dependabot-pr:
permissions: write-all
uses: spring-io/spring-github-workflows/.github/workflows/spring-merge-dependabot-pr.yml@v7
with:
mergeArguments: --auto --rebase
+1 -1
View File
@@ -31,6 +31,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Send Notification
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@7d42d82298553f123a9dad622e0eac725aaf52ef # v1.0.13
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14
with:
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
@@ -17,7 +17,7 @@ permissions:
jobs:
build:
name: Build
uses: spring-io/spring-security-release-tools/.github/workflows/build.yml@7d42d82298553f123a9dad622e0eac725aaf52ef # v1.0.13
uses: spring-io/spring-security-release-tools/.github/workflows/build.yml@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14
strategy:
matrix:
os: [ ubuntu-latest, windows-latest ]
@@ -30,22 +30,24 @@ jobs:
deploy-artifacts:
name: Deploy Artifacts
needs: [ build]
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-artifacts.yml@7d42d82298553f123a9dad622e0eac725aaf52ef # v1.0.13
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-artifacts.yml@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14
with:
should-deploy-artifacts: ${{ needs.build.outputs.should-deploy-artifacts }}
default-publish-milestones-central: true
java-version: 25
secrets: inherit
deploy-schema:
name: Deploy Schema
needs: [ build ]
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-schema.yml@7d42d82298553f123a9dad622e0eac725aaf52ef # v1.0.13
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-schema.yml@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14
with:
should-deploy-schema: ${{ needs.build.outputs.should-deploy-artifacts }}
java-version: 25
secrets: inherit
perform-release:
name: Perform Release
needs: [ deploy-artifacts, deploy-schema ]
uses: spring-io/spring-security-release-tools/.github/workflows/perform-release.yml@7d42d82298553f123a9dad622e0eac725aaf52ef # v1.0.13
uses: spring-io/spring-security-release-tools/.github/workflows/perform-release.yml@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14
with:
should-perform-release: ${{ needs.deploy-artifacts.outputs.artifacts-deployed }}
project-version: ${{ needs.deploy-artifacts.outputs.project-version }}
@@ -53,6 +55,7 @@ jobs:
release-repo-url: https://repo1.maven.org/maven2
artifact-path: org/springframework/security/spring-security-core
slack-announcing-id: spring-security-announcing
java-version: 25:q
secrets: inherit
send-notification:
name: Send Notification
@@ -61,6 +64,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Send Notification
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@7d42d82298553f123a9dad622e0eac725aaf52ef # v1.0.13
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14
with:
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
+1 -1
View File
@@ -16,7 +16,7 @@ permissions:
jobs:
perform-release:
name: Perform Release
uses: spring-io/spring-security-release-tools/.github/workflows/perform-release.yml@7d42d82298553f123a9dad622e0eac725aaf52ef # v1.0.13
uses: spring-io/spring-security-release-tools/.github/workflows/perform-release.yml@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14
with:
should-perform-release: true
project-version: ${{ inputs.version }}
@@ -30,6 +30,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Send Notification
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@7d42d82298553f123a9dad622e0eac725aaf52ef # v1.0.13
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14
with:
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
+1 -1
View File
@@ -46,6 +46,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Send Notification
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@7d42d82298553f123a9dad622e0eac725aaf52ef # v1.0.13
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14
with:
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
@@ -9,7 +9,7 @@ permissions:
jobs:
update-scheduled-release-version:
name: Update Scheduled Release Version
uses: spring-io/spring-security-release-tools/.github/workflows/update-scheduled-release-version.yml@7d42d82298553f123a9dad622e0eac725aaf52ef # v1.0.13
uses: spring-io/spring-security-release-tools/.github/workflows/update-scheduled-release-version.yml@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14
secrets: inherit
send-notification:
name: Send Notification
@@ -18,6 +18,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Send Notification
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@7d42d82298553f123a9dad622e0eac725aaf52ef # v1.0.13
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14
with:
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
+2 -2
View File
@@ -31,7 +31,7 @@ If you have a question, check Stack Overflow using
https://stackoverflow.com/questions/tagged/spring-security+or+spring-ldap+or+spring-authorization-server+or+spring-session?tab=Newest[this list of tags].
Find an existing discussion, or start a new one if necessary.
If you believe there is an issue, search through https://github.com/spring-projects/spring-security/issues[existing issues] trying a few different ways to find discussions, past or current, that are related to the issue.
If you believe there is an issue, search through https://github.com/spring-projects/spring-security/issues[existing issues] trying a few different ways to find discussions, past or current, that are related to the issue.
Reading those discussions helps you to learn about the issue, and helps us to make a decision.
[[find-an-issue]]
@@ -94,7 +94,7 @@ Don't worry if you don't get them all correct the first time, we will help you.
1. [[sign-cla]] All commits must include a __Signed-off-by__ trailer at the end of each commit message to indicate that the contributor agrees to the Developer Certificate of Origin.
For additional details, please refer to the blog post https://spring.io/blog/2025/01/06/hello-dco-goodbye-cla-simplifying-contributions-to-spring[Hello DCO, Goodbye CLA: Simplifying Contributions to Spring].
2. [[create-an-issue-list]] Must you https://github.com/spring-projects/spring-security/issues/new/choose[create an issue] first? No, but it is recommended for features and larger bug fixes. It's easier discuss with the team first to determine the right fix or enhancement.
2. [[create-an-issue-list]] Must you https://github.com/spring-projects/spring-security/issues/new/choose[create an issue] first? No, but it is recommended for features and larger bug fixes. It's easier to discuss with the team first to determine the right fix or enhancement.
For typos and straightforward bug fixes, starting with a pull request is encouraged.
Please include a description for context and motivation.
Note that the team may close your pull request if it's not a fit for the project.
@@ -949,6 +949,21 @@ fun getMessages(): List<Message> { }
----
======
[[method-security-has-scope]]
=== Using `hasScope` in Method Security
Because method security expressions can evaluation `AuthorizationManager` instances, you can also use the `hasScope` API by publishing a `DefaultOAuth2AuthorizationManagerFactory` `@Bean`:
include-code::./MethodSecurityHasScopeConfiguration[tag=declare-factory,indent=0]
and then doing:
include-code::./MessageService[tag=protected-method,indent=0]
If you are using xref:servlet/authentication/mfa.adoc[Spring Security's MFA feature], then you can supply its `AuthorizationManagerFactory` instance to ensure that your authentication factors are automatically checked as well by including it in your `DefaultOAuth2AuthorizationManagerFactory` constructor as follows:
include-code::./MethodSecurityHasScopeMfaConfiguration[tag=declare-factory,indent=0]
[[oauth2resourceserver-jwt-authorization-extraction]]
=== Extracting Authorities Manually
@@ -638,6 +638,21 @@ fun getMessages(): List<Message?> {}
----
======
[[method-security-has-scope]]
=== Using `hasScope` in Method Security
Because method security expressions can evaluation `AuthorizationManager` instances, you can also use the `hasScope` API by publishing a `DefaultOAuth2AuthorizationManagerFactory` `@Bean`:
include-code::./MethodSecurityHasScopeConfiguration[tag=declare-factory,indent=0]
and then doing:
include-code::./MessageService[tag=protected-method,indent=0]
If you are using xref:servlet/authentication/mfa.adoc[Spring Security's MFA feature], then you can supply its `AuthorizationManagerFactory` instance to ensure that your authentication factors are automatically checked as well by including it in your `DefaultOAuth2AuthorizationManagerFactory` constructor as follows:
include-code::./MethodSecurityHasScopeMfaConfiguration[tag=declare-factory,indent=0]
[[oauth2resourceserver-opaque-authorization-extraction]]
=== Extracting Authorities Manually
@@ -0,0 +1,16 @@
package org.springframework.security.docs.servlet.oauth2.resourceserver.methodsecurityhasscope;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
@Service
class MessageService {
// tag::protected-method[]
@PreAuthorize("@oauth2.hasScope('message:read')")
String readMessage() {
return "message";
}
// end::protected-method[]
}
@@ -0,0 +1,18 @@
package org.springframework.security.docs.servlet.oauth2.resourceserver.methodsecurityhasscope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.oauth2.core.authorization.DefaultOAuth2AuthorizationManagerFactory;
import org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagerFactory;
@Configuration
@EnableMethodSecurity
class MethodSecurityHasScopeConfiguration {
// tag::declare-factory[]
@Bean
OAuth2AuthorizationManagerFactory<?> oauth2() {
return new DefaultOAuth2AuthorizationManagerFactory<>();
}
// end::declare-factory[]
}
@@ -0,0 +1,59 @@
package org.springframework.security.docs.servlet.oauth2.resourceserver.methodsecurityhasscope;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@ExtendWith(SpringTestContextExtension.class)
@ExtendWith(SpringExtension.class)
@SecurityTestExecutionListeners
public class MethodSecurityHasScopeConfigurationTests {
public final SpringTestContext spring = new SpringTestContext(this).mockMvcAfterSpringSecurityOk();
@Autowired
private MessageService messages;
@Test
@WithMockUser(authorities = "SCOPE_message:read")
void readMessageWhenMessageReadThenAllowed() {
this.spring.register(MethodSecurityHasScopeConfiguration.class, MessageService.class).autowire();
this.messages.readMessage();
}
@Test
@WithMockUser
void readMessageWhenNoScopeThenDenied() {
this.spring.register(MethodSecurityHasScopeConfiguration.class, MessageService.class).autowire();
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.messages::readMessage);
}
@Test
@WithMockUser(authorities = { "SCOPE_message:read", "FACTOR_BEARER", "FACTOR_X509" })
void mfaReadMessageWhenMessageReadAndFactorsThenAllowed() {
this.spring.register(MethodSecurityHasScopeMfaConfiguration.class, MessageService.class).autowire();
this.messages.readMessage();
}
@Test
@WithMockUser(authorities = { "SCOPE_message:read" })
void mfaReadMessageWhenMessageReadThenDenied() {
this.spring.register(MethodSecurityHasScopeMfaConfiguration.class, MessageService.class).autowire();
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.messages::readMessage);
}
@Test
@WithMockUser
void mfaReadMessageWhenNoScopeThenDenied() {
this.spring.register(MethodSecurityHasScopeMfaConfiguration.class, MessageService.class).autowire();
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.messages::readMessage);
}
}
@@ -0,0 +1,21 @@
package org.springframework.security.docs.servlet.oauth2.resourceserver.methodsecurityhasscope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authorization.AuthorizationManagerFactory;
import org.springframework.security.config.annotation.authorization.EnableMultiFactorAuthentication;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.oauth2.core.authorization.DefaultOAuth2AuthorizationManagerFactory;
import org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagerFactory;
@Configuration
@EnableMethodSecurity
@EnableMultiFactorAuthentication(authorities = { "FACTOR_BEARER", "FACTOR_X509" })
class MethodSecurityHasScopeMfaConfiguration {
// tag::declare-factory[]
@Bean
OAuth2AuthorizationManagerFactory<?> oauth2(AuthorizationManagerFactory<?> authz) {
return new DefaultOAuth2AuthorizationManagerFactory<>(authz);
}
// end::declare-factory[]
}
@@ -0,0 +1,15 @@
package org.springframework.security.kt.docs.servlet.oauth2.resourceserver.methodsecurityhasscope
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.stereotype.Service
@Service
open class MessageService {
// tag::protected-method[]
@PreAuthorize("@oauth2.hasScope('message:read')")
open fun readMessage(): String {
return "message"
}
// end::protected-method[]
}
@@ -0,0 +1,18 @@
package org.springframework.security.kt.docs.servlet.oauth2.resourceserver.methodsecurityhasscope
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity
import org.springframework.security.oauth2.core.authorization.DefaultOAuth2AuthorizationManagerFactory
import org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagerFactory
@Configuration
@EnableMethodSecurity
open class MethodSecurityHasScopeConfiguration {
// tag::declare-factory[]
@Bean
open fun oauth2(): OAuth2AuthorizationManagerFactory<Any> {
return DefaultOAuth2AuthorizationManagerFactory()
}
// end::declare-factory[]
}
@@ -0,0 +1,61 @@
package org.springframework.security.kt.docs.servlet.oauth2.resourceserver.methodsecurityhasscope
import org.assertj.core.api.Assertions
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.access.AccessDeniedException
import org.springframework.security.config.test.SpringTestContext
import org.springframework.security.config.test.SpringTestContextExtension
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners
import org.springframework.security.test.context.support.WithMockUser
import org.springframework.test.context.junit.jupiter.SpringExtension
@ExtendWith(SpringTestContextExtension::class)
@ExtendWith(SpringExtension::class)
@SecurityTestExecutionListeners
class MethodSecurityHasScopeConfigurationTests {
@JvmField
val spring: SpringTestContext = SpringTestContext(this).mockMvcAfterSpringSecurityOk()
@Autowired
var messages: MessageService? = null
@Test
@WithMockUser(authorities = ["SCOPE_message:read"])
fun readMessageWhenMessageReadThenAllowed() {
this.spring.register(MethodSecurityHasScopeConfiguration::class.java, MessageService::class.java).autowire()
this.messages!!.readMessage()
}
@Test
@WithMockUser
fun readMessageWhenNoScopeThenDenied() {
this.spring.register(MethodSecurityHasScopeConfiguration::class.java, MessageService::class.java).autowire()
Assertions.assertThatExceptionOfType<AccessDeniedException?>(AccessDeniedException::class.java)
.isThrownBy({ this.messages!!.readMessage() })
}
@Test
@WithMockUser(authorities = ["SCOPE_message:read", "FACTOR_BEARER", "FACTOR_X509"])
fun mfaReadMessageWhenMessageReadAndFactorsThenAllowed() {
this.spring.register(MethodSecurityHasScopeMfaConfiguration::class.java, MessageService::class.java).autowire()
this.messages!!.readMessage()
}
@Test
@WithMockUser(authorities = ["SCOPE_message:read"])
fun mfaReadMessageWhenMessageReadThenDenied() {
this.spring.register(MethodSecurityHasScopeMfaConfiguration::class.java, MessageService::class.java).autowire()
Assertions.assertThatExceptionOfType<AccessDeniedException?>(AccessDeniedException::class.java)
.isThrownBy({ this.messages!!.readMessage() })
}
@Test
@WithMockUser
fun mfaReadMessageWhenNoScopeThenDenied() {
this.spring.register(MethodSecurityHasScopeMfaConfiguration::class.java, MessageService::class.java).autowire()
Assertions.assertThatExceptionOfType<AccessDeniedException?>(AccessDeniedException::class.java)
.isThrownBy({ this.messages!!.readMessage() })
}
}
@@ -0,0 +1,20 @@
package org.springframework.security.kt.docs.servlet.oauth2.resourceserver.methodsecurityhasscope
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.authorization.AuthorizationManagerFactory
import org.springframework.security.config.annotation.authorization.EnableMultiFactorAuthentication
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity
import org.springframework.security.oauth2.core.authorization.DefaultOAuth2AuthorizationManagerFactory
import org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagerFactory
@Configuration
@EnableMethodSecurity
@EnableMultiFactorAuthentication(authorities = ["FACTOR_BEARER", "FACTOR_X509"])
open class MethodSecurityHasScopeMfaConfiguration {
// tag::declare-factory[]
@Bean
open fun oauth2(authz: AuthorizationManagerFactory<Any>): OAuth2AuthorizationManagerFactory<Any> {
return DefaultOAuth2AuthorizationManagerFactory(authz)
} // end::declare-factory[]
}
+1 -1
View File
@@ -14,7 +14,7 @@
# limitations under the License.
#
springBootVersion=4.1.0-SNAPSHOT
version=7.1.0-SNAPSHOT
version=7.1.0-M2
samplesBranch=main
org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError
org.gradle.parallel=true
+10 -10
View File
@@ -12,31 +12,31 @@ org-jetbrains-kotlin = "2.3.10"
org-jetbrains-kotlinx = "1.10.2"
org-mockito = "5.21.0"
org-opensaml5 = "5.2.0"
org-springframework = "7.0.3"
org-springframework = "7.0.4"
com-password4j = "1.8.4"
[libraries]
ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.5.28"
ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.5.29"
com-fasterxml-jackson-jackson-bom = "com.fasterxml.jackson:jackson-bom:2.21.0"
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-nimbusds-nimbus-jose-jwt = "com.nimbusds:nimbus-jose-jwt:10.6"
com-nimbusds-oauth2-oidc-sdk = "com.nimbusds:oauth2-oidc-sdk:11.33"
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.4"
com-jayway-jsonpath-json-path = "com.jayway.jsonpath:json-path:2.10.0"
commons-collections = "commons-collections:commons-collections:3.2.2"
io-micrometer-context-propagation = "io.micrometer:context-propagation:1.2.1"
io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.16.2"
io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.16.3"
io-mockk = "io.mockk:mockk:1.14.9"
io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2025.0.2"
io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2025.0.3"
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.13"
io-spring-security-release-plugin = "io.spring.gradle:spring-security-release-plugin:1.0.14"
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"
@@ -81,8 +81,8 @@ 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.2"
org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:4.0.1"
org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2025.1.3"
org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:4.0.2"
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.4"
@@ -102,7 +102,7 @@ org-sonarsource-scanner-gradle-sonarqube-gradle-plugin = "org.sonarsource.scanne
org-instancio-instancio-junit = "org.instancio:instancio-junit:3.7.1"
spring-nullability = 'io.spring.nullability:io.spring.nullability.gradle.plugin:0.0.11'
webauthn4j-core = 'com.webauthn4j:webauthn4j-core:0.29.7.RELEASE'
webauthn4j-core = 'com.webauthn4j:webauthn4j-core:0.31.0.RELEASE'
com-password4j-password4j = { module = "com.password4j:password4j", version.ref = "com-password4j" }
[plugins]
@@ -105,7 +105,7 @@ public final class ClientRegistrations {
* @return the {@link ClientRegistration} built from the configuration
*/
public static ClientRegistration.Builder fromOidcConfiguration(Map<String, Object> configuration) {
OIDCProviderMetadata metadata = parse(configuration, OIDCProviderMetadata::parse);
OIDCProviderMetadata metadata = parseInput(configuration, OIDCProviderMetadata::parse);
ClientRegistration.Builder builder = withProviderConfiguration(metadata, metadata.getIssuer().getValue());
builder.jwkSetUri(metadata.getJWKSetURI().toASCIIString());
if (metadata.getUserInfoEndpointURI() != null) {
@@ -292,6 +292,15 @@ public final class ClientRegistrations {
throw new IllegalArgumentException(errorMessage);
}
private static <T> T parseInput(Map<String, Object> body, ThrowingFunction<JSONObject, T, ParseException> parser) {
try {
return parse(body, parser);
}
catch (RuntimeException ex) {
throw new IllegalArgumentException(ex);
}
}
private static <T> T parse(Map<String, Object> body, ThrowingFunction<JSONObject, T, ParseException> parser) {
try {
return parser.apply(new JSONObject(body));
@@ -40,7 +40,6 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.assertj.core.api.Assertions.assertThatNullPointerException;
/**
* @author Rob Winch
@@ -475,7 +474,7 @@ public class ClientRegistrationsTests {
@Test
public void issuerWhenOidcConfigurationResponseMissingJwksUriThenThrowsIllegalArgumentException() throws Exception {
this.response.remove("jwks_uri");
assertThatNullPointerException().isThrownBy(() -> registration(this.response).build());
assertThatIllegalArgumentException().isThrownBy(() -> registration(this.response).build());
}
@Test
@@ -0,0 +1,89 @@
/*
* Copyright 2025-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.core.authorization;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.AuthorizationManagerFactory;
import org.springframework.security.authorization.DefaultAuthorizationManagerFactory;
import org.springframework.util.Assert;
/**
* A factory for creating different kinds of {@link AuthorizationManager} instances.
*
* @param <T> the type of object that the authorization check is being done on
* @author Ngoc Nhan
* @since 7.1
*/
public final class DefaultOAuth2AuthorizationManagerFactory<T> implements OAuth2AuthorizationManagerFactory<T> {
private String scopePrefix = "SCOPE_";
private final AuthorizationManagerFactory<T> authorizationManagerFactory;
public DefaultOAuth2AuthorizationManagerFactory() {
this(new DefaultAuthorizationManagerFactory<>());
}
public DefaultOAuth2AuthorizationManagerFactory(AuthorizationManagerFactory<T> authorizationManagerFactory) {
Assert.notNull(authorizationManagerFactory, "authorizationManagerFactory can not be null");
this.authorizationManagerFactory = authorizationManagerFactory;
}
/**
* Sets the prefix used to create an authority name from a scope name. Can be an empty
* string.
* @param scopePrefix the scope prefix to use
*/
public void setScopePrefix(String scopePrefix) {
Assert.notNull(scopePrefix, "scopePrefix can not be null");
this.scopePrefix = scopePrefix;
}
@Override
public AuthorizationManager<T> hasScope(String scope) {
Assert.notNull(scope, "scope can not be null");
assertScope(scope);
return this.authorizationManagerFactory.hasAuthority(this.scopePrefix + scope);
}
@Override
public AuthorizationManager<T> hasAnyScope(String... scopes) {
return this.authorizationManagerFactory.hasAnyAuthority(this.mappedScopes(scopes));
}
@Override
public AuthorizationManager<T> hasAllScopes(String... scopes) {
return this.authorizationManagerFactory.hasAllAuthorities(this.mappedScopes(scopes));
}
private String[] mappedScopes(String... scopes) {
Assert.notNull(scopes, "scopes can not be null");
String[] mappedScopes = new String[scopes.length];
for (int i = 0; i < scopes.length; i++) {
assertScope(scopes[i]);
mappedScopes[i] = this.scopePrefix + scopes[i];
}
return mappedScopes;
}
private void assertScope(String scope) {
Assert.isTrue(!scope.startsWith(this.scopePrefix), () -> scope + " should not start with '" + this.scopePrefix
+ "' since '" + this.scopePrefix
+ "' is automatically prepended when using hasScope and hasAnyScope. Consider using AuthorizationManagerFactory#hasAuthority or #hasAnyAuthority instead.");
}
}
@@ -0,0 +1,95 @@
/*
* Copyright 2025-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.core.authorization;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.Authentication;
/**
* A factory for creating different kinds of {@link AuthorizationManager} instances.
*
* @param <T> the type of object that the authorization check is being done on
* @author Ngoc Nhan
* @since 7.1
*/
public interface OAuth2AuthorizationManagerFactory<T> {
/**
* Create an {@link AuthorizationManager} that requires an {@link Authentication} to
* have a {@code SCOPE_scope} authority.
*
* <p>
* For example, if you call {@code hasScope("read")}, then this will require that each
* authentication have a {@link org.springframework.security.core.GrantedAuthority}
* whose value is {@code SCOPE_read}.
*
* <p>
* This would equivalent to calling
* {@code AuthorityAuthorizationManager#hasAuthority("SCOPE_read")}.
* @param scope the scope value to require
* @return an {@link AuthorizationManager} that requires a {@code "SCOPE_scope"}
* authority
*/
default AuthorizationManager<T> hasScope(String scope) {
return OAuth2AuthorizationManagers.hasScope(scope);
}
/**
* Create an {@link AuthorizationManager} that requires an {@link Authentication} to
* have at least one authority among {@code SCOPE_scope1}, {@code SCOPE_scope2}, ...
* {@code SCOPE_scopeN}.
*
* <p>
* For example, if you call {@code hasAnyScope("read", "write")}, then this will
* require that each authentication have at least a
* {@link org.springframework.security.core.GrantedAuthority} whose value is either
* {@code SCOPE_read} or {@code SCOPE_write}.
*
* <p>
* This would equivalent to calling
* {@code AuthorityAuthorizationManager#hasAnyAuthority("SCOPE_read", "SCOPE_write")}.
* @param scopes the scope values to allow
* @return an {@link AuthorizationManager} that requires at least one authority among
* {@code "SCOPE_scope1"}, {@code SCOPE_scope2}, ... {@code SCOPE_scopeN}.
*/
default AuthorizationManager<T> hasAnyScope(String... scopes) {
return OAuth2AuthorizationManagers.hasAnyScope(scopes);
}
/**
* Create an {@link AuthorizationManager} that requires an {@link Authentication} to
* have all authorities {@code SCOPE_scope1}, {@code SCOPE_scope2}, ...
* {@code SCOPE_scopeN}.
*
* <p>
* For example, if you call {@code hasAllScopes("read", "write")}, then each
* {@link org.springframework.security.core.Authentication} must have all
* {@link org.springframework.security.core.GrantedAuthority} values of
* {@code SCOPE_read} and {@code SCOPE_write}.
*
* <p>
* This would be equivalent to calling
* {@code AllAuthoritiesAuthorizationManager#hasAllAuthorities("SCOPE_read", "SCOPE_write")}.
* @param scopes the scope values to require
* @return an {@link AuthorizationManager} that requires all authorities
* {@code SCOPE_scope1}, {@code SCOPE_scope2}, ... {@code SCOPE_scopeN}.
*/
default AuthorizationManager<T> hasAllScopes(String... scopes) {
return OAuth2AuthorizationManagers.hasAllScopes(scopes);
}
}
@@ -16,6 +16,7 @@
package org.springframework.security.oauth2.core.authorization;
import org.springframework.security.authorization.AllAuthoritiesAuthorizationManager;
import org.springframework.security.authorization.AuthorityAuthorizationManager;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.Authentication;
@@ -28,6 +29,7 @@ import org.springframework.util.Assert;
* @author Josh Cummings
* @since 6.2
* @see AuthorityAuthorizationManager
* @see AllAuthoritiesAuthorizationManager
*/
public final class OAuth2AuthorizationManagers {
@@ -85,6 +87,34 @@ public final class OAuth2AuthorizationManagers {
return AuthorityAuthorizationManager.hasAnyAuthority(mappedScopes);
}
/**
* Create an {@link AuthorizationManager} that requires an {@link Authentication} to
* have all authorities {@code SCOPE_scope1}, {@code SCOPE_scope2}, ...
* {@code SCOPE_scopeN}.
*
* <p>
* For example, if you call {@code hasAllScopes("read", "write")}, then each
* {@link org.springframework.security.core.Authentication} must have all
* {@link org.springframework.security.core.GrantedAuthority} values of
* {@code SCOPE_read} and {@code SCOPE_write}.
*
* <p>
* This would be equivalent to calling
* {@code AllAuthoritiesAuthorizationManager#hasAllAuthorities("SCOPE_read", "SCOPE_write")}.
* @param scopes the scope values to require
* @return an {@link AuthorizationManager} that requires all authorities
* {@code SCOPE_scope1}, {@code SCOPE_scope2}, ... {@code SCOPE_scopeN}.
* @since 7.1
*/
public static <T> AuthorizationManager<T> hasAllScopes(String... scopes) {
String[] mappedScopes = new String[scopes.length];
for (int i = 0; i < scopes.length; i++) {
assertScope(scopes[i]);
mappedScopes[i] = "SCOPE_" + scopes[i];
}
return AllAuthoritiesAuthorizationManager.hasAllAuthorities(mappedScopes);
}
private static void assertScope(String scope) {
Assert.isTrue(!scope.startsWith("SCOPE_"),
() -> scope + " should not start with SCOPE_ since SCOPE_"
@@ -0,0 +1,108 @@
/*
* Copyright 2025-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.core.authorization;
import org.junit.jupiter.api.Test;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authorization.AuthorityAuthorizationManager;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.AuthorizationManagerFactories;
import org.springframework.security.authorization.AuthorizationResult;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link OAuth2AuthorizationManagerFactory}.
*
* @author Ngoc Nhan
*/
public class OAuth2AuthorizationManagerFactoryTests {
private static final String MSG_READ = "message:read";
private static final String MSG_WRITE = "message:write";
private static final String SCOPE_MSG_READ = "SCOPE_message:read";
private static final String SCOPE_MSG_WRITE = "SCOPE_message:write";
@Test
public void hasScopeReturnsAuthorityAuthorizationManagerByDefault() {
OAuth2AuthorizationManagerFactory<String> factory = new DefaultOAuth2AuthorizationManagerFactory<>();
AuthorizationManager<String> authorizationManager = factory.hasScope(MSG_READ);
assertThat(authorizationManager).isInstanceOf(AuthorityAuthorizationManager.class);
}
@Test
public void hasAnyScopeReturnsAuthorityAuthorizationManagerByDefault() {
OAuth2AuthorizationManagerFactory<String> factory = new DefaultOAuth2AuthorizationManagerFactory<>();
AuthorizationManager<String> authorizationManager = factory.hasAnyScope(MSG_READ, MSG_WRITE);
assertThat(authorizationManager).isInstanceOf(AuthorityAuthorizationManager.class);
}
@Test
public void hasAllScopesReturnsAuthorityAuthorizationManagerByDefault() {
OAuth2AuthorizationManagerFactory<String> factory = new DefaultOAuth2AuthorizationManagerFactory<>();
AuthorizationManager<String> authorizationManager = factory.hasAnyScope(MSG_READ, MSG_WRITE);
assertThat(authorizationManager).isInstanceOf(AuthorityAuthorizationManager.class);
}
@Test
public void hasScopeWhenSetAuthorizationManagerFactories() {
DefaultOAuth2AuthorizationManagerFactory<String> factory = new DefaultOAuth2AuthorizationManagerFactory<>(
AuthorizationManagerFactories.<String>multiFactor().requireFactors(SCOPE_MSG_READ).build());
assertUserGranted(factory.hasScope(MSG_READ), SCOPE_MSG_READ);
assertUserDenied(factory.hasScope(MSG_WRITE), SCOPE_MSG_READ);
}
@Test
public void hasAnyScopeWhenSetAuthorizationManagerFactories() {
DefaultOAuth2AuthorizationManagerFactory<String> factory = new DefaultOAuth2AuthorizationManagerFactory<>(
AuthorizationManagerFactories.<String>multiFactor().requireFactors(SCOPE_MSG_READ).build());
assertUserGranted(factory.hasAnyScope(MSG_READ), SCOPE_MSG_READ);
assertUserDenied(factory.hasAnyScope(MSG_WRITE), SCOPE_MSG_READ);
}
@Test
public void hasAllScopesWhenSetAuthorizationManagerFactories() {
DefaultOAuth2AuthorizationManagerFactory<String> factory = new DefaultOAuth2AuthorizationManagerFactory<>(
AuthorizationManagerFactories.<String>multiFactor()
.requireFactors(SCOPE_MSG_READ, SCOPE_MSG_WRITE)
.build());
assertUserGranted(factory.hasAllScopes(MSG_READ, MSG_WRITE), SCOPE_MSG_READ, SCOPE_MSG_WRITE);
assertUserDenied(factory.hasAllScopes(MSG_READ, MSG_WRITE), SCOPE_MSG_READ);
}
private void assertUserGranted(AuthorizationManager<String> manager, String... authorities) {
AuthorizationResult authorizationResult = createAuthorizationResult(manager, authorities);
assertThat(authorizationResult).isNotNull();
assertThat(authorizationResult.isGranted()).isTrue();
}
private void assertUserDenied(AuthorizationManager<String> manager, String... authorities) {
AuthorizationResult authorizationResult = createAuthorizationResult(manager, authorities);
assertThat(authorizationResult).isNotNull();
assertThat(authorizationResult.isGranted()).isFalse();
}
private AuthorizationResult createAuthorizationResult(AuthorizationManager<String> manager, String... authorities) {
TestingAuthenticationToken authenticatedUser = new TestingAuthenticationToken("user", "pass", authorities);
return manager.authorize(() -> authenticatedUser, "");
}
}
+2
View File
@@ -18,10 +18,12 @@ dependencies {
optional 'org.springframework:spring-jdbc'
optional 'org.springframework:spring-tx'
optional 'tools.jackson.core:jackson-databind'
optional 'com.fasterxml.jackson.core:jackson-databind'
provided 'jakarta.servlet:jakarta.servlet-api'
testImplementation project(path: ':spring-security-core', configuration: 'tests')
testImplementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-cbor'
testImplementation 'io.projectreactor:reactor-test'
testImplementation 'jakarta.xml.bind:jakarta.xml.bind-api'
testImplementation 'jakarta.websocket:jakarta.websocket-api'
@@ -21,7 +21,7 @@ public final class TestAuthenticatorAttestationResponses {
public static AuthenticatorAttestationResponse.AuthenticatorAttestationResponseBuilder createAuthenticatorAttestationResponse() {
return AuthenticatorAttestationResponse.builder()
.attestationObject(Bytes.fromBase64(
"o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViUy9GqwTRaMpzVDbXq1dyEAXVOxrou08k22ggRC45MKNhdAAAAALraVWanqkAfvZZFYZpVEg0AEDWRLOHq0Wxw4cOkCemynKqlAQIDJiABIVgg4Hkrn2kbGmpZTdoDZUNrppo93OqgQV7ONzVvo5GLCFciWCCrf6yIQggq2BfZntawxRsBBbWG_FWkYAoU8yPipS-5hg-p1VREax-qKTGn1Bp92fRMjRMunH7XwSlf9mMWJdY3VvAkicTChlxVd256yk4jEiXiJ5BuwugdpT7fBOYtymjpQECAyYgASFYIJK-2epPEw0ujHN-gvVp2Hp3ef8CzU3zqwO5ylx8L2OsIlggK5x5OlTGEPxLS-85TAABum4aqVK4CSWJ7LYDdkjuBLk"))
"o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViUy9GqwTRaMpzVDbXq1dyEAXVOxrou08k22ggRC45MKNhdAAAAALraVWanqkAfvZZFYZpVEg0AEDWRLOHq0Wxw4cOkCemynKqlAQIDJiABIVgg4Hkrn2kbGmpZTdoDZUNrppo93OqgQV7ONzVvo5GLCFciWCCrf6yIQggq2BfZntawxRsBBbWG_FWkYAoU8yPipS-5hg=="))
.clientDataJSON(Bytes.fromBase64(
"eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoicTdsQ2RkM1NWUXhkQy12OHBuUkFHRW4xQjJNLXQ3WkVDV1B3Q0FtaFd2YyIsIm9yaWdpbiI6Imh0dHBzOi8vZXhhbXBsZS5sb2NhbGhvc3Q6ODQ0MyIsImNyb3NzT3JpZ2luIjpmYWxzZX0"))
.transports(AuthenticatorTransport.HYBRID, AuthenticatorTransport.INTERNAL);
@@ -43,6 +43,8 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import tools.jackson.databind.json.JsonMapper;
import tools.jackson.dataformat.cbor.CBORMapper;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
@@ -78,6 +80,7 @@ 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.RETURNS_SELF;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyNoInteractions;
@@ -613,9 +616,11 @@ class Webauthn4jRelyingPartyOperationsTests {
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));
JsonMapper json = new JsonMapper();
CBORMapper cbor = mock(CBORMapper.class);
CBORMapper.Builder builder = mock(CBORMapper.Builder.class, RETURNS_SELF);
given(builder.build()).willReturn(cbor);
given(cbor.rebuild()).willReturn(builder);
AttestationObject attestationObject = mock(AttestationObject.class);
AuthenticatorData wa4jAuthData = mock(AuthenticatorData.class);
given(attestationObject.getAuthenticatorData()).willReturn(wa4jAuthData);