1
0
mirror of synced 2026-07-14 15:15:11 +00:00

Compare commits

..

4 Commits

Author SHA1 Message Date
Rob Winch 02b8d4cb32 rm .github/workflows for unsupported branch 2025-05-02 12:35:27 -05:00
Rob Winch e0655bee7c rm merge-dependabot-pr.yml from Unsupported Branch 2025-04-25 13:16:06 -05:00
Marcus Hert Da Coregio 1a90041271 Only Merge PRs from Original Repository
(cherry picked from commit 31babecfbd)
2024-07-26 14:44:35 -03:00
Marcus Hert Da Coregio 2ee188ee36 Change Skip Condition
(cherry picked from commit 4c12940304)
2024-07-26 14:44:35 -03:00
1915 changed files with 12316 additions and 136379 deletions
-1
View File
@@ -15,4 +15,3 @@
*.otg binary
*.png binary
*.hsx binary
*.serialized binary
-40
View File
@@ -1,40 +0,0 @@
version: 2
registries:
spring-milestones:
type: maven-repository
url: https://repo.spring.io/milestone
updates:
- 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" # 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" ]
# GitHub Actions
- package-ecosystem: github-actions
target-branch: "main"
directory: "/"
schedule:
interval: weekly
ignore:
- dependency-name: "spring-io/*"
- dependency-name: "spring-security-release-tools/*"
-110
View File
@@ -1,110 +0,0 @@
version: 2
registries:
spring-milestones:
type: maven-repository
url: https://repo.spring.io/milestone
updates:
- 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.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.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
- package-ecosystem: github-actions
target-branch: 6.3.x
directory: /
schedule:
interval: weekly
labels:
- 'type: task'
- 'in: build'
- package-ecosystem: github-actions
target-branch: main
directory: /
schedule:
interval: weekly
labels:
- 'type: task'
- 'in: build'
- package-ecosystem: github-actions
target-branch: docs-build
directory: /
schedule:
interval: weekly
labels:
- 'type: task'
- 'in: build'
- package-ecosystem: npm
target-branch: docs-build
directory: /
schedule:
interval: weekly
labels:
- 'type: task'
- 'in: build'
- package-ecosystem: npm
target-branch: main
directory: /docs
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'
@@ -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;
@@ -1,125 +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
check-tangles:
name: Check for Package Tangles
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 for package tangles
env:
STRUCTURE101_LICENSEID: ${{ secrets.STRUCTURE101_LICENSEID }}
run: |
./gradlew assemble && ./gradlew s101 -Ps101.licenseId="$STRUCTURE101_LICENSEID" --stacktrace
deploy-artifacts:
name: Deploy Artifacts
needs: [ build, test, check-samples, check-tangles ]
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, check-tangles ]
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, check-tangles ]
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 }}
@@ -1,57 +0,0 @@
name: Auto Merge Forward Dependabot Commits
on:
workflow_dispatch:
permissions:
contents: read
concurrency:
group: dependabot-auto-merge-forward
jobs:
get-supported-branches:
uses: spring-io/spring-security-release-tools/.github/workflows/retrieve-spring-supported-versions.yml@actions-v1
with:
project: spring-security
type: oss
repository_name: spring-projects/spring-security
auto-merge-forward-dependabot:
name: Auto Merge Forward Dependabot Commits
runs-on: ubuntu-latest
needs: [get-supported-branches]
permissions:
contents: write
steps:
- name: Checkout
id: checkout
uses: actions/checkout@v4
with:
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
- name: Setup GitHub User
id: setup-gh-user
run: |
git config user.name 'github-actions[bot]'
git config user.email 'github-actions[bot]@users.noreply.github.com'
- name: Run Auto Merge Forward
id: run-auto-merge-forward
uses: spring-io/spring-security-release-tools/.github/actions/auto-merge-forward@actions-v1
with:
branches: ${{ needs.get-supported-branches.outputs.supported_versions }},main
from-author: dependabot[bot]
notify_result:
name: Check for failures
needs: [ auto-merge-forward-dependabot ]
if: failure()
runs-on: ubuntu-latest
permissions:
actions: read
steps:
- name: Send Slack message
uses: Gamesight/slack-workflow-status@v1.3.0
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
slack_webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }}
channel: '#spring-security-ci'
name: 'CI Notifier'
-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,32 +0,0 @@
name: Execute Gradle Wrapper Upgrade
on:
schedule:
- cron: '0 2 * * *' # 2am UTC
workflow_dispatch:
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@v3
- name: Upgrade Wrappers
run: ./gradlew clean upgradeGradleWrapperAll --continue -Porg.gradle.java.installations.auto-download=false
env:
WRAPPER_UPGRADE_GIT_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -1,45 +0,0 @@
name: Mark Duplicate Dependabot PRs
on:
pull_request:
types: [closed]
jobs:
check_duplicate_prs:
runs-on: ubuntu-latest
if: github.event.pull_request.merged == true && github.event.pull_request.user.login == 'dependabot[bot]'
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Extract Dependency Name from PR Title
id: extract
run: |
PR_TITLE="${{ github.event.pull_request.title }}"
DEPENDENCY_NAME=$(echo "$PR_TITLE" | awk -F ' from ' '{print $1}')
echo "dependency_name=$DEPENDENCY_NAME" >> $GITHUB_OUTPUT
- name: Find PRs
id: find_duplicates
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PRS=$(gh pr list --search 'milestone:${{ github.event.pull_request.milestone.title }} is:merged in:title "${{ steps.extract.outputs.dependency_name }}"' --json number --jq 'map(.number) | join(",")')
echo "prs=$PRS" >> $GITHUB_OUTPUT
- name: Label Duplicate PRs
if: steps.find_duplicates.outputs.prs != ''
env:
PRS: ${{ steps.find_duplicates.outputs.prs }}
CURRENT_PR_NUMBER: ${{ github.event.pull_request.number }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: bash
run: |
for i in ${PRS//,/ }
do
if [ ! $i -eq "$CURRENT_PR_NUMBER" ]; then
echo "Marking PR $i as duplicate"
gh pr edit "$i" --add-label "status: duplicate"
gh pr comment "$i" --body "Duplicate of #$CURRENT_PR_NUMBER"
fi
done
-63
View File
@@ -1,63 +0,0 @@
name: Merge Dependabot PR
on: pull_request_target
run-name: Merge Dependabot PR ${{ github.ref_name }}
permissions: write-all
jobs:
merge-dependabot-pr:
name: Merge Dependabot PR
runs-on: ubuntu-latest
if: ${{ github.event.pull_request.user.login == 'dependabot[bot]' && github.repository == 'spring-projects/spring-security' }}
steps:
- uses: actions/checkout@v4
with:
show-progress: false
ref: ${{ github.event.pull_request.head.sha }}
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 17
- name: Set Milestone to Dependabot Pull Request
id: set-milestone
run: |
if test -f pom.xml
then
CURRENT_VERSION=$(mvn help:evaluate -Dexpression="project.version" -q -DforceStdout)
else
CURRENT_VERSION=$(cat gradle.properties | sed -n '/^version=/ { s/^version=//;p }')
fi
export CANDIDATE_VERSION=${CURRENT_VERSION/-SNAPSHOT}
MILESTONE=$(gh api repos/$GITHUB_REPOSITORY/milestones --jq 'map(select(.due_on != null and (.title | startswith(env.CANDIDATE_VERSION)))) | .[0] | .title')
if [ -z $MILESTONE ]
then
gh run cancel ${{ github.run_id }}
echo "::warning title=Cannot merge::No scheduled milestone for $CURRENT_VERSION version"
else
gh pr edit ${{ github.event.pull_request.number }} --milestone $MILESTONE
echo mergeEnabled=true >> $GITHUB_OUTPUT
fi
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Merge Dependabot pull request
if: steps.set-milestone.outputs.mergeEnabled
run: gh pr merge ${{ github.event.pull_request.number }} --auto --rebase
env:
GH_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
send-notification:
name: Send Notification
needs: [ merge-dependabot-pr ]
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 }}
@@ -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.3.x, 6.2.x, 5.8.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,22 +0,0 @@
name: Trigger Dependabot Auto Merge Forward
on:
push:
branches:
- '*.x'
permissions: read-all
jobs:
trigger-worflow:
name: Trigger Workflow
runs-on: ubuntu-latest
if: ${{ github.event.commits[0].author.username == 'dependabot[bot]' && github.repository == 'spring-projects/spring-security' }}
steps:
- name: Checkout
id: checkout
uses: actions/checkout@v4
- id: trigger
env:
GH_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
run: gh workflow run dependabot-auto-merge-forward.yml -r main
@@ -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@852920ba3fb1f28b35a2f13201133bc00ef33677
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@852920ba3fb1f28b35a2f13201133bc00ef33677
name: Update
with:
docs-branch: 'docs-build'
token: ${{ secrets.GITHUB_TOKEN }}
-36
View File
@@ -1,36 +0,0 @@
name: Update dependabot.yml
on:
workflow_dispatch:
permissions:
contents: read
jobs:
get-supported-branches:
uses: spring-io/spring-security-release-tools/.github/workflows/retrieve-spring-supported-versions.yml@actions-v1
with:
project: spring-security
type: oss
repository_name: spring-projects/spring-security
main:
runs-on: ubuntu-latest
needs: [get-supported-branches]
if: ${{ (github.repository == 'spring-projects/spring-security') && (github.ref == 'refs/heads/main') }}
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- uses: spring-io/spring-security-release-tools/.github/actions/generate-dependabot-yml@actions-v1
name: Update dependabot.yml
with:
gradle-branches: ${{ needs.get-supported-branches.outputs.supported_versions }},main
github-actions-branches: ${{ needs.get-supported-branches.outputs.supported_versions }},main,docs-build
gh-token: ${{ secrets.GITHUB_TOKEN }}
- uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: Update dependabot.yml
@@ -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 }}
+1 -1
View File
@@ -105,7 +105,7 @@ Once merged, the fix will be forwarded-ported to applicable branches including `
1. Create a local branch
If this is for an issue, consider a branch name with the issue number, like `gh-22276`.
[[write-tests]]
1. Add documentation and JUnit Tests for your changes.
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]]
+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.
@@ -118,7 +118,8 @@ public class AclPermissionEvaluator implements PermissionEvaluator {
if (permission instanceof Permission[]) {
return Arrays.asList((Permission[]) permission);
}
if (permission instanceof String permString) {
if (permission instanceof String) {
String permString = (String) permission;
Permission p = buildPermission(permString);
if (p != null) {
return Arrays.asList(p);
@@ -56,9 +56,10 @@ public abstract class AbstractPermission implements Permission {
if (obj == null) {
return false;
}
if (!(obj instanceof Permission other)) {
if (!(obj instanceof Permission)) {
return false;
}
Permission other = (Permission) obj;
return (this.mask == other.getMask());
}
@@ -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());
@@ -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);
@@ -112,7 +112,7 @@ public class JdbcAclServiceTests {
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);
assertThat(objectIdentities.size()).isEqualTo(1);
assertThat(objectIdentities.get(0).getIdentifier()).isEqualTo("5577");
}
@@ -127,7 +127,7 @@ public class JdbcAclServiceTests {
public void findChildrenWithoutIdType() {
ObjectIdentity objectIdentity = new ObjectIdentityImpl(MockLongIdDomainObject.class, 4711L);
List<ObjectIdentity> objectIdentities = this.aclServiceIntegration.findChildren(objectIdentity);
assertThat(objectIdentities).hasSize(1);
assertThat(objectIdentities.size()).isEqualTo(1);
assertThat(objectIdentities.get(0).getType()).isEqualTo(MockUntypedIdDomainObject.class.getName());
assertThat(objectIdentities.get(0).getIdentifier()).isEqualTo(5000L);
}
@@ -143,7 +143,7 @@ public class JdbcAclServiceTests {
public void findChildrenOfIdTypeLong() {
ObjectIdentity objectIdentity = new ObjectIdentityImpl("location", "US-PAL");
List<ObjectIdentity> objectIdentities = this.aclServiceIntegration.findChildren(objectIdentity);
assertThat(objectIdentities).hasSize(2);
assertThat(objectIdentities.size()).isEqualTo(2);
assertThat(objectIdentities.get(0).getType()).isEqualTo(MockLongIdDomainObject.class.getName());
assertThat(objectIdentities.get(0).getIdentifier()).isEqualTo(4711L);
assertThat(objectIdentities.get(1).getType()).isEqualTo(MockLongIdDomainObject.class.getName());
@@ -155,7 +155,7 @@ public class JdbcAclServiceTests {
ObjectIdentity objectIdentity = new ObjectIdentityImpl("location", "US");
this.aclServiceIntegration.setAclClassIdSupported(true);
List<ObjectIdentity> objectIdentities = this.aclServiceIntegration.findChildren(objectIdentity);
assertThat(objectIdentities).hasSize(1);
assertThat(objectIdentities.size()).isEqualTo(1);
assertThat(objectIdentities.get(0).getType()).isEqualTo("location");
assertThat(objectIdentities.get(0).getIdentifier()).isEqualTo("US-PAL");
}
@@ -165,7 +165,7 @@ public class JdbcAclServiceTests {
ObjectIdentity objectIdentity = new ObjectIdentityImpl(MockUntypedIdDomainObject.class, 5000L);
this.aclServiceIntegration.setAclClassIdSupported(true);
List<ObjectIdentity> objectIdentities = this.aclServiceIntegration.findChildren(objectIdentity);
assertThat(objectIdentities).hasSize(1);
assertThat(objectIdentities.size()).isEqualTo(1);
assertThat(objectIdentities.get(0).getType()).isEqualTo("costcenter");
assertThat(objectIdentities.get(0).getIdentifier())
.isEqualTo(UUID.fromString("25d93b3f-c3aa-4814-9d5e-c7c96ced7762"));
@@ -186,7 +186,7 @@ public class JdbcAclServiceTests {
ObjectIdentity objectIdentity = new ObjectIdentityImpl("location", "US");
this.aclServiceIntegration.setAclClassIdSupported(true);
List<ObjectIdentity> objectIdentities = this.aclServiceIntegration.findChildren(objectIdentity);
assertThat(objectIdentities).hasSize(1);
assertThat(objectIdentities.size()).isEqualTo(1);
assertThat(objectIdentities.get(0).getType()).isEqualTo("location");
assertThat(objectIdentities.get(0).getIdentifier()).isEqualTo("prefix:US-PAL");
}
@@ -454,7 +454,7 @@ public class JdbcMutableAclServiceTests {
CustomSid customSid = new CustomSid("Custom sid");
given(customJdbcMutableAclService.createOrRetrieveSidPrimaryKey("Custom sid", false, false)).willReturn(1L);
Long result = customJdbcMutableAclService.createOrRetrieveSidPrimaryKey(customSid, false);
assertThat(Long.valueOf(1L)).isEqualTo(result);
assertThat(new Long(1L)).isEqualTo(result);
}
protected Authentication getAuth() {
@@ -120,9 +120,9 @@ public class SidTests {
PrincipalSid principalSid = new PrincipalSid(authentication);
GrantedAuthority ga = new SimpleGrantedAuthority("ROLE_TEST");
GrantedAuthoritySid gaSid = new GrantedAuthoritySid(ga);
assertThat("johndoe").isEqualTo(principalSid.getPrincipal());
assertThat("johndoe".equals(principalSid.getPrincipal())).isTrue();
assertThat("scott".equals(principalSid.getPrincipal())).isFalse();
assertThat("ROLE_TEST").isEqualTo(gaSid.getGrantedAuthority());
assertThat("ROLE_TEST".equals(gaSid.getGrantedAuthority())).isTrue();
assertThat("ROLE_TEST2".equals(gaSid.getGrantedAuthority())).isFalse();
}
+6
View File
@@ -29,4 +29,10 @@ dependencies {
testAspect sourceSets.main.output
}
sourceSets.main.aspectj.srcDir "src/main/java"
sourceSets.main.java.srcDirs = files()
sourceSets.test.aspectj.srcDir "src/test/java"
sourceSets.test.java.srcDirs = files()
compileAspectj.ajcOptions.outxmlfile = "META-INF/aop.xml"
@@ -143,8 +143,8 @@ public class AnnotationSecurityAspectTests {
SecurityContextHolder.getContext().setAuthentication(this.anne);
List<String> objects = this.prePostSecured.postFilterMethod();
assertThat(objects).hasSize(2);
assertThat(objects).contains("apple");
assertThat(objects).contains("aubergine");
assertThat(objects.contains("apple")).isTrue();
assertThat(objects.contains("aubergine")).isTrue();
}
private void configureForElAnnotations() {
@@ -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-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.
@@ -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')")
@@ -149,10 +134,6 @@ public class PreAuthorizeAspectTests {
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.
+12 -37
View File
@@ -14,17 +14,12 @@ buildscript {
}
}
plugins {
alias(libs.plugins.org.gradle.wrapper.upgrade)
}
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'
apply plugin: 'org.springframework.security.check-expected-branch-version'
apply plugin: 'io.spring.security.release'
group = 'org.springframework.security'
@@ -36,7 +31,6 @@ ext.milestoneBuild = !(snapshotBuild || releaseBuild)
repositories {
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
}
springRelease {
@@ -47,31 +41,19 @@ springRelease {
replaceSnapshotVersionInReferenceDocUrl = true
}
def toolchainVersion() {
if (project.hasProperty('testToolchain')) {
return project.property('testToolchain').toString().toInteger()
}
return 17
}
subprojects {
java {
toolchain {
languageVersion = JavaLanguageVersion.of(toolchainVersion())
plugins.withType(JavaPlugin) {
java {
sourceCompatibility=JavaVersion.VERSION_17
}
}
kotlin {
jvmToolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
tasks.withType(JavaCompile).configureEach {
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
options.compilerArgs.add("-parameters")
options.release.set(17)
}
}
allprojects {
if (!['spring-security-bom', 'spring-security-docs'].contains(project.name)) {
apply plugin: 'io.spring.javaformat'
@@ -96,6 +78,12 @@ allprojects {
}
}
}
tasks.withType(JavaCompile).configureEach {
javaCompiler = javaToolchains.compilerFor {
languageVersion = JavaLanguageVersion.of(17)
}
}
}
develocity {
@@ -106,14 +94,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")
@@ -125,12 +109,3 @@ s101 {
repository = 'https://structure101.com/binaries/latest'
configurationDirectory = project.file("etc/s101")
}
wrapperUpgrade {
gradle {
'spring-security' {
repo = 'spring-projects/spring-security'
baseBranch = '6.2.x' // runs only on 6.2.x and the update is merged forward to main
}
}
}
-5
View File
@@ -11,7 +11,6 @@ java {
repositories {
gradlePluginPortal()
mavenCentral()
maven { url 'https://repo.spring.io/milestone' }
}
sourceSets {
@@ -47,10 +46,6 @@ gradlePlugin {
id = "org.springframework.security.versions.verify-dependencies-versions"
implementationClass = "org.springframework.security.convention.versions.VerifyDependenciesVersionsPlugin"
}
checkExpectedBranchVersion {
id = "org.springframework.security.check-expected-branch-version"
implementationClass = "org.springframework.security.CheckExpectedBranchVersionPlugin"
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2016-2019 the original author or authors.
* Copyright 2016-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
@@ -63,7 +63,6 @@ class EclipsePlugin implements Plugin<Project> {
// test sources. Relax those from error to warning
properties['org.eclipse.jdt.core.circularClasspath'] = 'warning'
properties['org.eclipse.jdt.core.incompleteClasspath'] = 'warning'
properties['org.eclipse.jdt.core.compiler.codegen.methodParameters'] = 'generate'
}
}
}
@@ -35,7 +35,9 @@ public class CheckClasspathForProhibitedDependenciesPlugin implements Plugin<Pro
@Override
public void apply(Project project) {
project.getPlugins().apply(CheckProhibitedDependenciesLifecyclePlugin.class);
project.getPlugins().withType(JavaBasePlugin.class, javaBasePlugin -> configureProhibitedDependencyChecks(project));
project.getPlugins().withType(JavaBasePlugin.class, javaBasePlugin -> {
configureProhibitedDependencyChecks(project);
});
}
private void configureProhibitedDependencyChecks(Project project) {
@@ -34,6 +34,8 @@ public class CheckProhibitedDependenciesLifecyclePlugin implements Plugin<Projec
task.setGroup(JavaBasePlugin.VERIFICATION_GROUP);
task.setDescription("Checks both the compile/runtime classpath of every SourceSet for prohibited dependencies");
});
project.getTasks().named(JavaBasePlugin.CHECK_TASK_NAME, checkTask -> checkTask.dependsOn(checkProhibitedDependencies));
project.getTasks().named(JavaBasePlugin.CHECK_TASK_NAME, checkTask -> {
checkTask.dependsOn(checkProhibitedDependencies);
});
}
}
@@ -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;
@@ -21,6 +21,7 @@ import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.publish.Publication;
import org.gradle.api.publish.PublishingExtension;
import org.gradle.api.publish.plugins.PublishingPlugin;
import org.gradle.plugins.signing.SigningExtension;
import org.gradle.plugins.signing.SigningPlugin;
@@ -43,7 +44,12 @@ public class SpringSigningPlugin implements Plugin<Project> {
private void sign(Project project) {
SigningExtension signing = project.getExtensions().findByType(SigningExtension.class);
signing.setRequired((Callable<Boolean>) () -> project.getGradle().getTaskGraph().hasTask("publishArtifacts"));
signing.setRequired(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
return project.getGradle().getTaskGraph().hasTask("publishArtifacts");
}
});
String signingKeyId = (String) project.findProperty("signingKeyId");
String signingKey = (String) project.findProperty("signingKey");
String signingPassword = (String) project.findProperty("signingPassword");
@@ -1,115 +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;
import org.gradle.api.DefaultTask;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.plugins.JavaBasePlugin;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.CacheableTask;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;
import org.gradle.api.tasks.TaskExecutionException;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.api.tasks.VerificationException;
import java.io.IOException;
import java.nio.file.Files;
/**
* @author Marcus da Coregio
*/
public class CheckExpectedBranchVersionPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
TaskProvider<CheckExpectedBranchVersionTask> checkExpectedBranchVersionTask = project.getTasks().register("checkExpectedBranchVersion", CheckExpectedBranchVersionTask.class, (task) -> {
task.setGroup("Build");
task.setDescription("Check if the project version matches the branch version");
task.onlyIf("skipCheckExpectedBranchVersion property is false or not present", CheckExpectedBranchVersionPlugin::skipPropertyFalseOrNotPresent);
task.getVersion().convention(project.provider(() -> project.getVersion().toString()));
task.getBranchName().convention(project.getProviders().exec(execSpec -> execSpec.setCommandLine("git", "symbolic-ref", "--short", "HEAD")).getStandardOutput().getAsText());
task.getOutputFile().convention(project.getLayout().getBuildDirectory().file("check-expected-branch-version"));
});
project.getTasks().named(JavaBasePlugin.CHECK_TASK_NAME, checkTask -> checkTask.dependsOn(checkExpectedBranchVersionTask));
}
private static boolean skipPropertyFalseOrNotPresent(Task task) {
return task.getProject()
.getProviders()
.gradleProperty("skipCheckExpectedBranchVersion")
.orElse("false")
.map("false"::equalsIgnoreCase)
.get();
}
@CacheableTask
public static abstract class CheckExpectedBranchVersionTask extends DefaultTask {
@Input
abstract Property<String> getVersion();
@Input
abstract Property<String> getBranchName();
@OutputFile
abstract RegularFileProperty getOutputFile();
@TaskAction
public void run() {
String version = getVersion().get();
String branchVersion = getBranchName().map(String::trim).get();
if (!branchVersion.matches("^[0-9]+\\.[0-9]+\\.x$")) {
String msg = String.format("Branch version [%s] does not match *.x, ignoring", branchVersion);
getLogger().warn(msg);
writeExpectedVersionOutput(msg);
return;
}
if (!versionsMatch(version, branchVersion)) {
String msg = String.format("Project version [%s] does not match branch version [%s]. " +
"Please verify that the branch contains the right version.", version, branchVersion);
writeExpectedVersionOutput(msg);
throw new VerificationException(msg);
}
writeExpectedVersionOutput(version);
}
private void writeExpectedVersionOutput(String fileContent) {
try {
Files.writeString(getOutputFile().get().getAsFile().toPath(), fileContent);
} catch (IOException e) {
throw new TaskExecutionException(this, e);
}
}
private boolean versionsMatch(String projectVersion, String branchVersion) {
String[] projectVersionParts = projectVersion.split("\\.");
String[] branchVersionParts = branchVersion.split("\\.");
if (projectVersionParts.length < 2 || branchVersionParts.length < 2) {
return false;
}
return projectVersionParts[0].equals(branchVersionParts[0]) && projectVersionParts[1].equals(branchVersionParts[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.
@@ -19,79 +19,52 @@ package org.springframework.security.convention.versions;
import org.gradle.api.DefaultTask;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
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.RegularFileProperty;
import org.gradle.api.plugins.JavaBasePlugin;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.CacheableTask;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;
import org.gradle.api.tasks.TaskExecutionException;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.api.tasks.VerificationException;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Optional;
public class VerifyDependenciesVersionsPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
VersionCatalog versionCatalog = project.getExtensions().getByType(VersionCatalogsExtension.class).named("libs");
Optional<Provider<MinimalExternalModuleDependency>> oauth2OidcSdk = versionCatalog.findLibrary("com-nimbusds-oauth2-oidc-sdk");
Optional<Provider<MinimalExternalModuleDependency>> nimbusJoseJwt = versionCatalog.findLibrary("com-nimbusds-nimbus-jose-jwt");
if (oauth2OidcSdk.isEmpty()) {
throw new VerificationException("Library [com-nimbusds-oauth2-oidc-sdk] does not exist in the version catalog named libs.");
}
if (nimbusJoseJwt.isEmpty()) {
throw new VerificationException("Library [com-nimbusds-nimbus-jose-jwt] does not exist in the version catalog named libs.");
}
TaskProvider<VerifyDependenciesVersionsTask> verifyDependenciesVersionsTaskProvider = project.getTasks().register("verifyDependenciesVersions", VerifyDependenciesVersionsTask.class, (task) -> {
task.setGroup("Verification");
task.setDescription("Verify that specific dependencies are using the same version");
task.getOauth2OidcSdkVersion().convention(oauth2OidcSdk.get().map(Dependency::getVersion));
task.getExpectedNimbusJoseJwtVersion().convention(nimbusJoseJwt.get().map(Dependency::getVersion));
task.getOutputFile().convention(project.getLayout().getBuildDirectory().file("verify-dependencies-versions"));
VersionCatalog versionCatalog = project.getExtensions().getByType(VersionCatalogsExtension.class).named("libs");
MinimalExternalModuleDependency oauth2OidcSdk = versionCatalog.findLibrary("com-nimbusds-oauth2-oidc-sdk").get().get();
MinimalExternalModuleDependency nimbusJoseJwt = versionCatalog.findLibrary("com-nimbusds-nimbus-jose-jwt").get().get();
task.setOauth2OidcSdkVersion(oauth2OidcSdk.getVersion());
task.setExpectedNimbusJoseJwtVersion(nimbusJoseJwt.getVersion());
});
project.getTasks().named(JavaBasePlugin.CHECK_TASK_NAME, checkTask -> checkTask.dependsOn(verifyDependenciesVersionsTaskProvider));
}
@CacheableTask
public abstract static class VerifyDependenciesVersionsTask extends DefaultTask {
public static class VerifyDependenciesVersionsTask extends DefaultTask {
@Input
abstract Property<String> getOauth2OidcSdkVersion();
private String oauth2OidcSdkVersion;
@Input
abstract Property<String> getExpectedNimbusJoseJwtVersion();
private String expectedNimbusJoseJwtVersion;
@OutputFile
abstract RegularFileProperty getOutputFile();
public void setOauth2OidcSdkVersion(String oauth2OidcSdkVersion) {
this.oauth2OidcSdkVersion = oauth2OidcSdkVersion;
}
public void setExpectedNimbusJoseJwtVersion(String expectedNimbusJoseJwtVersion) {
this.expectedNimbusJoseJwtVersion = expectedNimbusJoseJwtVersion;
}
@TaskAction
public void verify() {
String oauth2OidcSdkVersion = this.getOauth2OidcSdkVersion().get();
String transitiveNimbusJoseJwtVersion = TransitiveDependencyLookupUtils.lookupJwtVersion(oauth2OidcSdkVersion);
String expectedNimbusJoseJwtVersion = this.getExpectedNimbusJoseJwtVersion().get();
if (!transitiveNimbusJoseJwtVersion.equals(expectedNimbusJoseJwtVersion)) {
String message = String.format("Found transitive nimbus-jose-jwt:%s in oauth2-oidc-sdk:%s, but the project contains a different version of nimbus-jose-jwt [%s]. Please align the versions.", transitiveNimbusJoseJwtVersion, oauth2OidcSdkVersion, expectedNimbusJoseJwtVersion);
throw new VerificationException(message);
}
String message = String.format("Found transitive nimbus-jose-jwt:%s in oauth2-oidc-sdk:%s, the project contains expected version of nimbus-jose-jwt [%s]. Verified all versions align.", transitiveNimbusJoseJwtVersion, oauth2OidcSdkVersion, expectedNimbusJoseJwtVersion);
try {
Files.writeString(getOutputFile().get().getAsFile().toPath(), message);
} catch (IOException e) {
throw new TaskExecutionException(this, e);
public void verify() {
String transitiveNimbusJoseJwtVersion = TransitiveDependencyLookupUtils.lookupJwtVersion(this.oauth2OidcSdkVersion);
if (!transitiveNimbusJoseJwtVersion.equals(this.expectedNimbusJoseJwtVersion)) {
String message = String.format("Found transitive nimbus-jose-jwt:%s in oauth2-oidc-sdk:%s, but the project contains a different version of nimbus-jose-jwt [%s]. Please align the versions.", transitiveNimbusJoseJwtVersion, this.oauth2OidcSdkVersion, this.expectedNimbusJoseJwtVersion);
throw new IllegalStateException(message);
}
}
}
}
+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
@@ -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() {
@@ -18,7 +18,6 @@ package org.springframework.security.cas.userdetails;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import org.apereo.cas.client.validation.Assertion;
@@ -74,8 +73,7 @@ public final class GrantedAuthorityFromAssertionAttributesUserDetailsService
}
private SimpleGrantedAuthority createSimpleGrantedAuthority(Object o) {
return new SimpleGrantedAuthority(
this.convertToUpperCase ? o.toString().toUpperCase(Locale.ROOT) : o.toString());
return new SimpleGrantedAuthority(this.convertToUpperCase ? o.toString().toUpperCase() : o.toString());
}
/**
@@ -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;
@@ -40,21 +39,14 @@ import org.springframework.security.core.Authentication;
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.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
@@ -62,14 +54,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>
@@ -80,22 +72,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
@@ -204,13 +196,6 @@ public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFil
private SecurityContextRepository securityContextRepository = new HttpSessionSecurityContextRepository();
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
.getContextHolderStrategy();
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
private RequestCache requestCache = new HttpSessionRequestCache();
private final AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
public CasAuthenticationFilter() {
@@ -229,10 +214,9 @@ public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFil
}
this.logger.debug(
LogMessage.format("Authentication success. Updating SecurityContextHolder to contain: %s", authResult));
SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authResult);
this.securityContextHolderStrategy.setContext(context);
SecurityContextHolder.setContext(context);
this.securityContextRepository.saveContext(context, request, response);
if (this.eventPublisher != null) {
this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
@@ -251,22 +235,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 = "";
}
@@ -338,34 +307,6 @@ public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFil
this.securityContextRepository = securityContextRepository;
}
@Override
public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
super.setSecurityContextHolderStrategy(securityContextHolderStrategy);
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;
@@ -38,11 +37,8 @@ import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.AuthorityUtils;
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.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.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
@@ -226,25 +222,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);
@@ -255,15 +232,4 @@ public class CasAuthenticationFilterTests {
verify(securityContextRepository).saveContext(any(SecurityContext.class), any(), any());
}
@Test
void successfulAuthenticationWhenSecurityContextHolderStrategySetThenUses() throws ServletException, IOException {
SecurityContextHolderStrategy securityContextRepository = mock(SecurityContextHolderStrategy.class);
given(securityContextRepository.createEmptyContext()).willReturn(new SecurityContextImpl());
CasAuthenticationFilter filter = new CasAuthenticationFilter();
filter.setSecurityContextHolderStrategy(securityContextRepository);
filter.successfulAuthentication(new MockHttpServletRequest(), new MockHttpServletResponse(),
new MockFilterChain(), mock(Authentication.class));
verify(securityContextRepository).setContext(any(SecurityContext.class));
}
}
@@ -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();
}
}
+4 -44
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,8 @@ dependencies {
testImplementation "org.hibernate.orm:hibernate-core"
testImplementation 'org.hsqldb:hsqldb'
testImplementation 'org.mockito:mockito-core'
testImplementation('org.seleniumhq.selenium:htmlunit3-driver') {
testImplementation "org.mockito:mockito-inline"
testImplementation('org.seleniumhq.selenium:htmlunit-driver') {
exclude group: 'commons-logging', module: 'commons-logging'
exclude group: 'xml-apis', module: 'xml-apis'
}
@@ -121,9 +113,6 @@ dependencies {
testImplementation ('org.apache.maven.resolver:maven-resolver-transport-http') {
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'
}
@@ -165,32 +154,3 @@ tasks.withType(KotlinCompile).configureEach {
jvmTarget = "17"
}
}
configure(project.tasks.withType(Test)) {
doFirst {
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,356 +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
*/
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);
}
}
}
@@ -294,7 +294,7 @@ public class RSocketMessageHandlerITests {
@MessageMapping({ "secure.send", "send" })
Mono<Void> send(Mono<String> payload) {
return payload.doOnNext(this::add).then(Mono.fromRunnable(this::doNotifyAll));
return payload.doOnNext(this::add).then(Mono.fromRunnable(() -> doNotifyAll()));
}
private synchronized void doNotifyAll() {
@@ -87,7 +87,7 @@ public class LdapUserServiceBeanDefinitionParserTests {
Set<String> authorities = AuthorityUtils.authorityListToSet(ben.getAuthorities());
assertThat(authorities).hasSize(3);
assertThat(authorities).contains("ROLE_DEVELOPERS");
assertThat(authorities.contains("ROLE_DEVELOPERS")).isTrue();
}
@Test
@@ -128,7 +128,7 @@ public class LdapUserServiceBeanDefinitionParserTests {
Set<String> authorities = AuthorityUtils.authorityListToSet(ben.getAuthorities());
assertThat(authorities).hasSize(3);
assertThat(authorities).contains("ROLE_DEVELOPER");
assertThat(authorities.contains("ROLE_DEVELOPER")).isTrue();
}
@@ -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.4. Please update your schema declarations to the 6.4 schema.",
+ "with Spring Security 6.1. Please update your schema declarations to the 6.1 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\\.4.*.xsd.*")
return schemaLocation.matches("(?m).*spring-security-6\\.1.*.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-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.
@@ -27,8 +27,6 @@ import java.util.Map;
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 +77,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 +92,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.
@@ -140,10 +117,7 @@ public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBui
* @param configurer
* @return the {@link SecurityConfigurerAdapter} for further customizations
* @throws Exception
* @deprecated For removal in 7.0. Use
* {@link #with(SecurityConfigurerAdapter, Customizer)} instead.
*/
@Deprecated(since = "6.2", forRemoval = true)
@SuppressWarnings("unchecked")
public <C extends SecurityConfigurerAdapter<O, B>> C apply(C configurer) throws Exception {
configurer.addObjectPostProcessor(this.objectPostProcessor);
@@ -165,23 +139,6 @@ public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBui
return configurer;
}
/**
* Applies a {@link SecurityConfigurerAdapter} to this {@link SecurityBuilder} and
* invokes {@link SecurityConfigurerAdapter#setBuilder(SecurityBuilder)}.
* @param configurer
* @return the {@link SecurityBuilder} for further customizations
* @throws Exception
* @since 6.2
*/
@SuppressWarnings("unchecked")
public <C extends SecurityConfigurerAdapter<O, B>> B with(C configurer, Customizer<C> customizer) throws Exception {
configurer.addObjectPostProcessor(this.objectPostProcessor);
configurer.setBuilder((B) this);
add(configurer);
customizer.customize(configurer);
return (B) this;
}
/**
* Sets an object that is shared by multiple {@link SecurityConfigurer}.
* @param sharedType the Class to key the shared object by.
@@ -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;
@@ -67,23 +67,12 @@ public class AuthenticationManagerBuilder
/**
* Creates a new instance
* @param objectPostProcessor the
* {@link org.springframework.security.config.annotation.ObjectPostProcessor} instance
* to use.
* @param objectPostProcessor the {@link ObjectPostProcessor} instance to use.
*/
public AuthenticationManagerBuilder(ObjectPostProcessor<Object> objectPostProcessor) {
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,57 +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;
if (passwordEncoder != null) {
provider = new DaoAuthenticationProvider(passwordEncoder);
}
else {
provider = new DaoAuthenticationProvider();
}
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]));
}
/**
@@ -120,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,7 +22,7 @@ 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;
@@ -140,16 +140,6 @@ public class LdapAuthenticationProviderConfigurer<B extends ProviderManagerBuild
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;
@@ -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,66 +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 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.AuthorizeReturnObjectMethodInterceptor;
import org.springframework.security.config.Customizer;
@Configuration(proxyBeanMethods = false)
final class AuthorizationProxyConfiguration implements AopInfrastructureBean {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static AuthorizationAdvisorProxyFactory authorizationProxyFactory(
ObjectProvider<Customizer<AuthorizationAdvisorProxyFactory>> customizers) {
AuthorizationAdvisorProxyFactory factory = new AuthorizationAdvisorProxyFactory(new ArrayList<>());
customizers.forEach((c) -> c.customize(factory));
return factory;
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static MethodInterceptor authorizeReturnObjectMethodInterceptor(ObjectProvider<AuthorizationAdvisor> provider,
AuthorizationAdvisorProxyFactory authorizationProxyFactory) {
provider.forEach(authorizationProxyFactory::addAdvisor);
AuthorizeReturnObjectMethodInterceptor interceptor = new AuthorizeReturnObjectMethodInterceptor(
authorizationProxyFactory);
authorizationProxyFactory.addAdvisor(interceptor);
return interceptor;
}
@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,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;
}
}
@@ -19,26 +19,18 @@ package org.springframework.security.config.annotation.method.configuration;
import java.util.function.Supplier;
import io.micrometer.observation.ObservationRegistry;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.authorization.ObservationAuthorizationManager;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
import org.springframework.security.authorization.method.MethodInvocationResult;
import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedHandler;
import org.springframework.security.core.Authentication;
import org.springframework.util.function.SingletonSupplier;
final class DeferringObservationAuthorizationManager<T>
implements AuthorizationManager<T>, MethodAuthorizationDeniedHandler {
final class DeferringObservationAuthorizationManager<T> implements AuthorizationManager<T> {
private final Supplier<AuthorizationManager<T>> delegate;
private MethodAuthorizationDeniedHandler handler = new ThrowingMethodAuthorizationDeniedHandler();
DeferringObservationAuthorizationManager(ObjectProvider<ObservationRegistry> provider,
AuthorizationManager<T> delegate) {
this.delegate = SingletonSupplier.of(() -> {
@@ -48,9 +40,6 @@ final class DeferringObservationAuthorizationManager<T>
}
return new ObservationAuthorizationManager<>(registry, delegate);
});
if (delegate instanceof MethodAuthorizationDeniedHandler h) {
this.handler = h;
}
}
@Override
@@ -58,15 +47,4 @@ final class DeferringObservationAuthorizationManager<T>
return this.delegate.get().check(authentication, object);
}
@Override
public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
return this.handler.handleDeniedInvocation(methodInvocation, authorizationResult);
}
@Override
public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult,
AuthorizationResult authorizationResult) {
return this.handler.handleDeniedInvocationResult(methodInvocationResult, authorizationResult);
}
}
@@ -19,27 +19,19 @@ package org.springframework.security.config.annotation.method.configuration;
import java.util.function.Supplier;
import io.micrometer.observation.ObservationRegistry;
import org.aopalliance.intercept.MethodInvocation;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.authorization.ObservationReactiveAuthorizationManager;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
import org.springframework.security.authorization.method.MethodInvocationResult;
import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedHandler;
import org.springframework.security.core.Authentication;
import org.springframework.util.function.SingletonSupplier;
final class DeferringObservationReactiveAuthorizationManager<T>
implements ReactiveAuthorizationManager<T>, MethodAuthorizationDeniedHandler {
final class DeferringObservationReactiveAuthorizationManager<T> implements ReactiveAuthorizationManager<T> {
private final Supplier<ReactiveAuthorizationManager<T>> delegate;
private MethodAuthorizationDeniedHandler handler = new ThrowingMethodAuthorizationDeniedHandler();
DeferringObservationReactiveAuthorizationManager(ObjectProvider<ObservationRegistry> provider,
ReactiveAuthorizationManager<T> delegate) {
this.delegate = SingletonSupplier.of(() -> {
@@ -49,9 +41,6 @@ final class DeferringObservationReactiveAuthorizationManager<T>
}
return new ObservationReactiveAuthorizationManager<>(registry, delegate);
});
if (delegate instanceof MethodAuthorizationDeniedHandler h) {
this.handler = h;
}
}
@Override
@@ -59,15 +48,4 @@ final class DeferringObservationReactiveAuthorizationManager<T>
return this.delegate.get().check(authentication, object);
}
@Override
public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
return this.handler.handleDeniedInvocation(methodInvocation, authorizationResult);
}
@Override
public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult,
AuthorizationResult authorizationResult) {
return this.handler.handleDeniedInvocationResult(methodInvocationResult, authorizationResult);
}
}
@@ -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-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,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,18 +37,11 @@ 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 isObservabilityPresent = ClassUtils
.isPresent("io.micrometer.observation.ObservationRegistry", null);
private final ImportSelector autoProxy = new AutoProxyRegistrarSelector();
@Override
public String[] selectImports(@NonNull AnnotationMetadata importMetadata) {
if (!importMetadata.hasAnnotation(EnableMethodSecurity.class.getName())
&& !importMetadata.hasMetaAnnotation(EnableMethodSecurity.class.getName())) {
if (!importMetadata.hasAnnotation(EnableMethodSecurity.class.getName())) {
return new String[0];
}
EnableMethodSecurity annotation = importMetadata.getAnnotations().get(EnableMethodSecurity.class).synthesize();
@@ -63,13 +55,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 (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-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,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,115 +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;
@Autowired(required = false)
ReactiveAuthorizationManagerMethodSecurityConfiguration(MethodSecurityExpressionHandler expressionHandler,
ObjectProvider<ObjectPostProcessor<ReactiveAuthorizationManager<MethodInvocation>>> preAuthorizePostProcessor,
ObjectProvider<ObjectPostProcessor<ReactiveAuthorizationManager<MethodInvocationResult>>> postAuthorizePostProcessor) {
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();
@@ -171,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();
@@ -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,12 +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 isObservabilityPresent = ClassUtils
.isPresent("io.micrometer.observation.ObservationRegistry", null);
private final ImportSelector autoProxy = new AutoProxyRegistrarSelector();
@Override
@@ -58,21 +51,13 @@ class ReactiveMethodSecuritySelector implements ImportSelector {
else {
imports.add(ReactiveMethodSecurityConfiguration.class.getName());
}
if (isDataPresent) {
imports.add(AuthorizationProxyDataConfiguration.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 {
}
@@ -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() };
}
}

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