1
0
mirror of synced 2026-07-16 08:05:13 +00:00

Compare commits

..

2 Commits

Author SHA1 Message Date
Rob Winch 1a6915d3c0 rm .github/workflows for unsupported branch 2025-05-02 12:35:31 -05:00
Rob Winch 8325728035 rm merge-dependabot-pr.yml from Unsupported Branch 2025-04-25 13:16:44 -05:00
2042 changed files with 12503 additions and 139702 deletions
-2
View File
@@ -1,2 +0,0 @@
require:
members: false
+59 -120
View File
@@ -1,133 +1,72 @@
version: 2
registries:
spring-milestones:
type: maven-repository
url: https://repo.spring.io/milestone
updates:
- package-ecosystem: gradle
target-branch: 6.4.x
directory: /
schedule:
interval: daily
time: '03:00'
timezone: Etc/UTC
labels:
- 'type: dependency-upgrade'
registries:
- spring-milestones
ignore:
- dependency-name: com.nimbusds:nimbus-jose-jwt
- dependency-name: org.python:jython
- dependency-name: org.apache.directory.server:*
- dependency-name: org.apache.directory.shared:*
- dependency-name: org.junit:junit-bom
update-types:
- version-update:semver-major
- dependency-name: org.mockito:mockito-bom
update-types:
- version-update:semver-major
- dependency-name: '*'
update-types:
- version-update:semver-major
- version-update:semver-minor
- package-ecosystem: gradle
target-branch: 6.3.x
directory: /
schedule:
interval: daily
time: '03:00'
timezone: Etc/UTC
labels:
- 'type: dependency-upgrade'
registries:
- spring-milestones
ignore:
- dependency-name: com.nimbusds:nimbus-jose-jwt
- dependency-name: org.python:jython
- dependency-name: org.apache.directory.server:*
- dependency-name: org.apache.directory.shared:*
- dependency-name: org.junit:junit-bom
update-types:
- version-update:semver-major
- dependency-name: org.mockito:mockito-bom
update-types:
- version-update:semver-major
- dependency-name: '*'
update-types:
- version-update:semver-major
- version-update:semver-minor
- package-ecosystem: gradle
target-branch: main
directory: /
schedule:
interval: daily
time: '03:00'
timezone: Etc/UTC
labels:
- 'type: dependency-upgrade'
registries:
- spring-milestones
ignore:
- dependency-name: com.nimbusds:nimbus-jose-jwt
- dependency-name: org.python:jython
- dependency-name: org.apache.directory.server:*
- dependency-name: org.apache.directory.shared:*
- dependency-name: org.junit:junit-bom
update-types:
- version-update:semver-major
- dependency-name: org.mockito:mockito-bom
update-types:
- version-update:semver-major
- dependency-name: com.gradle.enterprise
update-types:
- version-update:semver-major
- version-update:semver-minor
- dependency-name: '*'
update-types:
- version-update:semver-major
- version-update:semver-minor
- package-ecosystem: github-actions
target-branch: 6.3.x
directory: /
- package-ecosystem: "gradle"
target-branch: "main"
milestone: 319 # 6.2.x
directory: "/"
schedule:
interval: weekly
labels:
- 'type: task'
- 'in: build'
interval: "daily"
time: "03:00"
timezone: "Etc/UTC"
labels: [ "type: dependency-upgrade" ]
registries:
- "spring-milestones"
ignore:
- dependency-name: sjohnr/*
- package-ecosystem: github-actions
target-branch: docs-build
directory: /
schedule:
interval: weekly
labels:
- 'type: task'
- 'in: build'
- dependency-name: "com.nimbusds:nimbus-jose-jwt" # nimbus-jose-jwt gets updated when oauth2-oidc-sdk is updated to ensure consistency
- dependency-name: "org.python:jython" # jython updates break integration tests
- dependency-name: "org.apache.directory.server:*" # ApacheDS version > 1.5.5 contains break changes
- dependency-name: "org.junit:junit-bom"
update-types: [ "version-update:semver-major" ]
- dependency-name: "org.mockito:mockito-bom"
update-types: [ "version-update:semver-major" ]
- dependency-name: "*"
update-types: [ "version-update:semver-major", "version-update:semver-minor" ]
- package-ecosystem: npm
target-branch: docs-build
directory: /
- package-ecosystem: "gradle"
target-branch: "6.1.x"
milestone: 318 # 6.1.x
directory: "/"
schedule:
interval: weekly
labels:
- 'type: task'
- 'in: build'
interval: "daily"
time: "03:00"
timezone: "Etc/UTC"
labels: [ "type: dependency-upgrade" ]
ignore:
- dependency-name: "com.nimbusds:nimbus-jose-jwt" # nimbus-jose-jwt gets updated when oauth2-oidc-sdk is updated to ensure consistency
- dependency-name: "org.python:jython" # jython updates break integration tests
- dependency-name: "org.apache.directory.server:*" # ApacheDS version > 1.5.5 contains break changes
- dependency-name: "org.junit:junit-bom"
update-types: [ "version-update:semver-major" ]
- dependency-name: "org.mockito:mockito-bom"
update-types: [ "version-update:semver-major" ]
- dependency-name: "*"
update-types: [ "version-update:semver-major", "version-update:semver-minor" ]
- package-ecosystem: npm
target-branch: main
directory: /docs
- package-ecosystem: "gradle"
target-branch: "5.8.x"
milestone: 246 # 5.8.x
directory: "/"
schedule:
interval: weekly
labels:
- 'type: task'
- 'in: build'
- package-ecosystem: npm
target-branch: 6.3.x
directory: /docs
schedule:
interval: weekly
labels:
- 'type: task'
- 'in: build'
interval: "daily"
time: "03:00"
timezone: "Etc/UTC"
labels: [ "type: dependency-upgrade" ]
ignore:
- dependency-name: "com.nimbusds:nimbus-jose-jwt" # nimbus-jose-jwt gets updated when oauth2-oidc-sdk is updated to ensure consistency
- dependency-name: "org.python:jython" # jython updates break integration tests
- dependency-name: "org.apache.directory.server:*" # ApacheDS version > 1.5.5 contains break changes
- dependency-name: "io.mockk:mockk" # mockk updates break tests
- dependency-name: "org.opensaml:*" # org.opensaml maintains two different versions, so it must be updated manually
- dependency-name: "org.junit:junit-bom"
update-types: [ "version-update:semver-major" ]
- dependency-name: "org.mockito:mockito-bom"
update-types: [ "version-update:semver-major" ]
- dependency-name: "*"
update-types: [ "version-update:semver-major", "version-update:semver-minor" ]
@@ -1,23 +0,0 @@
name: Clean build artifacts
on:
schedule:
- cron: '0 10 * * *' # Once per day at 10am UTC
permissions:
contents: read
jobs:
main:
runs-on: ubuntu-latest
if: ${{ github.repository == 'spring-projects/spring-security' }}
permissions:
contents: none
steps:
- name: Delete artifacts in cron job
env:
GH_ACTIONS_REPO_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
run: |
echo "Running clean build artifacts logic"
output=$(curl -X GET -H "Authorization: token $GH_ACTIONS_REPO_TOKEN" https://api.github.com/repos/spring-projects/spring-security/actions/artifacts | grep '"id"' | cut -d : -f2 | sed 's/,*$//g')
echo Output is $output
for id in $output; do curl -X DELETE -H "Authorization: token $GH_ACTIONS_REPO_TOKEN" https://api.github.com/repos/spring-projects/spring-security/actions/artifacts/$id; done;
-17
View File
@@ -1,17 +0,0 @@
name: "CodeQL Advanced"
on:
push:
pull_request:
workflow_dispatch:
schedule:
# https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#schedule
- cron: '0 5 * * *'
permissions: read-all
jobs:
codeql-analysis-call:
permissions:
actions: read
contents: read
security-events: write
uses: spring-io/github-actions/.github/workflows/codeql-analysis.yml@1
@@ -1,109 +0,0 @@
name: CI
on:
push:
branches-ignore:
- "dependabot/**"
schedule:
- cron: '0 10 * * *' # Once per day at 10am UTC
workflow_dispatch: # Manual trigger
env:
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
permissions:
contents: read
jobs:
build:
name: Build
uses: spring-io/spring-security-release-tools/.github/workflows/build.yml@v1
strategy:
matrix:
os: [ ubuntu-latest, windows-latest ]
jdk: [ 17 ]
with:
runs-on: ${{ matrix.os }}
java-version: ${{ matrix.jdk }}
distribution: temurin
secrets: inherit
test:
name: Test Against Snapshots
uses: spring-io/spring-security-release-tools/.github/workflows/test.yml@v1
strategy:
matrix:
include:
- java-version: 21-ea
toolchain: 21
- java-version: 17
toolchain: 17
with:
java-version: ${{ matrix.java-version }}
test-args: --refresh-dependencies -PforceMavenRepositories=snapshot -PisOverrideVersionCatalog -PtestToolchain=${{ matrix.toolchain }} -PspringFrameworkVersion=6.2.+ -PreactorVersion=2023.0.+ -PspringDataVersion=2024.0.+ --stacktrace
secrets: inherit
check-samples:
name: Check Samples
runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'spring-projects' }}
steps:
- uses: actions/checkout@v4
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v2
with:
java-version: 17
distribution: temurin
- name: Check samples project
env:
LOCAL_REPOSITORY_PATH: ${{ github.workspace }}/build/publications/repos
SAMPLES_DIR: ../spring-security-samples
run: |
# Extract version from gradle.properties
version=$(cat gradle.properties | grep "version=" | awk -F'=' '{print $2}')
# Extract samplesBranch from gradle.properties
samples_branch=$(cat gradle.properties | grep "samplesBranch=" | awk -F'=' '{print $2}')
./gradlew publishMavenJavaPublicationToLocalRepository
./gradlew cloneRepository -PrepositoryName="spring-projects/spring-security-samples" -Pref="$samples_branch" -PcloneOutputDirectory="$SAMPLES_DIR"
./gradlew --refresh-dependencies --project-dir "$SAMPLES_DIR" --init-script spring-security-ci.gradle -PlocalRepositoryPath="$LOCAL_REPOSITORY_PATH" -PspringSecurityVersion="$version" test integrationTest
deploy-artifacts:
name: Deploy Artifacts
needs: [ build, test, check-samples ]
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-artifacts.yml@v1
with:
should-deploy-artifacts: ${{ needs.build.outputs.should-deploy-artifacts }}
secrets: inherit
deploy-docs:
name: Deploy Docs
needs: [ build, test, check-samples ]
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, test, check-samples ]
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-schema.yml@v1
with:
should-deploy-schema: ${{ needs.build.outputs.should-deploy-artifacts }}
secrets: inherit
perform-release:
name: Perform Release
needs: [ deploy-artifacts, deploy-docs, 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 }}
project-version: ${{ needs.deploy-artifacts.outputs.project-version }}
milestone-repo-url: https://repo.spring.io/artifactory/milestone
release-repo-url: https://repo1.maven.org/maven2
artifact-path: org/springframework/security/spring-security-core
slack-announcing-id: spring-security-announcing
secrets: inherit
send-notification:
name: Send Notification
needs: [ perform-release ]
if: ${{ !success() }}
runs-on: ubuntu-latest
steps:
- name: Send Notification
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@v1
with:
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
-33
View File
@@ -1,33 +0,0 @@
name: Deploy Docs
on:
push:
branches-ignore:
- "gh-pages"
- "dependabot/**"
tags: '**'
repository_dispatch:
types: request-build-reference # legacy
#schedule:
#- cron: '0 10 * * *' # Once per day at 10am UTC
workflow_dispatch:
permissions: read-all
jobs:
build:
runs-on: ubuntu-latest
if: github.repository_owner == 'spring-projects'
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: docs-build
fetch-depth: 1
- name: Dispatch (partial build)
if: github.ref_type == 'branch'
env:
GH_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
run: gh workflow run deploy-docs.yml -r $(git rev-parse --abbrev-ref HEAD) -f build-refname=${{ github.ref_name }}
- name: Dispatch (full build)
if: github.ref_type == 'tag'
env:
GH_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
run: gh workflow run deploy-docs.yml -r $(git rev-parse --abbrev-ref HEAD)
@@ -1,33 +0,0 @@
name: Execute Gradle Wrapper Upgrade
on:
schedule:
- cron: '0 2 * * *' # 2am UTC
workflow_dispatch:
permissions:
pull-requests: write
jobs:
upgrade_wrapper:
name: Execution
runs-on: ubuntu-latest
steps:
- name: Set up Git configuration
env:
TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git config --global url."https://unused-username:${TOKEN}@github.com/".insteadOf "https://github.com/"
git config --global user.name 'github-actions[bot]'
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
- name: Checkout
uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Set up Gradle
uses: gradle/gradle-build-action@v2
- name: Upgrade Wrappers
run: ./gradlew clean upgradeGradleWrapperAll --continue -Porg.gradle.java.installations.auto-download=false
env:
WRAPPER_UPGRADE_GIT_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -1,35 +0,0 @@
name: Check Milestone
on:
milestone:
types: [created, opened, edited]
env:
DUE_ON: ${{ github.event.milestone.due_on }}
TITLE: ${{ github.event.milestone.title }}
permissions:
contents: read
jobs:
spring-releasetrain-checks:
name: Check DueOn is on a Release Date
runs-on: ubuntu-latest
if: ${{ github.repository == 'spring-projects/spring-security' }}
permissions:
contents: none
steps:
- name: Print Milestone Being Checked
run: echo "Validating DueOn '$DUE_ON' for milestone '$TITLE'"
- name: Validate DueOn
if: env.DUE_ON != ''
run: |
export TOOL_VERSION=0.1.1
wget "https://repo.maven.apache.org/maven2/io/spring/releasetrain/spring-release-train-tools/$TOOL_VERSION/spring-release-train-tools-$TOOL_VERSION.jar"
java -cp "spring-release-train-tools-$TOOL_VERSION.jar" io.spring.releasetrain.CheckMilestoneDueOnMain --dueOn "$DUE_ON" --expectedDayOfWeek MONDAY --expectedMondayCount 3
send-notification:
name: Send Notification
needs: [ spring-releasetrain-checks ]
if: ${{ failure() || cancelled() }}
runs-on: ubuntu-latest
steps:
- name: Send Notification
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@v1
with:
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
-51
View File
@@ -1,51 +0,0 @@
name: PR Build
on: pull_request
permissions:
contents: read
jobs:
build:
name: Build
runs-on: ubuntu-latest
if: ${{ github.repository == 'spring-projects/spring-security' }}
steps:
- uses: actions/checkout@v4
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v2
with:
java-version: '17'
distribution: 'temurin'
- name: Build with Gradle
run: ./gradlew clean build -PskipCheckExpectedBranchVersion --continue --scan
generate-docs:
name: Generate Docs
runs-on: ubuntu-latest
if: ${{ github.repository == 'spring-projects/spring-security' }}
steps:
- uses: actions/checkout@v4
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v2
with:
java-version: '17'
distribution: 'temurin'
- name: Run Antora
run: ./gradlew -PbuildSrc.skipTests=true :spring-security-docs:antora
- name: Upload Docs
id: upload
uses: actions/upload-artifact@v4
with:
name: docs
path: docs/build/site
overwrite: true
send-notification:
name: Send Notification
needs: [ build, generate-docs ]
if: ${{ failure() && github.event.pull_request.user.login == 'dependabot[bot]' && github.repository == 'spring-projects/spring-security' }}
runs-on: ubuntu-latest
steps:
- name: Send Notification
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@v1
with:
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
-24
View File
@@ -1,24 +0,0 @@
name: Release Scheduler
on:
schedule:
- cron: '15 15 * * MON' # Every Monday at 3:15pm UTC
workflow_dispatch:
permissions: read-all
jobs:
dispatch_scheduled_releases:
name: Dispatch scheduled releases
if: github.repository_owner == 'spring-projects'
strategy:
matrix:
# List of active maintenance branches.
branch: [ main, 6.4.x, 6.3.x ]
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Dispatch
env:
GH_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
run: gh workflow run update-scheduled-release-version.yml -r ${{ matrix.branch }}
@@ -1,35 +0,0 @@
name: Update Antora UI Spring
on:
schedule:
- cron: '0 10 * * *' # Once per day at 10am UTC
workflow_dispatch:
permissions:
pull-requests: write
issues: write
contents: write
jobs:
update-antora-ui-spring:
runs-on: ubuntu-latest
name: Update on Supported Branches
strategy:
matrix:
branch: [ '5.8.x', '6.2.x', '6.3.x', 'main' ]
steps:
- uses: spring-io/spring-doc-actions/update-antora-spring-ui@e28269199d1d27975cf7f65e16d6095c555b3cd0
name: Update
with:
docs-branch: ${{ matrix.branch }}
token: ${{ secrets.GITHUB_TOKEN }}
antora-file-path: 'docs/antora-playbook.yml'
update-antora-ui-spring-docs-build:
runs-on: ubuntu-latest
name: Update on docs-build
steps:
- uses: spring-io/spring-doc-actions/update-antora-spring-ui@e28269199d1d27975cf7f65e16d6095c555b3cd0
name: Update
with:
docs-branch: 'docs-build'
token: ${{ secrets.GITHUB_TOKEN }}
@@ -1,23 +0,0 @@
name: Update Scheduled Release Version
on:
workflow_dispatch: # Manual trigger only. Triggered by release-scheduler.yml on main.
permissions:
contents: read
jobs:
update-scheduled-release-version:
name: Update Scheduled Release Version
uses: spring-io/spring-security-release-tools/.github/workflows/update-scheduled-release-version.yml@v1
secrets: inherit
send-notification:
name: Send Notification
needs: [ update-scheduled-release-version ]
if: ${{ failure() || cancelled() }}
runs-on: ubuntu-latest
steps:
- name: Send Notification
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@v1
with:
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
+25 -14
View File
@@ -79,7 +79,7 @@ See https://github.com/spring-projects/spring-security/tree/main#building-from-s
The wiki pages https://github.com/spring-projects/spring-framework/wiki/Code-Style[Code Style] and https://github.com/spring-projects/spring-framework/wiki/IntelliJ-IDEA-Editor-Settings[IntelliJ IDEA Editor Settings] define the source file coding standards we use along with some IDEA editor settings we customize.
To format the code as well as check the style, run `./gradlew format && ./gradlew check`.
To format the code as well as check the style, run `./gradlew format check`.
[[submit-a-pull-request]]
=== Submit a Pull Request
@@ -89,30 +89,41 @@ We are excited for your pull request! :heart:
Please do your best to follow these steps.
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.
[[sign-cla]]
1. If you have not previously done so, please sign the https://cla.spring.io/sign/spring[Contributor License Agreement].
You will be reminded automatically when you submit the PR.
[[create-an-issue]]
1. 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.
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.
3. [[choose-a-branch]] Always check out the branch indicated in the milestone and submit pull requests against it (for example, for milestone `5.8.3` use the `5.8.x` branch).
[[choose-a-branch]]
1. Always check out the branch indicated in the milestone and submit pull requests against it (for example, for milestone `5.8.3` use the `5.8.x` branch).
If there is no milestone, choose `main`.
Once merged, the fix will be forwarded-ported to applicable branches including `main`.
4. [[create-a-local-branch]] Create a local branch
[[create-a-local-branch]]
1. Create a local branch
If this is for an issue, consider a branch name with the issue number, like `gh-22276`.
5. [[write-tests]] Add documentation and JUnit Tests for your changes.
6. [[update-copyright]] In all files you edited, if the copyright header is of the form 2002-20xx, update the final copyright year to the current year.
7. [[add-since]] If on `main`, add `@since` JavaDoc attributes to new public APIs that your PR adds
8. [[change-rnc]] If you are updating the XSD, please instead update the RNC file and then run `./gradlew :spring-security-config:rncToXsd`.
9. [[format-code]] For each commit, build the code using `./gradlew format && ./gradlew check`.
[[write-tests]]
1. Add JUnit Tests for your changes
[[update-copyright]]
1. In all files you edited, if the copyright header is of the form 2002-20xx, update the final copyright year to the current year.
[[add-since]]
1. If on `main`, add `@since` JavaDoc attributes to new public APIs that your PR adds
[[change-rnc]]
1. If you are updating the XSD, please instead update the RNC file and then run `./gradlew :spring-security-config:rncToXsd`.
[[format-code]]
1. For each commit, build the code using `./gradlew format check`.
This command ensures the code meets most of <<code-style,the style guide>>; a notable exception is import order.
10. [[commit-atomically]] Choose the granularity of your commits consciously and squash commits that represent
[[commit-atomically]]
1. Choose the granularity of your commits consciously and squash commits that represent
multiple edits or corrections of the same logical change.
See https://git-scm.com/book/en/Git-Tools-Rewriting-History[Rewriting History section of Pro Git] for an overview of streamlining the commit history.
11. [[format-commit-messages]] Format commit messages using 55 characters for the subject line, 72 characters per line
[[format-commit-messages]]
1. Format commit messages using 55 characters for the subject line, 72 characters per line
for the description, followed by the issue fixed, for example, `Closes gh-22276`.
See the https://git-scm.com/book/en/Distributed-Git-Contributing-to-a-Project#Commit-Guidelines[Commit Guidelines section of Pro Git] for best practices around commit messages, and use `git log` to see some examples.
Favor imperative tense over present tense (use "Fix" instead of "Fixes"); avoid past tense (use "Fix" instead of "Fixed").
Present tense is preferred.
+
[indent=0]
----
+5 -7
View File
@@ -1,8 +1,8 @@
image::https://badges.gitter.im/Join%20Chat.svg[Gitter,link=https://gitter.im/spring-projects/spring-security?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge]
image:https://github.com/spring-projects/spring-security/actions/workflows/continuous-integration-workflow.yml/badge.svg?branch=main["Build Status", link="https://github.com/spring-projects/spring-security/actions/workflows/continuous-integration-workflow.yml"]
image:https://github.com/spring-projects/spring-security/workflows/CI/badge.svg?branch=main["Build Status", link="https://github.com/spring-projects/spring-security/actions?query=workflow%3ACI"]
image:https://img.shields.io/badge/Revved%20up%20by-Develocity-06A0CE?logo=Gradle&labelColor=02303A["Revved up by Develocity", link="https://ge.spring.io/scans?search.rootProjectNames=spring-security"]
image:https://img.shields.io/badge/Revved%20up%20by-Gradle%20Enterprise-06A0CE?logo=Gradle&labelColor=02303A["Revved up by Gradle Enterprise", link="https://ge.spring.io/scans?search.rootProjectNames=spring-security"]
= Spring Security
@@ -15,16 +15,14 @@ For a detailed list of features and access to the latest release, please visit h
Please see our https://github.com/spring-projects/.github/blob/main/CODE_OF_CONDUCT.md[code of conduct]
== Downloading Artifacts
See https://docs.spring.io/spring-security/reference/getting-spring-security.html[Getting Spring Security] for how to obtain Spring Security.
See https://docs.spring.io/spring-security/site/docs/current/reference/html5/#getting[Getting Spring Security] for how to obtain Spring Security.
== Documentation
Be sure to read the https://docs.spring.io/spring-security/reference/[Spring Security Reference].
Be sure to read the https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/[Spring Security Reference].
Extensive JavaDoc for the Spring Security code is also available in the https://docs.spring.io/spring-security/site/docs/current/api/[Spring Security API Documentation].
You may also want to check out https://docs.spring.io/spring-security/reference/whats-new.html[what's new in the latest release].
== Quick Start
See https://docs.spring.io/spring-security/reference/servlet/getting-started.html[Hello Spring Security] to get started with a "Hello, World" application.
See https://docs.spring.io/spring-security/site/docs/current/reference/html5/#servlet-hello[Hello Spring Security] to get started with a "Hello, World" application.
== Building from Source
Spring Security uses a https://gradle.org[Gradle]-based build system.
-1
View File
@@ -20,5 +20,4 @@ dependencies {
testImplementation "org.springframework:spring-test"
testRuntimeOnly 'org.hsqldb:hsqldb'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
@@ -96,11 +96,7 @@ import org.springframework.util.StringUtils;
* All comparisons and prefixes are case sensitive.
*
* @author Ben Alex
* @deprecated please use {@link AclPermissionEvaluator} instead. Spring Method Security
* annotations may also prove useful, for example
* {@code @PreAuthorize("hasPermission(#id, ObjectsReturnType.class, read)")}
*/
@Deprecated
public class AclEntryVoter extends AbstractAclVoter {
private static final Log logger = LogFactory.getLog(AclEntryVoter.class);
@@ -20,7 +20,6 @@ import java.util.List;
import org.springframework.security.access.AfterInvocationProvider;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.acls.AclPermissionEvaluator;
import org.springframework.security.acls.domain.ObjectIdentityRetrievalStrategyImpl;
import org.springframework.security.acls.domain.SidRetrievalStrategyImpl;
import org.springframework.security.acls.model.Acl;
@@ -40,11 +39,7 @@ import org.springframework.util.ObjectUtils;
* services.
*
* @author Ben Alex
* @deprecated please use {@link AclPermissionEvaluator} instead. Spring Method Security
* annotations may also prove useful, for example
* {@code @PostAuthorize("hasPermission(filterObject, read)")}
*/
@Deprecated
public abstract class AbstractAclProvider implements AfterInvocationProvider {
protected final AclService aclService;
@@ -26,7 +26,6 @@ import org.springframework.core.log.LogMessage;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.AuthorizationServiceException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.acls.AclPermissionEvaluator;
import org.springframework.security.acls.model.AclService;
import org.springframework.security.acls.model.Permission;
import org.springframework.security.core.Authentication;
@@ -63,11 +62,7 @@ import org.springframework.security.core.Authentication;
*
* @author Ben Alex
* @author Paulo Neves
* @deprecated please use {@link AclPermissionEvaluator} instead. Spring Method Security
* annotations may also prove useful, for example
* {@code @PostFilter("hasPermission(filterObject, read)")}
*/
@Deprecated
public class AclEntryAfterInvocationCollectionFilteringProvider extends AbstractAclProvider {
protected static final Log logger = LogFactory.getLog(AclEntryAfterInvocationCollectionFilteringProvider.class);
@@ -27,7 +27,6 @@ import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.acls.AclPermissionEvaluator;
import org.springframework.security.acls.model.AclService;
import org.springframework.security.acls.model.Permission;
import org.springframework.security.core.Authentication;
@@ -60,12 +59,7 @@ import org.springframework.security.core.SpringSecurityMessageSource;
* granted and <code>null</code> will be returned.
* <p>
* All comparisons and prefixes are case sensitive.
*
* @deprecated please use {@link AclPermissionEvaluator} instead. Spring Method Security
* annotations may also prove useful, for example
* {@code @PostAuthorize("hasPermission(filterObject, read)")}
*/
@Deprecated
public class AclEntryAfterInvocationProvider extends AbstractAclProvider implements MessageSourceAware {
protected static final Log logger = LogFactory.getLog(AclEntryAfterInvocationProvider.class);
@@ -32,9 +32,7 @@ import org.springframework.core.log.LogMessage;
*
* @author Ben Alex
* @author Paulo Neves
* @deprecated please see {@code PostFilter}
*/
@Deprecated
class ArrayFilterer<T> implements Filterer<T> {
protected static final Log logger = LogFactory.getLog(ArrayFilterer.class);
@@ -31,9 +31,7 @@ import org.springframework.core.log.LogMessage;
*
* @author Ben Alex
* @author Paulo Neves
* @deprecated please see {@code PostFilter}
*/
@Deprecated
class CollectionFilterer<T> implements Filterer<T> {
protected static final Log logger = LogFactory.getLog(CollectionFilterer.class);
@@ -23,9 +23,7 @@ import java.util.Iterator;
*
* @author Ben Alex
* @author Paulo Neves
* @deprecated please use {@code PreFilter} and {@code @PostFilter} instead
*/
@Deprecated
interface Filterer<T> extends Iterable<T> {
/**
@@ -17,13 +17,10 @@
package org.springframework.security.acls.domain;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.acls.model.Acl;
import org.springframework.security.acls.model.Sid;
import org.springframework.security.acls.model.SidRetrievalStrategy;
@@ -62,8 +59,6 @@ public class AclAuthorizationStrategyImpl implements AclAuthorizationStrategy {
private SidRetrievalStrategy sidRetrievalStrategy = new SidRetrievalStrategyImpl();
private RoleHierarchy roleHierarchy = new NullRoleHierarchy();
/**
* Constructor. The only mandatory parameter relates to the system-wide
* {@link GrantedAuthority} instances that can be held to always permit ACL changes.
@@ -105,9 +100,7 @@ public class AclAuthorizationStrategyImpl implements AclAuthorizationStrategy {
}
// Iterate this principal's authorities to determine right
Collection<? extends GrantedAuthority> reachableGrantedAuthorities = this.roleHierarchy
.getReachableGrantedAuthorities(authentication.getAuthorities());
Set<String> authorities = AuthorityUtils.authorityListToSet(reachableGrantedAuthorities);
Set<String> authorities = AuthorityUtils.authorityListToSet(authentication.getAuthorities());
if (acl.getOwner() instanceof GrantedAuthoritySid
&& authorities.contains(((GrantedAuthoritySid) acl.getOwner()).getGrantedAuthority())) {
return;
@@ -169,14 +162,4 @@ public class AclAuthorizationStrategyImpl implements AclAuthorizationStrategy {
this.securityContextHolderStrategy = securityContextHolderStrategy;
}
/**
* Sets the {@link RoleHierarchy} to use. The default is to use a
* {@link NullRoleHierarchy}
* @since 6.4
*/
public void setRoleHierarchy(RoleHierarchy roleHierarchy) {
Assert.notNull(roleHierarchy, "roleHierarchy cannot be null");
this.roleHierarchy = roleHierarchy;
}
}
@@ -202,7 +202,7 @@ public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl {
public boolean isSidLoaded(List<Sid> sids) {
// If loadedSides is null, this indicates all SIDs were loaded
// Also return true if the caller didn't specify a SID to find
if ((this.loadedSids == null) || (sids == null) || sids.isEmpty()) {
if ((this.loadedSids == null) || (sids == null) || (sids.size() == 0)) {
return true;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2018 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.
@@ -140,7 +140,7 @@ public class DefaultPermissionFactory implements PermissionFactory {
@Override
public List<Permission> buildFromNames(List<String> names) {
if ((names == null) || names.isEmpty()) {
if ((names == null) || (names.size() == 0)) {
return Collections.emptyList();
}
List<Permission> permissions = new ArrayList<>(names.size());
@@ -100,8 +100,8 @@ public class JdbcAclService implements AclService {
@Override
public List<ObjectIdentity> findChildren(ObjectIdentity parentIdentity) {
Object[] args = { parentIdentity.getIdentifier().toString(), parentIdentity.getType() };
List<ObjectIdentity> objects = this.jdbcOperations.query(this.findChildrenSql,
(rs, rowNum) -> mapObjectIdentityRow(rs), args);
List<ObjectIdentity> objects = this.jdbcOperations.query(this.findChildrenSql, args,
(rs, rowNum) -> mapObjectIdentityRow(rs));
return (!objects.isEmpty()) ? objects : null;
}
@@ -190,7 +190,8 @@ public class JdbcMutableAclService extends JdbcAclService implements MutableAclS
* @return the primary key or null if not found
*/
protected Long createOrRetrieveClassPrimaryKey(String type, boolean allowCreate, Class idType) {
List<Long> classIds = this.jdbcOperations.queryForList(this.selectClassPrimaryKey, Long.class, type);
List<Long> classIds = this.jdbcOperations.queryForList(this.selectClassPrimaryKey, new Object[] { type },
Long.class);
if (!classIds.isEmpty()) {
return classIds.get(0);
@@ -241,8 +242,8 @@ public class JdbcMutableAclService extends JdbcAclService implements MutableAclS
* @return the primary key or null if not found
*/
protected Long createOrRetrieveSidPrimaryKey(String sidName, boolean sidIsPrincipal, boolean allowCreate) {
List<Long> sidIds = this.jdbcOperations.queryForList(this.selectSidPrimaryKey, Long.class, sidIsPrincipal,
sidName);
List<Long> sidIds = this.jdbcOperations.queryForList(this.selectSidPrimaryKey,
new Object[] { sidIsPrincipal, sidName }, Long.class);
if (!sidIds.isEmpty()) {
return sidIds.get(0);
}
@@ -25,7 +25,6 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.acls.model.Acl;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
@@ -35,7 +34,6 @@ import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.core.context.SecurityContextImpl;
import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.verify;
@@ -88,15 +86,6 @@ public class AclAuthorizationStrategyImplTests {
this.strategy.securityCheck(this.acl, AclAuthorizationStrategy.CHANGE_GENERAL);
}
@Test
public void securityCheckWhenRoleReachableByHierarchyThenAuthorized() {
given(this.acl.getOwner()).willReturn(new GrantedAuthoritySid("ROLE_AUTH_B"));
this.strategy = new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_SYSTEM_ADMIN"));
this.strategy.setRoleHierarchy(RoleHierarchyImpl.fromHierarchy("ROLE_AUTH > ROLE_AUTH_B"));
assertThatNoException()
.isThrownBy(() -> this.strategy.securityCheck(this.acl, AclAuthorizationStrategy.CHANGE_GENERAL));
}
@Test
public void securityCheckWhenCustomSecurityContextHolderStrategyThenUses() {
given(this.securityContextHolderStrategy.getContext()).willReturn(this.context);
@@ -109,7 +109,7 @@ public class JdbcAclServiceTests {
List<ObjectIdentity> result = new ArrayList<>();
result.add(new ObjectIdentityImpl(Object.class, "5577"));
Object[] args = { "1", "org.springframework.security.acls.jdbc.JdbcAclServiceTests$MockLongIdDomainObject" };
given(this.jdbcOperations.query(anyString(), any(RowMapper.class), eq(args))).willReturn(result);
given(this.jdbcOperations.query(anyString(), eq(args), any(RowMapper.class))).willReturn(result);
ObjectIdentity objectIdentity = new ObjectIdentityImpl(MockLongIdDomainObject.class, 1L);
List<ObjectIdentity> objectIdentities = this.aclService.findChildren(objectIdentity);
assertThat(objectIdentities).hasSize(1);
-2
View File
@@ -27,8 +27,6 @@ dependencies {
testImplementation "org.mockito:mockito-junit-jupiter"
testImplementation "org.springframework:spring-test"
testAspect sourceSets.main.output
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
compileAspectj.ajcOptions.outxmlfile = "META-INF/aop.xml"
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 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.
@@ -103,13 +103,6 @@ public class PostAuthorizeAspectTests {
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.prePostSecured::denyAllMethod);
}
@Test
public void nestedDenyAllPostAuthorizeDeniesAccess() {
SecurityContextHolder.getContext().setAuthentication(this.anne);
assertThatExceptionOfType(AccessDeniedException.class)
.isThrownBy(() -> this.secured.myObject().denyAllMethod());
}
interface SecuredInterface {
@PostAuthorize("hasRole('X')")
@@ -141,10 +134,6 @@ public class PostAuthorizeAspectTests {
privateMethod();
}
NestedObject myObject() {
return new NestedObject();
}
}
static class SecuredImplSubclass extends SecuredImpl {
@@ -168,13 +157,4 @@ public class PostAuthorizeAspectTests {
}
static class NestedObject {
@PostAuthorize("denyAll")
void denyAllMethod() {
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 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.
@@ -54,11 +54,6 @@ public class PostFilterAspectTests {
assertThat(this.prePostSecured.postFilterMethod(objects)).containsExactly("apple", "aubergine");
}
@Test
public void nestedDenyAllPostFilterDeniesAccess() {
assertThat(this.prePostSecured.myObject().denyAllMethod()).isEmpty();
}
static class PrePostSecured {
@PostFilter("filterObject.startsWith('a')")
@@ -66,19 +61,6 @@ public class PostFilterAspectTests {
return objects;
}
NestedObject myObject() {
return new NestedObject();
}
}
static class NestedObject {
@PostFilter("filterObject == null")
List<String> denyAllMethod() {
return new ArrayList<>(List.of("deny"));
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* Copyright 2002-2022 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.
@@ -47,8 +47,6 @@ public class PreAuthorizeAspectTests {
private PrePostSecured prePostSecured = new PrePostSecured();
private MultipleInterfaces multiple = new MultipleInterfaces();
@BeforeEach
public final void setUp() {
MockitoAnnotations.initMocks(this);
@@ -105,19 +103,6 @@ public class PreAuthorizeAspectTests {
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.prePostSecured::denyAllMethod);
}
@Test
public void nestedDenyAllPreAuthorizeDeniesAccess() {
SecurityContextHolder.getContext().setAuthentication(this.anne);
assertThatExceptionOfType(AccessDeniedException.class)
.isThrownBy(() -> this.secured.myObject().denyAllMethod());
}
@Test
public void multipleInterfacesPreAuthorizeAllows() {
// aspectj doesn't inherit annotations
this.multiple.securedMethod();
}
interface SecuredInterface {
@PreAuthorize("hasRole('X')")
@@ -144,15 +129,11 @@ public class PreAuthorizeAspectTests {
protected void protectedMethod() {
}
@PreAuthorize("hasRole('A')")
@PreAuthorize("hasRole('X')")
void publicCallsPrivate() {
privateMethod();
}
NestedObject myObject() {
return new NestedObject();
}
}
static class SecuredImplSubclass extends SecuredImpl {
@@ -176,28 +157,4 @@ public class PreAuthorizeAspectTests {
}
static class NestedObject {
@PreAuthorize("denyAll")
void denyAllMethod() {
}
}
interface AnotherSecuredInterface {
@PreAuthorize("hasRole('Y')")
void securedMethod();
}
static class MultipleInterfaces implements SecuredInterface, AnotherSecuredInterface {
@Override
public void securedMethod() {
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 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.
@@ -54,11 +54,6 @@ public class PreFilterAspectTests {
assertThat(this.prePostSecured.preFilterMethod(objects)).containsExactly("apple", "aubergine");
}
@Test
public void nestedDenyAllPreFilterDeniesAccess() {
assertThat(this.prePostSecured.myObject().denyAllMethod(new ArrayList<>(List.of("deny")))).isEmpty();
}
static class PrePostSecured {
@PreFilter("filterObject.startsWith('a')")
@@ -66,19 +61,6 @@ public class PreFilterAspectTests {
return objects;
}
NestedObject myObject() {
return new NestedObject();
}
}
static class NestedObject {
@PreFilter("filterObject == null")
List<String> denyAllMethod(List<String> list) {
return list;
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 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.
+8 -6
View File
@@ -20,6 +20,7 @@ plugins {
apply plugin: 'io.spring.nohttp'
apply plugin: 'locks'
apply plugin: 's101'
apply plugin: 'io.spring.convention.root'
apply plugin: 'org.jetbrains.kotlin.jvm'
apply plugin: 'org.springframework.security.versions.verify-dependencies-versions'
@@ -105,14 +106,10 @@ develocity {
}
nohttp {
source.exclude "buildSrc/build/**", "javascript/.gradle/**", "javascript/package-lock.json", "javascript/node_modules/**", "javascript/build/**", "javascript/dist/**"
source.exclude "buildSrc/build/**"
source.builtBy(project(':spring-security-config').tasks.withType(RncToXsd))
}
tasks.named('checkstyleNohttp') {
maxHeapSize = '1g'
}
tasks.register('cloneRepository', IncludeRepoTask) {
repository = project.getProperties().get("repositoryName")
ref = project.getProperties().get("ref")
@@ -120,11 +117,16 @@ tasks.register('cloneRepository', IncludeRepoTask) {
outputDirectory = project.hasProperty("cloneOutputDirectory") ? project.file("$cloneOutputDirectory") : defaultDirectory
}
s101 {
repository = 'https://structure101.com/binaries/latest'
configurationDirectory = project.file("etc/s101")
}
wrapperUpgrade {
gradle {
'spring-security' {
repo = 'spring-projects/spring-security'
baseBranch = '6.3.x' // runs only on 6.3.x and the update is merged forward to main
baseBranch = '6.1.x' // runs only on 6.1.x and the update is merged forward to main
}
}
}
-2
View File
@@ -95,8 +95,6 @@ dependencies {
testImplementation 'org.mockito:mockito-core'
testImplementation 'org.mockito:mockito-junit-jupiter'
testImplementation libs.com.squareup.okhttp3.mockwebserver
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
@@ -7,6 +7,8 @@ import org.gradle.api.Project;
import org.gradle.api.plugins.JavaPlatformPlugin;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.publish.PublishingExtension;
import org.gradle.api.publish.VariantVersionMappingStrategy;
import org.gradle.api.publish.VersionMappingStrategy;
import org.gradle.api.publish.maven.MavenPublication;
import org.gradle.api.publish.maven.plugins.MavenPublishPlugin;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 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.
@@ -23,6 +23,7 @@ import org.gradle.api.artifacts.Dependency;
import org.gradle.api.artifacts.MinimalExternalModuleDependency;
import org.gradle.api.artifacts.VersionCatalog;
import org.gradle.api.artifacts.VersionCatalogsExtension;
import org.gradle.api.file.RegularFile;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.plugins.JavaBasePlugin;
import org.gradle.api.provider.Property;
@@ -35,6 +36,7 @@ import org.gradle.api.tasks.TaskExecutionException;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.api.tasks.VerificationException;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Optional;
+1 -1
View File
@@ -50,7 +50,7 @@ public class S101Plugin implements Plugin<Project> {
private void configure(JavaExec exec) {
exec.setDescription("Runs Structure101 headless analysis, installing and configuring if necessary");
exec.dependsOn("assemble");
exec.dependsOn("check");
Project project = exec.getProject();
S101PluginExtension extension = project.getExtensions().getByType(S101PluginExtension.class);
exec
@@ -6,7 +6,5 @@ dependencies {
implementation 'org.springframework:spring-core'
testImplementation "org.junit.jupiter:junit-jupiter-api"
testImplementation "org.junit.jupiter:junit-jupiter-engine"
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
@@ -5,6 +5,4 @@ dependencies {
optional 'ch.qos.logback:logback-classic'
testImplementation "org.junit.jupiter:junit-jupiter-api"
testImplementation "org.junit.jupiter:junit-jupiter-engine"
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
-2
View File
@@ -22,6 +22,4 @@ dependencies {
testImplementation "org.mockito:mockito-junit-jupiter"
testImplementation "org.springframework:spring-test"
testImplementation 'org.skyscreamer:jsonassert'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
@@ -56,7 +56,6 @@ import org.springframework.util.Assert;
*
* @author Ben Alex
* @author Scott Battaglia
* @author Kim Youngwoong
*/
public class CasAuthenticationProvider implements AuthenticationProvider, InitializingBean, MessageSourceAware {
@@ -64,7 +63,7 @@ public class CasAuthenticationProvider implements AuthenticationProvider, Initia
private AuthenticationUserDetailsService<CasAssertionAuthenticationToken> authenticationUserDetailsService;
private UserDetailsChecker userDetailsChecker = new AccountStatusUserDetailsChecker();
private final UserDetailsChecker userDetailsChecker = new AccountStatusUserDetailsChecker();
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
@@ -188,17 +187,6 @@ public class CasAuthenticationProvider implements AuthenticationProvider, Initia
this.authenticationUserDetailsService = authenticationUserDetailsService;
}
/**
* Sets the UserDetailsChecker to be used for checking the status of retrieved user
* details. This allows customization of the UserDetailsChecker implementation.
* @param userDetailsChecker the UserDetailsChecker to be set
* @since 6.4
*/
public void setUserDetailsChecker(final UserDetailsChecker userDetailsChecker) {
Assert.notNull(userDetailsChecker, "userDetailsChecker cannot be null");
this.userDetailsChecker = userDetailsChecker;
}
public void setServiceProperties(final ServiceProperties serviceProperties) {
this.serviceProperties = serviceProperties;
}
@@ -115,8 +115,15 @@ public class CasAuthenticationToken extends AbstractAuthenticationToken implemen
if (!super.equals(obj)) {
return false;
}
if (obj instanceof CasAuthenticationToken test) {
return this.assertion.equals(test.getAssertion()) && this.getKeyHash() == test.getKeyHash();
if (obj instanceof CasAuthenticationToken) {
CasAuthenticationToken test = (CasAuthenticationToken) obj;
if (!this.assertion.equals(test.getAssertion())) {
return false;
}
if (this.getKeyHash() != test.getKeyHash()) {
return false;
}
return true;
}
return false;
}
@@ -41,7 +41,6 @@ import org.springframework.security.jackson2.SecurityJackson2Modules;
* @since 4.2
* @see org.springframework.security.jackson2.SecurityJackson2Modules
*/
@SuppressWarnings("serial")
public class CasJackson2Module extends SimpleModule {
public CasJackson2Module() {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 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.
@@ -28,7 +28,6 @@ import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.util.Assert;
/**
@@ -62,8 +61,6 @@ public class CasAuthenticationEntryPoint implements AuthenticationEntryPoint, In
*/
private boolean encodeServiceUrlWithSessionId = true;
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
@Override
public void afterPropertiesSet() {
Assert.hasLength(this.loginUrl, "loginUrl must be specified");
@@ -77,7 +74,8 @@ public class CasAuthenticationEntryPoint implements AuthenticationEntryPoint, In
String urlEncodedService = createServiceUrl(servletRequest, response);
String redirectUrl = createRedirectUrl(urlEncodedService);
preCommence(servletRequest, response);
this.redirectStrategy.sendRedirect(servletRequest, response, redirectUrl);
new DefaultRedirectStrategy().sendRedirect(servletRequest, response, redirectUrl);
// response.sendRedirect(redirectUrl);
}
/**
@@ -151,14 +149,4 @@ public class CasAuthenticationEntryPoint implements AuthenticationEntryPoint, In
return this.encodeServiceUrlWithSessionId;
}
/**
* Sets the {@link RedirectStrategy} to use
* @param redirectStrategy the {@link RedirectStrategy} to use
* @since 6.3
*/
public void setRedirectStrategy(RedirectStrategy redirectStrategy) {
Assert.notNull(redirectStrategy, "redirectStrategy cannot be null");
this.redirectStrategy = redirectStrategy;
}
}
@@ -22,7 +22,6 @@ import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.apereo.cas.client.proxy.ProxyGrantingTicketStorage;
import org.apereo.cas.client.util.WebUtils;
import org.apereo.cas.client.validation.TicketValidator;
@@ -41,21 +40,14 @@ import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.SavedRequest;
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Processes a CAS service ticket, obtains proxy granting tickets, and processes proxy
@@ -63,14 +55,14 @@ import org.springframework.util.StringUtils;
* <h2>Service Tickets</h2>
* <p>
* A service ticket consists of an opaque ticket string. It arrives at this filter by the
* user's browser successfully authenticating using CAS, and then receiving an HTTP
* user's browser successfully authenticating using CAS, and then receiving a HTTP
* redirect to a <code>service</code>. The opaque ticket string is presented in the
* <code>ticket</code> request parameter.
* <p>
* This filter monitors the <code>service</code> URL so that it can receive the service
* ticket and process it. By default, this filter processes the URL <tt>/login/cas</tt>.
* When processing this URL, the value of {@link ServiceProperties#getService()} is used
* as the <tt>service</tt> when validating the <code>ticket</code>. This means that it is
* This filter monitors the <code>service</code> URL so it can receive the service ticket
* and process it. By default this filter processes the URL <tt>/login/cas</tt>. When
* processing this URL, the value of {@link ServiceProperties#getService()} is used as the
* <tt>service</tt> when validating the <code>ticket</code>. This means that it is
* important that {@link ServiceProperties#getService()} specifies the same value as the
* <tt>filterProcessesUrl</tt>.
* <p>
@@ -81,22 +73,22 @@ import org.springframework.util.StringUtils;
* <h2>Obtaining Proxy Granting Tickets</h2>
* <p>
* If specified, the filter can also monitor the <code>proxyReceptorUrl</code>. The filter
* will respond to the requests matching this url so that the CAS Server can provide a PGT
* to the filter. Note that in addition to the <code>proxyReceptorUrl</code> a non-null
* will respond to requests matching this url so that the CAS Server can provide a PGT to
* the filter. Note that in addition to the <code>proxyReceptorUrl</code> a non-null
* <code>proxyGrantingTicketStorage</code> must be provided in order for the filter to
* respond to proxy receptor requests. By configuring a shared
* {@link ProxyGrantingTicketStorage} between the {@link TicketValidator} and the
* <code>CasAuthenticationFilter</code>, one can have the
* <code>CasAuthenticationFilter</code> handling the proxying requirements for CAS.
* CasAuthenticationFilter one can have the CasAuthenticationFilter handle the proxying
* requirements for CAS.
* <h2>Proxy Tickets</h2>
* <p>
* The filter can process tickets present on any url. This is useful when one wants to
* process proxy tickets. In order for proxy tickets to get processed,
* The filter can process tickets present on any url. This is useful when wanting to
* process proxy tickets. In order for proxy tickets to get processed
* {@link ServiceProperties#isAuthenticateAllArtifacts()} must return <code>true</code>.
* Additionally, if the request is already authenticated, authentication will <b>not</b>
* occur. Last, {@link AuthenticationDetailsSource#buildDetails(Object)} must return a
* {@link ServiceAuthenticationDetails}. This can be accomplished using the
* {@link ServiceAuthenticationDetailsSource}. In this case,
* {@link ServiceAuthenticationDetailsSource}. In this case
* {@link ServiceAuthenticationDetails#getServiceUrl()} will be used for the service url.
* <p>
* Processing the proxy ticket involves creating a
@@ -208,16 +200,10 @@ public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFil
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
.getContextHolderStrategy();
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
private RequestCache requestCache = new HttpSessionRequestCache();
private final AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
public CasAuthenticationFilter() {
super("/login/cas");
RequestMatcher processUri = PathPatternRequestMatcher.withDefaults().matcher("/login/cas");
setRequiresAuthenticationRequestMatcher(processUri);
setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler());
setSecurityContextRepository(this.securityContextRepository);
}
@@ -254,22 +240,7 @@ public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFil
return null;
}
String serviceTicket = obtainArtifact(request);
if (!StringUtils.hasText(serviceTicket)) {
HttpSession session = request.getSession(false);
if (session != null && session
.getAttribute(CasGatewayAuthenticationRedirectFilter.CAS_GATEWAY_AUTHENTICATION_ATTR) != null) {
this.logger.debug("Failed authentication response from CAS gateway request");
session.removeAttribute(CasGatewayAuthenticationRedirectFilter.CAS_GATEWAY_AUTHENTICATION_ATTR);
SavedRequest savedRequest = this.requestCache.getRequest(request, response);
if (savedRequest != null) {
String redirectUrl = savedRequest.getRedirectUrl();
this.logger.debug(LogMessage.format("Redirecting to: %s", redirectUrl));
this.requestCache.removeRequest(request, response);
this.redirectStrategy.sendRedirect(request, response, redirectUrl);
return null;
}
}
if (serviceTicket == null) {
this.logger.debug("Failed to obtain an artifact (cas ticket)");
serviceTicket = "";
}
@@ -322,18 +293,6 @@ public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFil
super.setAuthenticationFailureHandler(new CasAuthenticationFailureHandler(failureHandler));
}
/**
* Use this {@code RequestMatcher} to match proxy receptor requests. Without setting
* this matcher, {@link CasAuthenticationFilter} will not capture any proxy receptor
* requets.
* @param proxyReceptorMatcher the {@link RequestMatcher} to use
* @since 6.5
*/
public final void setProxyReceptorMatcher(RequestMatcher proxyReceptorMatcher) {
Assert.notNull(proxyReceptorMatcher, "proxyReceptorMatcher cannot be null");
this.proxyReceptorMatcher = proxyReceptorMatcher;
}
public final void setProxyReceptorUrl(final String proxyReceptorUrl) {
this.proxyReceptorMatcher = new AntPathRequestMatcher("/**" + proxyReceptorUrl);
}
@@ -359,28 +318,6 @@ public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFil
this.securityContextHolderStrategy = securityContextHolderStrategy;
}
/**
* Set the {@link RedirectStrategy} used to redirect to the saved request if there is
* one saved. Defaults to {@link DefaultRedirectStrategy}.
* @param redirectStrategy the redirect strategy to use
* @since 6.3
*/
public final void setRedirectStrategy(RedirectStrategy redirectStrategy) {
Assert.notNull(redirectStrategy, "redirectStrategy cannot be null");
this.redirectStrategy = redirectStrategy;
}
/**
* The {@link RequestCache} used to retrieve the saved request in failed gateway
* authentication scenarios.
* @param requestCache the request cache to use
* @since 6.3
*/
public final void setRequestCache(RequestCache requestCache) {
Assert.notNull(requestCache, "requestCache cannot be null");
this.requestCache = requestCache;
}
/**
* Indicates if the request is elgible to process a service ticket. This method exists
* for readability.
@@ -1,126 +0,0 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.cas.web;
import java.io.IOException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.apereo.cas.client.util.CommonUtils;
import org.apereo.cas.client.util.WebUtils;
import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.web.filter.GenericFilterBean;
/**
* Redirects the request to the CAS server appending {@code gateway=true} to the URL. Upon
* redirection, the {@link ServiceProperties#isSendRenew()} is ignored and considered as
* {@code false} to align with the specification says that the {@code sendRenew} parameter
* is not compatible with the {@code gateway} parameter. See the <a href=
* "https://apereo.github.io/cas/6.6.x/protocol/CAS-Protocol-V2-Specification.html#:~:text=This%20parameter%20is%20not%20compatible%20with%20the%20%E2%80%9Crenew%E2%80%9D%20parameter.%20Behavior%20is%20undefined%20if%20both%20are%20set.">CAS
* Protocol Specification</a> for more details. To allow other filters to know if the
* request is a gateway request, this filter creates a session and add an attribute with
* name {@link #CAS_GATEWAY_AUTHENTICATION_ATTR} which can be checked by other filters if
* needed. It is recommended that this filter is placed after
* {@link CasAuthenticationFilter} if it is defined.
*
* @author Michael Remond
* @author Jerome LELEU
* @author Marcus da Coregio
* @since 6.3
*/
public final class CasGatewayAuthenticationRedirectFilter extends GenericFilterBean {
public static final String CAS_GATEWAY_AUTHENTICATION_ATTR = "CAS_GATEWAY_AUTHENTICATION";
private final String casLoginUrl;
private final ServiceProperties serviceProperties;
private RequestMatcher requestMatcher;
private RequestCache requestCache = new HttpSessionRequestCache();
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
/**
* Constructs a new instance of this class
* @param serviceProperties the {@link ServiceProperties}
*/
public CasGatewayAuthenticationRedirectFilter(String casLoginUrl, ServiceProperties serviceProperties) {
Assert.hasText(casLoginUrl, "casLoginUrl cannot be null or empty");
Assert.notNull(serviceProperties, "serviceProperties cannot be null");
this.casLoginUrl = casLoginUrl;
this.serviceProperties = serviceProperties;
this.requestMatcher = new CasGatewayResolverRequestMatcher(this.serviceProperties);
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (!this.requestMatcher.matches(request)) {
chain.doFilter(request, response);
return;
}
this.requestCache.saveRequest(request, response);
HttpSession session = request.getSession(true);
session.setAttribute(CAS_GATEWAY_AUTHENTICATION_ATTR, true);
String urlEncodedService = WebUtils.constructServiceUrl(request, response, this.serviceProperties.getService(),
null, this.serviceProperties.getServiceParameter(), this.serviceProperties.getArtifactParameter(),
true);
String redirectUrl = CommonUtils.constructRedirectUrl(this.casLoginUrl,
this.serviceProperties.getServiceParameter(), urlEncodedService, false, true);
this.redirectStrategy.sendRedirect(request, response, redirectUrl);
}
/**
* Sets the {@link RequestMatcher} used to trigger this filter. Defaults to
* {@link CasGatewayResolverRequestMatcher}.
* @param requestMatcher the {@link RequestMatcher} to use
*/
public void setRequestMatcher(RequestMatcher requestMatcher) {
Assert.notNull(requestMatcher, "requestMatcher cannot be null");
this.requestMatcher = requestMatcher;
}
/**
* Sets the {@link RequestCache} used to store the current request to be replayed
* after redirect from the CAS server. Defaults to {@link HttpSessionRequestCache}.
* @param requestCache the {@link RequestCache} to use
*/
public void setRequestCache(RequestCache requestCache) {
Assert.notNull(requestCache, "requestCache cannot be null");
this.requestCache = requestCache;
}
}
@@ -1,67 +0,0 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.cas.web;
import jakarta.servlet.http.HttpServletRequest;
import org.apereo.cas.client.authentication.DefaultGatewayResolverImpl;
import org.apereo.cas.client.authentication.GatewayResolver;
import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
/**
* A {@link RequestMatcher} implementation that delegates the check to an instance of
* {@link GatewayResolver}. The request is marked as "gatewayed" using the configured
* {@link GatewayResolver} to avoid infinite loop.
*
* @author Michael Remond
* @author Marcus da Coregio
* @since 6.3
*/
public final class CasGatewayResolverRequestMatcher implements RequestMatcher {
private final ServiceProperties serviceProperties;
private GatewayResolver gatewayStorage = new DefaultGatewayResolverImpl();
public CasGatewayResolverRequestMatcher(ServiceProperties serviceProperties) {
Assert.notNull(serviceProperties, "serviceProperties cannot be null");
this.serviceProperties = serviceProperties;
}
@Override
public boolean matches(HttpServletRequest request) {
boolean wasGatewayed = this.gatewayStorage.hasGatewayedAlready(request, this.serviceProperties.getService());
if (!wasGatewayed) {
this.gatewayStorage.storeGatewayInformation(request, this.serviceProperties.getService());
return true;
}
return false;
}
/**
* Sets the {@link GatewayResolver} to check if the request was already gatewayed.
* Defaults to {@link DefaultGatewayResolverImpl}
* @param gatewayStorage the {@link GatewayResolver} to use. Cannot be null.
*/
public void setGatewayStorage(GatewayResolver gatewayStorage) {
Assert.notNull(gatewayStorage, "gatewayStorage cannot be null");
this.gatewayStorage = gatewayStorage;
}
}
@@ -18,7 +18,6 @@ package org.springframework.security.cas.authentication;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import org.apereo.cas.client.validation.Assertion;
import org.apereo.cas.client.validation.AssertionImpl;
@@ -32,13 +31,11 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio
import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.cas.web.authentication.ServiceAuthenticationDetails;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsChecker;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.WebAuthenticationDetails;
@@ -58,7 +55,6 @@ import static org.mockito.Mockito.verify;
*
* @author Ben Alex
* @author Scott Battaglia
* @author Kim Youngwoong
*/
@SuppressWarnings("unchecked")
public class CasAuthenticationProviderTests {
@@ -324,29 +320,6 @@ public class CasAuthenticationProviderTests {
assertThat(cap.supports(CasAuthenticationToken.class)).isTrue();
}
@Test
public void testSetUserDetailsChecker() throws AuthenticationException {
CasAuthenticationProvider cap = new CasAuthenticationProvider();
cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
cap.setKey("qwerty");
cap.setTicketValidator(new MockTicketValidator(true));
cap.setServiceProperties(makeServiceProperties());
cap.afterPropertiesSet();
CasServiceTicketAuthenticationToken token = CasServiceTicketAuthenticationToken.stateful("ST-123");
AtomicInteger checkCount = new AtomicInteger(0);
UserDetailsChecker userDetailsChecker = new UserDetailsChecker() {
@Override
public void check(UserDetails user) {
checkCount.incrementAndGet();
}
};
cap.setUserDetailsChecker(userDetailsChecker);
cap.authenticate(token);
assertThat(checkCount.get()).isEqualTo(1);
}
private class MockAuthoritiesPopulator implements AuthenticationUserDetailsService {
@Override
@@ -16,22 +16,16 @@
package org.springframework.security.cas.web;
import java.io.IOException;
import java.net.URLEncoder;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.web.RedirectStrategy;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/**
* Tests {@link CasAuthenticationEntryPoint}.
@@ -101,25 +95,4 @@ public class CasAuthenticationEntryPointTests {
.isEqualTo(response.getRedirectedUrl());
}
@Test
void setRedirectStrategyThenUses() throws IOException {
CasAuthenticationEntryPoint ep = new CasAuthenticationEntryPoint();
ServiceProperties sp = new ServiceProperties();
sp.setService("https://mycompany.com/login/cas");
ep.setServiceProperties(sp);
ep.setLoginUrl("https://cas/login");
RedirectStrategy redirectStrategy = mock();
ep.setRedirectStrategy(redirectStrategy);
MockHttpServletRequest req = new MockHttpServletRequest();
MockHttpServletResponse res = new MockHttpServletResponse();
ep.commence(req, res, new BadCredentialsException("bad credentials"));
verify(redirectStrategy).sendRedirect(eq(req), eq(res),
eq("https://cas/login?service=https%3A%2F%2Fmycompany.com%2Flogin%2Fcas"));
}
}
@@ -20,7 +20,6 @@ import java.io.IOException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpSession;
import org.apereo.cas.client.proxy.ProxyGrantingTicketStorage;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
@@ -42,8 +41,6 @@ import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
@@ -79,7 +76,7 @@ public class CasAuthenticationFilterTests {
@Test
public void testNormalOperation() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/login/cas");
MockHttpServletRequest request = new MockHttpServletRequest();
request.setServletPath("/login/cas");
request.addParameter("ticket", "ST-0-ER94xMJmn6pha35CQRoZ");
CasAuthenticationFilter filter = new CasAuthenticationFilter();
@@ -104,7 +101,7 @@ public class CasAuthenticationFilterTests {
String url = "/login/cas";
CasAuthenticationFilter filter = new CasAuthenticationFilter();
filter.setFilterProcessesUrl(url);
MockHttpServletRequest request = new MockHttpServletRequest("POST", url);
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
request.setServletPath(url);
assertThat(filter.requiresAuthentication(request, response)).isTrue();
@@ -133,11 +130,10 @@ public class CasAuthenticationFilterTests {
CasAuthenticationFilter filter = new CasAuthenticationFilter();
filter.setFilterProcessesUrl(url);
filter.setServiceProperties(properties);
MockHttpServletRequest request = new MockHttpServletRequest("POST", url);
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
request.setServletPath(url);
assertThat(filter.requiresAuthentication(request, response)).isTrue();
request = new MockHttpServletRequest("POST", "/other");
request.setServletPath("/other");
assertThat(filter.requiresAuthentication(request, response)).isFalse();
request.setParameter(properties.getArtifactParameter(), "value");
@@ -172,7 +168,7 @@ public class CasAuthenticationFilterTests {
given(manager.authenticate(any(Authentication.class))).willReturn(authentication);
ServiceProperties serviceProperties = new ServiceProperties();
serviceProperties.setAuthenticateAllArtifacts(true);
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/authenticate");
MockHttpServletRequest request = new MockHttpServletRequest();
request.setParameter("ticket", "ST-1-123");
request.setServletPath("/authenticate");
MockHttpServletResponse response = new MockHttpServletResponse();
@@ -228,25 +224,6 @@ public class CasAuthenticationFilterTests {
verify(securityContextRepository).saveContext(any(SecurityContext.class), eq(request), eq(response));
}
@Test
public void attemptAuthenticationWhenNoServiceTicketAndIsGatewayRequestThenRedirectToSavedRequestAndClearAttribute()
throws Exception {
CasAuthenticationFilter filter = new CasAuthenticationFilter();
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
HttpSession session = request.getSession(true);
session.setAttribute(CasGatewayAuthenticationRedirectFilter.CAS_GATEWAY_AUTHENTICATION_ATTR, true);
new HttpSessionRequestCache().saveRequest(request, response);
Authentication authn = filter.attemptAuthentication(request, response);
assertThat(authn).isNull();
assertThat(response.getStatus()).isEqualTo(302);
assertThat(response.getRedirectedUrl()).isEqualTo("http://localhost?continue");
assertThat(session.getAttribute(CasGatewayAuthenticationRedirectFilter.CAS_GATEWAY_AUTHENTICATION_ATTR))
.isNull();
}
@Test
void successfulAuthenticationWhenSecurityContextRepositorySetThenUses() throws ServletException, IOException {
SecurityContextRepository securityContextRepository = mock(SecurityContextRepository.class);
@@ -268,20 +245,4 @@ public class CasAuthenticationFilterTests {
verify(securityContextRepository).setContext(any(SecurityContext.class));
}
@Test
public void requiresAuthenticationWhenProxyRequestMatcherThenMatches() {
CasAuthenticationFilter filter = new CasAuthenticationFilter();
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/pgtCallback");
MockHttpServletResponse response = new MockHttpServletResponse();
request.setServletPath("/pgtCallback");
assertThat(filter.requiresAuthentication(request, response)).isFalse();
filter.setProxyReceptorMatcher(PathPatternRequestMatcher.withDefaults().matcher(request.getServletPath()));
assertThat(filter.requiresAuthentication(request, response)).isFalse();
filter.setProxyGrantingTicketStorage(mock(ProxyGrantingTicketStorage.class));
assertThat(filter.requiresAuthentication(request, response)).isTrue();
request.setRequestURI("/other");
request.setServletPath("/other");
assertThat(filter.requiresAuthentication(request, response)).isFalse();
}
}
@@ -1,95 +0,0 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.cas.web;
import java.io.IOException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpStatus;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.web.savedrequest.RequestCache;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
/**
* Tests for {@link CasGatewayAuthenticationRedirectFilter}.
*
* @author Jerome LELEU
* @author Marcus da Coregio
*/
public class CasGatewayAuthenticationRedirectFilterTests {
private static final String CAS_LOGIN_URL = "http://mycasserver/login";
CasGatewayAuthenticationRedirectFilter filter = new CasGatewayAuthenticationRedirectFilter(CAS_LOGIN_URL,
serviceProperties());
@Test
void doFilterWhenMatchesThenSavesRequestAndSavesAttributeAndSendRedirect() throws IOException, ServletException {
RequestCache requestCache = mock();
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
this.filter.setRequestMatcher((req) -> true);
this.filter.setRequestCache(requestCache);
this.filter.doFilter(request, response, new MockFilterChain());
assertThat(response.getStatus()).isEqualTo(HttpStatus.FOUND.value());
assertThat(response.getHeader("Location"))
.isEqualTo("http://mycasserver/login?service=http%3A%2F%2Flocalhost%2Flogin%2Fcas&gateway=true");
verify(requestCache).saveRequest(request, response);
}
@Test
void doFilterWhenNotMatchThenContinueFilter() throws ServletException, IOException {
this.filter.setRequestMatcher((req) -> false);
FilterChain chain = mock();
MockHttpServletResponse response = mock();
this.filter.doFilter(new MockHttpServletRequest(), response, chain);
verify(chain).doFilter(any(), any());
verifyNoInteractions(response);
}
@Test
void doFilterWhenSendRenewTrueThenIgnores() throws ServletException, IOException {
ServiceProperties serviceProperties = serviceProperties();
serviceProperties.setSendRenew(true);
this.filter = new CasGatewayAuthenticationRedirectFilter(CAS_LOGIN_URL, serviceProperties);
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
this.filter.setRequestMatcher((req) -> true);
this.filter.doFilter(request, response, new MockFilterChain());
assertThat(response.getStatus()).isEqualTo(HttpStatus.FOUND.value());
assertThat(response.getHeader("Location"))
.isEqualTo("http://mycasserver/login?service=http%3A%2F%2Flocalhost%2Flogin%2Fcas&gateway=true");
}
private static ServiceProperties serviceProperties() {
ServiceProperties serviceProperties = new ServiceProperties();
serviceProperties.setService("http://localhost/login/cas");
return serviceProperties;
}
}
@@ -1,74 +0,0 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.cas.web;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.security.cas.ServiceProperties;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests {@link CasGatewayResolverRequestMatcher}.
*
* @author Marcus da Coregio
*/
class CasGatewayResolverRequestMatcherTests {
CasGatewayResolverRequestMatcher matcher = new CasGatewayResolverRequestMatcher(new ServiceProperties());
@Test
void constructorWhenServicePropertiesNullThenException() {
assertThatIllegalArgumentException().isThrownBy(() -> new CasGatewayResolverRequestMatcher(null))
.withMessage("serviceProperties cannot be null");
}
@Test
void matchesWhenAlreadyGatewayedThenReturnsFalse() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.getSession().setAttribute("_const_cas_gateway_", "yes");
boolean matches = this.matcher.matches(request);
assertThat(matches).isFalse();
}
@Test
void matchesWhenNotGatewayedThenReturnsTrue() {
MockHttpServletRequest request = new MockHttpServletRequest();
boolean matches = this.matcher.matches(request);
assertThat(matches).isTrue();
}
@Test
void matchesWhenNoSessionThenReturnsTrue() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setSession(null);
boolean matches = this.matcher.matches(request);
assertThat(matches).isTrue();
}
@Test
void matchesWhenNotGatewayedAndCheckedAgainThenSavesAsGatewayedAndReturnsFalse() {
MockHttpServletRequest request = new MockHttpServletRequest();
boolean matches = this.matcher.matches(request);
boolean secondMatch = this.matcher.matches(request);
assertThat(matches).isTrue();
assertThat(secondMatch).isFalse();
}
}
+3 -38
View File
@@ -6,12 +6,6 @@ apply plugin: 'io.spring.convention.spring-module'
apply plugin: 'trang'
apply plugin: 'kotlin'
configurations {
opensaml5 {
extendsFrom(optional, tests)
}
}
dependencies {
management platform(project(":spring-security-dependencies"))
// NB: Don't add other compile time dependencies to the config module as this breaks tooling
@@ -21,11 +15,9 @@ dependencies {
api 'org.springframework:spring-context'
api 'org.springframework:spring-core'
optional project(':spring-security-data')
optional project(':spring-security-ldap')
optional project(':spring-security-messaging')
optional project(path: ':spring-security-saml2-service-provider')
opensaml5 project(path: ':spring-security-saml2-service-provider', configuration: 'opensamlFiveMain')
optional project(':spring-security-saml2-service-provider')
optional project(':spring-security-oauth2-client')
optional project(':spring-security-oauth2-jose')
optional project(':spring-security-oauth2-resource-server')
@@ -43,7 +35,6 @@ dependencies {
optional 'org.jetbrains.kotlin:kotlin-reflect'
optional 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
optional 'jakarta.annotation:jakarta.annotation-api'
optional libs.webauthn4j.core
provided 'jakarta.servlet:jakarta.servlet-api'
@@ -74,7 +65,7 @@ dependencies {
testImplementation 'jakarta.websocket:jakarta.websocket-api'
testImplementation 'jakarta.websocket:jakarta.websocket-client-api'
testImplementation 'ldapsdk:ldapsdk:4.1'
testImplementation('org.htmlunit:htmlunit') {
testImplementation('net.sourceforge.htmlunit:htmlunit') {
exclude group: 'commons-logging', module: 'commons-logging'
exclude group: 'xml-apis', module: 'xml-apis'
}
@@ -89,7 +80,7 @@ dependencies {
testImplementation "org.hibernate.orm:hibernate-core"
testImplementation 'org.hsqldb:hsqldb'
testImplementation 'org.mockito:mockito-core'
testImplementation('org.seleniumhq.selenium:htmlunit3-driver') {
testImplementation('org.seleniumhq.selenium:htmlunit-driver') {
exclude group: 'commons-logging', module: 'commons-logging'
exclude group: 'xml-apis', module: 'xml-apis'
}
@@ -122,11 +113,8 @@ dependencies {
exclude group: "org.slf4j", module: "jcl-over-slf4j"
}
testImplementation libs.org.instancio.instancio.junit
testImplementation libs.org.eclipse.jetty.jetty.server
testImplementation libs.org.eclipse.jetty.jetty.servlet
testRuntimeOnly 'org.hsqldb:hsqldb'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
def rncToXsd = tasks.named('rncToXsd', RncToXsd)
@@ -172,26 +160,3 @@ configure(project.tasks.withType(Test)) {
systemProperties['springSecurityVersion'] = version
}
}
test {
onOutput { descriptor, event ->
if (!project.hasProperty('serialization')) {
return
}
if (descriptor.name=='listClassesMissingSerialVersion()') {
logger.lifecycle(event.message)
}
}
}
tasks.register("opensaml5Test", Test) {
filter {
includeTestsMatching "org.springframework.security.config.annotation.web.configurers.saml2.*"
}
useJUnitPlatform()
classpath = sourceSets.main.output + sourceSets.test.output + configurations.opensaml5
}
tasks.named("check") {
dependsOn opensaml5Test
}
@@ -1,357 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.configurers;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import org.assertj.core.api.AbstractAssert;
import org.assertj.core.api.AbstractStringAssert;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletContextHandler;
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.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriverService;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.chromium.HasCdp;
import org.openqa.selenium.devtools.HasDevTools;
import org.openqa.selenium.remote.Augmenter;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.support.ui.FluentWait;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.mock.env.MockPropertySource;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
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.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.filter.DelegatingFilterProxy;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Webdriver-based tests for the WebAuthnConfigurer. This uses a full browser because
* these features require Javascript and browser APIs to be available.
*
* @author Daniel Garnier-Moiroux
*/
@org.junit.jupiter.api.Disabled
class WebAuthnWebDriverTests {
private String baseUrl;
private static ChromeDriverService driverService;
private Server server;
private RemoteWebDriver driver;
private static final String USERNAME = "user";
private static final String PASSWORD = "password";
@BeforeAll
static void startChromeDriverService() throws Exception {
driverService = new ChromeDriverService.Builder().usingAnyFreePort().build();
driverService.start();
}
@AfterAll
static void stopChromeDriverService() {
driverService.stop();
}
@BeforeEach
void startServer() throws Exception {
// Create the server on port 8080
this.server = new Server(0);
// Set up the ServletContextHandler
ServletContextHandler contextHandler = new ServletContextHandler(ServletContextHandler.SESSIONS);
contextHandler.setContextPath("/");
this.server.setHandler(contextHandler);
this.server.start();
int serverPort = ((ServerConnector) this.server.getConnectors()[0]).getLocalPort();
this.baseUrl = "http://localhost:" + serverPort;
// Set up Spring application context
AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
applicationContext.register(WebAuthnConfiguration.class);
applicationContext.setServletContext(contextHandler.getServletContext());
// Add the server port
MockPropertySource propertySource = new MockPropertySource().withProperty("server.port", serverPort);
applicationContext.getEnvironment().getPropertySources().addFirst(propertySource);
// Register the filter chain
DelegatingFilterProxy filterProxy = new DelegatingFilterProxy("securityFilterChain", applicationContext);
FilterHolder filterHolder = new FilterHolder(filterProxy);
contextHandler.addFilter(filterHolder, "/*", null);
}
@AfterEach
void stopServer() throws Exception {
this.server.stop();
}
@BeforeEach
void setupDriver() {
ChromeOptions options = new ChromeOptions();
options.addArguments("--headless=new");
RemoteWebDriver baseDriver = new RemoteWebDriver(driverService.getUrl(), options);
// Enable dev tools
this.driver = (RemoteWebDriver) new Augmenter().augment(baseDriver);
this.driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(1));
}
@AfterEach
void cleanupDriver() {
this.driver.quit();
}
@Test
void loginWhenNoValidAuthenticatorCredentialsThenRejects() {
createVirtualAuthenticator(true);
this.driver.get(this.baseUrl);
this.driver.findElement(signinWithPasskeyButton()).click();
await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/login?error"));
}
@Test
void registerWhenNoLabelThenRejects() {
login();
this.driver.get(this.baseUrl + "/webauthn/register");
this.driver.findElement(registerPasskeyButton()).click();
assertHasAlertStartingWith("error", "Error: Passkey Label is required");
}
@Test
void registerWhenAuthenticatorNoUserVerificationThenRejects() {
createVirtualAuthenticator(false);
login();
this.driver.get(this.baseUrl + "/webauthn/register");
this.driver.findElement(passkeyLabel()).sendKeys("Virtual authenticator");
this.driver.findElement(registerPasskeyButton()).click();
await(() -> assertHasAlertStartingWith("error",
"Registration failed. Call to navigator.credentials.create failed:"));
}
/**
* Test in 4 steps to verify the end-to-end flow of registering an authenticator and
* using it to register.
* <ul>
* <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>
* </ul>
*/
@Test
void loginWhenAuthenticatorRegisteredThenSuccess() {
// Setup
createVirtualAuthenticator(true);
// Step 1: log in with username / password
login();
// Step 2: register a credential from the virtual authenticator
this.driver.get(this.baseUrl + "/webauthn/register");
this.driver.findElement(passkeyLabel()).sendKeys("Virtual authenticator");
this.driver.findElement(registerPasskeyButton()).click();
// Ensure the page location has changed before performing further assertions.
// This is required because the location change is asynchronously performed in
// javascript, and performing assertions based on this.driver.findElement(...)
// may result in a StaleElementReferenceException.
await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/webauthn/register?success"));
await(() -> assertHasAlertStartingWith("success", "Success!"));
List<WebElement> passkeyRows = this.driver.findElements(passkeyTableRows());
assertThat(passkeyRows).hasSize(1)
.first()
.extracting((row) -> row.findElement(firstCell()))
.extracting(WebElement::getText)
.isEqualTo("Virtual authenticator");
// Step 3: log out
logout();
// Step 4: log in with the virtual authenticator
this.driver.get(this.baseUrl + "/webauthn/register");
this.driver.findElement(signinWithPasskeyButton()).click();
await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/webauthn/register?continue"));
}
/**
* Add a virtual authenticator.
* <p>
* Note that Selenium docs for {@link HasCdp} strongly encourage to use
* {@link HasDevTools} instead. However, devtools require more dependencies and
* boilerplate, notably to sync the Devtools-CDP version with the current browser
* version, whereas CDP runs out of the box.
* <p>
* @param userIsVerified whether the authenticator simulates user verification.
* Setting it to false will make the ceremonies fail.
* @see <a href=
* "https://chromedevtools.github.io/devtools-protocol/tot/WebAuthn/">https://chromedevtools.github.io/devtools-protocol/tot/WebAuthn/</a>
*/
private void createVirtualAuthenticator(boolean userIsVerified) {
HasCdp cdpDriver = (HasCdp) this.driver;
cdpDriver.executeCdpCommand("WebAuthn.enable", Map.of("enableUI", false));
// this.driver.addVirtualAuthenticator(createVirtualAuthenticatorOptions());
//@formatter:off
cdpDriver.executeCdpCommand("WebAuthn.addVirtualAuthenticator",
Map.of(
"options",
Map.of(
"protocol", "ctap2",
"transport", "usb",
"hasUserVerification", true,
"hasResidentKey", true,
"isUserVerified", userIsVerified,
"automaticPresenceSimulation", true
)
));
//@formatter:on
}
private void login() {
this.driver.get(this.baseUrl);
this.driver.findElement(usernameField()).sendKeys(USERNAME);
this.driver.findElement(passwordField()).sendKeys(PASSWORD);
this.driver.findElement(signinWithUsernamePasswordButton()).click();
}
private void logout() {
this.driver.get(this.baseUrl + "/logout");
this.driver.findElement(logoutButton()).click();
await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/login?logout"));
}
private AbstractStringAssert<?> assertHasAlertStartingWith(String alertType, String alertMessage) {
WebElement alert = this.driver.findElement(new By.ById(alertType));
assertThat(alert.isDisplayed())
.withFailMessage(
() -> alertType + " alert was not displayed. Full page source:\n\n" + this.driver.getPageSource())
.isTrue();
return assertThat(alert.getText()).startsWith(alertMessage);
}
/**
* Await until the assertion passes. If the assertion fails, it will display the
* assertion error in stdout. WebDriver-related exceptions are ignored, so that
* {@code assertion}s can interact with the page and be retried on error, e.g.
* {@code assertThat(this.driver.findElement(By.Id("some-id")).isNotNull()}.
*/
private void await(Supplier<AbstractAssert<?, ?>> assertion) {
new FluentWait<>(this.driver).withTimeout(Duration.ofSeconds(2))
.pollingEvery(Duration.ofMillis(100))
.ignoring(AssertionError.class, WebDriverException.class)
.until((d) -> {
assertion.get();
return true;
});
}
private static By.ById passkeyLabel() {
return new By.ById("label");
}
private static By.ById registerPasskeyButton() {
return new By.ById("register");
}
private static By.ByCssSelector passkeyTableRows() {
return new By.ByCssSelector("table > tbody > tr");
}
private static By.ByCssSelector firstCell() {
return new By.ByCssSelector("td:first-child");
}
private static By.ById passwordField() {
return new By.ById(PASSWORD);
}
private static By.ById usernameField() {
return new By.ById("username");
}
private static By.ByCssSelector signinWithUsernamePasswordButton() {
return new By.ByCssSelector("form > button[type=\"submit\"]");
}
private static By.ById signinWithPasskeyButton() {
return new By.ById("passkey-signin");
}
private static By.ByCssSelector logoutButton() {
return new By.ByCssSelector("button");
}
/**
* The configuration for WebAuthN tests. It accesses the Server's current port, so we
* can configurer WebAuthnConfigurer#allowedOrigin
*/
@Configuration
@EnableWebMvc
@EnableWebSecurity
static class WebAuthnConfiguration {
@Bean
UserDetailsService userDetailsService() {
return new InMemoryUserDetailsManager(
User.withDefaultPasswordEncoder().username(USERNAME).password(PASSWORD).build());
}
@Bean
FilterChainProxy securityFilterChain(HttpSecurity http, Environment environment) throws Exception {
SecurityFilterChain securityFilterChain = http
.authorizeHttpRequests((auth) -> auth.anyRequest().authenticated())
.formLogin(Customizer.withDefaults())
.webAuthn((passkeys) -> passkeys.rpId("localhost")
.rpName("Spring Security WebAuthN tests")
.allowedOrigins("http://localhost:" + environment.getProperty("server.port")))
.build();
return new FilterChainProxy(securityFilterChain);
}
}
}
@@ -1,200 +0,0 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.rsocket;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationHandler;
import io.micrometer.observation.ObservationRegistry;
import io.rsocket.core.RSocketServer;
import io.rsocket.frame.decoder.PayloadDecoder;
import io.rsocket.metadata.WellKnownMimeType;
import io.rsocket.transport.netty.server.CloseableChannel;
import io.rsocket.transport.netty.server.TcpServerTransport;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.rsocket.RSocketRequester;
import org.springframework.messaging.rsocket.RSocketStrategies;
import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler;
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.rsocket.core.SecuritySocketAcceptorInterceptor;
import org.springframework.security.rsocket.metadata.SimpleAuthenticationEncoder;
import org.springframework.security.rsocket.metadata.UsernamePasswordMetadata;
import org.springframework.stereotype.Controller;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.util.MimeTypeUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* @author Rob Winch
*/
@ContextConfiguration
@ExtendWith(SpringExtension.class)
public class HelloRSocketObservationITests {
@Autowired
RSocketMessageHandler handler;
@Autowired
SecuritySocketAcceptorInterceptor interceptor;
@Autowired
ServerController controller;
@Autowired
ObservationHandler<Observation.Context> observationHandler;
private CloseableChannel server;
private RSocketRequester requester;
@BeforeEach
public void setup() {
// @formatter:off
this.server = RSocketServer.create()
.payloadDecoder(PayloadDecoder.ZERO_COPY)
.interceptors((registry) ->
registry.forSocketAcceptor(this.interceptor)
)
.acceptor(this.handler.responder())
.bind(TcpServerTransport.create("localhost", 0))
.block();
// @formatter:on
}
@AfterEach
public void dispose() {
this.requester.rsocket().dispose();
this.server.dispose();
this.controller.payloads.clear();
}
@Test
public void getWhenUsingObservationRegistryThenObservesRequest() {
UsernamePasswordMetadata credentials = new UsernamePasswordMetadata("rob", "password");
// @formatter:off
this.requester = RSocketRequester.builder()
.setupMetadata(credentials, MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.getString()))
.rsocketStrategies(this.handler.getRSocketStrategies())
.connectTcp("localhost", this.server.address().getPort())
.block();
// @formatter:on
String data = "rob";
// @formatter:off
this.requester.route("secure.retrieve-mono")
.metadata(credentials, MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.getString()))
.data(data)
.retrieveMono(String.class)
.block();
// @formatter:on
ArgumentCaptor<Observation.Context> captor = ArgumentCaptor.forClass(Observation.Context.class);
verify(this.observationHandler, times(2)).onStart(captor.capture());
Iterator<Observation.Context> contexts = captor.getAllValues().iterator();
// once for setup
assertThat(contexts.next().getName()).isEqualTo("spring.security.authentications");
// once for request
assertThat(contexts.next().getName()).isEqualTo("spring.security.authentications");
}
@Configuration
@EnableRSocketSecurity
static class Config {
private ObservationHandler<Observation.Context> handler = mock(ObservationHandler.class);
@Bean
ServerController controller() {
return new ServerController();
}
@Bean
RSocketMessageHandler messageHandler() {
RSocketMessageHandler handler = new RSocketMessageHandler();
handler.setRSocketStrategies(rsocketStrategies());
return handler;
}
@Bean
RSocketStrategies rsocketStrategies() {
return RSocketStrategies.builder().encoder(new SimpleAuthenticationEncoder()).build();
}
@Bean
MapReactiveUserDetailsService uds() {
// @formatter:off
UserDetails rob = User.withDefaultPasswordEncoder()
.username("rob")
.password("password")
.roles("USER", "ADMIN")
.build();
// @formatter:on
return new MapReactiveUserDetailsService(rob);
}
@Bean
ObservationHandler<Observation.Context> observationHandler() {
return this.handler;
}
@Bean
ObservationRegistry observationRegistry() {
given(this.handler.supportsContext(any())).willReturn(true);
ObservationRegistry registry = ObservationRegistry.create();
registry.observationConfig().observationHandler(this.handler);
return registry;
}
}
@Controller
static class ServerController {
private List<String> payloads = new ArrayList<>();
@MessageMapping("**")
String retrieveMono(String payload) {
add(payload);
return "Hi " + payload;
}
private void add(String p) {
this.payloads.add(p);
}
}
}
@@ -1,169 +0,0 @@
/*
* Copyright 2019-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.rsocket;
import java.util.ArrayList;
import java.util.List;
import io.rsocket.core.RSocketServer;
import io.rsocket.exceptions.RejectedSetupException;
import io.rsocket.frame.decoder.PayloadDecoder;
import io.rsocket.transport.netty.server.CloseableChannel;
import io.rsocket.transport.netty.server.TcpServerTransport;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.rsocket.RSocketRequester;
import org.springframework.messaging.rsocket.RSocketStrategies;
import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.rsocket.core.SecuritySocketAcceptorInterceptor;
import org.springframework.security.rsocket.metadata.BasicAuthenticationEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* @author Rob Winch
*/
@ContextConfiguration
@ExtendWith(SpringExtension.class)
public class HelloRSocketWithWebFluxITests {
@Autowired
RSocketMessageHandler handler;
@Autowired
SecuritySocketAcceptorInterceptor interceptor;
@Autowired
ServerController controller;
private CloseableChannel server;
private RSocketRequester requester;
@BeforeEach
public void setup() {
// @formatter:off
this.server = RSocketServer.create()
.payloadDecoder(PayloadDecoder.ZERO_COPY)
.interceptors((registry) ->
registry.forSocketAcceptor(this.interceptor)
)
.acceptor(this.handler.responder())
.bind(TcpServerTransport.create("localhost", 0))
.block();
// @formatter:on
}
@AfterEach
public void dispose() {
this.requester.rsocket().dispose();
this.server.dispose();
this.controller.payloads.clear();
}
// gh-16161
@Test
public void retrieveMonoWhenSecureThenDenied() {
// @formatter:off
this.requester = RSocketRequester.builder()
.rsocketStrategies(this.handler.getRSocketStrategies())
.connectTcp("localhost", this.server.address().getPort())
.block();
// @formatter:on
String data = "rob";
// @formatter:off
assertThatExceptionOfType(Exception.class).isThrownBy(
() -> this.requester.route("secure.retrieve-mono")
.data(data)
.retrieveMono(String.class)
.block()
)
.matches((ex) -> ex instanceof RejectedSetupException
|| ex.getClass().toString().contains("ReactiveException"));
// @formatter:on
assertThat(this.controller.payloads).isEmpty();
}
@Configuration
@EnableRSocketSecurity
@EnableWebFluxSecurity
static class Config {
@Bean
ServerController controller() {
return new ServerController();
}
@Bean
RSocketMessageHandler messageHandler() {
RSocketMessageHandler handler = new RSocketMessageHandler();
handler.setRSocketStrategies(rsocketStrategies());
return handler;
}
@Bean
RSocketStrategies rsocketStrategies() {
return RSocketStrategies.builder().encoder(new BasicAuthenticationEncoder()).build();
}
@Bean
MapReactiveUserDetailsService uds() {
// @formatter:off
UserDetails rob = User.withDefaultPasswordEncoder()
.username("rob")
.password("password")
.roles("USER", "ADMIN")
.build();
// @formatter:on
return new MapReactiveUserDetailsService(rob);
}
}
@Controller
static class ServerController {
private List<String> payloads = new ArrayList<>();
@MessageMapping("**")
String retrieveMono(String payload) {
add(payload);
return "Hi " + payload;
}
private void add(String p) {
this.payloads.add(p);
}
}
}
@@ -1,51 +0,0 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config;
import org.springframework.beans.factory.Aware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
/**
* Allows initialization of Objects. Typically this is used to call the {@link Aware}
* methods, {@link InitializingBean#afterPropertiesSet()}, and ensure that
* {@link DisposableBean#destroy()} has been invoked.
*
* @param <T> the bound of the types of Objects this {@link ObjectPostProcessor} supports.
* @author Rob Winch
* @since 3.2
*/
public interface ObjectPostProcessor<T> {
static <S> ObjectPostProcessor<S> identity() {
return new ObjectPostProcessor<>() {
@Override
public <O extends S> O postProcess(O object) {
return object;
}
};
}
/**
* Initialize the object possibly returning a modified instance that should be used
* instead.
* @param object the object to initialize
* @return the initialized version of the object
*/
<O extends T> O postProcess(O object);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2009-2022 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.
@@ -96,7 +96,7 @@ public final class SecurityNamespaceHandler implements NamespaceHandler {
pc.getReaderContext()
.fatal("You cannot use a spring-security-2.0.xsd or spring-security-3.0.xsd or "
+ "spring-security-3.1.xsd schema or spring-security-3.2.xsd schema or spring-security-4.0.xsd schema "
+ "with Spring Security 6.5. Please update your schema declarations to the 6.5 schema.",
+ "with Spring Security 6.2. Please update your schema declarations to the 6.2 schema.",
element);
}
String name = pc.getDelegate().getLocalName(element);
@@ -221,7 +221,7 @@ public final class SecurityNamespaceHandler implements NamespaceHandler {
private boolean matchesVersionInternal(Element element) {
String schemaLocation = element.getAttributeNS("http://www.w3.org/2001/XMLSchema-instance", "schemaLocation");
return schemaLocation.matches("(?m).*spring-security-6\\.5.*.xsd.*")
return schemaLocation.matches("(?m).*spring-security-6\\.2.*.xsd.*")
|| schemaLocation.matches("(?m).*spring-security.xsd.*")
|| !schemaLocation.matches("(?m).*spring-security.*");
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 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.
@@ -28,7 +28,6 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.util.Assert;
import org.springframework.web.filter.DelegatingFilterProxy;
@@ -79,15 +78,6 @@ public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBui
this(objectPostProcessor, false);
}
/**
* @deprecated
*/
@Deprecated(since = "6.4", forRemoval = true)
protected AbstractConfiguredSecurityBuilder(
org.springframework.security.config.annotation.ObjectPostProcessor<Object> objectPostProcessor) {
this(objectPostProcessor, false);
}
/***
* Creates a new instance with the provided {@link ObjectPostProcessor}. This post
* processor must support Object since there are many types of objects that may be
@@ -103,18 +93,6 @@ public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBui
this.allowConfigurersOfSameType = allowConfigurersOfSameType;
}
/**
* @deprecated
*/
@Deprecated(since = "6.4", forRemoval = true)
protected AbstractConfiguredSecurityBuilder(
org.springframework.security.config.annotation.ObjectPostProcessor<Object> objectPostProcessor,
boolean allowConfigurersOfSameType) {
Assert.notNull(objectPostProcessor, "objectPostProcessor cannot be null");
this.objectPostProcessor = objectPostProcessor;
this.allowConfigurersOfSameType = allowConfigurersOfSameType;
}
/**
* Similar to {@link #build()} and {@link #getObject()} but checks the state to
* determine if {@link #build()} needs to be called first.
@@ -28,11 +28,8 @@ import org.springframework.beans.factory.InitializingBean;
* @param <T> the bound of the types of Objects this {@link ObjectPostProcessor} supports.
* @author Rob Winch
* @since 3.2
* @deprecated please use {@link org.springframework.security.config.ObjectPostProcessor}
* instead
*/
@Deprecated
public interface ObjectPostProcessor<T> extends org.springframework.security.config.ObjectPostProcessor<T> {
public interface ObjectPostProcessor<T> {
/**
* Initialize the object possibly returning a modified instance that should be used
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 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.
@@ -21,7 +21,6 @@ import java.util.List;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.security.config.ObjectPostProcessor;
import org.springframework.util.Assert;
/**
@@ -92,15 +91,6 @@ public abstract class SecurityConfigurerAdapter<O, B extends SecurityBuilder<O>>
this.objectPostProcessor.addObjectPostProcessor(objectPostProcessor);
}
/**
* @deprecated
*/
@Deprecated(since = "6.4", forRemoval = true)
public void addObjectPostProcessor(
org.springframework.security.config.annotation.ObjectPostProcessor<?> objectPostProcessor) {
this.objectPostProcessor.addObjectPostProcessor(objectPostProcessor);
}
/**
* Sets the {@link SecurityBuilder} to be used. This is automatically set when using
* {@link AbstractConfiguredSecurityBuilder#apply(SecurityConfigurerAdapter)}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 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.
@@ -26,8 +26,8 @@ import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.ObjectPostProcessor;
import org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.SecurityBuilder;
import org.springframework.security.config.annotation.SecurityConfigurer;
import org.springframework.security.config.annotation.authentication.ProviderManagerBuilder;
@@ -73,15 +73,6 @@ public class AuthenticationManagerBuilder
super(objectPostProcessor, true);
}
/**
* @deprecated
*/
@Deprecated(since = "6.4", forRemoval = true)
public AuthenticationManagerBuilder(
org.springframework.security.config.annotation.ObjectPostProcessor<Object> objectPostProcessor) {
super(objectPostProcessor, true);
}
/**
* Allows providing a parent {@link AuthenticationManager} that will be tried if this
* {@link AuthenticationManager} was unable to attempt to authenticate the provided
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 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.
@@ -28,6 +28,7 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.aop.framework.ProxyFactoryBean;
import org.springframework.aop.target.LazyInitTargetSource;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
@@ -39,7 +40,7 @@ import org.springframework.core.log.LogMessage;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
import org.springframework.security.config.ObjectPostProcessor;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configurers.provisioning.InMemoryUserDetailsManagerConfigurer;
import org.springframework.security.config.annotation.authentication.configurers.provisioning.JdbcUserDetailsManagerConfigurer;
@@ -56,7 +57,6 @@ import org.springframework.util.Assert;
* Exports the authentication {@link Configuration}
*
* @author Rob Winch
* @author Ngoc Nhan
* @since 3.2
*
*/
@@ -197,6 +197,15 @@ public class AuthenticationConfiguration {
return lazyBean(AuthenticationManager.class);
}
private static <T> T getBeanOrNull(ApplicationContext applicationContext, Class<T> type) {
try {
return applicationContext.getBean(type);
}
catch (NoSuchBeanDefinitionException notFound) {
return null;
}
}
private static class EnableGlobalAuthenticationAutowiredConfigurer extends GlobalAuthenticationConfigurerAdapter {
private final ApplicationContext context;
@@ -321,9 +330,12 @@ public class AuthenticationConfiguration {
if (this.passwordEncoder != null) {
return this.passwordEncoder;
}
this.passwordEncoder = this.applicationContext.getBeanProvider(PasswordEncoder.class)
.getIfUnique(PasswordEncoderFactories::createDelegatingPasswordEncoder);
return this.passwordEncoder;
PasswordEncoder passwordEncoder = getBeanOrNull(this.applicationContext, PasswordEncoder.class);
if (passwordEncoder == null) {
passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
this.passwordEncoder = passwordEncoder;
return passwordEncoder;
}
@Override
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2019 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.
@@ -16,14 +16,8 @@
package org.springframework.security.config.annotation.authentication.configuration;
import java.util.Arrays;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.Order;
import org.springframework.core.log.LogMessage;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
@@ -55,31 +49,29 @@ class InitializeAuthenticationProviderBeanManagerConfigurer extends GlobalAuthen
class InitializeAuthenticationProviderManagerConfigurer extends GlobalAuthenticationConfigurerAdapter {
private final Log logger = LogFactory.getLog(getClass());
@Override
public void configure(AuthenticationManagerBuilder auth) {
if (auth.isConfigured()) {
return;
}
String[] beanNames = InitializeAuthenticationProviderBeanManagerConfigurer.this.context
.getBeanNamesForType(AuthenticationProvider.class);
if (beanNames.length == 0) {
AuthenticationProvider authenticationProvider = getBeanOrNull(AuthenticationProvider.class);
if (authenticationProvider == null) {
return;
}
else if (beanNames.length > 1) {
this.logger.info(LogMessage.format("Found %s AuthenticationProvider beans, with names %s. "
+ "Global Authentication Manager will not be configured with AuthenticationProviders. "
+ "Consider publishing a single AuthenticationProvider bean, or wiring your Providers directly "
+ "using the DSL.", beanNames.length, Arrays.toString(beanNames)));
return;
}
AuthenticationProvider authenticationProvider = InitializeAuthenticationProviderBeanManagerConfigurer.this.context
.getBean(beanNames[0], AuthenticationProvider.class);
auth.authenticationProvider(authenticationProvider);
this.logger.info(LogMessage.format(
"Global AuthenticationManager configured with AuthenticationProvider bean with name %s",
beanNames[0]));
}
/**
* @return a bean of the requested class if there's just a single registered
* component, null otherwise.
*/
private <T> T getBeanOrNull(Class<T> type) {
String[] beanNames = InitializeAuthenticationProviderBeanManagerConfigurer.this.context
.getBeanNamesForType(type);
if (beanNames.length != 1) {
return null;
}
return InitializeAuthenticationProviderBeanManagerConfigurer.this.context.getBean(beanNames[0], type);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2019 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.
@@ -16,17 +16,10 @@
package org.springframework.security.config.annotation.authentication.configuration;
import java.util.Arrays;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.log.LogMessage;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.authentication.password.CompromisedPasswordChecker;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.core.userdetails.UserDetailsPasswordService;
import org.springframework.security.core.userdetails.UserDetailsService;
@@ -38,7 +31,6 @@ import org.springframework.security.crypto.password.PasswordEncoder;
* {@link PasswordEncoder} is defined will wire this up too.
*
* @author Rob Winch
* @author Ngoc Nhan
* @since 4.1
*/
@Order(InitializeUserDetailsBeanManagerConfigurer.DEFAULT_ORDER)
@@ -62,53 +54,27 @@ class InitializeUserDetailsBeanManagerConfigurer extends GlobalAuthenticationCon
class InitializeUserDetailsManagerConfigurer extends GlobalAuthenticationConfigurerAdapter {
private final Log logger = LogFactory.getLog(getClass());
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
String[] beanNames = InitializeUserDetailsBeanManagerConfigurer.this.context
.getBeanNamesForType(UserDetailsService.class);
if (auth.isConfigured()) {
if (beanNames.length > 0) {
this.logger.warn("Global AuthenticationManager configured with an AuthenticationProvider bean. "
+ "UserDetailsService beans will not be used by Spring Security for automatically configuring username/password login. "
+ "Consider removing the AuthenticationProvider bean. "
+ "Alternatively, consider using the UserDetailsService in a manually instantiated DaoAuthenticationProvider. "
+ "If the current configuration is intentional, to turn off this warning, "
+ "increase the logging level of 'org.springframework.security.config.annotation.authentication.configuration.InitializeUserDetailsBeanManagerConfigurer' to ERROR");
}
return;
}
if (beanNames.length == 0) {
UserDetailsService userDetailsService = getBeanOrNull(UserDetailsService.class);
if (userDetailsService == null) {
return;
}
else if (beanNames.length > 1) {
this.logger.warn(LogMessage.format("Found %s UserDetailsService beans, with names %s. "
+ "Global Authentication Manager will not use a UserDetailsService for username/password login. "
+ "Consider publishing a single UserDetailsService bean.", beanNames.length,
Arrays.toString(beanNames)));
return;
}
UserDetailsService userDetailsService = InitializeUserDetailsBeanManagerConfigurer.this.context
.getBean(beanNames[0], UserDetailsService.class);
PasswordEncoder passwordEncoder = getBeanOrNull(PasswordEncoder.class);
UserDetailsPasswordService passwordManager = getBeanOrNull(UserDetailsPasswordService.class);
CompromisedPasswordChecker passwordChecker = getBeanOrNull(CompromisedPasswordChecker.class);
DaoAuthenticationProvider provider = new DaoAuthenticationProvider(userDetailsService);
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
if (passwordEncoder != null) {
provider.setPasswordEncoder(passwordEncoder);
}
if (passwordManager != null) {
provider.setUserDetailsPasswordService(passwordManager);
}
if (passwordChecker != null) {
provider.setCompromisedPasswordChecker(passwordChecker);
}
provider.afterPropertiesSet();
auth.authenticationProvider(provider);
this.logger.info(LogMessage.format(
"Global AuthenticationManager configured with UserDetailsService bean with name %s", beanNames[0]));
}
/**
@@ -116,7 +82,11 @@ class InitializeUserDetailsBeanManagerConfigurer extends GlobalAuthenticationCon
* component, null otherwise.
*/
private <T> T getBeanOrNull(Class<T> type) {
return InitializeUserDetailsBeanManagerConfigurer.this.context.getBeanProvider(type).getIfUnique();
String[] beanNames = InitializeUserDetailsBeanManagerConfigurer.this.context.getBeanNamesForType(type);
if (beanNames.length != 1) {
return null;
}
return InitializeUserDetailsBeanManagerConfigurer.this.context.getBean(beanNames[0], type);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2021 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.
@@ -22,9 +22,10 @@ import java.net.ServerSocket;
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.ObjectPostProcessor;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.authentication.ProviderManagerBuilder;
import org.springframework.security.config.annotation.web.configurers.ChannelSecurityConfigurer;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
@@ -132,23 +133,13 @@ public class LdapAuthenticationProviderConfigurer<B extends ProviderManagerBuild
/**
* Adds an {@link ObjectPostProcessor} for this class.
* @param objectPostProcessor
* @return the {@link LdapAuthenticationProviderConfigurer} for further customizations
* @return the {@link ChannelSecurityConfigurer} for further customizations
*/
public LdapAuthenticationProviderConfigurer<B> withObjectPostProcessor(ObjectPostProcessor<?> objectPostProcessor) {
addObjectPostProcessor(objectPostProcessor);
return this;
}
/**
* @deprecated
*/
@Deprecated(since = "6.4", forRemoval = true)
public LdapAuthenticationProviderConfigurer<B> withObjectPostProcessor(
org.springframework.security.config.annotation.ObjectPostProcessor<?> objectPostProcessor) {
addObjectPostProcessor(objectPostProcessor);
return this;
}
/**
* Gets the {@link LdapAuthoritiesPopulator} and defaults to
* {@link DefaultLdapAuthoritiesPopulator}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2013 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.
@@ -17,7 +17,7 @@
package org.springframework.security.config.annotation.authentication.configurers.userdetails;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.ObjectPostProcessor;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.SecurityBuilder;
import org.springframework.security.config.annotation.authentication.ProviderManagerBuilder;
import org.springframework.security.core.userdetails.UserDetailsPasswordService;
@@ -36,7 +36,7 @@ import org.springframework.security.crypto.password.PasswordEncoder;
public abstract class AbstractDaoAuthenticationConfigurer<B extends ProviderManagerBuilder<B>, C extends AbstractDaoAuthenticationConfigurer<B, C, U>, U extends UserDetailsService>
extends UserDetailsAwareConfigurer<B, U> {
private DaoAuthenticationProvider provider;
private DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
private final U userDetailsService;
@@ -46,7 +46,7 @@ public abstract class AbstractDaoAuthenticationConfigurer<B extends ProviderMana
*/
AbstractDaoAuthenticationConfigurer(U userDetailsService) {
this.userDetailsService = userDetailsService;
this.provider = new DaoAuthenticationProvider(userDetailsService);
this.provider.setUserDetailsService(userDetailsService);
if (userDetailsService instanceof UserDetailsPasswordService) {
this.provider.setUserDetailsPasswordService((UserDetailsPasswordService) userDetailsService);
}
@@ -63,17 +63,6 @@ public abstract class AbstractDaoAuthenticationConfigurer<B extends ProviderMana
return (C) this;
}
/**
* @deprecated
*/
@Deprecated(since = "6.4", forRemoval = true)
@SuppressWarnings("unchecked")
public C withObjectPostProcessor(
org.springframework.security.config.annotation.ObjectPostProcessor<?> objectPostProcessor) {
addObjectPostProcessor(objectPostProcessor);
return (C) this;
}
/**
* Allows specifying the {@link PasswordEncoder} to use with the
* {@link DaoAuthenticationProvider}. The default is to use plain text.
@@ -30,7 +30,7 @@ import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.core.NativeDetector;
import org.springframework.security.config.ObjectPostProcessor;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.util.Assert;
/**
@@ -21,7 +21,7 @@ import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
import org.springframework.security.config.ObjectPostProcessor;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@@ -1,70 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.method.configuration;
import java.util.ArrayList;
import java.util.List;
import org.aopalliance.intercept.MethodInterceptor;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
import org.springframework.security.aot.hint.AuthorizeReturnObjectCoreHintsRegistrar;
import org.springframework.security.aot.hint.SecurityHintsRegistrar;
import org.springframework.security.authorization.AuthorizationProxyFactory;
import org.springframework.security.authorization.method.AuthorizationAdvisor;
import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory;
import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory.TargetVisitor;
import org.springframework.security.authorization.method.AuthorizeReturnObjectMethodInterceptor;
import org.springframework.security.config.Customizer;
@Configuration(proxyBeanMethods = false)
final class AuthorizationProxyConfiguration implements AopInfrastructureBean {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static AuthorizationAdvisorProxyFactory authorizationProxyFactory(
ObjectProvider<AuthorizationAdvisor> authorizationAdvisors, ObjectProvider<TargetVisitor> targetVisitors,
ObjectProvider<Customizer<AuthorizationAdvisorProxyFactory>> customizers) {
List<AuthorizationAdvisor> advisors = new ArrayList<>();
authorizationAdvisors.forEach(advisors::add);
List<TargetVisitor> visitors = new ArrayList<>();
targetVisitors.orderedStream().forEach(visitors::add);
visitors.add(TargetVisitor.defaults());
AuthorizationAdvisorProxyFactory factory = new AuthorizationAdvisorProxyFactory(advisors);
factory.setTargetVisitor(TargetVisitor.of(visitors.toArray(TargetVisitor[]::new)));
customizers.forEach((c) -> c.customize(factory));
return factory;
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static MethodInterceptor authorizeReturnObjectMethodInterceptor() {
return new AuthorizeReturnObjectMethodInterceptor();
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static SecurityHintsRegistrar authorizeReturnObjectHintsRegistrar(AuthorizationProxyFactory proxyFactory) {
return new AuthorizeReturnObjectCoreHintsRegistrar(proxyFactory);
}
}
@@ -1,37 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.method.configuration;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
import org.springframework.security.aot.hint.SecurityHintsRegistrar;
import org.springframework.security.authorization.AuthorizationProxyFactory;
import org.springframework.security.data.aot.hint.AuthorizeReturnObjectDataHintsRegistrar;
@Configuration(proxyBeanMethods = false)
final class AuthorizationProxyDataConfiguration implements AopInfrastructureBean {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static SecurityHintsRegistrar authorizeReturnObjectDataHintsRegistrar(AuthorizationProxyFactory proxyFactory) {
return new AuthorizeReturnObjectDataHintsRegistrar(proxyFactory);
}
}
@@ -1,73 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.method.configuration;
import java.util.Map;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
import org.springframework.core.Ordered;
import org.springframework.http.HttpEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.View;
@Configuration
class AuthorizationProxyWebConfiguration {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
AuthorizationAdvisorProxyFactory.TargetVisitor webTargetVisitor() {
return new WebTargetVisitor();
}
static class WebTargetVisitor implements AuthorizationAdvisorProxyFactory.TargetVisitor, Ordered {
private static final int DEFAULT_ORDER = 100;
@Override
public Object visit(AuthorizationAdvisorProxyFactory proxyFactory, Object target) {
if (target instanceof ResponseEntity<?> entity) {
return new ResponseEntity<>(proxyFactory.proxy(entity.getBody()), entity.getHeaders(),
entity.getStatusCode());
}
if (target instanceof HttpEntity<?> entity) {
return new HttpEntity<>(proxyFactory.proxy(entity.getBody()), entity.getHeaders());
}
if (target instanceof ModelAndView mav) {
View view = mav.getView();
String viewName = mav.getViewName();
Map<String, Object> model = (Map<String, Object>) proxyFactory.proxy(mav.getModel());
ModelAndView proxied = (view != null) ? new ModelAndView(view, model)
: new ModelAndView(viewName, model);
proxied.setStatus(mav.getStatus());
return proxied;
}
return null;
}
@Override
public int getOrder() {
return DEFAULT_ORDER;
}
}
}
@@ -1,67 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.method.configuration;
import java.util.function.Supplier;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInvocation;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.aop.Pointcut;
import org.springframework.security.authorization.method.AuthorizationAdvisor;
import org.springframework.util.function.SingletonSupplier;
final class DeferringMethodInterceptor<M extends AuthorizationAdvisor> implements AuthorizationAdvisor {
private final Pointcut pointcut;
private final Supplier<M> delegate;
DeferringMethodInterceptor(Pointcut pointcut, Supplier<M> delegate) {
this.pointcut = pointcut;
this.delegate = SingletonSupplier.of(delegate);
}
@Nullable
@Override
public Object invoke(@NotNull MethodInvocation invocation) throws Throwable {
return this.delegate.get().invoke(invocation);
}
@Override
public Pointcut getPointcut() {
return this.pointcut;
}
@Override
public Advice getAdvice() {
return this;
}
@Override
public int getOrder() {
return this.delegate.get().getOrder();
}
@Override
public boolean isPerInstance() {
return true;
}
}
@@ -0,0 +1,50 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.method.configuration;
import java.util.function.Supplier;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.ObservationAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.util.function.SingletonSupplier;
final class DeferringObservationAuthorizationManager<T> implements AuthorizationManager<T> {
private final Supplier<AuthorizationManager<T>> delegate;
DeferringObservationAuthorizationManager(ObjectProvider<ObservationRegistry> provider,
AuthorizationManager<T> delegate) {
this.delegate = SingletonSupplier.of(() -> {
ObservationRegistry registry = provider.getIfAvailable(() -> ObservationRegistry.NOOP);
if (registry.isNoop()) {
return delegate;
}
return new ObservationAuthorizationManager<>(registry, delegate);
});
}
@Override
public AuthorizationDecision check(Supplier<Authentication> authentication, T object) {
return this.delegate.get().check(authentication, object);
}
}
@@ -0,0 +1,51 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.method.configuration;
import java.util.function.Supplier;
import io.micrometer.observation.ObservationRegistry;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ObservationReactiveAuthorizationManager;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.util.function.SingletonSupplier;
final class DeferringObservationReactiveAuthorizationManager<T> implements ReactiveAuthorizationManager<T> {
private final Supplier<ReactiveAuthorizationManager<T>> delegate;
DeferringObservationReactiveAuthorizationManager(ObjectProvider<ObservationRegistry> provider,
ReactiveAuthorizationManager<T> delegate) {
this.delegate = SingletonSupplier.of(() -> {
ObservationRegistry registry = provider.getIfAvailable(() -> ObservationRegistry.NOOP);
if (registry.isNoop()) {
return delegate;
}
return new ObservationReactiveAuthorizationManager<>(registry, delegate);
});
}
@Override
public Mono<AuthorizationDecision> check(Mono<Authentication> authentication, T object) {
return this.delegate.get().check(authentication, object);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2021 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.
@@ -87,14 +87,4 @@ public @interface EnableMethodSecurity {
*/
AdviceMode mode() default AdviceMode.PROXY;
/**
* Indicate additional offset in the ordering of the execution of the security
* interceptors when multiple advices are applied at a specific joinpoint. I.e.,
* precedence of each security interceptor enabled by this annotation will be
* calculated as sum of its default precedence and offset. The default is 0.
* @return the offset in the order the security advisor should be applied
* @since 6.3
*/
int offset() default 0;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 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.
@@ -27,6 +27,7 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
@@ -68,7 +69,7 @@ import org.springframework.security.access.vote.RoleVoter;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
import org.springframework.security.config.ObjectPostProcessor;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.core.GrantedAuthorityDefaults;
@@ -83,7 +84,6 @@ import org.springframework.util.Assert;
*
* @author Rob Winch
* @author Eddú Meléndez
* @author Ngoc Nhan
* @since 3.2
* @see EnableGlobalMethodSecurity
* @deprecated Use {@link PrePostMethodSecurityConfiguration},
@@ -97,7 +97,7 @@ public class GlobalMethodSecurityConfiguration implements ImportAware, SmartInit
private static final Log logger = LogFactory.getLog(GlobalMethodSecurityConfiguration.class);
private ObjectPostProcessor<Object> objectPostProcessor = new ObjectPostProcessor<>() {
private ObjectPostProcessor<Object> objectPostProcessor = new ObjectPostProcessor<Object>() {
@Override
public <T> T postProcess(T object) {
@@ -168,19 +168,19 @@ public class GlobalMethodSecurityConfiguration implements ImportAware, SmartInit
catch (Exception ex) {
throw new RuntimeException(ex);
}
PermissionEvaluator permissionEvaluator = getBeanOrNull(PermissionEvaluator.class);
PermissionEvaluator permissionEvaluator = getSingleBeanOrNull(PermissionEvaluator.class);
if (permissionEvaluator != null) {
this.defaultMethodExpressionHandler.setPermissionEvaluator(permissionEvaluator);
}
RoleHierarchy roleHierarchy = getBeanOrNull(RoleHierarchy.class);
RoleHierarchy roleHierarchy = getSingleBeanOrNull(RoleHierarchy.class);
if (roleHierarchy != null) {
this.defaultMethodExpressionHandler.setRoleHierarchy(roleHierarchy);
}
AuthenticationTrustResolver trustResolver = getBeanOrNull(AuthenticationTrustResolver.class);
AuthenticationTrustResolver trustResolver = getSingleBeanOrNull(AuthenticationTrustResolver.class);
if (trustResolver != null) {
this.defaultMethodExpressionHandler.setTrustResolver(trustResolver);
}
GrantedAuthorityDefaults grantedAuthorityDefaults = getBeanOrNull(GrantedAuthorityDefaults.class);
GrantedAuthorityDefaults grantedAuthorityDefaults = getSingleBeanOrNull(GrantedAuthorityDefaults.class);
if (grantedAuthorityDefaults != null) {
this.defaultMethodExpressionHandler.setDefaultRolePrefix(grantedAuthorityDefaults.getRolePrefix());
}
@@ -188,8 +188,13 @@ public class GlobalMethodSecurityConfiguration implements ImportAware, SmartInit
this.defaultMethodExpressionHandler = this.objectPostProcessor.postProcess(this.defaultMethodExpressionHandler);
}
private <T> T getBeanOrNull(Class<T> type) {
return this.context.getBeanProvider(type).getIfUnique();
private <T> T getSingleBeanOrNull(Class<T> type) {
try {
return this.context.getBean(type);
}
catch (NoSuchBeanDefinitionException ex) {
}
return null;
}
private void initializeMethodSecurityInterceptor() throws Exception {
@@ -257,7 +262,7 @@ public class GlobalMethodSecurityConfiguration implements ImportAware, SmartInit
decisionVoters.add(new Jsr250Voter());
}
RoleVoter roleVoter = new RoleVoter();
GrantedAuthorityDefaults grantedAuthorityDefaults = getBeanOrNull(GrantedAuthorityDefaults.class);
GrantedAuthorityDefaults grantedAuthorityDefaults = getSingleBeanOrNull(GrantedAuthorityDefaults.class);
if (grantedAuthorityDefaults != null) {
roleVoter.setRolePrefix(grantedAuthorityDefaults.getRolePrefix());
}
@@ -368,7 +373,7 @@ public class GlobalMethodSecurityConfiguration implements ImportAware, SmartInit
sources.add(new SecuredAnnotationSecurityMetadataSource());
}
if (isJsr250Enabled) {
GrantedAuthorityDefaults grantedAuthorityDefaults = getBeanOrNull(GrantedAuthorityDefaults.class);
GrantedAuthorityDefaults grantedAuthorityDefaults = getSingleBeanOrNull(GrantedAuthorityDefaults.class);
Jsr250MethodSecurityMetadataSource jsr250MethodSecurityMetadataSource = this.context
.getBean(Jsr250MethodSecurityMetadataSource.class);
if (grantedAuthorityDefaults != null) {
@@ -407,16 +412,6 @@ public class GlobalMethodSecurityConfiguration implements ImportAware, SmartInit
this.objectPostProcessor = objectPostProcessor;
}
/**
* @deprecated
*/
@Deprecated(since = "6.4", forRemoval = true)
@Autowired(required = false)
public void setObjectPostProcessor(
org.springframework.security.config.annotation.ObjectPostProcessor<Object> objectPostProcessor) {
this.objectPostProcessor = objectPostProcessor;
}
@Autowired(required = false)
public void setMethodSecurityExpressionHandler(List<MethodSecurityExpressionHandler> handlers) {
if (handlers.size() != 1) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 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.
@@ -16,29 +16,20 @@
package org.springframework.security.config.annotation.method.configuration;
import java.util.function.Supplier;
import io.micrometer.observation.ObservationRegistry;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.Pointcut;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportAware;
import org.springframework.context.annotation.Role;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.authorization.AuthoritiesAuthorizationManager;
import org.springframework.security.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
import org.springframework.security.authorization.method.Jsr250AuthorizationManager;
import org.springframework.security.config.ObjectPostProcessor;
import org.springframework.security.config.core.GrantedAuthorityDefaults;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
/**
@@ -49,61 +40,26 @@ import org.springframework.security.core.context.SecurityContextHolderStrategy;
* @since 5.6
* @see EnableMethodSecurity
*/
@Configuration(value = "_jsr250MethodSecurityConfiguration", proxyBeanMethods = false)
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
final class Jsr250MethodSecurityConfiguration implements ImportAware, AopInfrastructureBean {
private static final Pointcut pointcut = AuthorizationManagerBeforeMethodInterceptor.jsr250().getPointcut();
private final Jsr250AuthorizationManager authorizationManager = new Jsr250AuthorizationManager();
private final AuthorizationManagerBeforeMethodInterceptor methodInterceptor;
Jsr250MethodSecurityConfiguration(
ObjectProvider<ObjectPostProcessor<AuthorizationManager<MethodInvocation>>> postProcessors) {
ObjectPostProcessor<AuthorizationManager<MethodInvocation>> postProcessor = postProcessors
.getIfUnique(ObjectPostProcessor::identity);
AuthorizationManager<MethodInvocation> manager = postProcessor.postProcess(this.authorizationManager);
this.methodInterceptor = AuthorizationManagerBeforeMethodInterceptor.jsr250(manager);
}
final class Jsr250MethodSecurityConfiguration {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static MethodInterceptor jsr250AuthorizationMethodInterceptor(
ObjectProvider<Jsr250MethodSecurityConfiguration> _jsr250MethodSecurityConfiguration) {
Supplier<AuthorizationManagerBeforeMethodInterceptor> supplier = () -> {
Jsr250MethodSecurityConfiguration configuration = _jsr250MethodSecurityConfiguration.getObject();
return configuration.methodInterceptor;
};
return new DeferringMethodInterceptor<>(pointcut, supplier);
}
@Override
public void setImportMetadata(AnnotationMetadata importMetadata) {
EnableMethodSecurity annotation = importMetadata.getAnnotations().get(EnableMethodSecurity.class).synthesize();
this.methodInterceptor.setOrder(this.methodInterceptor.getOrder() + annotation.offset());
}
@Autowired(required = false)
void setGrantedAuthorityDefaults(GrantedAuthorityDefaults defaults) {
this.authorizationManager.setRolePrefix(defaults.getRolePrefix());
}
@Autowired(required = false)
void setRoleHierarchy(RoleHierarchy roleHierarchy) {
AuthoritiesAuthorizationManager authoritiesAuthorizationManager = new AuthoritiesAuthorizationManager();
authoritiesAuthorizationManager.setRoleHierarchy(roleHierarchy);
this.authorizationManager.setAuthoritiesAuthorizationManager(authoritiesAuthorizationManager);
}
@Autowired(required = false)
void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
this.methodInterceptor.setSecurityContextHolderStrategy(securityContextHolderStrategy);
}
@Autowired(required = false)
void setEventPublisher(AuthorizationEventPublisher eventPublisher) {
this.methodInterceptor.setAuthorizationEventPublisher(eventPublisher);
ObjectProvider<GrantedAuthorityDefaults> defaultsProvider,
ObjectProvider<SecurityContextHolderStrategy> strategyProvider,
ObjectProvider<ObservationRegistry> registryProvider) {
Jsr250AuthorizationManager jsr250 = new Jsr250AuthorizationManager();
defaultsProvider.ifAvailable((d) -> jsr250.setRolePrefix(d.getRolePrefix()));
SecurityContextHolderStrategy strategy = strategyProvider
.getIfAvailable(SecurityContextHolder::getContextHolderStrategy);
AuthorizationManager<MethodInvocation> manager = new DeferringObservationAuthorizationManager<>(
registryProvider, jsr250);
AuthorizationManagerBeforeMethodInterceptor interceptor = AuthorizationManagerBeforeMethodInterceptor
.jsr250(manager);
interceptor.setSecurityContextHolderStrategy(strategy);
return interceptor;
}
}
@@ -1,71 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.method.configuration;
import io.micrometer.observation.ObservationRegistry;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.ObservationAuthorizationManager;
import org.springframework.security.authorization.method.MethodInvocationResult;
import org.springframework.security.config.ObjectPostProcessor;
import org.springframework.security.config.observation.SecurityObservationSettings;
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
class MethodObservationConfiguration {
private static final SecurityObservationSettings all = SecurityObservationSettings.withDefaults()
.shouldObserveRequests(true)
.shouldObserveAuthentications(true)
.shouldObserveAuthorizations(true)
.build();
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static ObjectPostProcessor<AuthorizationManager<MethodInvocation>> methodAuthorizationManagerPostProcessor(
ObjectProvider<ObservationRegistry> registry, ObjectProvider<SecurityObservationSettings> predicate) {
return new ObjectPostProcessor<>() {
@Override
public AuthorizationManager postProcess(AuthorizationManager object) {
ObservationRegistry r = registry.getIfUnique(() -> ObservationRegistry.NOOP);
boolean active = !r.isNoop() && predicate.getIfUnique(() -> all).shouldObserveAuthorizations();
return active ? new ObservationAuthorizationManager<>(r, object) : object;
}
};
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static ObjectPostProcessor<AuthorizationManager<MethodInvocationResult>> methodResultAuthorizationManagerPostProcessor(
ObjectProvider<ObservationRegistry> registry, ObjectProvider<SecurityObservationSettings> predicate) {
return new ObjectPostProcessor<>() {
@Override
public AuthorizationManager postProcess(AuthorizationManager object) {
ObservationRegistry r = registry.getIfUnique(() -> ObservationRegistry.NOOP);
boolean active = !r.isNoop() && predicate.getIfUnique(() -> all).shouldObserveAuthorizations();
return active ? new ObservationAuthorizationManager<>(r, object) : object;
}
};
}
}
@@ -16,24 +16,12 @@
package org.springframework.security.config.annotation.method.configuration;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.aop.Advisor;
import org.springframework.aop.Pointcut;
import org.springframework.aop.PointcutAdvisor;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.Ordered;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.security.authorization.method.AuthorizationAdvisor;
class MethodSecurityAdvisorRegistrar implements ImportBeanDefinitionRegistrar {
@@ -45,7 +33,6 @@ class MethodSecurityAdvisorRegistrar implements ImportBeanDefinitionRegistrar {
registerAsAdvisor("postAuthorizeAuthorization", registry);
registerAsAdvisor("securedAuthorization", registry);
registerAsAdvisor("jsr250Authorization", registry);
registerAsAdvisor("authorizeReturnObject", registry);
}
private void registerAsAdvisor(String prefix, BeanDefinitionRegistry registry) {
@@ -61,49 +48,9 @@ class MethodSecurityAdvisorRegistrar implements ImportBeanDefinitionRegistrar {
if (!(definition instanceof RootBeanDefinition)) {
return;
}
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(AdvisorWrapper.class);
builder.setFactoryMethod("of");
builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
builder.addConstructorArgReference(interceptorName);
RootBeanDefinition advisor = (RootBeanDefinition) builder.getBeanDefinition();
RootBeanDefinition advisor = new RootBeanDefinition((RootBeanDefinition) definition);
advisor.setTargetType(Advisor.class);
registry.registerBeanDefinition(advisorName, advisor);
}
public static final class AdvisorWrapper
implements PointcutAdvisor, MethodInterceptor, Ordered, AopInfrastructureBean {
private final AuthorizationAdvisor advisor;
private AdvisorWrapper(AuthorizationAdvisor advisor) {
this.advisor = advisor;
}
public static AdvisorWrapper of(AuthorizationAdvisor advisor) {
return new AdvisorWrapper(advisor);
}
@Override
public Advice getAdvice() {
return this.advisor.getAdvice();
}
@Override
public Pointcut getPointcut() {
return this.advisor.getPointcut();
}
@Override
public int getOrder() {
return this.advisor.getOrder();
}
@Nullable
@Override
public Object invoke(@NotNull MethodInvocation invocation) throws Throwable {
return this.advisor.invoke(invocation);
}
registry.registerBeanDefinition(prefix + "Advisor", advisor);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* Copyright 2002-2023 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.
@@ -26,7 +26,6 @@ import org.springframework.context.annotation.AutoProxyRegistrar;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.lang.NonNull;
import org.springframework.util.ClassUtils;
/**
* Dynamically determines which imports to include using the {@link EnableMethodSecurity}
@@ -38,15 +37,6 @@ import org.springframework.util.ClassUtils;
*/
final class MethodSecuritySelector implements ImportSelector {
private static final boolean isDataPresent = ClassUtils
.isPresent("org.springframework.security.data.aot.hint.AuthorizeReturnObjectDataHintsRegistrar", null);
private static final boolean isWebPresent = ClassUtils
.isPresent("org.springframework.web.servlet.DispatcherServlet", null);
private static final boolean isObservabilityPresent = ClassUtils
.isPresent("io.micrometer.observation.ObservationRegistry", null);
private final ImportSelector autoProxy = new AutoProxyRegistrarSelector();
@Override
@@ -66,16 +56,6 @@ final class MethodSecuritySelector implements ImportSelector {
if (annotation.jsr250Enabled()) {
imports.add(Jsr250MethodSecurityConfiguration.class.getName());
}
imports.add(AuthorizationProxyConfiguration.class.getName());
if (isDataPresent) {
imports.add(AuthorizationProxyDataConfiguration.class.getName());
}
if (isWebPresent) {
imports.add(AuthorizationProxyWebConfiguration.class.getName());
}
if (isObservabilityPresent) {
imports.add(MethodObservationConfiguration.class.getName());
}
return imports.toArray(new String[0]);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 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.
@@ -16,41 +16,35 @@
package org.springframework.security.config.annotation.method.configuration;
import java.util.function.Supplier;
import io.micrometer.observation.ObservationRegistry;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.Pointcut;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportAware;
import org.springframework.context.annotation.Role;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.aot.hint.PrePostAuthorizeHintsRegistrar;
import org.springframework.security.aot.hint.SecurityHintsRegistrar;
import org.springframework.security.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor;
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
import org.springframework.security.authorization.method.MethodInvocationResult;
import org.springframework.security.authorization.method.PostAuthorizeAuthorizationManager;
import org.springframework.security.authorization.method.PostFilterAuthorizationMethodInterceptor;
import org.springframework.security.authorization.method.PreAuthorizeAuthorizationManager;
import org.springframework.security.authorization.method.PreFilterAuthorizationMethodInterceptor;
import org.springframework.security.authorization.method.PrePostTemplateDefaults;
import org.springframework.security.config.ObjectPostProcessor;
import org.springframework.security.config.core.GrantedAuthorityDefaults;
import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.util.function.SingletonSupplier;
/**
* Base {@link Configuration} for enabling Spring Security Method Security.
@@ -60,153 +54,122 @@ import org.springframework.security.core.context.SecurityContextHolderStrategy;
* @since 5.6
* @see EnableMethodSecurity
*/
@Configuration(value = "_prePostMethodSecurityConfiguration", proxyBeanMethods = false)
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
final class PrePostMethodSecurityConfiguration implements ImportAware, ApplicationContextAware, AopInfrastructureBean {
private static final Pointcut preFilterPointcut = new PreFilterAuthorizationMethodInterceptor().getPointcut();
private static final Pointcut preAuthorizePointcut = AuthorizationManagerBeforeMethodInterceptor.preAuthorize()
.getPointcut();
private static final Pointcut postAuthorizePointcut = AuthorizationManagerAfterMethodInterceptor.postAuthorize()
.getPointcut();
private static final Pointcut postFilterPointcut = new PostFilterAuthorizationMethodInterceptor().getPointcut();
private final PreAuthorizeAuthorizationManager preAuthorizeAuthorizationManager = new PreAuthorizeAuthorizationManager();
private final PostAuthorizeAuthorizationManager postAuthorizeAuthorizationManager = new PostAuthorizeAuthorizationManager();
private final PreFilterAuthorizationMethodInterceptor preFilterMethodInterceptor = new PreFilterAuthorizationMethodInterceptor();
private final AuthorizationManagerBeforeMethodInterceptor preAuthorizeMethodInterceptor;
private final AuthorizationManagerAfterMethodInterceptor postAuthorizeMethodInterceptor;
private final PostFilterAuthorizationMethodInterceptor postFilterMethodInterceptor = new PostFilterAuthorizationMethodInterceptor();
private final DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
PrePostMethodSecurityConfiguration(
ObjectProvider<ObjectPostProcessor<AuthorizationManager<MethodInvocation>>> preAuthorizeProcessor,
ObjectProvider<ObjectPostProcessor<AuthorizationManager<MethodInvocationResult>>> postAuthorizeProcessor) {
this.preFilterMethodInterceptor.setExpressionHandler(this.expressionHandler);
this.preAuthorizeAuthorizationManager.setExpressionHandler(this.expressionHandler);
this.postAuthorizeAuthorizationManager.setExpressionHandler(this.expressionHandler);
this.postFilterMethodInterceptor.setExpressionHandler(this.expressionHandler);
AuthorizationManager<MethodInvocation> preAuthorize = preAuthorizeProcessor
.getIfUnique(ObjectPostProcessor::identity)
.postProcess(this.preAuthorizeAuthorizationManager);
this.preAuthorizeMethodInterceptor = AuthorizationManagerBeforeMethodInterceptor.preAuthorize(preAuthorize);
AuthorizationManager<MethodInvocationResult> postAuthorize = postAuthorizeProcessor
.getIfUnique(ObjectPostProcessor::identity)
.postProcess(this.postAuthorizeAuthorizationManager);
this.postAuthorizeMethodInterceptor = AuthorizationManagerAfterMethodInterceptor.postAuthorize(postAuthorize);
}
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
this.expressionHandler.setApplicationContext(context);
this.preAuthorizeAuthorizationManager.setApplicationContext(context);
this.postAuthorizeAuthorizationManager.setApplicationContext(context);
}
@Autowired(required = false)
void setGrantedAuthorityDefaults(GrantedAuthorityDefaults grantedAuthorityDefaults) {
this.expressionHandler.setDefaultRolePrefix(grantedAuthorityDefaults.getRolePrefix());
}
@Autowired(required = false)
void setRoleHierarchy(RoleHierarchy roleHierarchy) {
this.expressionHandler.setRoleHierarchy(roleHierarchy);
}
@Autowired(required = false)
void setTemplateDefaults(AnnotationTemplateExpressionDefaults templateDefaults) {
this.preFilterMethodInterceptor.setTemplateDefaults(templateDefaults);
this.preAuthorizeAuthorizationManager.setTemplateDefaults(templateDefaults);
this.postAuthorizeAuthorizationManager.setTemplateDefaults(templateDefaults);
this.postFilterMethodInterceptor.setTemplateDefaults(templateDefaults);
}
@Autowired(required = false)
void setTemplateDefaults(PrePostTemplateDefaults templateDefaults) {
this.preFilterMethodInterceptor.setTemplateDefaults(templateDefaults);
this.preAuthorizeAuthorizationManager.setTemplateDefaults(templateDefaults);
this.postAuthorizeAuthorizationManager.setTemplateDefaults(templateDefaults);
this.postFilterMethodInterceptor.setTemplateDefaults(templateDefaults);
}
@Autowired(required = false)
void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
this.preFilterMethodInterceptor.setExpressionHandler(expressionHandler);
this.preAuthorizeAuthorizationManager.setExpressionHandler(expressionHandler);
this.postAuthorizeAuthorizationManager.setExpressionHandler(expressionHandler);
this.postFilterMethodInterceptor.setExpressionHandler(expressionHandler);
}
@Autowired(required = false)
void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
this.preFilterMethodInterceptor.setSecurityContextHolderStrategy(securityContextHolderStrategy);
this.preAuthorizeMethodInterceptor.setSecurityContextHolderStrategy(securityContextHolderStrategy);
this.postAuthorizeMethodInterceptor.setSecurityContextHolderStrategy(securityContextHolderStrategy);
this.postFilterMethodInterceptor.setSecurityContextHolderStrategy(securityContextHolderStrategy);
}
@Autowired(required = false)
void setAuthorizationEventPublisher(AuthorizationEventPublisher publisher) {
this.preAuthorizeMethodInterceptor.setAuthorizationEventPublisher(publisher);
this.postAuthorizeMethodInterceptor.setAuthorizationEventPublisher(publisher);
}
final class PrePostMethodSecurityConfiguration {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static MethodInterceptor preFilterAuthorizationMethodInterceptor(
ObjectProvider<PrePostMethodSecurityConfiguration> _prePostMethodSecurityConfiguration) {
return new DeferringMethodInterceptor<>(preFilterPointcut,
() -> _prePostMethodSecurityConfiguration.getObject().preFilterMethodInterceptor);
ObjectProvider<GrantedAuthorityDefaults> defaultsProvider,
ObjectProvider<MethodSecurityExpressionHandler> expressionHandlerProvider,
ObjectProvider<SecurityContextHolderStrategy> strategyProvider, ApplicationContext context) {
PreFilterAuthorizationMethodInterceptor preFilter = new PreFilterAuthorizationMethodInterceptor();
strategyProvider.ifAvailable(preFilter::setSecurityContextHolderStrategy);
preFilter.setExpressionHandler(
new DeferringMethodSecurityExpressionHandler(expressionHandlerProvider, defaultsProvider, context));
return preFilter;
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static MethodInterceptor preAuthorizeAuthorizationMethodInterceptor(
ObjectProvider<PrePostMethodSecurityConfiguration> _prePostMethodSecurityConfiguration) {
return new DeferringMethodInterceptor<>(preAuthorizePointcut,
() -> _prePostMethodSecurityConfiguration.getObject().preAuthorizeMethodInterceptor);
ObjectProvider<GrantedAuthorityDefaults> defaultsProvider,
ObjectProvider<MethodSecurityExpressionHandler> expressionHandlerProvider,
ObjectProvider<SecurityContextHolderStrategy> strategyProvider,
ObjectProvider<AuthorizationEventPublisher> eventPublisherProvider,
ObjectProvider<ObservationRegistry> registryProvider, ApplicationContext context) {
PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
manager.setExpressionHandler(
new DeferringMethodSecurityExpressionHandler(expressionHandlerProvider, defaultsProvider, context));
AuthorizationManagerBeforeMethodInterceptor preAuthorize = AuthorizationManagerBeforeMethodInterceptor
.preAuthorize(manager(manager, registryProvider));
strategyProvider.ifAvailable(preAuthorize::setSecurityContextHolderStrategy);
eventPublisherProvider.ifAvailable(preAuthorize::setAuthorizationEventPublisher);
return preAuthorize;
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static MethodInterceptor postAuthorizeAuthorizationMethodInterceptor(
ObjectProvider<PrePostMethodSecurityConfiguration> _prePostMethodSecurityConfiguration) {
return new DeferringMethodInterceptor<>(postAuthorizePointcut,
() -> _prePostMethodSecurityConfiguration.getObject().postAuthorizeMethodInterceptor);
ObjectProvider<GrantedAuthorityDefaults> defaultsProvider,
ObjectProvider<MethodSecurityExpressionHandler> expressionHandlerProvider,
ObjectProvider<SecurityContextHolderStrategy> strategyProvider,
ObjectProvider<AuthorizationEventPublisher> eventPublisherProvider,
ObjectProvider<ObservationRegistry> registryProvider, ApplicationContext context) {
PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
manager.setExpressionHandler(
new DeferringMethodSecurityExpressionHandler(expressionHandlerProvider, defaultsProvider, context));
AuthorizationManagerAfterMethodInterceptor postAuthorize = AuthorizationManagerAfterMethodInterceptor
.postAuthorize(manager(manager, registryProvider));
strategyProvider.ifAvailable(postAuthorize::setSecurityContextHolderStrategy);
eventPublisherProvider.ifAvailable(postAuthorize::setAuthorizationEventPublisher);
return postAuthorize;
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static MethodInterceptor postFilterAuthorizationMethodInterceptor(
ObjectProvider<PrePostMethodSecurityConfiguration> _prePostMethodSecurityConfiguration) {
return new DeferringMethodInterceptor<>(postFilterPointcut,
() -> _prePostMethodSecurityConfiguration.getObject().postFilterMethodInterceptor);
ObjectProvider<GrantedAuthorityDefaults> defaultsProvider,
ObjectProvider<MethodSecurityExpressionHandler> expressionHandlerProvider,
ObjectProvider<SecurityContextHolderStrategy> strategyProvider, ApplicationContext context) {
PostFilterAuthorizationMethodInterceptor postFilter = new PostFilterAuthorizationMethodInterceptor();
strategyProvider.ifAvailable(postFilter::setSecurityContextHolderStrategy);
postFilter.setExpressionHandler(
new DeferringMethodSecurityExpressionHandler(expressionHandlerProvider, defaultsProvider, context));
return postFilter;
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static SecurityHintsRegistrar prePostAuthorizeExpressionHintsRegistrar() {
return new PrePostAuthorizeHintsRegistrar();
private static MethodSecurityExpressionHandler defaultExpressionHandler(
ObjectProvider<GrantedAuthorityDefaults> defaultsProvider, ApplicationContext context) {
DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
defaultsProvider.ifAvailable((d) -> handler.setDefaultRolePrefix(d.getRolePrefix()));
handler.setApplicationContext(context);
return handler;
}
@Override
public void setImportMetadata(AnnotationMetadata importMetadata) {
EnableMethodSecurity annotation = importMetadata.getAnnotations().get(EnableMethodSecurity.class).synthesize();
this.preFilterMethodInterceptor.setOrder(this.preFilterMethodInterceptor.getOrder() + annotation.offset());
this.preAuthorizeMethodInterceptor
.setOrder(this.preAuthorizeMethodInterceptor.getOrder() + annotation.offset());
this.postAuthorizeMethodInterceptor
.setOrder(this.postAuthorizeMethodInterceptor.getOrder() + annotation.offset());
this.postFilterMethodInterceptor.setOrder(this.postFilterMethodInterceptor.getOrder() + annotation.offset());
static <T> AuthorizationManager<T> manager(AuthorizationManager<T> delegate,
ObjectProvider<ObservationRegistry> registryProvider) {
return new DeferringObservationAuthorizationManager<>(registryProvider, delegate);
}
private static final class DeferringMethodSecurityExpressionHandler implements MethodSecurityExpressionHandler {
private final Supplier<MethodSecurityExpressionHandler> expressionHandler;
private DeferringMethodSecurityExpressionHandler(
ObjectProvider<MethodSecurityExpressionHandler> expressionHandlerProvider,
ObjectProvider<GrantedAuthorityDefaults> defaultsProvider, ApplicationContext applicationContext) {
this.expressionHandler = SingletonSupplier.of(() -> expressionHandlerProvider
.getIfAvailable(() -> defaultExpressionHandler(defaultsProvider, applicationContext)));
}
@Override
public ExpressionParser getExpressionParser() {
return this.expressionHandler.get().getExpressionParser();
}
@Override
public EvaluationContext createEvaluationContext(Authentication authentication, MethodInvocation invocation) {
return this.expressionHandler.get().createEvaluationContext(authentication, invocation);
}
@Override
public EvaluationContext createEvaluationContext(Supplier<Authentication> authentication,
MethodInvocation invocation) {
return this.expressionHandler.get().createEvaluationContext(authentication, invocation);
}
@Override
public Object filter(Object filterTarget, Expression filterExpression, EvaluationContext ctx) {
return this.expressionHandler.get().filter(filterTarget, filterExpression, ctx);
}
@Override
public void setReturnObject(Object returnObject, EvaluationContext ctx) {
this.expressionHandler.get().setReturnObject(returnObject, ctx);
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* Copyright 2002-2023 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.
@@ -16,20 +16,14 @@
package org.springframework.security.config.annotation.method.configuration;
import org.aopalliance.intercept.MethodInterceptor;
import io.micrometer.observation.ObservationRegistry;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.Pointcut;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Fallback;
import org.springframework.context.annotation.Role;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
@@ -42,10 +36,7 @@ import org.springframework.security.authorization.method.PostAuthorizeReactiveAu
import org.springframework.security.authorization.method.PostFilterAuthorizationReactiveMethodInterceptor;
import org.springframework.security.authorization.method.PreAuthorizeReactiveAuthorizationManager;
import org.springframework.security.authorization.method.PreFilterAuthorizationReactiveMethodInterceptor;
import org.springframework.security.authorization.method.PrePostTemplateDefaults;
import org.springframework.security.config.ObjectPostProcessor;
import org.springframework.security.config.core.GrantedAuthorityDefaults;
import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults;
/**
* Configuration for a {@link ReactiveAuthenticationManager} based Method Security.
@@ -53,116 +44,43 @@ import org.springframework.security.core.annotation.AnnotationTemplateExpression
* @author Evgeniy Cheban
* @since 5.8
*/
@Configuration(value = "_reactiveMethodSecurityConfiguration", proxyBeanMethods = false)
final class ReactiveAuthorizationManagerMethodSecurityConfiguration
implements AopInfrastructureBean, ApplicationContextAware {
@Configuration(proxyBeanMethods = false)
final class ReactiveAuthorizationManagerMethodSecurityConfiguration {
private static final Pointcut preFilterPointcut = new PreFilterAuthorizationReactiveMethodInterceptor()
.getPointcut();
private static final Pointcut preAuthorizePointcut = AuthorizationManagerBeforeReactiveMethodInterceptor
.preAuthorize()
.getPointcut();
private static final Pointcut postAuthorizePointcut = AuthorizationManagerAfterReactiveMethodInterceptor
.postAuthorize()
.getPointcut();
private static final Pointcut postFilterPointcut = new PostFilterAuthorizationReactiveMethodInterceptor()
.getPointcut();
private PreFilterAuthorizationReactiveMethodInterceptor preFilterMethodInterceptor = new PreFilterAuthorizationReactiveMethodInterceptor();
private PreAuthorizeReactiveAuthorizationManager preAuthorizeAuthorizationManager = new PreAuthorizeReactiveAuthorizationManager();
private PostAuthorizeReactiveAuthorizationManager postAuthorizeAuthorizationManager = new PostAuthorizeReactiveAuthorizationManager();
private PostFilterAuthorizationReactiveMethodInterceptor postFilterMethodInterceptor = new PostFilterAuthorizationReactiveMethodInterceptor();
private final AuthorizationManagerBeforeReactiveMethodInterceptor preAuthorizeMethodInterceptor;
private final AuthorizationManagerAfterReactiveMethodInterceptor postAuthorizeMethodInterceptor;
ReactiveAuthorizationManagerMethodSecurityConfiguration(
ObjectProvider<MethodSecurityExpressionHandler> expressionHandlers,
ObjectProvider<ObjectPostProcessor<ReactiveAuthorizationManager<MethodInvocation>>> preAuthorizePostProcessor,
ObjectProvider<ObjectPostProcessor<ReactiveAuthorizationManager<MethodInvocationResult>>> postAuthorizePostProcessor) {
MethodSecurityExpressionHandler expressionHandler = expressionHandlers.getIfUnique();
if (expressionHandler != null) {
this.preFilterMethodInterceptor = new PreFilterAuthorizationReactiveMethodInterceptor(expressionHandler);
this.preAuthorizeAuthorizationManager = new PreAuthorizeReactiveAuthorizationManager(expressionHandler);
this.postFilterMethodInterceptor = new PostFilterAuthorizationReactiveMethodInterceptor(expressionHandler);
this.postAuthorizeAuthorizationManager = new PostAuthorizeReactiveAuthorizationManager(expressionHandler);
}
ReactiveAuthorizationManager<MethodInvocation> preAuthorize = preAuthorizePostProcessor
.getIfUnique(ObjectPostProcessor::identity)
.postProcess(this.preAuthorizeAuthorizationManager);
this.preAuthorizeMethodInterceptor = AuthorizationManagerBeforeReactiveMethodInterceptor
.preAuthorize(preAuthorize);
ReactiveAuthorizationManager<MethodInvocationResult> postAuthorize = postAuthorizePostProcessor
.getIfAvailable(ObjectPostProcessor::identity)
.postProcess(this.postAuthorizeAuthorizationManager);
this.postAuthorizeMethodInterceptor = AuthorizationManagerAfterReactiveMethodInterceptor
.postAuthorize(postAuthorize);
}
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
this.preAuthorizeAuthorizationManager.setApplicationContext(context);
this.postAuthorizeAuthorizationManager.setApplicationContext(context);
}
@Autowired(required = false)
void setTemplateDefaults(PrePostTemplateDefaults templateDefaults) {
this.preFilterMethodInterceptor.setTemplateDefaults(templateDefaults);
this.preAuthorizeAuthorizationManager.setTemplateDefaults(templateDefaults);
this.postAuthorizeAuthorizationManager.setTemplateDefaults(templateDefaults);
this.postFilterMethodInterceptor.setTemplateDefaults(templateDefaults);
}
@Autowired(required = false)
void setTemplateDefaults(AnnotationTemplateExpressionDefaults templateDefaults) {
this.preFilterMethodInterceptor.setTemplateDefaults(templateDefaults);
this.preAuthorizeAuthorizationManager.setTemplateDefaults(templateDefaults);
this.postAuthorizeAuthorizationManager.setTemplateDefaults(templateDefaults);
this.postFilterMethodInterceptor.setTemplateDefaults(templateDefaults);
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static PreFilterAuthorizationReactiveMethodInterceptor preFilterInterceptor(
MethodSecurityExpressionHandler expressionHandler) {
return new PreFilterAuthorizationReactiveMethodInterceptor(expressionHandler);
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static MethodInterceptor preFilterAuthorizationMethodInterceptor(
ObjectProvider<ReactiveAuthorizationManagerMethodSecurityConfiguration> _reactiveMethodSecurityConfiguration) {
return new DeferringMethodInterceptor<>(preFilterPointcut,
() -> _reactiveMethodSecurityConfiguration.getObject().preFilterMethodInterceptor);
static AuthorizationManagerBeforeReactiveMethodInterceptor preAuthorizeInterceptor(
MethodSecurityExpressionHandler expressionHandler, ObjectProvider<ObservationRegistry> registryProvider) {
ReactiveAuthorizationManager<MethodInvocation> authorizationManager = manager(
new PreAuthorizeReactiveAuthorizationManager(expressionHandler), registryProvider);
return AuthorizationManagerBeforeReactiveMethodInterceptor.preAuthorize(authorizationManager);
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static MethodInterceptor preAuthorizeAuthorizationMethodInterceptor(
ObjectProvider<ReactiveAuthorizationManagerMethodSecurityConfiguration> _reactiveMethodSecurityConfiguration) {
return new DeferringMethodInterceptor<>(preAuthorizePointcut,
() -> _reactiveMethodSecurityConfiguration.getObject().preAuthorizeMethodInterceptor);
static PostFilterAuthorizationReactiveMethodInterceptor postFilterInterceptor(
MethodSecurityExpressionHandler expressionHandler) {
return new PostFilterAuthorizationReactiveMethodInterceptor(expressionHandler);
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static MethodInterceptor postFilterAuthorizationMethodInterceptor(
ObjectProvider<ReactiveAuthorizationManagerMethodSecurityConfiguration> _reactiveMethodSecurityConfiguration) {
return new DeferringMethodInterceptor<>(postFilterPointcut,
() -> _reactiveMethodSecurityConfiguration.getObject().postFilterMethodInterceptor);
static AuthorizationManagerAfterReactiveMethodInterceptor postAuthorizeInterceptor(
MethodSecurityExpressionHandler expressionHandler, ObjectProvider<ObservationRegistry> registryProvider) {
ReactiveAuthorizationManager<MethodInvocationResult> authorizationManager = manager(
new PostAuthorizeReactiveAuthorizationManager(expressionHandler), registryProvider);
return AuthorizationManagerAfterReactiveMethodInterceptor.postAuthorize(authorizationManager);
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static MethodInterceptor postAuthorizeAuthorizationMethodInterceptor(
ObjectProvider<ReactiveAuthorizationManagerMethodSecurityConfiguration> _reactiveMethodSecurityConfiguration) {
return new DeferringMethodInterceptor<>(postAuthorizePointcut,
() -> _reactiveMethodSecurityConfiguration.getObject().postAuthorizeMethodInterceptor);
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@Fallback
static DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler(
@Autowired(required = false) GrantedAuthorityDefaults grantedAuthorityDefaults) {
DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
@@ -172,4 +90,9 @@ final class ReactiveAuthorizationManagerMethodSecurityConfiguration
return handler;
}
static <T> ReactiveAuthorizationManager<T> manager(ReactiveAuthorizationManager<T> delegate,
ObjectProvider<ObservationRegistry> registryProvider) {
return new DeferringObservationReactiveAuthorizationManager<>(registryProvider, delegate);
}
}
@@ -1,71 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.method.configuration;
import io.micrometer.observation.ObservationRegistry;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
import org.springframework.security.authorization.ObservationReactiveAuthorizationManager;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.authorization.method.MethodInvocationResult;
import org.springframework.security.config.ObjectPostProcessor;
import org.springframework.security.config.observation.SecurityObservationSettings;
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
class ReactiveMethodObservationConfiguration {
private static final SecurityObservationSettings all = SecurityObservationSettings.withDefaults()
.shouldObserveRequests(true)
.shouldObserveAuthentications(true)
.shouldObserveAuthorizations(true)
.build();
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static ObjectPostProcessor<ReactiveAuthorizationManager<MethodInvocation>> methodAuthorizationManagerPostProcessor(
ObjectProvider<ObservationRegistry> registry, ObjectProvider<SecurityObservationSettings> predicate) {
return new ObjectPostProcessor<>() {
@Override
public ReactiveAuthorizationManager postProcess(ReactiveAuthorizationManager object) {
ObservationRegistry r = registry.getIfUnique(() -> ObservationRegistry.NOOP);
boolean active = !r.isNoop() && predicate.getIfUnique(() -> all).shouldObserveAuthorizations();
return active ? new ObservationReactiveAuthorizationManager<>(r, object) : object;
}
};
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static ObjectPostProcessor<ReactiveAuthorizationManager<MethodInvocationResult>> methodResultAuthorizationManagerPostProcessor(
ObjectProvider<ObservationRegistry> registry, ObjectProvider<SecurityObservationSettings> predicate) {
return new ObjectPostProcessor<>() {
@Override
public ReactiveAuthorizationManager postProcess(ReactiveAuthorizationManager object) {
ObservationRegistry r = registry.getIfUnique(() -> ObservationRegistry.NOOP);
boolean active = !r.isNoop() && predicate.getIfUnique(() -> all).shouldObserveAuthorizations();
return active ? new ObservationReactiveAuthorizationManager<>(r, object) : object;
}
};
}
}
@@ -22,7 +22,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Fallback;
import org.springframework.context.annotation.ImportAware;
import org.springframework.context.annotation.Role;
import org.springframework.core.type.AnnotationMetadata;
@@ -83,7 +82,6 @@ class ReactiveMethodSecurityConfiguration implements ImportAware {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@Fallback
static DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler(
ReactiveMethodSecurityConfiguration configuration) {
DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* Copyright 2002-2022 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.
@@ -26,7 +26,6 @@ import org.springframework.context.annotation.AutoProxyRegistrar;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.lang.NonNull;
import org.springframework.util.ClassUtils;
/**
* @author Rob Winch
@@ -35,15 +34,6 @@ import org.springframework.util.ClassUtils;
*/
class ReactiveMethodSecuritySelector implements ImportSelector {
private static final boolean isDataPresent = ClassUtils
.isPresent("org.springframework.security.data.aot.hint.AuthorizeReturnObjectDataHintsRegistrar", null);
private static final boolean isWebPresent = ClassUtils.isPresent("org.springframework.web.server.ServerWebExchange",
null);
private static final boolean isObservabilityPresent = ClassUtils
.isPresent("io.micrometer.observation.ObservationRegistry", null);
private final ImportSelector autoProxy = new AutoProxyRegistrarSelector();
@Override
@@ -61,24 +51,13 @@ class ReactiveMethodSecuritySelector implements ImportSelector {
else {
imports.add(ReactiveMethodSecurityConfiguration.class.getName());
}
if (isDataPresent) {
imports.add(AuthorizationProxyDataConfiguration.class.getName());
}
if (isWebPresent) {
imports.add(AuthorizationProxyWebConfiguration.class.getName());
}
if (isObservabilityPresent) {
imports.add(ReactiveMethodObservationConfiguration.class.getName());
}
imports.add(AuthorizationProxyConfiguration.class.getName());
return imports.toArray(new String[0]);
}
private static final class AutoProxyRegistrarSelector
extends AdviceModeImportSelector<EnableReactiveMethodSecurity> {
private static final String[] IMPORTS = new String[] { AutoProxyRegistrar.class.getName(),
MethodSecurityAdvisorRegistrar.class.getName() };
private static final String[] IMPORTS = new String[] { AutoProxyRegistrar.class.getName() };
@Override
protected String[] selectImports(@NonNull AdviceMode adviceMode) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 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.
@@ -16,29 +16,20 @@
package org.springframework.security.config.annotation.method.configuration;
import java.util.function.Supplier;
import io.micrometer.observation.ObservationRegistry;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.Pointcut;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportAware;
import org.springframework.context.annotation.Role;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.authorization.AuthoritiesAuthorizationManager;
import org.springframework.security.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
import org.springframework.security.authorization.method.SecuredAuthorizationManager;
import org.springframework.security.config.ObjectPostProcessor;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
/**
@@ -49,56 +40,24 @@ import org.springframework.security.core.context.SecurityContextHolderStrategy;
* @since 5.6
* @see EnableMethodSecurity
*/
@Configuration(value = "_securedMethodSecurityConfiguration", proxyBeanMethods = false)
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
final class SecuredMethodSecurityConfiguration implements ImportAware, AopInfrastructureBean {
private static final Pointcut pointcut = AuthorizationManagerBeforeMethodInterceptor.secured().getPointcut();
private final SecuredAuthorizationManager authorizationManager = new SecuredAuthorizationManager();
private final AuthorizationManagerBeforeMethodInterceptor methodInterceptor;
SecuredMethodSecurityConfiguration(
ObjectProvider<ObjectPostProcessor<AuthorizationManager<MethodInvocation>>> postProcessors) {
ObjectPostProcessor<AuthorizationManager<MethodInvocation>> postProcessor = postProcessors
.getIfUnique(ObjectPostProcessor::identity);
AuthorizationManager<MethodInvocation> manager = postProcessor.postProcess(this.authorizationManager);
this.methodInterceptor = AuthorizationManagerBeforeMethodInterceptor.secured(manager);
}
final class SecuredMethodSecurityConfiguration {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static MethodInterceptor securedAuthorizationMethodInterceptor(
ObjectProvider<SecuredMethodSecurityConfiguration> securedMethodSecurityConfiguration) {
Supplier<AuthorizationManagerBeforeMethodInterceptor> supplier = () -> {
SecuredMethodSecurityConfiguration configuration = securedMethodSecurityConfiguration.getObject();
return configuration.methodInterceptor;
};
return new DeferringMethodInterceptor<>(pointcut, supplier);
}
@Override
public void setImportMetadata(AnnotationMetadata importMetadata) {
EnableMethodSecurity annotation = importMetadata.getAnnotations().get(EnableMethodSecurity.class).synthesize();
this.methodInterceptor.setOrder(this.methodInterceptor.getOrder() + annotation.offset());
}
@Autowired(required = false)
void setRoleHierarchy(RoleHierarchy roleHierarchy) {
AuthoritiesAuthorizationManager authoritiesAuthorizationManager = new AuthoritiesAuthorizationManager();
authoritiesAuthorizationManager.setRoleHierarchy(roleHierarchy);
this.authorizationManager.setAuthoritiesAuthorizationManager(authoritiesAuthorizationManager);
}
@Autowired(required = false)
void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
this.methodInterceptor.setSecurityContextHolderStrategy(securityContextHolderStrategy);
}
@Autowired(required = false)
void setEventPublisher(AuthorizationEventPublisher eventPublisher) {
this.methodInterceptor.setAuthorizationEventPublisher(eventPublisher);
ObjectProvider<SecurityContextHolderStrategy> strategyProvider,
ObjectProvider<ObservationRegistry> registryProvider) {
SecuredAuthorizationManager secured = new SecuredAuthorizationManager();
SecurityContextHolderStrategy strategy = strategyProvider
.getIfAvailable(SecurityContextHolder::getContextHolderStrategy);
AuthorizationManager<MethodInvocation> manager = new DeferringObservationAuthorizationManager<>(
registryProvider, secured);
AuthorizationManagerBeforeMethodInterceptor interceptor = AuthorizationManagerBeforeMethodInterceptor
.secured(manager);
interceptor.setSecurityContextHolderStrategy(strategy);
return interceptor;
}
}
@@ -35,8 +35,7 @@ import org.springframework.context.annotation.Import;
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import({ RSocketSecurityConfiguration.class, SecuritySocketAcceptorInterceptorConfiguration.class,
ReactiveObservationImportSelector.class })
@Import({ RSocketSecurityConfiguration.class, SecuritySocketAcceptorInterceptorConfiguration.class })
public @interface EnableRSocketSecurity {
}
@@ -33,16 +33,12 @@ public enum PayloadInterceptorOrder implements Ordered {
/**
* Where basic authentication is placed.
* @see RSocketSecurity#basicAuthentication(Customizer)
* @deprecated please see {@link PayloadInterceptorOrder#AUTHENTICATION}
*/
@Deprecated
BASIC_AUTHENTICATION,
/**
* Where JWT based authentication is performed.
* @see RSocketSecurity#jwt(Customizer)
* @deprecated please see {@link PayloadInterceptorOrder#AUTHENTICATION}
*/
@Deprecated
JWT_AUTHENTICATION,
/**
* A generic placeholder for other types of authentication.
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2024 the original author or authors.
* Copyright 2019-2021 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.
@@ -108,7 +108,6 @@ import org.springframework.security.rsocket.util.matcher.RoutePayloadExchangeMat
* @author Luis Felipe Vega
* @author Manuel Tejeda
* @author Ebert Toribio
* @author Ngoc Nhan
* @since 5.2
*/
public class RSocketSecurity {
@@ -239,12 +238,15 @@ public class RSocketSecurity {
return getBeanOrNull(ResolvableType.forClass(beanClass));
}
@SuppressWarnings("unchecked")
private <T> T getBeanOrNull(ResolvableType type) {
if (this.context == null) {
return null;
}
return (T) this.context.getBeanProvider(type).getIfUnique();
String[] names = this.context.getBeanNamesForType(type);
if (names.length == 1) {
return (T) this.context.getBean(names[0]);
}
return null;
}
protected void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2024 the original author or authors.
* Copyright 2019 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.
@@ -16,16 +16,16 @@
package org.springframework.security.config.annotation.rsocket;
import java.util.Map;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.security.authentication.ObservationReactiveAuthenticationManager;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager;
import org.springframework.security.config.ObjectPostProcessor;
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
@@ -46,7 +46,7 @@ class RSocketSecurityConfiguration {
private PasswordEncoder passwordEncoder;
private ObjectPostProcessor<ReactiveAuthenticationManager> postProcessor = ObjectPostProcessor.identity();
private ObservationRegistry observationRegistry = ObservationRegistry.NOOP;
@Autowired(required = false)
void setAuthenticationManager(ReactiveAuthenticationManager authenticationManager) {
@@ -64,12 +64,8 @@ class RSocketSecurityConfiguration {
}
@Autowired(required = false)
void setAuthenticationManagerPostProcessor(
Map<String, ObjectPostProcessor<ReactiveAuthenticationManager>> postProcessors) {
if (postProcessors.size() == 1) {
this.postProcessor = postProcessors.values().iterator().next();
}
this.postProcessor = postProcessors.get("rSocketAuthenticationManagerPostProcessor");
void setObservationRegistry(ObservationRegistry observationRegistry) {
this.observationRegistry = observationRegistry;
}
@Bean(name = RSOCKET_SECURITY_BEAN_NAME)
@@ -90,7 +86,10 @@ class RSocketSecurityConfiguration {
if (this.passwordEncoder != null) {
manager.setPasswordEncoder(this.passwordEncoder);
}
return this.postProcessor.postProcess(manager);
if (!this.observationRegistry.isNoop()) {
return new ObservationReactiveAuthenticationManager(this.observationRegistry, manager);
}
return manager;
}
return null;
}
@@ -1,72 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.rsocket;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
import org.springframework.security.authentication.ObservationReactiveAuthenticationManager;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authorization.ObservationReactiveAuthorizationManager;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.config.ObjectPostProcessor;
import org.springframework.security.config.observation.SecurityObservationSettings;
import org.springframework.security.rsocket.api.PayloadExchange;
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
class ReactiveObservationConfiguration {
private static final SecurityObservationSettings all = SecurityObservationSettings.withDefaults()
.shouldObserveRequests(true)
.shouldObserveAuthentications(true)
.shouldObserveAuthorizations(true)
.build();
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static ObjectPostProcessor<ReactiveAuthorizationManager<PayloadExchange>> rSocketAuthorizationManagerPostProcessor(
ObjectProvider<ObservationRegistry> registry, ObjectProvider<SecurityObservationSettings> predicate) {
return new ObjectPostProcessor<>() {
@Override
public ReactiveAuthorizationManager postProcess(ReactiveAuthorizationManager object) {
ObservationRegistry r = registry.getIfUnique(() -> ObservationRegistry.NOOP);
boolean active = !r.isNoop() && predicate.getIfUnique(() -> all).shouldObserveAuthorizations();
return active ? new ObservationReactiveAuthorizationManager<>(r, object) : object;
}
};
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static ObjectPostProcessor<ReactiveAuthenticationManager> rSocketAuthenticationManagerPostProcessor(
ObjectProvider<ObservationRegistry> registry, ObjectProvider<SecurityObservationSettings> predicate) {
return new ObjectPostProcessor<>() {
@Override
public ReactiveAuthenticationManager postProcess(ReactiveAuthenticationManager object) {
ObservationRegistry r = registry.getIfUnique(() -> ObservationRegistry.NOOP);
boolean active = !r.isNoop() && predicate.getIfUnique(() -> all).shouldObserveAuthentications();
return active ? new ObservationReactiveAuthenticationManager(r, object) : object;
}
};
}
}
@@ -1,50 +0,0 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.rsocket;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.util.ClassUtils;
/**
* Used by {@link EnableWebFluxSecurity} to conditionally import observation configuration
* when {@link ObservationRegistry} is present.
*
* @author Josh Cummings
* @since 6.4
*/
class ReactiveObservationImportSelector implements ImportSelector {
private static final boolean observabilityPresent;
static {
ClassLoader classLoader = ReactiveObservationImportSelector.class.getClassLoader();
observabilityPresent = ClassUtils.isPresent("io.micrometer.observation.ObservationRegistry", classLoader);
}
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
if (!observabilityPresent) {
return new String[0];
}
return new String[] { ReactiveObservationConfiguration.class.getName() };
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* Copyright 2002-2024 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.
@@ -34,13 +34,11 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.ApplicationContext;
import org.springframework.core.ResolvableType;
import org.springframework.http.HttpMethod;
import org.springframework.lang.Nullable;
import org.springframework.security.config.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.ServletRegistrationsSupport.RegistrationMapping;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.configurers.AbstractConfigAttributeRequestMatcherRegistry;
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
@@ -115,9 +113,7 @@ public abstract class AbstractRequestMatcherRegistry<C> {
*/
protected final List<MvcRequestMatcher> createMvcMatchers(HttpMethod method, String... mvcPatterns) {
Assert.state(!this.anyRequestConfigured, "Can't configure mvcMatchers after anyRequest");
ResolvableType type = ResolvableType.forClassWithGenerics(ObjectPostProcessor.class, Object.class);
ObjectProvider<ObjectPostProcessor<Object>> postProcessors = this.context.getBeanProvider(type);
ObjectPostProcessor<Object> opp = postProcessors.getObject();
ObjectPostProcessor<Object> opp = this.context.getBean(ObjectPostProcessor.class);
if (!this.context.containsBean(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME)) {
throw new NoSuchBeanDefinitionException("A Bean named " + HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME
+ " of type " + HandlerMappingIntrospector.class.getName()
@@ -169,7 +165,7 @@ public abstract class AbstractRequestMatcherRegistry<C> {
/**
* Associates a list of {@link RequestMatcher} instances with the
* {@link AbstractRequestMatcherRegistry}
* {@link AbstractConfigAttributeRequestMatcherRegistry}
* @param requestMatchers the {@link RequestMatcher} instances
* @return the object that is chained after creating the {@link RequestMatcher}
*/
@@ -199,12 +195,6 @@ public abstract class AbstractRequestMatcherRegistry<C> {
* @since 5.8
*/
public C requestMatchers(HttpMethod method, String... patterns) {
if (anyPathsDontStartWithLeadingSlash(patterns)) {
this.logger.warn("One of the patterns in " + Arrays.toString(patterns)
+ " is missing a leading slash. This is discouraged; please include the "
+ "leading slash in all your request matcher patterns. In future versions of "
+ "Spring Security, leaving out the leading slash will result in an exception.");
}
if (!mvcPresent) {
return requestMatchers(RequestMatchers.antMatchersAsArray(method, patterns));
}
@@ -218,63 +208,119 @@ public abstract class AbstractRequestMatcherRegistry<C> {
}
List<RequestMatcher> matchers = new ArrayList<>();
for (String pattern : patterns) {
if (RequestMatcherFactory.usesPathPatterns()) {
matchers.add(RequestMatcherFactory.matcher(method, pattern));
}
else {
AntPathRequestMatcher ant = new AntPathRequestMatcher(pattern, (method != null) ? method.name() : null);
MvcRequestMatcher mvc = createMvcMatchers(method, pattern).get(0);
matchers.add(new DeferredRequestMatcher((c) -> resolve(ant, mvc, c), mvc, ant));
}
AntPathRequestMatcher ant = new AntPathRequestMatcher(pattern, (method != null) ? method.name() : null);
MvcRequestMatcher mvc = createMvcMatchers(method, pattern).get(0);
matchers.add(new DeferredRequestMatcher((c) -> resolve(ant, mvc, c), mvc, ant));
}
return requestMatchers(matchers.toArray(new RequestMatcher[0]));
}
private boolean anyPathsDontStartWithLeadingSlash(String... patterns) {
for (String pattern : patterns) {
if (!pattern.startsWith("/")) {
private RequestMatcher resolve(AntPathRequestMatcher ant, MvcRequestMatcher mvc, ServletContext servletContext) {
Map<String, ? extends ServletRegistration> registrations = mappableServletRegistrations(servletContext);
if (registrations.isEmpty()) {
return new DispatcherServletDelegatingRequestMatcher(ant, mvc, new MockMvcRequestMatcher());
}
if (!hasDispatcherServlet(registrations)) {
return new DispatcherServletDelegatingRequestMatcher(ant, mvc, new MockMvcRequestMatcher());
}
ServletRegistration dispatcherServlet = requireOneRootDispatcherServlet(registrations);
if (dispatcherServlet != null) {
if (registrations.size() == 1) {
return mvc;
}
return new DispatcherServletDelegatingRequestMatcher(ant, mvc, servletContext);
}
dispatcherServlet = requireOnlyPathMappedDispatcherServlet(registrations);
if (dispatcherServlet != null) {
String mapping = dispatcherServlet.getMappings().iterator().next();
mvc.setServletPath(mapping.substring(0, mapping.length() - 2));
return mvc;
}
String errorMessage = computeErrorMessage(registrations.values());
throw new IllegalArgumentException(errorMessage);
}
private Map<String, ? extends ServletRegistration> mappableServletRegistrations(ServletContext servletContext) {
Map<String, ServletRegistration> mappable = new LinkedHashMap<>();
for (Map.Entry<String, ? extends ServletRegistration> entry : servletContext.getServletRegistrations()
.entrySet()) {
if (!entry.getValue().getMappings().isEmpty()) {
mappable.put(entry.getKey(), entry.getValue());
}
}
return mappable;
}
private boolean hasDispatcherServlet(Map<String, ? extends ServletRegistration> registrations) {
if (registrations == null) {
return false;
}
for (ServletRegistration registration : registrations.values()) {
if (isDispatcherServlet(registration)) {
return true;
}
}
return false;
}
private RequestMatcher resolve(AntPathRequestMatcher ant, MvcRequestMatcher mvc, ServletContext servletContext) {
ServletRegistrationsSupport registrations = new ServletRegistrationsSupport(servletContext);
Collection<RegistrationMapping> mappings = registrations.mappings();
if (mappings.isEmpty()) {
return new DispatcherServletDelegatingRequestMatcher(ant, mvc, new MockMvcRequestMatcher());
}
Collection<RegistrationMapping> dispatcherServletMappings = registrations.dispatcherServletMappings();
if (dispatcherServletMappings.isEmpty()) {
return new DispatcherServletDelegatingRequestMatcher(ant, mvc, new MockMvcRequestMatcher());
}
if (dispatcherServletMappings.size() > 1) {
String errorMessage = computeErrorMessage(servletContext.getServletRegistrations().values());
throw new IllegalArgumentException(errorMessage);
}
RegistrationMapping dispatcherServlet = dispatcherServletMappings.iterator().next();
if (mappings.size() > 1 && !dispatcherServlet.isDefault()) {
String errorMessage = computeErrorMessage(servletContext.getServletRegistrations().values());
throw new IllegalArgumentException(errorMessage);
}
if (dispatcherServlet.isDefault()) {
if (mappings.size() == 1) {
return mvc;
private ServletRegistration requireOneRootDispatcherServlet(
Map<String, ? extends ServletRegistration> registrations) {
ServletRegistration rootDispatcherServlet = null;
for (ServletRegistration registration : registrations.values()) {
if (!isDispatcherServlet(registration)) {
continue;
}
return new DispatcherServletDelegatingRequestMatcher(ant, mvc);
if (registration.getMappings().size() > 1) {
return null;
}
if (!"/".equals(registration.getMappings().iterator().next())) {
return null;
}
rootDispatcherServlet = registration;
}
return rootDispatcherServlet;
}
private ServletRegistration requireOnlyPathMappedDispatcherServlet(
Map<String, ? extends ServletRegistration> registrations) {
ServletRegistration pathDispatcherServlet = null;
for (ServletRegistration registration : registrations.values()) {
if (!isDispatcherServlet(registration)) {
return null;
}
if (registration.getMappings().size() > 1) {
return null;
}
String mapping = registration.getMappings().iterator().next();
if (!mapping.startsWith("/") || !mapping.endsWith("/*")) {
return null;
}
if (pathDispatcherServlet != null) {
return null;
}
pathDispatcherServlet = registration;
}
return pathDispatcherServlet;
}
private boolean isDispatcherServlet(ServletRegistration registration) {
Class<?> dispatcherServlet = ClassUtils.resolveClassName("org.springframework.web.servlet.DispatcherServlet",
null);
try {
Class<?> clazz = Class.forName(registration.getClassName());
return dispatcherServlet.isAssignableFrom(clazz);
}
catch (ClassNotFoundException ex) {
return false;
}
return mvc;
}
private static String computeErrorMessage(Collection<? extends ServletRegistration> registrations) {
String template = """
This method cannot decide whether these patterns are Spring MVC patterns or not. \
This is because there is more than one mappable servlet in your servlet context: %s.
To address this, please create one PathPatternRequestMatcher.Builder#servletPath for each servlet that has \
authorized endpoints and use them to construct request matchers manually.
""";
String template = "This method cannot decide whether these patterns are Spring MVC patterns or not. "
+ "If this endpoint is a Spring MVC endpoint, please use requestMatchers(MvcRequestMatcher); "
+ "otherwise, please use requestMatchers(AntPathRequestMatcher).\n\n"
+ "This is because there is more than one mappable servlet in your servlet context: %s.\n\n"
+ "For each MvcRequestMatcher, call MvcRequestMatcher#setServletPath to indicate the servlet path.";
Map<String, Collection<String>> mappings = new LinkedHashMap<>();
for (ServletRegistration registration : registrations) {
mappings.put(registration.getClassName(), registration.getMappings());
@@ -453,12 +499,18 @@ public abstract class AbstractRequestMatcherRegistry<C> {
static class DispatcherServletRequestMatcher implements RequestMatcher {
private final ServletContext servletContext;
DispatcherServletRequestMatcher(ServletContext servletContext) {
this.servletContext = servletContext;
}
@Override
public boolean matches(HttpServletRequest request) {
String name = request.getHttpServletMapping().getServletName();
ServletRegistration registration = request.getServletContext().getServletRegistration(name);
ServletRegistration registration = this.servletContext.getServletRegistration(name);
Assert.notNull(registration,
() -> computeErrorMessage(request.getServletContext().getServletRegistrations().values()));
() -> computeErrorMessage(this.servletContext.getServletRegistrations().values()));
try {
Class<?> clazz = Class.forName(registration.getClassName());
return DispatcherServlet.class.isAssignableFrom(clazz);
@@ -478,8 +530,10 @@ public abstract class AbstractRequestMatcherRegistry<C> {
private final RequestMatcher dispatcherServlet;
DispatcherServletDelegatingRequestMatcher(AntPathRequestMatcher ant, MvcRequestMatcher mvc) {
this(ant, mvc, new OrRequestMatcher(new MockMvcRequestMatcher(), new DispatcherServletRequestMatcher()));
DispatcherServletDelegatingRequestMatcher(AntPathRequestMatcher ant, MvcRequestMatcher mvc,
ServletContext servletContext) {
this(ant, mvc, new OrRequestMatcher(new MockMvcRequestMatcher(),
new DispatcherServletRequestMatcher(servletContext)));
}
DispatcherServletDelegatingRequestMatcher(AntPathRequestMatcher ant, MvcRequestMatcher mvc,
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* Copyright 2002-2024 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.
@@ -135,7 +135,6 @@ public interface HttpSecurityBuilder<H extends HttpSecurityBuilder<H>>
* <li>{@link DisableEncodeUrlFilter}</li>
* <li>{@link ForceEagerSessionCreationFilter}</li>
* <li>{@link ChannelProcessingFilter}</li>
* <li>{@link org.springframework.security.web.transport.HttpsRedirectFilter}</li>
* <li>{@link WebAsyncManagerIntegrationFilter}</li>
* <li>{@link SecurityContextHolderFilter}</li>
* <li>{@link SecurityContextPersistenceFilter}</li>
@@ -158,7 +157,6 @@ public interface HttpSecurityBuilder<H extends HttpSecurityBuilder<H>>
* <li>{@link DigestAuthenticationFilter}</li>
* <li>{@link BearerTokenAuthenticationFilter}</li>
* <li>{@link BasicAuthenticationFilter}</li>
* <li>{@link org.springframework.security.web.authentication.AuthenticationFilter}</li>
* <li>{@link RequestCacheAwareFilter}</li>
* <li>{@link SecurityContextHolderAwareRequestFilter}</li>
* <li>{@link JaasApiIntegrationFilter}</li>
@@ -1,59 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpMethod;
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
/**
* This utility exists only to facilitate applications opting into using path patterns in
* the HttpSecurity DSL. It is for internal use only.
*
* @deprecated
*/
@Deprecated(forRemoval = true)
public final class RequestMatcherFactory {
private static PathPatternRequestMatcher.Builder builder;
public static void setApplicationContext(ApplicationContext context) {
builder = context.getBeanProvider(PathPatternRequestMatcher.Builder.class).getIfUnique();
}
public static boolean usesPathPatterns() {
return builder != null;
}
public static RequestMatcher matcher(String path) {
return matcher(null, path);
}
public static RequestMatcher matcher(HttpMethod method, String path) {
if (builder != null) {
return builder.matcher(method, path);
}
return new AntPathRequestMatcher(path, (method != null) ? method.name() : null);
}
private RequestMatcherFactory() {
}
}
@@ -1,77 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletRegistration;
import org.springframework.util.ClassUtils;
class ServletRegistrationsSupport {
private final Collection<RegistrationMapping> registrations;
ServletRegistrationsSupport(ServletContext servletContext) {
Map<String, ? extends ServletRegistration> registrations = servletContext.getServletRegistrations();
Collection<RegistrationMapping> mappings = new ArrayList<>();
for (Map.Entry<String, ? extends ServletRegistration> entry : registrations.entrySet()) {
if (!entry.getValue().getMappings().isEmpty()) {
for (String mapping : entry.getValue().getMappings()) {
mappings.add(new RegistrationMapping(entry.getValue(), mapping));
}
}
}
this.registrations = mappings;
}
Collection<RegistrationMapping> dispatcherServletMappings() {
Collection<RegistrationMapping> mappings = new ArrayList<>();
for (RegistrationMapping registration : this.registrations) {
if (registration.isDispatcherServlet()) {
mappings.add(registration);
}
}
return mappings;
}
Collection<RegistrationMapping> mappings() {
return this.registrations;
}
record RegistrationMapping(ServletRegistration registration, String mapping) {
boolean isDispatcherServlet() {
Class<?> dispatcherServlet = ClassUtils
.resolveClassName("org.springframework.web.servlet.DispatcherServlet", null);
try {
Class<?> clazz = Class.forName(this.registration.getClassName());
return dispatcherServlet.isAssignableFrom(clazz);
}
catch (ClassNotFoundException ex) {
return false;
}
}
boolean isDefault() {
return "/".equals(this.mapping);
}
}
}

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