1
0
mirror of synced 2026-09-09 02:39:53 +00:00

Compare commits

..

1 Commits

Author SHA1 Message Date
Josh Cummings e3e7d76b70 Add Workflow to Finalize a Release 2025-11-04 09:32:41 -07:00
437 changed files with 2702 additions and 11054 deletions
@@ -1,7 +0,0 @@
name: Build Release
runs:
using: composite
steps:
- name: Build Release
shell: bash
run: ./gradlew -PdeploymentRepository=$(pwd)/deployment-repository publishAllPublicationsToDeploymentRepository
@@ -1,7 +0,0 @@
name: Test Release
runs:
using: composite
steps:
- name: Test Release
shell: bash
run: ./gradlew build
+2
View File
@@ -0,0 +1,2 @@
require:
members: false
+121
View File
@@ -0,0 +1,121 @@
version: 2
registries:
spring-milestones:
type: maven-repository
url: https://repo.spring.io/milestone
shibboleth:
type: maven-repository
url: https://build.shibboleth.net/maven/releases
updates:
- package-ecosystem: gradle
target-branch: 6.5.x
directory: /
schedule:
interval: daily
time: '03:00'
timezone: Etc/UTC
labels:
- 'type: dependency-upgrade'
registries:
- spring-milestones
- shibboleth
ignore:
- dependency-name: com.nimbusds:nimbus-jose-jwt
- dependency-name: org.python:jython
- dependency-name: org.apache.directory.server:*
- dependency-name: org.apache.directory.shared:*
- dependency-name: org.junit:junit-bom
update-types:
- version-update:semver-major
- dependency-name: org.mockito:mockito-bom
update-types:
- version-update:semver-major
- dependency-name: '*'
update-types:
- version-update:semver-major
- version-update:semver-minor
- package-ecosystem: gradle
target-branch: 6.4.x
directory: /
schedule:
interval: daily
time: '03:00'
timezone: Etc/UTC
labels:
- 'type: dependency-upgrade'
registries:
- spring-milestones
- shibboleth
ignore:
- dependency-name: com.nimbusds:nimbus-jose-jwt
- dependency-name: org.python:jython
- dependency-name: org.apache.directory.server:*
- dependency-name: org.apache.directory.shared:*
- dependency-name: org.junit:junit-bom
update-types:
- version-update:semver-major
- dependency-name: org.mockito:mockito-bom
update-types:
- version-update:semver-major
- dependency-name: '*'
update-types:
- version-update:semver-major
- version-update:semver-minor
- package-ecosystem: gradle
target-branch: main
directory: /
schedule:
interval: daily
time: '03:00'
timezone: Etc/UTC
labels:
- 'type: dependency-upgrade'
registries:
- spring-milestones
- shibboleth
ignore:
- dependency-name: com.nimbusds:nimbus-jose-jwt
- dependency-name: org.python:jython
- dependency-name: org.apache.directory.server:*
- dependency-name: org.apache.directory.shared:*
- dependency-name: org.junit:junit-bom
update-types:
- version-update:semver-major
- dependency-name: org.mockito:mockito-bom
update-types:
- version-update:semver-major
- dependency-name: com.gradle.enterprise
update-types:
- version-update:semver-major
- version-update:semver-minor
- dependency-name: '*'
update-types:
- version-update:semver-major
- version-update:semver-minor
- package-ecosystem: 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'
-17
View File
@@ -1,17 +0,0 @@
workflow:
generator:
project:
java:
versions:
primary: 17
workflows:
release-train:
build:
env:
COMMERCIAL_REPO_USERNAME: secrets.COMMERCIAL_ARTIFACTORY_USERNAME
COMMERCIAL_REPO_PASSWORD: secrets.COMMERCIAL_ARTIFACTORY_PASSWORD
test:
env:
COMMERCIAL_REPO_USERNAME: secrets.COMMERCIAL_ARTIFACTORY_USERNAME
COMMERCIAL_REPO_PASSWORD: secrets.COMMERCIAL_ARTIFACTORY_PASSWORD
+38
View File
@@ -0,0 +1,38 @@
name: CI
on:
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:
snapshot-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,https://oss.sonatype.org/content/repositories/snapshots -PisOverrideVersionCatalog -PtestToolchain=${{ matrix.toolchain }} -PspringFrameworkVersion=7.+ -PreactorVersion=2025.+ -PspringDataVersion=2025.+ --stacktrace
secrets: inherit
send-notification:
name: Send Notification
needs: [ snapshot-test ]
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 }}
@@ -0,0 +1,23 @@
name: Clean build artifacts
on:
schedule:
- cron: '0 10 * * *' # Once per day at 10am UTC
permissions:
contents: read
jobs:
main:
runs-on: ubuntu-latest
if: ${{ github.repository == 'spring-projects/spring-security' }}
permissions:
contents: none
steps:
- name: Delete artifacts in cron job
env:
GH_ACTIONS_REPO_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
run: |
echo "Running clean build artifacts logic"
output=$(curl -X GET -H "Authorization: token $GH_ACTIONS_REPO_TOKEN" https://api.github.com/repos/spring-projects/spring-security/actions/artifacts | grep '"id"' | cut -d : -f2 | sed 's/,*$//g')
echo Output is $output
for id in $output; do curl -X DELETE -H "Authorization: token $GH_ACTIONS_REPO_TOKEN" https://api.github.com/repos/spring-projects/spring-security/actions/artifacts/$id; done;
+17
View File
@@ -0,0 +1,17 @@
name: "CodeQL Advanced"
on:
push:
pull_request:
workflow_dispatch:
schedule:
# https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#schedule
- cron: '0 5 * * *'
permissions: read-all
jobs:
codeql-analysis-call:
permissions:
actions: read
contents: read
security-events: write
uses: spring-io/github-actions/.github/workflows/codeql-analysis.yml@1
@@ -8,44 +8,66 @@ on:
- cron: '0 10 * * *' # Once per day at 10am UTC - cron: '0 10 * * *' # Once per day at 10am UTC
workflow_dispatch: # Manual trigger workflow_dispatch: # Manual trigger
env:
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
permissions: permissions:
contents: read contents: read
jobs: jobs:
build: build:
name: Build name: Build
uses: spring-projects/spring-security-commercial/.github/workflows/build.yml@workflows/v1 uses: spring-io/spring-security-release-tools/.github/workflows/build.yml@v1
secrets: inherit strategy:
test: matrix:
name: Test Against Snapshots os: [ ubuntu-latest, windows-latest ]
uses: spring-projects/spring-security-commercial/.github/workflows/test.yml@workflows/v1 jdk: [ 17 ]
with: with:
java-version: '17' runs-on: ${{ matrix.os }}
test-args: --refresh-dependencies -PforceMavenRepositories=snapshot -PisOverrideVersionCatalog -PtestToolchain=17 -PspringFrameworkVersion=7.0.+ -PreactorVersion=2025.0.+ -PspringDataVersion=2025.1.+ -PmicrometerVersion=1.16.+ --stacktrace java-version: ${{ matrix.jdk }}
distribution: temurin
secrets: inherit secrets: inherit
compute-version:
name: Compute Version
runs-on: ubuntu-latest
outputs:
snapshot: ${{ steps.project-version.outputs.snapshot }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # 7.0.0
- id: project-version
name: Extract Project Version
uses: spring-io/spring-release-actions/compute-version@a1f321783a0769dd2aea4fad6c2ae2f95a52b885 # 0.0.5
deploy-artifacts: deploy-artifacts:
name: Deploy Artifacts name: Deploy Artifacts
needs: [ build, test, compute-version ] needs: [ build]
if: needs.compute-version.outputs.snapshot == 'true' uses: spring-io/spring-security-release-tools/.github/workflows/deploy-artifacts.yml@v1
uses: spring-projects/spring-security-commercial/.github/workflows/deploy-artifacts.yml@workflows/v1 with:
should-deploy-artifacts: ${{ needs.build.outputs.should-deploy-artifacts }}
default-publish-milestones-central: true
secrets: inherit
deploy-docs:
name: Deploy Docs
needs: [ build ]
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-docs.yml@v1
with:
should-deploy-docs: ${{ needs.build.outputs.should-deploy-artifacts }}
secrets: inherit
deploy-schema:
name: Deploy Schema
needs: [ build ]
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://repo1.maven.org/maven2
release-repo-url: https://repo1.maven.org/maven2
artifact-path: org/springframework/security/spring-security-core
slack-announcing-id: spring-security-announcing
secrets: inherit secrets: inherit
send-notification: send-notification:
name: Send Notification name: Send Notification
needs: [ deploy-artifacts ] needs: [ perform-release ]
if: ${{ !success() }} if: ${{ !success() }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Send Notification - name: Send Notification
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15 uses: spring-io/spring-security-release-tools/.github/actions/send-notification@v1
with: with:
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }} webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
@@ -1,10 +0,0 @@
name: Dependabot PR Build
on: pull_request_target
jobs:
build:
name: Build
uses: spring-projects/spring-security-commercial/.github/workflows/build-pull-request.yml@workflows/v1
if: ${{ github.actor == 'dependabot[bot]' }}
secrets: inherit
+33
View File
@@ -0,0 +1,33 @@
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)
+41
View File
@@ -0,0 +1,41 @@
name: Finalize Release
on:
workflow_dispatch: # Manual trigger
env:
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
permissions:
contents: read
jobs:
project-version:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.project-version.outputs.version }}
steps:
- id: project-version
run: echo "version=$(grep '^version=' gradle.properties | cut -d'=' -f2)" >> $GITHUB_OUTPUT
perform-release:
name: Perform Release
needs: [ project-version ]
uses: spring-io/spring-security-release-tools/.github/workflows/perform-release.yml@v1
with:
should-perform-release: true
project-version: ${{ needs.project-version.outputs.version }}
milestone-repo-url: https://repo1.maven.org/maven2
release-repo-url: https://repo1.maven.org/maven2
artifact-path: org/springframework/security/spring-security-core
slack-announcing-id: spring-security-announcing
secrets: inherit
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 }}
@@ -0,0 +1,33 @@
name: Execute Gradle Wrapper Upgrade
on:
schedule:
- cron: '0 2 * * *' # 2am UTC
workflow_dispatch:
permissions:
pull-requests: write
jobs:
upgrade_wrapper:
name: Execution
runs-on: ubuntu-latest
steps:
- name: Set up Git configuration
env:
TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git config --global url."https://unused-username:${TOKEN}@github.com/".insteadOf "https://github.com/"
git config --global user.name 'github-actions[bot]'
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
- name: Checkout
uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Set up Gradle
uses: gradle/gradle-build-action@v2
- name: Upgrade Wrappers
run: ./gradlew clean upgradeGradleWrapperAll --continue -Porg.gradle.java.installations.auto-download=false
env:
WRAPPER_UPGRADE_GIT_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-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-commercial' }}
steps:
- uses: actions/checkout@v5
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 }}
@@ -0,0 +1,35 @@
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 }}
+41 -3
View File
@@ -8,6 +8,44 @@ permissions:
jobs: jobs:
build: build:
name: Build name: Build
uses: spring-projects/spring-security-commercial/.github/workflows/build-pull-request.yml@workflows/v1 runs-on: ubuntu-latest
if: ${{ github.actor != 'dependabot[bot]' }} if: ${{ github.repository == 'spring-projects/spring-security' }}
secrets: inherit 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 }}
@@ -1,24 +0,0 @@
name: Release Announcements - Stage
on:
push:
tags:
- '[0-9]+.[0-9]+.[0-9]+'
- '[0-9]+.[0-9]+.[0-9]+.[0-9]+'
workflow_dispatch:
inputs:
version:
description: The version to stage
required: true
type: string
permissions:
contents: read
jobs:
stage-release-announcements:
name: Stage Release Announcements
uses: spring-projects/spring-security-commercial/.github/workflows/release-announcements-stage.yml@workflows/v1
with:
version: ${{ inputs.version || github.ref_name }}
secrets: inherit
+24
View File
@@ -0,0 +1,24 @@
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.5.x, 6.4.x, 6.3.x ]
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Dispatch
env:
GH_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
run: gh workflow run update-scheduled-release-version.yml -r ${{ matrix.branch }}
-92
View File
@@ -1,92 +0,0 @@
# This file was auto-generated by github-actions-workflow-generator 0.0.6. Do not edit.
# To update it, modify .github/workflow-generator.yml as needed and re-run the generator.
name: "Release Train Build"
run-name: "${{ inputs.callback-ref }} Build"
"on":
workflow_dispatch:
inputs:
callback:
description: "Repository to which a callback should be made upon completion"
required: true
type: "string"
callback-ref:
description: "Ref in the callback repository to which a callback should be made upon completion"
required: true
type: "string"
release-train-maven-repository-url:
description: "URL of a Maven repository to be used to resolve artifacts of projects earlier in the train"
required: true
type: "string"
permissions:
contents: "read"
concurrency:
group: "${{ github.workflow }}-${{ github.ref }}"
jobs:
build-release:
name: "Build Release"
runs-on: "ubuntu22-2-8"
steps:
- name: "Prevent Re-runs"
id: "prevent-re-runs"
run: |-
if [ "$GITHUB_RUN_ATTEMPT" -gt 1 ]; then
echo "Re-runs are prohibited. Use the 'Release Train  Retry' workflow to retry build failures"
exit 1
fi
- name: "Set up Java"
id: "set-up-java"
uses: "actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95" # v5.6.0
with:
distribution: "liberica"
java-version: "17"
- name: "Check Out Code"
id: "check-out-code"
uses: "actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0" # v7.0.0
- name: "Build Release"
id: "build-release"
uses: "./.github/actions/release-train-build"
env:
COMMERCIAL_REPO_PASSWORD: "${{ secrets.COMMERCIAL_ARTIFACTORY_PASSWORD }}"
COMMERCIAL_REPO_USERNAME: "${{ secrets.COMMERCIAL_ARTIFACTORY_USERNAME }}"
RELEASE_TRAIN_MAVEN_REPOSITORY_PASSWORD: "${{ secrets.RELEASE_TRAIN_PARTICIPANT_MAVEN_REPOSITORY_PASSWORD }}"
RELEASE_TRAIN_MAVEN_REPOSITORY_URL: "${{ inputs.release-train-maven-repository-url }}"
RELEASE_TRAIN_MAVEN_REPOSITORY_USERNAME: "${{ secrets.RELEASE_TRAIN_PARTICIPANT_MAVEN_REPOSITORY_USERNAME }}"
- name: "Upload Deployment Repository"
id: "upload-deployment-repository"
uses: "actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a" # v7.0.1
with:
name: "deployment-repository"
path: "deployment-repository/**"
- name: "Upload Deployment Spec"
id: "upload-deployment-spec"
uses: "actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a" # v7.0.1
with:
archive: "false"
if-no-files-found: "ignore"
name: "deployment-spec"
path: ".github/actions/release-train-build/deployment-spec.yml"
- name: "Save Build System Caches"
id: "save-build-system-caches"
uses: "actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9" # v6.1.0
with:
key: "release-train-${{ inputs.callback-ref }}-${{ github.ref_name }}"
path: |-
~/.gradle/caches
~/.gradle/wrapper
- name: "Send Callback"
id: "send-callback"
if: "${{ !cancelled() }}"
env:
GH_TOKEN: "${{ secrets.RELEASE_TRAIN_PARTICIPANT_GITHUB_TOKEN }}"
run: |-
gh workflow run callback \
--repo ${{ inputs.callback }} \
--ref ${{ inputs.callback-ref }} \
--field commit-hash=${{ steps.check-out-code.outputs.commit }} \
--field deployment-repository-artifact-identifier=${{ steps.upload-deployment-repository.outputs.artifact-id }} \
--field deployment-spec-artifact-identifier=${{ steps.upload-deployment-spec.outputs.artifact-id }} \
--field release-branch=${{ github.ref_name }} \
--field release-repository=${{ github.repository }} \
--field result=${{ job.status == 'success' && 'built' || 'build-failed' }} \
--field workflow-run-url=${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
-55
View File
@@ -1,55 +0,0 @@
# This file was auto-generated by github-actions-workflow-generator 0.0.6. Do not edit.
# To update it, modify .github/workflow-generator.yml as needed and re-run the generator.
name: "Release Train Join"
run-name: "${{ inputs.release-train }} Join"
"on":
workflow_dispatch:
inputs:
deployment-destination:
description: "Destination to which the release should be deployed"
options:
- "Maven Central"
- "Spring Enterprise"
required: true
type: "choice"
release-train:
description: "Release train"
required: true
type: "string"
release-train-repository:
default: "spring-io/release-train"
description: "Release train repository"
required: true
type: "string"
permissions:
contents: "none"
jobs:
join-release-train:
name: "Join Release Train"
runs-on: "ubuntu-latest"
steps:
- name: "Join Release Train"
id: "join-release-train"
env:
GH_TOKEN: "${{ secrets.RELEASE_TRAIN_PARTICIPANT_GITHUB_TOKEN }}"
run: |-
run_url=$(
gh workflow run join \
--repo ${{ inputs.release-train-repository }} \
--ref ${{ inputs.release-train }} \
--field commit-hash=${{ github.sha }} \
--field deployment-destination=${{ inputs.deployment-destination == 'Maven Central' && 'maven-central' || 'spring-enterprise' }} \
--field release-branch=${{ github.ref_name }} \
--field release-repository=${{ github.repository }}
)
echo "Dispatched workflow run. Waiting for $run_url to complete."
run_id=${run_url##*/}
watch_exit_code=0
gh run watch $run_id --repo ${{ inputs.release-train-repository }} --exit-status --interval=3 > /dev/null 2>&1 || watch_exit_code=$?
if [[ $watch_exit_code -eq 0 ]]; then
echo "Workflow run succeeded."
else
echo "Workflow run failed."
fi
exit $watch_exit_code
-46
View File
@@ -1,46 +0,0 @@
# This file was auto-generated by github-actions-workflow-generator 0.0.6. Do not edit.
# To update it, modify .github/workflow-generator.yml as needed and re-run the generator.
name: "Release Train Leave"
run-name: "${{ inputs.release-train }} Leave"
"on":
workflow_dispatch:
inputs:
release-train:
description: "Release train"
required: true
type: "string"
release-train-repository:
default: "spring-io/release-train"
description: "Release train repository"
required: true
type: "string"
permissions:
contents: "none"
jobs:
leave:
name: "Leave"
runs-on: "ubuntu-latest"
steps:
- name: "Leave"
id: "leave"
env:
GH_TOKEN: "${{ secrets.RELEASE_TRAIN_PARTICIPANT_GITHUB_TOKEN }}"
run: |-
run_url=$(
gh workflow run leave \
--repo ${{ inputs.release-train-repository }} \
--ref ${{ inputs.release-train }} \
--field release-branch=${{ github.ref_name }} \
--field release-repository=${{ github.repository }}
)
echo "Dispatched workflow run. Waiting for $run_url to complete."
run_id=${run_url##*/}
watch_exit_code=0
gh run watch $run_id --repo ${{ inputs.release-train-repository }} --exit-status --interval=3 > /dev/null 2>&1 || watch_exit_code=$?
if [[ $watch_exit_code -eq 0 ]]; then
echo "Workflow run succeeded."
else
echo "Workflow run failed."
fi
exit $watch_exit_code
-47
View File
@@ -1,47 +0,0 @@
# This file was auto-generated by github-actions-workflow-generator 0.0.6. Do not edit.
# To update it, modify .github/workflow-generator.yml as needed and re-run the generator.
name: "Release Train Ready"
run-name: "${{ inputs.release-train }} Ready"
"on":
workflow_dispatch:
inputs:
release-train:
description: "Release train"
required: true
type: "string"
release-train-repository:
default: "spring-io/release-train"
description: "Release train repository"
required: true
type: "string"
permissions:
contents: "none"
jobs:
ready:
name: "Ready"
runs-on: "ubuntu-latest"
steps:
- name: "Ready"
id: "ready"
env:
GH_TOKEN: "${{ secrets.RELEASE_TRAIN_PARTICIPANT_GITHUB_TOKEN }}"
run: |-
run_url=$(
gh workflow run ready \
--repo ${{ inputs.release-train-repository }} \
--ref ${{ inputs.release-train }} \
--field commit-hash=${{ github.sha }} \
--field release-branch=${{ github.ref_name }} \
--field release-repository=${{ github.repository }}
)
echo "Dispatched workflow run. Waiting for $run_url to complete."
run_id=${run_url##*/}
watch_exit_code=0
gh run watch $run_id --repo ${{ inputs.release-train-repository }} --exit-status --interval=3 > /dev/null 2>&1 || watch_exit_code=$?
if [[ $watch_exit_code -eq 0 ]]; then
echo "Workflow run succeeded."
else
echo "Workflow run failed."
fi
exit $watch_exit_code
-34
View File
@@ -1,34 +0,0 @@
# This file was auto-generated by github-actions-workflow-generator 0.0.6. Do not edit.
# To update it, modify .github/workflow-generator.yml as needed and re-run the generator.
name: "Release Train Retry"
run-name: "${{ inputs.release-train }} Retry"
"on":
workflow_dispatch:
inputs:
release-train:
description: "Release train"
required: true
type: "string"
release-train-repository:
default: "spring-io/release-train"
description: "Release train repository"
required: true
type: "string"
permissions:
contents: "none"
jobs:
trigger-retry:
name: "Trigger Retry"
runs-on: "ubuntu-latest"
steps:
- name: "Trigger Retry"
id: "trigger-retry"
env:
GH_TOKEN: "${{ secrets.RELEASE_TRAIN_PARTICIPANT_GITHUB_TOKEN }}"
run: |-
gh workflow run retry \
--repo ${{ inputs.release-train-repository }} \
--ref ${{ inputs.release-train }} \
--field release-branch=${{ github.ref_name }} \
--field release-repository=${{ github.repository }}
-83
View File
@@ -1,83 +0,0 @@
# This file was auto-generated by github-actions-workflow-generator 0.0.6. Do not edit.
# To update it, modify .github/workflow-generator.yml as needed and re-run the generator.
name: "Release Train Test"
run-name: "${{ inputs.callback-ref }} Test"
"on":
workflow_dispatch:
inputs:
callback:
description: "Repository to which a callback should be made upon completion"
required: true
type: "string"
callback-ref:
description: "Ref in the callback repository to which a callback should be made upon completion"
required: true
type: "string"
release-train-maven-repository-url:
description: "URL of a Maven repository to be used to resolve artifacts of projects earlier in the train"
required: true
type: "string"
permissions:
contents: "read"
concurrency:
group: "${{ github.workflow }}-${{ github.ref }}"
jobs:
test-release:
name: "Test Release"
runs-on: "ubuntu22-2-8"
steps:
- name: "Prevent Re-runs"
id: "prevent-re-runs"
run: |-
if [ "$GITHUB_RUN_ATTEMPT" -gt 1 ]; then
echo "Re-runs are prohibited. Use the 'Release Train  Retry' workflow to retry test failures"
exit 1
fi
- name: "Set up Java"
id: "set-up-java"
uses: "actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95" # v5.6.0
with:
distribution: "liberica"
java-version: "17"
- name: "Check Out Code"
id: "check-out-code"
uses: "actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0" # v7.0.0
- name: "Restore Build System Caches"
id: "restore-build-system-caches"
uses: "actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9" # v6.1.0
with:
key: "release-train-${{ inputs.callback-ref }}-${{ github.ref_name }}"
path: |-
~/.gradle/caches
~/.gradle/wrapper
- name: "Test Release"
id: "test-release"
uses: "./.github/actions/release-train-test"
env:
COMMERCIAL_REPO_PASSWORD: "${{ secrets.COMMERCIAL_ARTIFACTORY_PASSWORD }}"
COMMERCIAL_REPO_USERNAME: "${{ secrets.COMMERCIAL_ARTIFACTORY_USERNAME }}"
RELEASE_TRAIN_MAVEN_REPOSITORY_PASSWORD: "${{ secrets.RELEASE_TRAIN_PARTICIPANT_MAVEN_REPOSITORY_PASSWORD }}"
RELEASE_TRAIN_MAVEN_REPOSITORY_URL: "${{ inputs.release-train-maven-repository-url }}"
RELEASE_TRAIN_MAVEN_REPOSITORY_USERNAME: "${{ secrets.RELEASE_TRAIN_PARTICIPANT_MAVEN_REPOSITORY_USERNAME }}"
- name: "Send Callback"
id: "send-callback"
if: "${{ !cancelled() }}"
env:
GH_TOKEN: "${{ secrets.RELEASE_TRAIN_PARTICIPANT_GITHUB_TOKEN }}"
run: |-
gh workflow run callback \
--repo ${{ inputs.callback }} \
--ref ${{ inputs.callback-ref }} \
--field commit-hash=${{ steps.check-out-code.outputs.commit }} \
--field release-branch=${{ github.ref_name }} \
--field release-repository=${{ github.repository }} \
--field result=${{ job.status == 'success' && 'tested' || 'test-failed' }} \
--field workflow-run-url=${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
- name: "Upload Build System Reports"
id: "upload-build-system-reports"
if: "${{ failure() }}"
uses: "actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a" # v7.0.1
with:
name: "build-system-reports"
path: "**/build/reports"
@@ -0,0 +1,35 @@
name: Update Antora UI Spring
on:
schedule:
- cron: '0 10 * * *' # Once per day at 10am UTC
workflow_dispatch:
permissions:
pull-requests: write
issues: write
contents: write
jobs:
update-antora-ui-spring:
runs-on: ubuntu-latest
name: Update on Supported Branches
strategy:
matrix:
branch: [ '5.8.x', '6.2.x', '6.3.x', 'main' ]
steps:
- uses: spring-io/spring-doc-actions/update-antora-spring-ui@e28269199d1d27975cf7f65e16d6095c555b3cd0
name: Update
with:
docs-branch: ${{ matrix.branch }}
token: ${{ secrets.GITHUB_TOKEN }}
antora-file-path: 'docs/antora-playbook.yml'
update-antora-ui-spring-docs-build:
runs-on: ubuntu-latest
name: Update on docs-build
steps:
- uses: spring-io/spring-doc-actions/update-antora-spring-ui@e28269199d1d27975cf7f65e16d6095c555b3cd0
name: Update
with:
docs-branch: 'docs-build'
token: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,23 @@
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 }}
-21
View File
@@ -68,27 +68,6 @@ The https://github.com/spring-projects/spring-security/tree/docs-build[playbook
Discover more commands with `./gradlew tasks`. Discover more commands with `./gradlew tasks`.
=== IDE setup (IntelliJ)
No special steps are needed to open Spring Security in IntelliJ.
=== IDE setup (Eclipse and VS Code)
To work in Eclipse or VS Code, first generate Eclipse metadata so you can import the project into Eclipse or VS Code:
[indent=0]
----
./gradlew cleanEclipse eclipse
----
If you have not built the project yet, run `./gradlew publishToMavenLocal` first so dependencies are resolved.
*VS Code:* Open the repository root as a folder. The repository includes `.vscode/settings.json` which disables automatic Gradle import so that the generated Eclipse metadata (`.classpath`, `.project`) is used. Do not use the Gradle for Java extension to import the project.
*Eclipse:* File → Import → General → Existing Projects into Workspace, then select the repository root.
The build uses a custom Eclipse plugin to work around Gradle dependency cycles that confuse IDE metadata generation. You may see Eclipse warnings about `xml-apis` from some test dependencies; those are excluded in the build and can be ignored.
== Getting Support == Getting Support
Check out the https://stackoverflow.com/questions/tagged/spring-security[Spring Security tags on Stack Overflow]. Check out the https://stackoverflow.com/questions/tagged/spring-security[Spring Security tags on Stack Overflow].
https://spring.io/support[Commercial support] is available too. https://spring.io/support[Commercial support] is available too.
@@ -39,7 +39,7 @@ interface EvaluationContextPostProcessor<I> {
* that was passed in. * that was passed in.
* @param context the original {@link EvaluationContext} * @param context the original {@link EvaluationContext}
* @param invocation the security invocation object (i.e. Message) * @param invocation the security invocation object (i.e. Message)
* @return the updated context. * @return the upated context.
*/ */
EvaluationContext postProcess(EvaluationContext context, I invocation); EvaluationContext postProcess(EvaluationContext context, I invocation);
@@ -35,7 +35,6 @@ import org.springframework.security.web.FilterInvocation;
*/ */
@Deprecated @Deprecated
@NullUnmarked @NullUnmarked
@SuppressWarnings("serial")
class WebExpressionConfigAttribute implements ConfigAttribute, EvaluationContextPostProcessor<FilterInvocation> { class WebExpressionConfigAttribute implements ConfigAttribute, EvaluationContextPostProcessor<FilterInvocation> {
private final Expression authorizeExpression; private final Expression authorizeExpression;
@@ -20,7 +20,7 @@ import org.springframework.security.acls.model.Acl;
/** /**
* Strategy used by {@link AclImpl} to determine whether a principal is permitted to call * Strategy used by {@link AclImpl} to determine whether a principal is permitted to call
* administrative methods on the <code>AclImpl</code>. * adminstrative methods on the <code>AclImpl</code>.
* *
* @author Ben Alex * @author Ben Alex
*/ */
@@ -42,7 +42,7 @@ public class GrantedAuthoritySid implements Sid {
public GrantedAuthoritySid(GrantedAuthority grantedAuthority) { public GrantedAuthoritySid(GrantedAuthority grantedAuthority) {
Assert.notNull(grantedAuthority, "GrantedAuthority required"); Assert.notNull(grantedAuthority, "GrantedAuthority required");
Assert.notNull(grantedAuthority.getAuthority(), Assert.notNull(grantedAuthority.getAuthority(),
"This Sid is only compatible with GrantedAuthority that provide a non-null getAuthority()"); "This Sid is only compatible with GrantedAuthoritys that provide a non-null getAuthority()");
this.grantedAuthority = grantedAuthority.getAuthority(); this.grantedAuthority = grantedAuthority.getAuthority();
} }
@@ -160,7 +160,7 @@ public class JdbcAclService implements AclService {
this.findChildrenSql = DEFAULT_SELECT_ACL_WITH_PARENT_SQL_WITH_CLASS_ID_TYPE; this.findChildrenSql = DEFAULT_SELECT_ACL_WITH_PARENT_SQL_WITH_CLASS_ID_TYPE;
} }
else { else {
log.debug("Find children statement has already been overridden, so not overriding the default"); log.debug("Find children statement has already been overridden, so not overridding the default");
} }
} }
} }
@@ -50,7 +50,7 @@ import org.springframework.util.Assert;
* The default settings are for HSQLDB. If you are using a different database you will * The default settings are for HSQLDB. If you are using a different database you will
* probably need to set the {@link #setSidIdentityQuery(String) sidIdentityQuery} and * probably need to set the {@link #setSidIdentityQuery(String) sidIdentityQuery} and
* {@link #setClassIdentityQuery(String) classIdentityQuery} properties appropriately. The * {@link #setClassIdentityQuery(String) classIdentityQuery} properties appropriately. The
* other queries, SQL inserts and updates can also be customized to accommodate schema * other queries, SQL inserts and updates can also be customized to accomodate schema
* variations, but must produce results consistent with those expected by the defaults. * variations, but must produce results consistent with those expected by the defaults.
* <p> * <p>
* See the appendix of the Spring Security reference manual for more information on the * See the appendix of the Spring Security reference manual for more information on the
@@ -471,7 +471,7 @@ public class JdbcMutableAclService extends JdbcAclService implements MutableAclS
this.insertClass = DEFAULT_INSERT_INTO_ACL_CLASS_WITH_ID; this.insertClass = DEFAULT_INSERT_INTO_ACL_CLASS_WITH_ID;
} }
else { else {
log.debug("Insert class statement has already been overridden, so not overriding the default"); log.debug("Insert class statement has already been overridden, so not overridding the default");
} }
} }
} }
+4 -4
View File
@@ -2,12 +2,12 @@ apply plugin: 'io.spring.convention.spring-module'
apply plugin: 'io.freefair.aspectj' apply plugin: 'io.freefair.aspectj'
compileAspectj { compileAspectj {
sourceCompatibility = "17" sourceCompatibility "17"
targetCompatibility = "17" targetCompatibility "17"
} }
compileTestAspectj { compileTestAspectj {
sourceCompatibility = "17" sourceCompatibility "17"
targetCompatibility = "17" targetCompatibility "17"
} }
dependencies { dependencies {
+44 -14
View File
@@ -10,7 +10,7 @@ buildscript {
classpath libs.com.netflix.nebula.nebula.project.plugin classpath libs.com.netflix.nebula.nebula.project.plugin
} }
repositories { repositories {
maven { url='https://plugins.gradle.org/m2/' } maven { url 'https://plugins.gradle.org/m2/' }
} }
} }
@@ -25,7 +25,6 @@ apply plugin: 'org.jetbrains.kotlin.jvm'
apply plugin: 'org.springframework.security.versions.verify-dependencies-versions' apply plugin: 'org.springframework.security.versions.verify-dependencies-versions'
apply plugin: 'org.springframework.security.check-expected-branch-version' apply plugin: 'org.springframework.security.check-expected-branch-version'
apply plugin: 'io.spring.security.release' apply plugin: 'io.spring.security.release'
apply from: 'commercial-settings.gradle'
group = 'org.springframework.security' group = 'org.springframework.security'
description = 'Spring Security' description = 'Spring Security'
@@ -36,32 +35,57 @@ ext.milestoneBuild = !(snapshotBuild || releaseBuild)
repositories { repositories {
mavenCentral() mavenCentral()
maven { url = "https://repo.spring.io/milestone" } maven { url "https://repo.spring.io/milestone" }
} }
springRelease { springRelease {
repositoryOwner = "spring-projects"
repositoryName = "spring-security-commercial"
weekOfMonth = 3 weekOfMonth = 3
dayOfWeek = 1 dayOfWeek = 1
referenceDocUrl = "https://docs.spring.vmware.com/spring-security/reference/{version}/index.html" referenceDocUrl = "https://docs.spring.io/spring-security/reference/{version}/index.html"
apiDocUrl = "https://docs.spring.vmware.com/spring-security/reference/{version}/api/java/index.html" apiDocUrl = "https://docs.spring.io/spring-security/site/docs/{version}/api/"
replaceSnapshotVersionInReferenceDocUrl = true replaceSnapshotVersionInReferenceDocUrl = true
} }
def toolchainVersion() {
if (project.hasProperty('testToolchain')) {
return project.property('testToolchain').toString().toInteger()
}
return 17
}
subprojects {
java {
toolchain {
languageVersion = JavaLanguageVersion.of(toolchainVersion())
}
}
kotlin {
jvmToolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
tasks.withType(JavaCompile).configureEach {
options.encoding = "UTF-8"
options.compilerArgs.add("-parameters")
options.release.set(17)
}
}
allprojects { allprojects {
if (!['spring-security-bom', 'spring-security-docs'].contains(project.name)) { if (!['spring-security-bom', 'spring-security-docs'].contains(project.name)) {
apply plugin: 'io.spring.javaformat' apply plugin: 'io.spring.javaformat'
apply plugin: 'checkstyle' apply plugin: 'checkstyle'
pluginManager.withPlugin("io.spring.convention.checkstyle") { pluginManager.withPlugin("io.spring.convention.checkstyle", { plugin ->
dependencies { configure(plugin) {
checkstyle libs.io.spring.javaformat.spring.javaformat.checkstyle dependencies {
checkstyle libs.io.spring.javaformat.spring.javaformat.checkstyle
}
checkstyle {
toolVersion = '8.34'
}
} }
checkstyle { })
toolVersion = '8.34'
}
}
if (project.name.contains('sample')) { if (project.name.contains('sample')) {
tasks.whenTaskAdded { task -> tasks.whenTaskAdded { task ->
@@ -73,6 +97,12 @@ allprojects {
} }
} }
develocity {
buildScan {
termsOfUseUrl = 'https://gradle.com/help/legal-terms-of-use'
termsOfUseAgree = 'yes'
}
}
nohttp { nohttp {
source.exclude "buildSrc/build/**", "javascript/.gradle/**", "javascript/package-lock.json", "javascript/node_modules/**", "javascript/build/**", "javascript/dist/**" source.exclude "buildSrc/build/**", "javascript/.gradle/**", "javascript/package-lock.json", "javascript/node_modules/**", "javascript/build/**", "javascript/dist/**"
+1 -20
View File
@@ -12,26 +12,7 @@ java {
repositories { repositories {
gradlePluginPortal() gradlePluginPortal()
mavenCentral() mavenCentral()
if (project.hasProperty("artifactoryUsername") && project.hasProperty("artifactoryPassword")) { maven { url 'https://repo.spring.io/snapshot' }
maven {
name "spring-commercial-release"
url "https://usw1.packages.broadcom.com/spring-enterprise-maven-prod-local"
credentials {
username project.artifactoryUsername
password project.artifactoryPassword
}
}
}
if (System.getenv("RELEASE_TRAIN_MAVEN_REPOSITORY_URL") != null) {
maven {
name = "Release Train"
url = System.getenv("RELEASE_TRAIN_MAVEN_REPOSITORY_URL")
credentials {
username = System.getenv("RELEASE_TRAIN_MAVEN_REPOSITORY_USERNAME")
password = System.getenv("RELEASE_TRAIN_MAVEN_REPOSITORY_PASSWORD")
}
}
}
} }
sourceSets { sourceSets {
Binary file not shown.
+5
View File
@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored Executable
+240
View File
@@ -0,0 +1,240 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original 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.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
+91
View File
@@ -0,0 +1,91 @@
@rem
@rem Copyright 2004-present the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
@@ -81,8 +81,8 @@ class ArtifactoryPlugin implements Plugin<Project> {
repository { repository {
repoKey = isSnapshot ? snapshotRepository : isMilestone ? milestoneRepository : releaseRepository repoKey = isSnapshot ? snapshotRepository : isMilestone ? milestoneRepository : releaseRepository
if(project.hasProperty('artifactoryUsername')) { if(project.hasProperty('artifactoryUsername')) {
username = project.artifactoryUsername username = artifactoryUsername
password = project.artifactoryPassword password = artifactoryPassword
} }
} }
} }
@@ -0,0 +1,82 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package io.spring.gradle.convention
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.tasks.bundling.Zip
import org.gradle.api.Plugin
import org.gradle.api.Project
public class DeployDocsPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
project.getPluginManager().apply('org.hidetake.ssh')
project.ssh.settings {
knownHosts = allowAnyHosts
}
project.remotes {
docs {
role 'docs'
if (project.hasProperty('deployDocsHost')) {
host = project.findProperty('deployDocsHost')
} else {
host = 'docs.af.pivotal.io'
}
retryCount = 5 // retry 5 times (default is 0)
retryWaitSec = 10 // wait 10 seconds between retries (default is 0)
user = project.findProperty('deployDocsSshUsername')
if (project.hasProperty('deployDocsSshKeyPath')) {
identity = project.file(project.findProperty('deployDocsSshKeyPath'))
} else if (project.hasProperty('deployDocsSshKey')) {
identity = project.findProperty('deployDocsSshKey')
}
if(project.hasProperty('deployDocsSshPassphrase')) {
passphrase = project.findProperty('deployDocsSshPassphrase')
}
}
}
project.task('deployDocs') {
dependsOn 'docsZip'
doFirst {
project.ssh.run {
session(project.remotes.docs) {
def now = System.currentTimeMillis()
def name = project.rootProject.name
def version = project.rootProject.version
def tempPath = "/tmp/${name}-${now}-docs/".replaceAll(' ', '_')
execute "mkdir -p $tempPath"
project.tasks.docsZip.outputs.each { o ->
put from: o.files, into: tempPath
}
execute "unzip $tempPath*.zip -d $tempPath"
def extractPath = "/var/www/domains/spring.io/docs/htdocs/autorepo/docs/${name}/${version}/"
execute "rm -rf $extractPath"
execute "mkdir -p $extractPath"
execute "mv $tempPath/docs/* $extractPath"
execute "chmod -R g+w $extractPath"
}
}
}
}
}
}
@@ -17,6 +17,7 @@ public class DocsPlugin implements Plugin<Project> {
PluginManager pluginManager = project.getPluginManager(); PluginManager pluginManager = project.getPluginManager();
pluginManager.apply(BasePlugin); pluginManager.apply(BasePlugin);
pluginManager.apply(DeployDocsPlugin);
pluginManager.apply(JavadocApiPlugin); pluginManager.apply(JavadocApiPlugin);
Task docsZip = project.tasks.create('docsZip', Zip) { Task docsZip = project.tasks.create('docsZip', Zip) {
@@ -31,12 +32,12 @@ public class DocsPlugin implements Plugin<Project> {
into 'api' into 'api'
} }
into 'docs' into 'docs'
duplicatesStrategy = 'exclude' duplicatesStrategy 'exclude'
} }
Task docs = project.tasks.create("docs") { Task docs = project.tasks.create("docs") {
group = 'Documentation' group = 'Documentation'
description = 'An aggregator task to generate all the documentation' description 'An aggregator task to generate all the documentation'
dependsOn docsZip dependsOn docsZip
} }
project.tasks.assemble.dependsOn docs project.tasks.assemble.dependsOn docs
@@ -90,7 +90,7 @@ public class IntegrationTestPlugin implements Plugin<Project> {
project.plugins.withType(IdeaPlugin) { project.plugins.withType(IdeaPlugin) {
project.idea { project.idea {
module { module {
testSources.from(project.file('src/integration-test/java')) testSourceDirs += project.file('src/integration-test/java')
scopes.TEST.plus += [ project.configurations.integrationTestCompileClasspath ] scopes.TEST.plus += [ project.configurations.integrationTestCompileClasspath ]
} }
} }
@@ -105,7 +105,7 @@ public class IntegrationTestPlugin implements Plugin<Project> {
project.plugins.withType(IdeaPlugin) { project.plugins.withType(IdeaPlugin) {
project.idea { project.idea {
module { module {
testSources.from(project.file('src/integration-test/groovy')) testSourceDirs += project.file('src/integration-test/groovy')
} }
} }
} }
@@ -26,7 +26,7 @@ import org.gradle.api.Action;
import org.gradle.api.JavaVersion import org.gradle.api.JavaVersion
import org.gradle.api.Plugin; import org.gradle.api.Plugin;
import org.gradle.api.Project; import org.gradle.api.Project;
import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.plugins.JavaPluginConvention;
import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.javadoc.Javadoc; import org.gradle.api.tasks.javadoc.Javadoc;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -71,7 +71,7 @@ public class JavadocApiPlugin implements Plugin<Project> {
} }
api.setMaxMemory("1024m"); api.setMaxMemory("1024m");
api.setDestinationDir(project.layout.getBuildDirectory().dir("api").get().getAsFile()); api.setDestinationDir(new File(project.getBuildDir(), "api"));
project.getPluginManager().apply("io.spring.convention.javadoc-options"); project.getPluginManager().apply("io.spring.convention.javadoc-options");
} }
@@ -99,7 +99,7 @@ public class JavadocApiPlugin implements Plugin<Project> {
public void execute(SpringModulePlugin plugin) { public void execute(SpringModulePlugin plugin) {
logger.info("Added sources for {}", project); logger.info("Added sources for {}", project);
JavaPluginExtension java = project.getExtensions().getByType(JavaPluginExtension.class); JavaPluginConvention java = project.getConvention().getPlugin(JavaPluginConvention.class);
SourceSet mainSourceSet = java.getSourceSets().getByName("main"); SourceSet mainSourceSet = java.getSourceSets().getByName("main");
api.setSource(api.getSource().plus(mainSourceSet.getAllJava())); api.setSource(api.getSource().plus(mainSourceSet.getAllJava()));
@@ -43,16 +43,6 @@ class RepositoryConventionPlugin implements Plugin<Project> {
} }
} }
mavenCentral() mavenCentral()
if (System.getenv("RELEASE_TRAIN_MAVEN_REPOSITORY_URL") != null) {
maven {
name = "Release Train"
url = System.getenv("RELEASE_TRAIN_MAVEN_REPOSITORY_URL")
credentials {
username = System.getenv("RELEASE_TRAIN_MAVEN_REPOSITORY_USERNAME")
password = System.getenv("RELEASE_TRAIN_MAVEN_REPOSITORY_PASSWORD")
}
}
}
if (isSnapshot) { if (isSnapshot) {
maven { maven {
name = 'artifactory-snapshot' name = 'artifactory-snapshot'
@@ -40,7 +40,7 @@ public class SchemaZipPlugin implements Plugin<Project> {
throw new IllegalStateException("Could not find schema file for resource name " + schemaResourceName + " in src/main/resources") throw new IllegalStateException("Could not find schema file for resource name " + schemaResourceName + " in src/main/resources")
} }
schemaZip.into (shortName) { schemaZip.into (shortName) {
duplicatesStrategy = 'exclude' duplicatesStrategy 'exclude'
from xsdFile.path from xsdFile.path
} }
versionlessXsd.getInputFiles().from(xsdFile.path) versionlessXsd.getInputFiles().from(xsdFile.path)
@@ -35,7 +35,6 @@ class SpringModulePlugin extends AbstractSpringJavaPlugin {
pluginManager.apply(SpringMavenPlugin.class); pluginManager.apply(SpringMavenPlugin.class);
pluginManager.apply(CheckClasspathForProhibitedDependenciesPlugin.class); pluginManager.apply(CheckClasspathForProhibitedDependenciesPlugin.class);
pluginManager.apply("io.spring.convention.jacoco"); pluginManager.apply("io.spring.convention.jacoco");
pluginManager.apply("java-toolchain");
def deployArtifacts = project.task("deployArtifacts") def deployArtifacts = project.task("deployArtifacts")
deployArtifacts.group = 'Deploy tasks' deployArtifacts.group = 'Deploy tasks'
@@ -1,36 +0,0 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
def toolchainVersion() {
if (project.hasProperty('testToolchain')) {
return project.property('testToolchain').toString().toInteger()
}
return 17
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(toolchainVersion())
}
}
tasks.withType(JavaCompile).configureEach {
options.encoding = "UTF-8"
options.compilerArgs.add("-parameters")
options.release = 17
}
pluginManager.withPlugin("org.jetbrains.kotlin.jvm") {
kotlin {
jvmToolchain {
languageVersion = JavaLanguageVersion.of(toolchainVersion())
}
}
tasks.withType(KotlinCompile).configureEach {
compilerOptions {
javaParameters = true
jvmTarget.set(JvmTarget.JVM_17)
}
}
}
@@ -22,12 +22,6 @@ public class MavenPublishingConventionsPlugin implements Plugin<Project> {
@Override @Override
public void execute(MavenPublishPlugin mavenPublish) { public void execute(MavenPublishPlugin mavenPublish) {
PublishingExtension publishing = project.getExtensions().getByType(PublishingExtension.class); PublishingExtension publishing = project.getExtensions().getByType(PublishingExtension.class);
if (project.hasProperty("deploymentRepository")) {
publishing.getRepositories().maven((mavenRepository) -> {
mavenRepository.setUrl(project.property("deploymentRepository"));
mavenRepository.setName("deployment");
});
}
publishing.getPublications().withType(MavenPublication.class) publishing.getPublications().withType(MavenPublication.class)
.all((mavenPublication) -> MavenPublishingConventionsPlugin.this.customizePom(mavenPublication.getPom(), project)); .all((mavenPublication) -> MavenPublishingConventionsPlugin.this.customizePom(mavenPublication.getPom(), project));
MavenPublishingConventionsPlugin.this.customizeJavaPlugin(project); MavenPublishingConventionsPlugin.this.customizeJavaPlugin(project);
+11 -1
View File
@@ -30,6 +30,16 @@ ossrh: {
} }
} }
}, },
docs: {
stage('Deploy Docs') {
node {
checkout scm
withCredentials([file(credentialsId: 'docs.spring.io-jenkins_private_ssh_key', variable: 'DEPLOY_SSH_KEY')]) {
sh "./gradlew deployDocs -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME --refresh-dependencies --no-daemon --stacktrace"
}
}
}
},
schema: { schema: {
stage('Deploy Schema') { stage('Deploy Schema') {
node { node {
@@ -39,4 +49,4 @@ schema: {
} }
} }
} }
} }
@@ -108,7 +108,7 @@ public class CasAuthenticationToken extends AbstractAuthenticationToken implemen
protected CasAuthenticationToken(Builder<?> builder) { protected CasAuthenticationToken(Builder<?> builder) {
super(builder); super(builder);
Assert.isTrue(!"".equals(builder.principal), "principal cannot be null or empty"); Assert.isTrue(!"".equals(builder.principal), "principal cannot be null or empty");
Assert.isTrue(!"".equals(builder.credentials), "credentials cannot be null or empty"); Assert.notNull(!"".equals(builder.credentials), "credentials cannot be null or empty");
Assert.notNull(builder.userDetails, "userDetails cannot be null"); Assert.notNull(builder.userDetails, "userDetails cannot be null");
Assert.notNull(builder.assertion, "assertion cannot be null"); Assert.notNull(builder.assertion, "assertion cannot be null");
this.keyHash = builder.keyHash; this.keyHash = builder.keyHash;
@@ -48,7 +48,6 @@ import org.springframework.security.jackson.SecurityJacksonModules;
* @since 7.0 * @since 7.0
* @see SecurityJacksonModules * @see SecurityJacksonModules
*/ */
@SuppressWarnings("serial")
public class CasJacksonModule extends SecurityJacksonModule { public class CasJacksonModule extends SecurityJacksonModule {
public CasJacksonModule() { public CasJacksonModule() {
@@ -326,7 +326,7 @@ public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFil
/** /**
* Use this {@code RequestMatcher} to match proxy receptor requests. Without setting * Use this {@code RequestMatcher} to match proxy receptor requests. Without setting
* this matcher, {@link CasAuthenticationFilter} will not capture any proxy receptor * this matcher, {@link CasAuthenticationFilter} will not capture any proxy receptor
* requests. * requets.
* @param proxyReceptorMatcher the {@link RequestMatcher} to use * @param proxyReceptorMatcher the {@link RequestMatcher} to use
* @since 6.5 * @since 6.5
*/ */
@@ -383,8 +383,8 @@ public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFil
} }
/** /**
* Indicates if the request is eligible to process a service ticket. This method * Indicates if the request is elgible to process a service ticket. This method exists
* exists for readability. * for readability.
* @param request * @param request
* @param response * @param response
* @return * @return
@@ -396,7 +396,7 @@ public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFil
} }
/** /**
* Indicates if the request is eligible to process a proxy ticket. * Indicates if the request is elgible to process a proxy ticket.
* @param request * @param request
* @return * @return
*/ */
@@ -419,7 +419,7 @@ public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFil
} }
/** /**
* Indicates if the request is eligible to be processed as the proxy receptor. * Indicates if the request is elgible to be processed as the proxy receptor.
* @param request * @param request
* @return * @return
*/ */
@@ -34,7 +34,6 @@ import org.springframework.security.core.userdetails.UserDetails;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatNoException;
/** /**
* Tests {@link CasAuthenticationToken}. * Tests {@link CasAuthenticationToken}.
@@ -183,38 +182,4 @@ public class CasAuthenticationTokenTests {
assertThat(authorities).containsExactlyInAnyOrder("FACTOR_ONE", "FACTOR_TWO"); assertThat(authorities).containsExactlyInAnyOrder("FACTOR_ONE", "FACTOR_TWO");
} }
@Test
public void toBuilderWhenPrincipalIsEmpty() {
final Assertion assertion = new AssertionImpl("test");
CasAuthenticationToken token = new CasAuthenticationToken("key", makeUserDetails(), "Password", this.ROLES,
makeUserDetails(), assertion);
assertThatIllegalArgumentException().isThrownBy(() -> token.toBuilder().principal(null).build());
assertThatIllegalArgumentException().isThrownBy(() -> token.toBuilder().principal("").build());
}
@Test
public void toBuilderWhenPrincipalIsNotEmpty() {
final Assertion assertion = new AssertionImpl("test");
CasAuthenticationToken token = new CasAuthenticationToken("key", makeUserDetails(), "Password", this.ROLES,
makeUserDetails(), assertion);
assertThatNoException().isThrownBy(() -> token.toBuilder().principal("principal").build());
}
@Test
public void toBuilderWhenCredentialsIsEmpty() {
final Assertion assertion = new AssertionImpl("test");
CasAuthenticationToken token = new CasAuthenticationToken("key", makeUserDetails(), "Password", this.ROLES,
makeUserDetails(), assertion);
assertThatIllegalArgumentException().isThrownBy(() -> token.toBuilder().credentials(null).build());
assertThatIllegalArgumentException().isThrownBy(() -> token.toBuilder().credentials("").build());
}
@Test
public void toBuilderWhenCredentialsIsNotEmpty() {
final Assertion assertion = new AssertionImpl("test");
CasAuthenticationToken token = new CasAuthenticationToken("key", makeUserDetails(), "Password", this.ROLES,
makeUserDetails(), assertion);
assertThatNoException().isThrownBy(() -> token.toBuilder().credentials("credentials").build());
}
} }
-27
View File
@@ -1,27 +0,0 @@
subprojects {
repositories {
mavenCentral()
def repoUsername = project.findProperty("artifactoryUsername") ?: System.getenv("COMMERCIAL_REPO_USERNAME")
def repoPassword = project.findProperty("artifactoryPassword") ?: System.getenv("COMMERCIAL_REPO_PASSWORD")
if (repoUsername && repoPassword) {
maven {
name "spring-commercial-release"
url "https://usw1.packages.broadcom.com/spring-enterprise-maven-prod-local"
credentials {
username repoUsername
password repoPassword
}
}
if ("$version".endsWith("-SNAPSHOT")) {
maven {
name "spring-commercial-snapshot"
url "https://usw1.packages.broadcom.com/spring-enterprise-maven-dev-local"
credentials {
username repoUsername
password repoPassword
}
}
}
}
}
}
+2 -2
View File
@@ -144,14 +144,14 @@ tasks.named('processResources', ProcessResources).configure {
into 'org/springframework/security/config/' into 'org/springframework/security/config/'
} }
from(rncToXsd) { from(rncToXsd) {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE duplicatesStrategy DuplicatesStrategy.EXCLUDE
into 'org/springframework/security/config/' into 'org/springframework/security/config/'
} }
} }
tasks.named('sourcesJar', Jar).configure { tasks.named('sourcesJar', Jar).configure {
from(rncToXsd) { from(rncToXsd) {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE duplicatesStrategy DuplicatesStrategy.EXCLUDE
into 'org/springframework/security/config/' into 'org/springframework/security/config/'
} }
} }
@@ -31,7 +31,6 @@ import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.openqa.selenium.By; import org.openqa.selenium.By;
import org.openqa.selenium.WebDriverException; import org.openqa.selenium.WebDriverException;
@@ -56,7 +55,6 @@ import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.util.StringUtils;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.filter.DelegatingFilterProxy; import org.springframework.web.filter.DelegatingFilterProxy;
import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.EnableWebMvc;
@@ -69,7 +67,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* *
* @author Daniel Garnier-Moiroux * @author Daniel Garnier-Moiroux
*/ */
@Disabled @org.junit.jupiter.api.Disabled
class WebAuthnWebDriverTests { class WebAuthnWebDriverTests {
private String baseUrl; private String baseUrl;
@@ -84,8 +82,6 @@ class WebAuthnWebDriverTests {
private static final String PASSWORD = "password"; private static final String PASSWORD = "password";
private String authenticatorId = null;
@BeforeAll @BeforeAll
static void startChromeDriverService() throws Exception { static void startChromeDriverService() throws Exception {
driverService = new ChromeDriverService.Builder().usingAnyFreePort().build(); driverService = new ChromeDriverService.Builder().usingAnyFreePort().build();
@@ -148,7 +144,7 @@ class WebAuthnWebDriverTests {
@Test @Test
void loginWhenNoValidAuthenticatorCredentialsThenRejects() { void loginWhenNoValidAuthenticatorCredentialsThenRejects() {
createVirtualAuthenticator(true); createVirtualAuthenticator(true);
this.getAndWait("/", "/login"); this.driver.get(this.baseUrl);
this.driver.findElement(signinWithPasskeyButton()).click(); this.driver.findElement(signinWithPasskeyButton()).click();
await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/login?error")); await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/login?error"));
} }
@@ -157,7 +153,7 @@ class WebAuthnWebDriverTests {
void registerWhenNoLabelThenRejects() { void registerWhenNoLabelThenRejects() {
login(); login();
this.getAndWait("/webauthn/register"); this.driver.get(this.baseUrl + "/webauthn/register");
this.driver.findElement(registerPasskeyButton()).click(); this.driver.findElement(registerPasskeyButton()).click();
assertHasAlertStartingWith("error", "Error: Passkey Label is required"); assertHasAlertStartingWith("error", "Error: Passkey Label is required");
@@ -167,7 +163,7 @@ class WebAuthnWebDriverTests {
void registerWhenAuthenticatorNoUserVerificationThenRejects() { void registerWhenAuthenticatorNoUserVerificationThenRejects() {
createVirtualAuthenticator(false); createVirtualAuthenticator(false);
login(); login();
this.getAndWait("/webauthn/register"); this.driver.get(this.baseUrl + "/webauthn/register");
this.driver.findElement(passkeyLabel()).sendKeys("Virtual authenticator"); this.driver.findElement(passkeyLabel()).sendKeys("Virtual authenticator");
this.driver.findElement(registerPasskeyButton()).click(); this.driver.findElement(registerPasskeyButton()).click();
@@ -182,8 +178,7 @@ class WebAuthnWebDriverTests {
* <li>Step 1: Log in with username / password</li> * <li>Step 1: Log in with username / password</li>
* <li>Step 2: Register a credential from the virtual authenticator</li> * <li>Step 2: Register a credential from the virtual authenticator</li>
* <li>Step 3: Log out</li> * <li>Step 3: Log out</li>
* <li>Step 4: Log in with the authenticator (no allowCredentials)</li> * <li>Step 4: Log in with the authenticator</li>
* <li>Step 5: Log in again with the same authenticator (with allowCredentials)</li>
* </ul> * </ul>
*/ */
@Test @Test
@@ -195,7 +190,7 @@ class WebAuthnWebDriverTests {
login(); login();
// Step 2: register a credential from the virtual authenticator // Step 2: register a credential from the virtual authenticator
this.getAndWait("/webauthn/register"); this.driver.get(this.baseUrl + "/webauthn/register");
this.driver.findElement(passkeyLabel()).sendKeys("Virtual authenticator"); this.driver.findElement(passkeyLabel()).sendKeys("Virtual authenticator");
this.driver.findElement(registerPasskeyButton()).click(); this.driver.findElement(registerPasskeyButton()).click();
@@ -217,58 +212,9 @@ class WebAuthnWebDriverTests {
logout(); logout();
// Step 4: log in with the virtual authenticator // Step 4: log in with the virtual authenticator
this.getAndWait("/webauthn/register", "/login"); this.driver.get(this.baseUrl + "/webauthn/register");
this.driver.findElement(signinWithPasskeyButton()).click(); this.driver.findElement(signinWithPasskeyButton()).click();
await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/webauthn/register?continue")); await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/webauthn/register?continue"));
// Step 5: authenticate while being already logged in
// This simulates some use-cases with MFA. Since the user is already logged in,
// the "allowCredentials" property is populated
this.getAndWait("/login");
this.driver.findElement(signinWithPasskeyButton()).click();
await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/"));
}
@Test
void registerWhenAuthenticatorAlreadyRegisteredThenRejects() {
createVirtualAuthenticator(true);
login();
registerAuthenticator("Virtual authenticator");
// Cannot re-register the same authenticator because excludeCredentials
// is not empty and contains the given authenticator
this.driver.findElement(passkeyLabel()).sendKeys("Same authenticator");
this.driver.findElement(registerPasskeyButton()).click();
await(() -> assertHasAlertStartingWith("error", "Registration failed"));
}
@Test
void registerSecondAuthenticatorThenSucceeds() {
createVirtualAuthenticator(true);
login();
registerAuthenticator("Virtual authenticator");
this.getAndWait("/webauthn/register");
List<WebElement> passkeyRows = this.driver.findElements(passkeyTableRows());
assertThat(passkeyRows).hasSize(1)
.first()
.extracting((row) -> row.findElement(firstCell()))
.extracting(WebElement::getText)
.isEqualTo("Virtual authenticator");
// Create second authenticator and register
removeAuthenticator();
createVirtualAuthenticator(true);
registerAuthenticator("Second virtual authenticator");
this.getAndWait("/webauthn/register");
passkeyRows = this.driver.findElements(passkeyTableRows());
assertThat(passkeyRows).hasSize(2)
.extracting((row) -> row.findElement(firstCell()))
.extracting(WebElement::getText)
.contains("Second virtual authenticator");
} }
/** /**
@@ -285,14 +231,11 @@ class WebAuthnWebDriverTests {
* "https://chromedevtools.github.io/devtools-protocol/tot/WebAuthn/">https://chromedevtools.github.io/devtools-protocol/tot/WebAuthn/</a> * "https://chromedevtools.github.io/devtools-protocol/tot/WebAuthn/">https://chromedevtools.github.io/devtools-protocol/tot/WebAuthn/</a>
*/ */
private void createVirtualAuthenticator(boolean userIsVerified) { private void createVirtualAuthenticator(boolean userIsVerified) {
if (StringUtils.hasText(this.authenticatorId)) {
throw new IllegalStateException("Authenticator already exists, please remove it before re-creating one");
}
HasCdp cdpDriver = (HasCdp) this.driver; HasCdp cdpDriver = (HasCdp) this.driver;
cdpDriver.executeCdpCommand("WebAuthn.enable", Map.of("enableUI", false)); cdpDriver.executeCdpCommand("WebAuthn.enable", Map.of("enableUI", false));
// this.driver.addVirtualAuthenticator(createVirtualAuthenticatorOptions()); // this.driver.addVirtualAuthenticator(createVirtualAuthenticatorOptions());
//@formatter:off //@formatter:off
Map<String, Object> cmdResponse = cdpDriver.executeCdpCommand("WebAuthn.addVirtualAuthenticator", cdpDriver.executeCdpCommand("WebAuthn.addVirtualAuthenticator",
Map.of( Map.of(
"options", "options",
Map.of( Map.of(
@@ -305,38 +248,21 @@ class WebAuthnWebDriverTests {
) )
)); ));
//@formatter:on //@formatter:on
this.authenticatorId = cmdResponse.get("authenticatorId").toString();
}
private void removeAuthenticator() {
HasCdp cdpDriver = (HasCdp) this.driver;
cdpDriver.executeCdpCommand("WebAuthn.removeVirtualAuthenticator",
Map.of("authenticatorId", this.authenticatorId));
this.authenticatorId = null;
} }
private void login() { private void login() {
this.getAndWait("/", "/login"); this.driver.get(this.baseUrl);
this.driver.findElement(usernameField()).sendKeys(USERNAME); this.driver.findElement(usernameField()).sendKeys(USERNAME);
this.driver.findElement(passwordField()).sendKeys(PASSWORD); this.driver.findElement(passwordField()).sendKeys(PASSWORD);
this.driver.findElement(signinWithUsernamePasswordButton()).click(); this.driver.findElement(signinWithUsernamePasswordButton()).click();
// Ensure login has completed
await(() -> assertThat(this.driver.getCurrentUrl()).doesNotContain("/login"));
} }
private void logout() { private void logout() {
this.getAndWait("/logout"); this.driver.get(this.baseUrl + "/logout");
this.driver.findElement(logoutButton()).click(); this.driver.findElement(logoutButton()).click();
await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/login?logout")); await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/login?logout"));
} }
private void registerAuthenticator(String passkeyName) {
this.getAndWait("/webauthn/register");
this.driver.findElement(passkeyLabel()).sendKeys(passkeyName);
this.driver.findElement(registerPasskeyButton()).click();
await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/webauthn/register?success"));
}
private AbstractStringAssert<?> assertHasAlertStartingWith(String alertType, String alertMessage) { private AbstractStringAssert<?> assertHasAlertStartingWith(String alertType, String alertMessage) {
WebElement alert = this.driver.findElement(new By.ById(alertType)); WebElement alert = this.driver.findElement(new By.ById(alertType));
assertThat(alert.isDisplayed()) assertThat(alert.isDisplayed())
@@ -363,15 +289,6 @@ class WebAuthnWebDriverTests {
}); });
} }
private void getAndWait(String endpoint) {
this.getAndWait(endpoint, endpoint);
}
private void getAndWait(String endpoint, String redirectUrl) {
this.driver.get(this.baseUrl + endpoint);
this.await(() -> assertThat(this.driver.getCurrentUrl()).endsWith(redirectUrl));
}
private static By.ById passkeyLabel() { private static By.ById passkeyLabel() {
return new By.ById("label"); return new By.ById("label");
} }
@@ -408,10 +325,6 @@ class WebAuthnWebDriverTests {
return new By.ByCssSelector("button"); return new By.ByCssSelector("button");
} }
private static By.ByCssSelector deletePasskeyButton() {
return new By.ByCssSelector("table > tbody > tr > button");
}
/** /**
* The configuration for WebAuthN tests. It accesses the Server's current port, so we * The configuration for WebAuthN tests. It accesses the Server's current port, so we
* can configurer WebAuthnConfigurer#allowedOrigin * can configurer WebAuthnConfigurer#allowedOrigin
@@ -177,7 +177,7 @@ public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBui
} }
/** /**
* Gets a shared Object. Note that object hierarchies are not considered. * Gets a shared Object. Note that object heirarchies are not considered.
* @param sharedType the type of the shared Object * @param sharedType the type of the shared Object
* @return the shared Object or null if it is not found * @return the shared Object or null if it is not found
*/ */
@@ -360,7 +360,7 @@ public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBui
/** /**
* Subclasses must implement this method to build the object that is being returned. * Subclasses must implement this method to build the object that is being returned.
* @return the Object to be built or null if the implementation allows it * @return the Object to be buit or null if the implementation allows it
*/ */
protected abstract O performBuild(); protected abstract O performBuild();
@@ -414,13 +414,13 @@ public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBui
private enum BuildState { private enum BuildState {
/** /**
* This is the state before the {@link SecurityBuilder#build()} is invoked * This is the state before the {@link Builder#build()} is invoked
*/ */
UNBUILT(0), UNBUILT(0),
/** /**
* The state from when {@link SecurityBuilder#build()} is first invoked until all * The state from when {@link Builder#build()} is first invoked until all the
* the {@link SecurityConfigurer#init(SecurityBuilder)} methods have been invoked. * {@link SecurityConfigurer#init(SecurityBuilder)} methods have been invoked.
*/ */
INITIALIZING(1), INITIALIZING(1),
@@ -17,7 +17,6 @@
package org.springframework.security.config.annotation.authentication.configurers.ldap; package org.springframework.security.config.annotation.authentication.configurers.ldap;
import java.io.IOException; import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket; import java.net.ServerSocket;
import org.springframework.ldap.core.support.BaseLdapPathContextSource; import org.springframework.ldap.core.support.BaseLdapPathContextSource;
@@ -591,7 +590,7 @@ public class LdapAuthenticationProviderConfigurer<B extends ProviderManagerBuild
} }
private int getDefaultPort() { private int getDefaultPort() {
try (ServerSocket serverSocket = new ServerSocket(DEFAULT_PORT, 50, InetAddress.getLoopbackAddress())) { try (ServerSocket serverSocket = new ServerSocket(DEFAULT_PORT)) {
return serverSocket.getLocalPort(); return serverSocket.getLocalPort();
} }
catch (IOException ex) { catch (IOException ex) {
@@ -39,9 +39,6 @@ import org.springframework.util.ClassUtils;
@Deprecated @Deprecated
final class GlobalMethodSecuritySelector implements ImportSelector { final class GlobalMethodSecuritySelector implements ImportSelector {
private static final boolean isAccessPresent = ClassUtils.isPresent(
"org.springframework.security.access.intercept.aopalliance.MethodSecurityMetadataSourceAdvisor", null);
@Override @Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) { public String[] selectImports(AnnotationMetadata importingClassMetadata) {
Class<EnableGlobalMethodSecurity> annoType = EnableGlobalMethodSecurity.class; Class<EnableGlobalMethodSecurity> annoType = EnableGlobalMethodSecurity.class;
@@ -62,26 +59,16 @@ final class GlobalMethodSecuritySelector implements ImportSelector {
boolean jsr250Enabled = attributes.getBoolean("jsr250Enabled"); boolean jsr250Enabled = attributes.getBoolean("jsr250Enabled");
List<String> classNames = new ArrayList<>(4); List<String> classNames = new ArrayList<>(4);
if (isProxy) { if (isProxy) {
assertAccessModulePresent();
classNames.add(MethodSecurityMetadataSourceAdvisorRegistrar.class.getName()); classNames.add(MethodSecurityMetadataSourceAdvisorRegistrar.class.getName());
} }
classNames.add(autoProxyClassName); classNames.add(autoProxyClassName);
if (!skipMethodSecurityConfiguration) { if (!skipMethodSecurityConfiguration) {
assertAccessModulePresent();
classNames.add(GlobalMethodSecurityConfiguration.class.getName()); classNames.add(GlobalMethodSecurityConfiguration.class.getName());
} }
if (jsr250Enabled) { if (jsr250Enabled) {
assertAccessModulePresent();
classNames.add(Jsr250MetadataSourceConfiguration.class.getName()); classNames.add(Jsr250MetadataSourceConfiguration.class.getName());
} }
return classNames.toArray(new String[0]); return classNames.toArray(new String[0]);
} }
private static void assertAccessModulePresent() {
Assert.state(isAccessPresent,
() -> "@EnableGlobalMethodSecurity requires the spring-security-access dependency on the "
+ "classpath. Please add spring-security-access, or migrate to @EnableMethodSecurity "
+ "which does not require it.");
}
} }
@@ -42,8 +42,7 @@ final class MethodSecuritySelector implements ImportSelector {
.isPresent("org.springframework.security.data.aot.hint.AuthorizeReturnObjectDataHintsRegistrar", null); .isPresent("org.springframework.security.data.aot.hint.AuthorizeReturnObjectDataHintsRegistrar", null);
private static final boolean isWebPresent = ClassUtils private static final boolean isWebPresent = ClassUtils
.isPresent("org.springframework.web.servlet.DispatcherServlet", null) .isPresent("org.springframework.web.servlet.DispatcherServlet", null);
&& ClassUtils.isPresent("org.springframework.security.web.util.ThrowableAnalyzer", null);
private static final boolean isObservabilityPresent = ClassUtils private static final boolean isObservabilityPresent = ClassUtils
.isPresent("io.micrometer.observation.ObservationRegistry", null); .isPresent("io.micrometer.observation.ObservationRegistry", null);
@@ -26,7 +26,6 @@ import org.springframework.context.annotation.AutoProxyRegistrar;
import org.springframework.context.annotation.ImportSelector; import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.AnnotationMetadata;
import org.springframework.lang.NonNull; import org.springframework.lang.NonNull;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
/** /**
@@ -36,8 +35,6 @@ import org.springframework.util.ClassUtils;
*/ */
class ReactiveMethodSecuritySelector implements ImportSelector { class ReactiveMethodSecuritySelector implements ImportSelector {
private static final String METHOD_SECURITY_METADATA_SOURCE_ADVISOR = "org.springframework.security.access.intercept.aopalliance.MethodSecurityMetadataSourceAdvisor";
private static final boolean isDataPresent = ClassUtils private static final boolean isDataPresent = ClassUtils
.isPresent("org.springframework.security.data.aot.hint.AuthorizeReturnObjectDataHintsRegistrar", null); .isPresent("org.springframework.security.data.aot.hint.AuthorizeReturnObjectDataHintsRegistrar", null);
@@ -59,11 +56,6 @@ class ReactiveMethodSecuritySelector implements ImportSelector {
imports.add(ReactiveAuthorizationManagerMethodSecurityConfiguration.class.getName()); imports.add(ReactiveAuthorizationManagerMethodSecurityConfiguration.class.getName());
} }
else { else {
Assert.state(
ClassUtils.isPresent(METHOD_SECURITY_METADATA_SOURCE_ADVISOR, ClassUtils.getDefaultClassLoader()),
() -> "@EnableReactiveMethodSecurity(useAuthorizationManager = false) requires the "
+ "spring-security-access dependency on the classpath. Please add spring-security-access, "
+ "or use the default useAuthorizationManager = true which does not require it.");
imports.add(ReactiveMethodSecurityConfiguration.class.getName()); imports.add(ReactiveMethodSecurityConfiguration.class.getName());
} }
if (isDataPresent) { if (isDataPresent) {
@@ -82,7 +82,7 @@ public interface HttpSecurityBuilder<H extends HttpSecurityBuilder<H>>
<C> void setSharedObject(Class<C> sharedType, C object); <C> void setSharedObject(Class<C> sharedType, C object);
/** /**
* Gets a shared Object. Note that object hierarchies are not considered. * Gets a shared Object. Note that object heirarchies are not considered.
* @param sharedType the type of the shared Object * @param sharedType the type of the shared Object
* @return the shared Object or null if it is not found * @return the shared Object or null if it is not found
*/ */
@@ -133,7 +133,7 @@ final class FilterOrderRegistration {
/** /**
* Register a {@link Filter} with its specific position. If the {@link Filter} was * Register a {@link Filter} with its specific position. If the {@link Filter} was
* already registered before, the position previously defined is not going to be * already registered before, the position previously defined is not going to be
* overridden * overriden
* @param filter the {@link Filter} to register * @param filter the {@link Filter} to register
* @param position the position to associate with the {@link Filter} * @param position the position to associate with the {@link Filter}
*/ */
@@ -2035,9 +2035,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
*/ */
public HttpSecurity securityMatcher(String... patterns) { public HttpSecurity securityMatcher(String... patterns) {
List<RequestMatcher> matchers = new ArrayList<>(); List<RequestMatcher> matchers = new ArrayList<>();
ApplicationContext context = getSharedObject(ApplicationContext.class); PathPatternRequestMatcher.Builder builder = getSharedObject(PathPatternRequestMatcher.Builder.class);
PathPatternRequestMatcher.Builder builder = context.getBeanProvider(PathPatternRequestMatcher.Builder.class)
.getIfUnique(() -> getSharedObject(PathPatternRequestMatcher.Builder.class));
for (String pattern : patterns) { for (String pattern : patterns) {
matchers.add(builder.matcher(pattern)); matchers.add(builder.matcher(pattern));
} }
@@ -2054,6 +2052,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
* http * http
* // ... * // ...
* .webAuthn((webAuthn) -&gt; webAuthn * .webAuthn((webAuthn) -&gt; webAuthn
* .rpName("Spring Security Relying Party")
* .rpId("example.com") * .rpId("example.com")
* .allowedOrigins("https://example.com") * .allowedOrigins("https://example.com")
* ); * );
@@ -379,8 +379,9 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
} }
if (filter instanceof AuthorizationFilter authorization) { if (filter instanceof AuthorizationFilter authorization) {
AuthorizationManager<HttpServletRequest> authorizationManager = authorization.getAuthorizationManager(); AuthorizationManager<HttpServletRequest> authorizationManager = authorization.getAuthorizationManager();
builder.add(securityFilterChain::matches, (authentication, context) -> authorizationManager builder.add(securityFilterChain::matches,
.authorize(authentication, context.getRequest())); (authentication, context) -> (AuthorizationDecision) authorizationManager
.authorize(authentication, context.getRequest()));
mappings = true; mappings = true;
} }
} }
@@ -305,7 +305,7 @@ public final class LogoutConfigurer<H extends HttpSecurityBuilder<H>>
} }
/** /**
* Gets the logoutSuccessUrl or null if a * Gets the logoutSuccesUrl or null if a
* {@link #logoutSuccessHandler(LogoutSuccessHandler)} was configured. * {@link #logoutSuccessHandler(LogoutSuccessHandler)} was configured.
* @return the logoutSuccessUrl * @return the logoutSuccessUrl
*/ */
@@ -146,7 +146,7 @@ public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
/** /**
* This should not use RequestAttributeSecurityContextRepository since that is * This should not use RequestAttributeSecurityContextRepository since that is
* stateless and session management is about state management. * stateless and sesison management is about state management.
*/ */
private SecurityContextRepository sessionManagementSecurityContextRepository = new HttpSessionSecurityContextRepository(); private SecurityContextRepository sessionManagementSecurityContextRepository = new HttpSessionSecurityContextRepository();
@@ -39,7 +39,6 @@ import org.springframework.security.web.webauthn.api.PublicKeyCredentialRpEntity
import org.springframework.security.web.webauthn.authentication.PublicKeyCredentialRequestOptionsFilter; import org.springframework.security.web.webauthn.authentication.PublicKeyCredentialRequestOptionsFilter;
import org.springframework.security.web.webauthn.authentication.WebAuthnAuthenticationFilter; import org.springframework.security.web.webauthn.authentication.WebAuthnAuthenticationFilter;
import org.springframework.security.web.webauthn.authentication.WebAuthnAuthenticationProvider; import org.springframework.security.web.webauthn.authentication.WebAuthnAuthenticationProvider;
import org.springframework.security.web.webauthn.management.CredentialRecordOwnerAuthorizationManager;
import org.springframework.security.web.webauthn.management.MapPublicKeyCredentialUserEntityRepository; import org.springframework.security.web.webauthn.management.MapPublicKeyCredentialUserEntityRepository;
import org.springframework.security.web.webauthn.management.MapUserCredentialRepository; import org.springframework.security.web.webauthn.management.MapUserCredentialRepository;
import org.springframework.security.web.webauthn.management.PublicKeyCredentialUserEntityRepository; import org.springframework.security.web.webauthn.management.PublicKeyCredentialUserEntityRepository;
@@ -178,11 +177,8 @@ public class WebAuthnConfigurer<H extends HttpSecurityBuilder<H>>
WebAuthnAuthenticationFilter webAuthnAuthnFilter = new WebAuthnAuthenticationFilter(); WebAuthnAuthenticationFilter webAuthnAuthnFilter = new WebAuthnAuthenticationFilter();
webAuthnAuthnFilter.setAuthenticationManager( webAuthnAuthnFilter.setAuthenticationManager(
new ProviderManager(new WebAuthnAuthenticationProvider(rpOperations, userDetailsService))); new ProviderManager(new WebAuthnAuthenticationProvider(rpOperations, userDetailsService)));
webAuthnAuthnFilter = postProcess(webAuthnAuthnFilter);
WebAuthnRegistrationFilter webAuthnRegistrationFilter = new WebAuthnRegistrationFilter(userCredentials, WebAuthnRegistrationFilter webAuthnRegistrationFilter = new WebAuthnRegistrationFilter(userCredentials,
rpOperations); rpOperations);
webAuthnRegistrationFilter.setDeleteCredentialAuthorizationManager(
new CredentialRecordOwnerAuthorizationManager(userCredentials, userEntities));
PublicKeyCredentialCreationOptionsFilter creationOptionsFilter = new PublicKeyCredentialCreationOptionsFilter( PublicKeyCredentialCreationOptionsFilter creationOptionsFilter = new PublicKeyCredentialCreationOptionsFilter(
rpOperations); rpOperations);
if (creationOptionsRepository != null) { if (creationOptionsRepository != null) {
@@ -260,10 +256,9 @@ public class WebAuthnConfigurer<H extends HttpSecurityBuilder<H>>
PublicKeyCredentialUserEntityRepository userEntities, UserCredentialRepository userCredentials) { PublicKeyCredentialUserEntityRepository userEntities, UserCredentialRepository userCredentials) {
Optional<WebAuthnRelyingPartyOperations> webauthnOperationsBean = getBeanOrNull( Optional<WebAuthnRelyingPartyOperations> webauthnOperationsBean = getBeanOrNull(
WebAuthnRelyingPartyOperations.class); WebAuthnRelyingPartyOperations.class);
String rpName = (this.rpName != null) ? this.rpName : this.rpId; return webauthnOperationsBean.orElseGet(() -> new Webauthn4JRelyingPartyOperations(userEntities,
return webauthnOperationsBean userCredentials, PublicKeyCredentialRpEntity.builder().id(this.rpId).name(this.rpName).build(),
.orElseGet(() -> new Webauthn4JRelyingPartyOperations(userEntities, userCredentials, this.allowedOrigins));
PublicKeyCredentialRpEntity.builder().id(this.rpId).name(rpName).build(), this.allowedOrigins));
} }
} }
@@ -16,12 +16,10 @@
package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization; package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
import jakarta.servlet.Filter;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
@@ -38,12 +36,10 @@ import org.springframework.security.oauth2.server.authorization.authentication.O
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationValidator; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationValidator;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationProvider; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationToken; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter; import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeRequestAuthenticationConverter; import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeRequestAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationConsentAuthenticationConverter; import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationConsentAuthenticationConverter;
import org.springframework.security.web.access.intercept.AuthorizationFilter;
import org.springframework.security.web.authentication.AuthenticationConverter; import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
@@ -54,7 +50,6 @@ import org.springframework.security.web.servlet.util.matcher.PathPatternRequestM
import org.springframework.security.web.util.matcher.OrRequestMatcher; import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
@@ -88,8 +83,6 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C
private Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authorizationCodeRequestAuthenticationValidator; private Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authorizationCodeRequestAuthenticationValidator;
private Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authorizationCodeRequestAuthenticationValidatorComposite;
private SessionAuthenticationStrategy sessionAuthenticationStrategy; private SessionAuthenticationStrategy sessionAuthenticationStrategy;
/** /**
@@ -255,16 +248,8 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C
authenticationProviders.addAll(0, this.authenticationProviders); authenticationProviders.addAll(0, this.authenticationProviders);
} }
this.authenticationProvidersConsumer.accept(authenticationProviders); this.authenticationProvidersConsumer.accept(authenticationProviders);
authenticationProviders.forEach((authenticationProvider) -> { authenticationProviders.forEach(
httpSecurity.authenticationProvider(postProcess(authenticationProvider)); (authenticationProvider) -> httpSecurity.authenticationProvider(postProcess(authenticationProvider)));
if (authenticationProvider instanceof OAuth2AuthorizationCodeRequestAuthenticationProvider) {
Method method = ReflectionUtils.findMethod(OAuth2AuthorizationCodeRequestAuthenticationProvider.class,
"getAuthenticationValidatorComposite");
ReflectionUtils.makeAccessible(method);
this.authorizationCodeRequestAuthenticationValidatorComposite = (Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext>) ReflectionUtils
.invokeMethod(method, authenticationProvider);
}
});
} }
@Override @Override
@@ -297,18 +282,7 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C
if (this.sessionAuthenticationStrategy != null) { if (this.sessionAuthenticationStrategy != null) {
authorizationEndpointFilter.setSessionAuthenticationStrategy(this.sessionAuthenticationStrategy); authorizationEndpointFilter.setSessionAuthenticationStrategy(this.sessionAuthenticationStrategy);
} }
httpSecurity.addFilterAfter(postProcess(authorizationEndpointFilter), AuthorizationFilter.class); httpSecurity.addFilterBefore(postProcess(authorizationEndpointFilter),
// Create and add
// OAuth2AuthorizationEndpointFilter.OAuth2AuthorizationCodeRequestValidatingFilter
Method method = ReflectionUtils.findMethod(OAuth2AuthorizationEndpointFilter.class,
"createAuthorizationCodeRequestValidatingFilter", RegisteredClientRepository.class, Consumer.class);
ReflectionUtils.makeAccessible(method);
RegisteredClientRepository registeredClientRepository = OAuth2ConfigurerUtils
.getRegisteredClientRepository(httpSecurity);
Filter authorizationCodeRequestValidatingFilter = (Filter) ReflectionUtils.invokeMethod(method,
authorizationEndpointFilter, registeredClientRepository,
this.authorizationCodeRequestAuthenticationValidatorComposite);
httpSecurity.addFilterBefore(postProcess(authorizationCodeRequestValidatingFilter),
AbstractPreAuthenticatedProcessingFilter.class); AbstractPreAuthenticatedProcessingFilter.class);
} }
@@ -16,9 +16,14 @@
package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization; package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
import java.util.Map;
import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext; import com.nimbusds.jose.proc.SecurityContext;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.core.ResolvableType; import org.springframework.core.ResolvableType;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@@ -40,6 +45,7 @@ import org.springframework.security.oauth2.server.authorization.token.OAuth2Toke
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer; import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/** /**
* Utility methods for the OAuth 2.0 Configurers. * Utility methods for the OAuth 2.0 Configurers.
@@ -201,16 +207,41 @@ final class OAuth2ConfigurerUtils {
} }
static <T> T getBean(HttpSecurity httpSecurity, Class<T> type) { static <T> T getBean(HttpSecurity httpSecurity, Class<T> type) {
return httpSecurity.getSharedObject(ApplicationContext.class).getBeanProvider(type).getObject(); return httpSecurity.getSharedObject(ApplicationContext.class).getBean(type);
}
@SuppressWarnings("unchecked")
static <T> T getBean(HttpSecurity httpSecurity, ResolvableType type) {
ApplicationContext context = httpSecurity.getSharedObject(ApplicationContext.class);
String[] names = context.getBeanNamesForType(type);
if (names.length == 1) {
return (T) context.getBean(names[0]);
}
if (names.length > 1) {
throw new NoUniqueBeanDefinitionException(type, names);
}
throw new NoSuchBeanDefinitionException(type);
} }
static <T> T getOptionalBean(HttpSecurity httpSecurity, Class<T> type) { static <T> T getOptionalBean(HttpSecurity httpSecurity, Class<T> type) {
return httpSecurity.getSharedObject(ApplicationContext.class).getBeanProvider(type).getIfUnique(); Map<String, T> beansMap = BeanFactoryUtils
.beansOfTypeIncludingAncestors(httpSecurity.getSharedObject(ApplicationContext.class), type);
if (beansMap.size() > 1) {
throw new NoUniqueBeanDefinitionException(type, beansMap.size(),
"Expected single matching bean of type '" + type.getName() + "' but found " + beansMap.size() + ": "
+ StringUtils.collectionToCommaDelimitedString(beansMap.keySet()));
}
return (!beansMap.isEmpty() ? beansMap.values().iterator().next() : null);
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
static <T> T getOptionalBean(HttpSecurity httpSecurity, ResolvableType type) { static <T> T getOptionalBean(HttpSecurity httpSecurity, ResolvableType type) {
return (T) httpSecurity.getSharedObject(ApplicationContext.class).getBeanProvider(type).getIfUnique(); ApplicationContext context = httpSecurity.getSharedObject(ApplicationContext.class);
String[] names = context.getBeanNamesForType(type);
if (names.length > 1) {
throw new NoUniqueBeanDefinitionException(type, names);
}
return (names.length == 1) ? (T) context.getBean(names[0]) : null;
} }
} }
@@ -40,11 +40,11 @@ import org.springframework.security.oauth2.server.authorization.settings.Authori
import org.springframework.security.oauth2.server.authorization.web.OAuth2DeviceVerificationEndpointFilter; import org.springframework.security.oauth2.server.authorization.web.OAuth2DeviceVerificationEndpointFilter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2DeviceAuthorizationConsentAuthenticationConverter; import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2DeviceAuthorizationConsentAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2DeviceVerificationAuthenticationConverter; import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2DeviceVerificationAuthenticationConverter;
import org.springframework.security.web.access.intercept.AuthorizationFilter;
import org.springframework.security.web.authentication.AuthenticationConverter; import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.DelegatingAuthenticationConverter; import org.springframework.security.web.authentication.DelegatingAuthenticationConverter;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher; import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher;
@@ -279,7 +279,8 @@ public final class OAuth2DeviceVerificationEndpointConfigurer extends AbstractOA
if (StringUtils.hasText(this.consentPage)) { if (StringUtils.hasText(this.consentPage)) {
deviceVerificationEndpointFilter.setConsentPage(this.consentPage); deviceVerificationEndpointFilter.setConsentPage(this.consentPage);
} }
builder.addFilterAfter(postProcess(deviceVerificationEndpointFilter), AuthorizationFilter.class); builder.addFilterBefore(postProcess(deviceVerificationEndpointFilter),
AbstractPreAuthenticatedProcessingFilter.class);
} }
@Override @Override
@@ -79,10 +79,8 @@ final class DPoPAuthenticationConfigurer<B extends HttpSecurityBuilder<B>>
@Override @Override
public void configure(B http) { public void configure(B http) {
DPoPAuthenticationProvider authenticationProvider = new DPoPAuthenticationProvider(
getTokenAuthenticationManager(http));
http.authenticationProvider(postProcess(authenticationProvider));
AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class); AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class);
http.authenticationProvider(new DPoPAuthenticationProvider(getTokenAuthenticationManager(http)));
AuthenticationFilter authenticationFilter = new AuthenticationFilter(authenticationManager, AuthenticationFilter authenticationFilter = new AuthenticationFilter(authenticationManager,
getAuthenticationConverter()); getAuthenticationConverter());
authenticationFilter.setRequestMatcher(getRequestMatcher()); authenticationFilter.setRequestMatcher(getRequestMatcher());
@@ -33,7 +33,6 @@ import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationManagerResolver; import org.springframework.security.authentication.AuthenticationManagerResolver;
import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.Customizer; import org.springframework.security.config.Customizer;
import org.springframework.security.config.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder; import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer; import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer;
@@ -299,8 +298,6 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
if (dPoPAuthenticationAvailable) { if (dPoPAuthenticationAvailable) {
DPoPAuthenticationConfigurer<H> dPoPAuthenticationConfigurer = new DPoPAuthenticationConfigurer<>(); DPoPAuthenticationConfigurer<H> dPoPAuthenticationConfigurer = new DPoPAuthenticationConfigurer<>();
dPoPAuthenticationConfigurer.withObjectPostProcessor(
(ObjectPostProcessor<Object>) OAuth2ResourceServerConfigurer.this::postProcess);
dPoPAuthenticationConfigurer.configure(http); dPoPAuthenticationConfigurer.configure(http);
} }
@@ -95,7 +95,7 @@ public class Saml2MetadataConfigurer<H extends HttpSecurityBuilder<H>>
* If there is no {@code registrationId} and your * If there is no {@code registrationId} and your
* {@link RelyingPartyRegistrationRepository} is {code Iterable}, the metadata * {@link RelyingPartyRegistrationRepository} is {code Iterable}, the metadata
* endpoint will try and show all relying parties' metadata in a single * endpoint will try and show all relying parties' metadata in a single
* {@code <md:EntitiesDescriptor} element. * {@code <md:EntitiesDecriptor} element.
* *
* <p> * <p>
* If you need a more sophisticated lookup strategy than these, use * If you need a more sophisticated lookup strategy than these, use
@@ -167,7 +167,7 @@ class ServerHttpSecurityConfiguration {
} }
/** /**
* Applies all {@code Customizer<ServerHttpSecurity>} Beans to * Applies all {@code Custmizer<ServerHttpSecurity>} Beans to
* {@link ServerHttpSecurity}. * {@link ServerHttpSecurity}.
* @param context the {@link ApplicationContext} * @param context the {@link ApplicationContext}
* @param http the {@link ServerHttpSecurity} * @param http the {@link ServerHttpSecurity}
@@ -255,9 +255,7 @@ class ServerHttpSecurityConfiguration {
if (this.passwordEncoder != null) { if (this.passwordEncoder != null) {
manager.setPasswordEncoder(this.passwordEncoder); manager.setPasswordEncoder(this.passwordEncoder);
} }
if (this.userDetailsPasswordService != null) { manager.setUserDetailsPasswordService(this.userDetailsPasswordService);
manager.setUserDetailsPasswordService(this.userDetailsPasswordService);
}
manager.setCompromisedPasswordChecker(this.compromisedPasswordChecker); manager.setCompromisedPasswordChecker(this.compromisedPasswordChecker);
return this.postProcessor.postProcess(manager); return this.postProcessor.postProcess(manager);
} }
@@ -538,7 +538,7 @@ final class AuthenticationConfigBuilder {
} }
injectAuthenticationDetailsSource(x509Elt, filterBuilder); injectAuthenticationDetailsSource(x509Elt, filterBuilder);
filter = (RootBeanDefinition) filterBuilder.getBeanDefinition(); filter = (RootBeanDefinition) filterBuilder.getBeanDefinition();
createPreauthEntryPoint(x509Elt); createPrauthEntryPoint(x509Elt);
createX509Provider(); createX509Provider();
} }
this.x509Filter = filter; this.x509Filter = filter;
@@ -562,7 +562,7 @@ final class AuthenticationConfigBuilder {
this.x509ProviderRef = new RuntimeBeanReference(this.pc.getReaderContext().registerWithGeneratedName(provider)); this.x509ProviderRef = new RuntimeBeanReference(this.pc.getReaderContext().registerWithGeneratedName(provider));
} }
private void createPreauthEntryPoint(Element source) { private void createPrauthEntryPoint(Element source) {
if (this.preAuthEntryPoint == null) { if (this.preAuthEntryPoint == null) {
this.preAuthEntryPoint = new RootBeanDefinition(Http403ForbiddenEntryPoint.class); this.preAuthEntryPoint = new RootBeanDefinition(Http403ForbiddenEntryPoint.class);
this.preAuthEntryPoint.setSource(this.pc.extractSource(source)); this.preAuthEntryPoint.setSource(this.pc.extractSource(source));
@@ -595,7 +595,7 @@ final class AuthenticationConfigBuilder {
adsBldr.addPropertyValue("mappableRolesRetriever", mappableRolesRetriever); adsBldr.addPropertyValue("mappableRolesRetriever", mappableRolesRetriever);
filterBuilder.addPropertyValue("authenticationDetailsSource", adsBldr.getBeanDefinition()); filterBuilder.addPropertyValue("authenticationDetailsSource", adsBldr.getBeanDefinition());
filter = (RootBeanDefinition) filterBuilder.getBeanDefinition(); filter = (RootBeanDefinition) filterBuilder.getBeanDefinition();
createPreauthEntryPoint(jeeElt); createPrauthEntryPoint(jeeElt);
createJeeProvider(); createJeeProvider();
} }
this.jeeFilter = filter; this.jeeFilter = filter;
@@ -70,7 +70,7 @@ public final class PathPatternRequestMatcherFactoryBean
@Override @Override
public void afterPropertiesSet() throws Exception { public void afterPropertiesSet() throws Exception {
if (this.basePath != null) { if (this.basePath != null) {
this.builder = this.builder.basePath(this.basePath); this.builder.basePath(this.basePath);
} }
} }
@@ -17,7 +17,6 @@
package org.springframework.security.config.ldap; package org.springframework.security.config.ldap;
import java.io.IOException; import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket; import java.net.ServerSocket;
import org.w3c.dom.Element; import org.w3c.dom.Element;
@@ -166,20 +165,20 @@ public class LdapServerBeanDefinitionParser implements BeanDefinitionParser {
} }
private RootBeanDefinition getRootBeanDefinition(String mode) { private RootBeanDefinition getRootBeanDefinition(String mode) {
if (isUnboundIdEnabled(mode)) { if (isUnboundidEnabled(mode)) {
return new RootBeanDefinition(UNBOUNDID_CONTAINER_CLASSNAME, null, null); return new RootBeanDefinition(UNBOUNDID_CONTAINER_CLASSNAME, null, null);
} }
throw new IllegalStateException("Embedded LDAP server is not provided"); throw new IllegalStateException("Embedded LDAP server is not provided");
} }
private String resolveBeanId(String mode) { private String resolveBeanId(String mode) {
if (isUnboundIdEnabled(mode)) { if (isUnboundidEnabled(mode)) {
return BeanIds.EMBEDDED_UNBOUNDID; return BeanIds.EMBEDDED_UNBOUNDID;
} }
return null; return null;
} }
private boolean isUnboundIdEnabled(String mode) { private boolean isUnboundidEnabled(String mode) {
return "unboundid".equals(mode) || unboundIdPresent; return "unboundid".equals(mode) || unboundIdPresent;
} }
@@ -189,7 +188,7 @@ public class LdapServerBeanDefinitionParser implements BeanDefinitionParser {
} }
private String getDefaultPort() { private String getDefaultPort() {
try (ServerSocket serverSocket = new ServerSocket(DEFAULT_PORT, 50, InetAddress.getLoopbackAddress())) { try (ServerSocket serverSocket = new ServerSocket(DEFAULT_PORT)) {
return String.valueOf(serverSocket.getLocalPort()); return String.valueOf(serverSocket.getLocalPort());
} }
catch (IOException ex) { catch (IOException ex) {
@@ -12,8 +12,8 @@ base64 =
## Whether a string should be base64 encoded ## Whether a string should be base64 encoded
attribute base64 {xsd:boolean} attribute base64 {xsd:boolean}
request-matcher = request-matcher =
## Defines the strategy use for matching incoming requests. Currently the options are 'path' (for PathPatternRequestMatcher), 'regex' for regular expressions and 'ciRegex' for case-insensitive regular expressions. ## Defines the strategy use for matching incoming requests. Currently the options are 'mvc' (for Spring MVC matcher), 'ant' (for ant path patterns), 'regex' for regular expressions and 'ciRegex' for case-insensitive regular expressions.
attribute request-matcher {"path" | "regex" | "ciRegex"} attribute request-matcher {"mvc" | "ant" | "regex" | "ciRegex"}
port = port =
## Specifies an IP port number. Used to configure an embedded LDAP server, for example. ## Specifies an IP port number. Used to configure an embedded LDAP server, for example.
attribute port { xsd:nonNegativeInteger } attribute port { xsd:nonNegativeInteger }
@@ -27,14 +27,15 @@
<xs:attributeGroup name="request-matcher"> <xs:attributeGroup name="request-matcher">
<xs:attribute name="request-matcher" use="required"> <xs:attribute name="request-matcher" use="required">
<xs:annotation> <xs:annotation>
<xs:documentation>Defines the strategy use for matching incoming requests. Currently the options are 'path' <xs:documentation>Defines the strategy use for matching incoming requests. Currently the options are 'mvc'
(for PathPatternRequestMatcher), 'regex' for regular expressions and 'ciRegex' for (for Spring MVC matcher), 'ant' (for ant path patterns), 'regex' for regular expressions
case-insensitive regular expressions. and 'ciRegex' for case-insensitive regular expressions.
</xs:documentation> </xs:documentation>
</xs:annotation> </xs:annotation>
<xs:simpleType> <xs:simpleType>
<xs:restriction base="xs:token"> <xs:restriction base="xs:token">
<xs:enumeration value="path"/> <xs:enumeration value="mvc"/>
<xs:enumeration value="ant"/>
<xs:enumeration value="regex"/> <xs:enumeration value="regex"/>
<xs:enumeration value="ciRegex"/> <xs:enumeration value="ciRegex"/>
</xs:restriction> </xs:restriction>
@@ -1305,14 +1306,15 @@
</xs:attribute> </xs:attribute>
<xs:attribute name="request-matcher"> <xs:attribute name="request-matcher">
<xs:annotation> <xs:annotation>
<xs:documentation>Defines the strategy use for matching incoming requests. Currently the options are 'path' <xs:documentation>Defines the strategy use for matching incoming requests. Currently the options are 'mvc'
(for PathPatternRequestMatcher), 'regex' for regular expressions and 'ciRegex' for (for Spring MVC matcher), 'ant' (for ant path patterns), 'regex' for regular expressions
case-insensitive regular expressions. and 'ciRegex' for case-insensitive regular expressions.
</xs:documentation> </xs:documentation>
</xs:annotation> </xs:annotation>
<xs:simpleType> <xs:simpleType>
<xs:restriction base="xs:token"> <xs:restriction base="xs:token">
<xs:enumeration value="path"/> <xs:enumeration value="mvc"/>
<xs:enumeration value="ant"/>
<xs:enumeration value="regex"/> <xs:enumeration value="regex"/>
<xs:enumeration value="ciRegex"/> <xs:enumeration value="ciRegex"/>
</xs:restriction> </xs:restriction>
@@ -2472,14 +2474,15 @@
<xs:attributeGroup name="filter-chain-map.attlist"> <xs:attributeGroup name="filter-chain-map.attlist">
<xs:attribute name="request-matcher"> <xs:attribute name="request-matcher">
<xs:annotation> <xs:annotation>
<xs:documentation>Defines the strategy use for matching incoming requests. Currently the options are 'path' <xs:documentation>Defines the strategy use for matching incoming requests. Currently the options are 'mvc'
(for PathPatternRequestMatcher), 'regex' for regular expressions and 'ciRegex' for (for Spring MVC matcher), 'ant' (for ant path patterns), 'regex' for regular expressions
case-insensitive regular expressions. and 'ciRegex' for case-insensitive regular expressions.
</xs:documentation> </xs:documentation>
</xs:annotation> </xs:annotation>
<xs:simpleType> <xs:simpleType>
<xs:restriction base="xs:token"> <xs:restriction base="xs:token">
<xs:enumeration value="path"/> <xs:enumeration value="mvc"/>
<xs:enumeration value="ant"/>
<xs:enumeration value="regex"/> <xs:enumeration value="regex"/>
<xs:enumeration value="ciRegex"/> <xs:enumeration value="ciRegex"/>
</xs:restriction> </xs:restriction>
@@ -2577,14 +2580,15 @@
</xs:attribute> </xs:attribute>
<xs:attribute name="request-matcher"> <xs:attribute name="request-matcher">
<xs:annotation> <xs:annotation>
<xs:documentation>Defines the strategy use for matching incoming requests. Currently the options are 'path' <xs:documentation>Defines the strategy use for matching incoming requests. Currently the options are 'mvc'
(for PathPatternRequestMatcher), 'regex' for regular expressions and 'ciRegex' for (for Spring MVC matcher), 'ant' (for ant path patterns), 'regex' for regular expressions
case-insensitive regular expressions. and 'ciRegex' for case-insensitive regular expressions.
</xs:documentation> </xs:documentation>
</xs:annotation> </xs:annotation>
<xs:simpleType> <xs:simpleType>
<xs:restriction base="xs:token"> <xs:restriction base="xs:token">
<xs:enumeration value="path"/> <xs:enumeration value="mvc"/>
<xs:enumeration value="ant"/>
<xs:enumeration value="regex"/> <xs:enumeration value="regex"/>
<xs:enumeration value="ciRegex"/> <xs:enumeration value="ciRegex"/>
</xs:restriction> </xs:restriction>
@@ -20,7 +20,6 @@ import java.io.IOException;
import java.io.Serializable; import java.io.Serializable;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.security.Principal; import java.security.Principal;
import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.util.Collection; import java.util.Collection;
import java.util.Date; import java.util.Date;
@@ -86,9 +85,6 @@ import org.springframework.security.authentication.password.CompromisedPasswordE
import org.springframework.security.authorization.AuthorityAuthorizationDecision; import org.springframework.security.authorization.AuthorityAuthorizationDecision;
import org.springframework.security.authorization.AuthorizationDecision; import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationDeniedException; import org.springframework.security.authorization.AuthorizationDeniedException;
import org.springframework.security.authorization.FactorAuthorizationDecision;
import org.springframework.security.authorization.RequiredFactor;
import org.springframework.security.authorization.RequiredFactorError;
import org.springframework.security.authorization.event.AuthorizationEvent; import org.springframework.security.authorization.event.AuthorizationEvent;
import org.springframework.security.authorization.event.AuthorizationGrantedEvent; import org.springframework.security.authorization.event.AuthorizationGrantedEvent;
import org.springframework.security.cas.authentication.CasAssertionAuthenticationToken; import org.springframework.security.cas.authentication.CasAssertionAuthenticationToken;
@@ -165,7 +161,6 @@ import org.springframework.security.oauth2.jwt.JwtException;
import org.springframework.security.oauth2.jwt.JwtValidationException; import org.springframework.security.oauth2.jwt.JwtValidationException;
import org.springframework.security.oauth2.jwt.TestJwts; import org.springframework.security.oauth2.jwt.TestJwts;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationServerMetadata; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationServerMetadata;
import org.springframework.security.oauth2.server.authorization.OAuth2ClientRegistration; import org.springframework.security.oauth2.server.authorization.OAuth2ClientRegistration;
@@ -173,22 +168,15 @@ import org.springframework.security.oauth2.server.authorization.OAuth2TokenIntro
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationException;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationToken; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationToken; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientRegistrationAuthenticationToken; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientRegistrationAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceAuthorizationConsentAuthenticationToken; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceAuthorizationConsentAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceAuthorizationRequestAuthenticationToken; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceAuthorizationRequestAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceCodeAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceVerificationAuthenticationToken; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceVerificationAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2PushedAuthorizationRequestAuthenticationToken; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2PushedAuthorizationRequestAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2RefreshTokenAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeActor;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeCompositeAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenIntrospectionAuthenticationToken; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenIntrospectionAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenRevocationAuthenticationToken; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenRevocationAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
@@ -202,7 +190,6 @@ import org.springframework.security.oauth2.server.authorization.settings.Authori
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat; import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings; import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimNames;
import org.springframework.security.oauth2.server.resource.BearerTokenError; import org.springframework.security.oauth2.server.resource.BearerTokenError;
import org.springframework.security.oauth2.server.resource.BearerTokenErrors; import org.springframework.security.oauth2.server.resource.BearerTokenErrors;
import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException; import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;
@@ -262,7 +249,6 @@ import org.springframework.security.web.webauthn.api.AuthenticationExtensionsCli
import org.springframework.security.web.webauthn.api.AuthenticationExtensionsClientOutputs; import org.springframework.security.web.webauthn.api.AuthenticationExtensionsClientOutputs;
import org.springframework.security.web.webauthn.api.AuthenticatorAssertionResponse; import org.springframework.security.web.webauthn.api.AuthenticatorAssertionResponse;
import org.springframework.security.web.webauthn.api.AuthenticatorAttachment; import org.springframework.security.web.webauthn.api.AuthenticatorAttachment;
import org.springframework.security.web.webauthn.api.AuthenticatorAttestationResponse;
import org.springframework.security.web.webauthn.api.AuthenticatorTransport; import org.springframework.security.web.webauthn.api.AuthenticatorTransport;
import org.springframework.security.web.webauthn.api.Bytes; import org.springframework.security.web.webauthn.api.Bytes;
import org.springframework.security.web.webauthn.api.CredProtectAuthenticationExtensionsClientInput; import org.springframework.security.web.webauthn.api.CredProtectAuthenticationExtensionsClientInput;
@@ -277,7 +263,6 @@ import org.springframework.security.web.webauthn.api.PublicKeyCredentialRequestO
import org.springframework.security.web.webauthn.api.PublicKeyCredentialType; import org.springframework.security.web.webauthn.api.PublicKeyCredentialType;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity; import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity;
import org.springframework.security.web.webauthn.api.TestAuthenticationAssertionResponses; import org.springframework.security.web.webauthn.api.TestAuthenticationAssertionResponses;
import org.springframework.security.web.webauthn.api.TestAuthenticatorAttestationResponses;
import org.springframework.security.web.webauthn.api.TestBytes; import org.springframework.security.web.webauthn.api.TestBytes;
import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialRequestOptions; import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialRequestOptions;
import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialUserEntities; import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialUserEntities;
@@ -442,8 +427,6 @@ final class SerializationSamples {
generatorByClassName.put(RegisteredClient.class, (r) -> registeredClient); generatorByClassName.put(RegisteredClient.class, (r) -> registeredClient);
generatorByClassName.put(OAuth2Authorization.class, (r) -> authorization); generatorByClassName.put(OAuth2Authorization.class, (r) -> authorization);
generatorByClassName.put(OAuth2Authorization.Token.class, (r) -> authorization.getAccessToken()); generatorByClassName.put(OAuth2Authorization.Token.class, (r) -> authorization.getAccessToken());
generatorByClassName.put(OAuth2AuthorizationCode.class,
(r) -> new OAuth2AuthorizationCode("code", Instant.now(), Instant.now().plusSeconds(300)));
generatorByClassName.put(OAuth2AuthorizationConsent.class, generatorByClassName.put(OAuth2AuthorizationConsent.class,
(r) -> OAuth2AuthorizationConsent.withId("registeredClientId", "principalName") (r) -> OAuth2AuthorizationConsent.withId("registeredClientId", "principalName")
.scope("scope1") .scope("scope1")
@@ -469,58 +452,6 @@ final class SerializationSamples {
authenticationToken.setDetails(details); authenticationToken.setDetails(details);
return authenticationToken; return authenticationToken;
}); });
generatorByClassName.put(
org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationToken.class,
(r) -> {
org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationToken token = new org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationToken(
"code", principal, "https://localhost/callback", Map.of("custom_param", "custom_value"));
token.setDetails(details);
return token;
});
generatorByClassName.put(OAuth2AuthorizationCodeRequestAuthenticationException.class, (r) -> {
OAuth2AuthorizationCodeRequestAuthenticationToken authToken = new OAuth2AuthorizationCodeRequestAuthenticationToken(
"https://localhost/authorize", "clientId", principal, "https://localhost/callback", "state",
authorizationRequest.getScopes(), authorizationRequest.getAdditionalParameters());
return new OAuth2AuthorizationCodeRequestAuthenticationException(
new OAuth2Error("invalid_request", "Missing required parameter", "https://example.com/error"),
authToken);
});
generatorByClassName.put(OAuth2ClientCredentialsAuthenticationToken.class, (r) -> {
OAuth2ClientCredentialsAuthenticationToken token = new OAuth2ClientCredentialsAuthenticationToken(principal,
Set.of("scope1", "scope2"), Map.of("custom_param", "custom_value"));
token.setDetails(details);
return token;
});
generatorByClassName.put(OAuth2DeviceCodeAuthenticationToken.class, (r) -> {
OAuth2DeviceCodeAuthenticationToken token = new OAuth2DeviceCodeAuthenticationToken("device-code",
principal, Map.of("custom_param", "custom_value"));
token.setDetails(details);
return token;
});
generatorByClassName.put(OAuth2RefreshTokenAuthenticationToken.class, (r) -> {
OAuth2RefreshTokenAuthenticationToken token = new OAuth2RefreshTokenAuthenticationToken("refresh-token",
principal, Set.of("scope1", "scope2"), Map.of("custom_param", "custom_value"));
token.setDetails(details);
return token;
});
generatorByClassName.put(OAuth2TokenExchangeAuthenticationToken.class, (r) -> {
OAuth2TokenExchangeAuthenticationToken token = new OAuth2TokenExchangeAuthenticationToken(
"urn:ietf:params:oauth:token-type:access_token", "subject-token",
"urn:ietf:params:oauth:token-type:jwt", principal, "actor-token",
"urn:ietf:params:oauth:token-type:jwt", Set.of("https://resource.example.com"), Set.of("audience"),
Set.of("scope1"), Map.of("custom_param", "custom_value"));
token.setDetails(details);
return token;
});
OAuth2TokenExchangeActor actor = new OAuth2TokenExchangeActor(Map.of(OAuth2TokenClaimNames.ISS,
"https://issuer.example.com", OAuth2TokenClaimNames.SUB, "actor-subject"));
generatorByClassName.put(OAuth2TokenExchangeActor.class, (r) -> actor);
generatorByClassName.put(OAuth2TokenExchangeCompositeAuthenticationToken.class, (r) -> {
AbstractAuthenticationToken token = new OAuth2TokenExchangeCompositeAuthenticationToken(authentication,
List.of(actor));
token.setDetails(details);
return token;
});
generatorByClassName.put(OAuth2AuthorizationConsentAuthenticationToken.class, (r) -> { generatorByClassName.put(OAuth2AuthorizationConsentAuthenticationToken.class, (r) -> {
OAuth2AuthorizationConsentAuthenticationToken authenticationToken = new OAuth2AuthorizationConsentAuthenticationToken( OAuth2AuthorizationConsentAuthenticationToken authenticationToken = new OAuth2AuthorizationConsentAuthenticationToken(
"authorizationUri", "clientId", principal, "state", authorizationRequest.getScopes(), "authorizationUri", "clientId", principal, "state", authorizationRequest.getScopes(),
@@ -737,12 +668,6 @@ final class SerializationSamples {
generatorByClassName.put(AuthorizationDecision.class, (r) -> new AuthorizationDecision(true)); generatorByClassName.put(AuthorizationDecision.class, (r) -> new AuthorizationDecision(true));
generatorByClassName.put(AuthorityAuthorizationDecision.class, generatorByClassName.put(AuthorityAuthorizationDecision.class,
(r) -> new AuthorityAuthorizationDecision(true, AuthorityUtils.createAuthorityList("ROLE_USER"))); (r) -> new AuthorityAuthorizationDecision(true, AuthorityUtils.createAuthorityList("ROLE_USER")));
RequiredFactor factor = RequiredFactor.withAuthority("authority").validDuration(Duration.ofSeconds(5)).build();
generatorByClassName.put(RequiredFactor.class, (r) -> factor);
RequiredFactorError error = RequiredFactorError.createMissing(factor);
generatorByClassName.put(RequiredFactorError.class, (r) -> error);
generatorByClassName.put(FactorAuthorizationDecision.class,
(r) -> new FactorAuthorizationDecision(List.of(error)));
generatorByClassName.put(CycleInRoleHierarchyException.class, (r) -> new CycleInRoleHierarchyException()); generatorByClassName.put(CycleInRoleHierarchyException.class, (r) -> new CycleInRoleHierarchyException());
generatorByClassName.put(AuthorizationEvent.class, generatorByClassName.put(AuthorizationEvent.class,
(r) -> new AuthorizationEvent(new SerializableSupplier<>(authentication), "source", (r) -> new AuthorizationEvent(new SerializableSupplier<>(authentication), "source",
@@ -933,8 +858,6 @@ final class SerializationSamples {
generatorByClassName.put(CredentialPropertiesOutput.class, (o) -> credentialOutput); generatorByClassName.put(CredentialPropertiesOutput.class, (o) -> credentialOutput);
generatorByClassName.put(ImmutableAuthenticationExtensionsClientOutputs.class, (o) -> outputs); generatorByClassName.put(ImmutableAuthenticationExtensionsClientOutputs.class, (o) -> outputs);
generatorByClassName.put(AuthenticatorAssertionResponse.class, (r) -> response); generatorByClassName.put(AuthenticatorAssertionResponse.class, (r) -> response);
generatorByClassName.put(AuthenticatorAttestationResponse.class,
(r) -> TestAuthenticatorAttestationResponses.createAuthenticatorAttestationResponse().build());
generatorByClassName.put(RelyingPartyAuthenticationRequest.class, (r) -> authRequest); generatorByClassName.put(RelyingPartyAuthenticationRequest.class, (r) -> authRequest);
generatorByClassName.put(PublicKeyCredential.class, (r) -> credential); generatorByClassName.put(PublicKeyCredential.class, (r) -> credential);
generatorByClassName.put(WebAuthnAuthenticationRequestToken.class, (r) -> requestToken); generatorByClassName.put(WebAuthnAuthenticationRequestToken.class, (r) -> requestToken);
@@ -33,10 +33,10 @@ import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.ObjectUtils;
@@ -207,7 +207,10 @@ class SpringSecurityCoreVersionSerializableTests {
boolean hasSerialVersion = Stream.of(clazz.getDeclaredFields()) boolean hasSerialVersion = Stream.of(clazz.getDeclaredFields())
.map(Field::getName) .map(Field::getName)
.anyMatch((n) -> n.equals("serialVersionUID")); .anyMatch((n) -> n.equals("serialVersionUID"));
if (!hasSerialVersion && !hasSuppressSerialInSource(clazz)) { SuppressWarnings suppressWarnings = clazz.getAnnotation(SuppressWarnings.class);
boolean hasSerialIgnore = suppressWarnings == null
|| Arrays.asList(suppressWarnings.value()).contains("Serial");
if (!hasSerialVersion && !hasSerialIgnore) {
classes.add(clazz); classes.add(clazz);
continue; continue;
} }
@@ -246,62 +249,11 @@ class SpringSecurityCoreVersionSerializableTests {
return classes.stream(); return classes.stream();
} }
private static boolean hasSuppressSerialInSource(Class<?> clazz) {
try {
Class<?> fileClass = clazz;
while (fileClass.getEnclosingClass() != null) {
fileClass = fileClass.getEnclosingClass();
}
var codeSource = fileClass.getProtectionDomain().getCodeSource();
if (codeSource == null) {
return false;
}
Path sourceFile = findSourceFile(Path.of(codeSource.getLocation().toURI()), fileClass);
if (sourceFile == null) {
return false;
}
return hasSuppressSerialAnnotation(Files.readAllLines(sourceFile), clazz.getSimpleName());
}
catch (Exception ex) {
return false;
}
}
private static Path findSourceFile(Path start, Class<?> clazz) {
String relativePath = clazz.getName().replace('.', '/') + ".java";
Path dir = start;
for (int i = 0; i < 10 && dir != null; i++) {
for (String sourceRoot : List.of("src/main/java", "src/test/java")) {
Path candidate = dir.resolve(sourceRoot).resolve(relativePath);
if (Files.exists(candidate)) {
return candidate;
}
}
dir = dir.getParent();
}
return null;
}
private static boolean hasSuppressSerialAnnotation(List<String> lines, String simpleClassName) {
Pattern classDeclaration = Pattern
.compile("\\b(?:class|interface|enum|record)\\s+" + Pattern.quote(simpleClassName) + "\\b");
for (int i = 0; i < lines.size(); i++) {
if (classDeclaration.matcher(lines.get(i)).find()) {
for (int j = Math.max(0, i - 5); j < i; j++) {
String line = lines.get(j);
if (line.contains("@SuppressWarnings") && line.contains("\"serial\"")) {
return true;
}
}
}
}
return false;
}
private static String getCurrentVersion() { private static String getCurrentVersion() {
String version = System.getProperty("springSecurityVersion"); String version = System.getProperty("springSecurityVersion");
String[] parts = version.split("\\."); String[] parts = version.split("\\.");
return parts[0] + "." + parts[1] + ".x"; parts[2] = "x";
return String.join(".", parts);
} }
private static String getPreviousVersion() { private static String getPreviousVersion() {
@@ -314,7 +266,8 @@ class SpringSecurityCoreVersionSerializableTests {
parts[0] = String.valueOf(Integer.parseInt(parts[0]) - 1); parts[0] = String.valueOf(Integer.parseInt(parts[0]) - 1);
parts[1] = "5"; // FIXME: this should not be hard coded parts[1] = "5"; // FIXME: this should not be hard coded
} }
return parts[0] + "." + parts[1] + ".x"; parts[2] = "x";
return String.join(".", parts);
} }
} }
@@ -1,138 +0,0 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.method.configuration;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AdviceMode;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.test.support.ClassPathExclusions;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Tests for gh-19441: {@code spring-security-access} moved
* {@link org.springframework.security.access.intercept.aopalliance.MethodSecurityMetadataSourceAdvisor}
* out of {@code spring-security-core} and into the optional
* {@code spring-security-access} module. {@link EnableMethodSecurity} and
* {@link EnableReactiveMethodSecurity}'s default (AuthorizationManager-based) mode never
* needed that class and continue to work without {@code spring-security-access} on the
* classpath, but the deprecated legacy method security annotations do need it and
* previously failed with a confusing {@link NoClassDefFoundError} instead of an
* actionable message.
*/
@ClassPathExclusions("spring-security-access-*.jar")
public class Gh19441Tests {
@Test
public void enableMethodSecurityWhenAccessModuleAbsentThenContextStartsCleanly() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
context.register(EnableMethodSecurityConfig.class);
context.refresh();
}
}
@Test
public void enableReactiveMethodSecurityWhenAccessModuleAbsentThenContextStartsCleanly() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
context.register(EnableReactiveMethodSecurityConfig.class);
context.refresh();
}
}
@Test
public void enableGlobalMethodSecurityWhenProxyModeAndAccessModuleAbsentThenClearException() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
context.register(EnableGlobalMethodSecurityProxyConfig.class);
assertThatExceptionOfType(Exception.class).isThrownBy(context::refresh)
.havingRootCause()
.isInstanceOf(IllegalStateException.class)
.withMessageContaining("spring-security-access");
}
}
@Test
public void enableGlobalMethodSecurityWhenAspectJModeAndAccessModuleAbsentThenClearException() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
context.register(EnableGlobalMethodSecurityAspectJConfig.class);
assertThatExceptionOfType(Exception.class).isThrownBy(context::refresh)
.havingRootCause()
.isInstanceOf(IllegalStateException.class)
.withMessageContaining("spring-security-access");
}
}
@Test
public void enableReactiveMethodSecurityWhenUseAuthorizationManagerFalseAndAccessModuleAbsentThenClearException() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
context.register(EnableReactiveMethodSecurityLegacyConfig.class);
assertThatExceptionOfType(Exception.class).isThrownBy(context::refresh)
.havingRootCause()
.isInstanceOf(IllegalStateException.class)
.withMessageContaining("spring-security-access");
}
}
@Test
public void enableGlobalMethodSecurityWhenAspectJModeAndJsr250EnabledAndConfigurationSubclassedAndAccessModuleAbsentThenClearException() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
context.register(EnableGlobalMethodSecurityAspectJJsr250SubclassedConfig.class);
assertThatExceptionOfType(Exception.class).isThrownBy(context::refresh)
.havingRootCause()
.isInstanceOf(IllegalStateException.class)
.withMessageContaining("spring-security-access");
}
}
@Configuration
@EnableMethodSecurity
static class EnableMethodSecurityConfig {
}
@Configuration
@EnableReactiveMethodSecurity
static class EnableReactiveMethodSecurityConfig {
}
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
static class EnableGlobalMethodSecurityProxyConfig {
}
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, mode = AdviceMode.ASPECTJ)
static class EnableGlobalMethodSecurityAspectJConfig {
}
@Configuration
@EnableReactiveMethodSecurity(useAuthorizationManager = false)
static class EnableReactiveMethodSecurityLegacyConfig {
}
@Configuration
@EnableGlobalMethodSecurity(jsr250Enabled = true, mode = AdviceMode.ASPECTJ)
static class EnableGlobalMethodSecurityAspectJJsr250SubclassedConfig extends GlobalMethodSecurityConfiguration {
}
}
@@ -37,7 +37,6 @@ import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockServletContext; import org.springframework.mock.web.MockServletContext;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.web.PathPatternRequestMatcherBuilderFactoryBean;
import org.springframework.security.core.userdetails.PasswordEncodedUser; import org.springframework.security.core.userdetails.PasswordEncodedUser;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.provisioning.InMemoryUserDetailsManager;
@@ -111,21 +110,6 @@ public class WebSecurityTests {
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_BAD_REQUEST); assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_BAD_REQUEST);
} }
// gh-19128
@Test
public void ignoringWhenBuilderBeanWithBasePathThenHonorsBasePath() throws Exception {
loadConfig(IgnoringBuilderBeanConfig.class);
this.request.setServletPath("/spring");
this.request.setRequestURI("/spring/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
setup();
this.request.setServletPath("");
this.request.setRequestURI("/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
}
public void loadConfig(Class<?>... configs) { public void loadConfig(Class<?>... configs) {
this.context = new AnnotationConfigWebApplicationContext(); this.context = new AnnotationConfigWebApplicationContext();
this.context.register(configs); this.context.register(configs);
@@ -217,52 +201,6 @@ public class WebSecurityTests {
} }
// gh-19128
@EnableWebSecurity
@Configuration
@EnableWebMvc
static class IgnoringBuilderBeanConfig {
@Bean
PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() {
PathPatternRequestMatcherBuilderFactoryBean bean = new PathPatternRequestMatcherBuilderFactoryBean();
bean.setBasePath("/spring");
return bean;
}
@Bean
WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().requestMatchers("/path");
}
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.httpBasic(withDefaults())
.authorizeHttpRequests((requests) -> requests
.anyRequest().denyAll());
// @formatter:on
return http.build();
}
@Bean
UserDetailsService userDetailsService() {
return new InMemoryUserDetailsManager(PasswordEncodedUser.user());
}
@RestController
static class PathController {
@RequestMapping("/path")
String path() {
return "path";
}
}
}
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
static class RequestRejectedHandlerConfig { static class RequestRejectedHandlerConfig {
@@ -452,18 +452,6 @@ public class AuthorizeHttpRequestsConfigurerTests {
this.mvc.perform(requestWithAdmin).andExpect(status().isOk()); this.mvc.perform(requestWithAdmin).andExpect(status().isOk());
} }
@Test
public void requestMatchersWhenBuilderBeanWithBasePathAndRawStringThenHonorsBasePath() throws Exception {
this.spring.register(RequestMatchersRawStringServletPathConfig.class, BasicController.class).autowire();
// @formatter:off
MockHttpServletRequestBuilder matchedByBasePath = get("/spring/path")
.servletPath("/spring")
.with(user("user").roles("USER"));
// @formatter:on
this.mvc.perform(matchedByBasePath).andExpect(status().isForbidden());
this.mvc.perform(get("/path").with(user("user").roles("USER"))).andExpect(status().isOk());
}
@Test @Test
public void getWhenAnyRequestAuthenticatedConfiguredAndNoUserThenRespondsWithUnauthorized() throws Exception { public void getWhenAnyRequestAuthenticatedConfiguredAndNoUserThenRespondsWithUnauthorized() throws Exception {
this.spring.register(AuthenticatedConfig.class, BasicController.class).autowire(); this.spring.register(AuthenticatedConfig.class, BasicController.class).autowire();
@@ -1352,7 +1340,7 @@ public class AuthorizeHttpRequestsConfigurerTests {
static class ServletPathConfig { static class ServletPathConfig {
@Bean @Bean
PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() { PathPatternRequestMatcherBuilderFactoryBean requesMatcherBuilder() {
PathPatternRequestMatcherBuilderFactoryBean bean = new PathPatternRequestMatcherBuilderFactoryBean(); PathPatternRequestMatcherBuilderFactoryBean bean = new PathPatternRequestMatcherBuilderFactoryBean();
bean.setBasePath("/spring"); bean.setBasePath("/spring");
return bean; return bean;
@@ -1371,32 +1359,6 @@ public class AuthorizeHttpRequestsConfigurerTests {
} }
@Configuration
@EnableWebMvc
@EnableWebSecurity
static class RequestMatchersRawStringServletPathConfig {
@Bean
PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() {
PathPatternRequestMatcherBuilderFactoryBean bean = new PathPatternRequestMatcherBuilderFactoryBean();
bean.setBasePath("/spring");
return bean;
}
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
return http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/path").hasRole("ADMIN")
.anyRequest().permitAll()
)
.build();
// @formatter:on
}
}
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
static class AuthenticatedConfig { static class AuthenticatedConfig {
@@ -40,7 +40,6 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.config.users.AuthenticationTestConfiguration; import org.springframework.security.config.users.AuthenticationTestConfiguration;
import org.springframework.security.config.web.PathPatternRequestMatcherBuilderFactoryBean;
import org.springframework.security.core.authority.FactorGrantedAuthority; import org.springframework.security.core.authority.FactorGrantedAuthority;
import org.springframework.security.core.context.SecurityContextChangedListener; import org.springframework.security.core.context.SecurityContextChangedListener;
import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.core.context.SecurityContextHolderStrategy;
@@ -154,17 +153,6 @@ public class FormLoginConfigurerTests {
// @formatter:on // @formatter:on
} }
// gh-19128
@Test
public void loginWhenBuilderBeanWithBasePathThenLoginProcessingUrlIgnoresBasePath() throws Exception {
this.spring.register(FormLoginBuilderBeanConfig.class).autowire();
// @formatter:off
this.mockMvc.perform(formLogin().user("invalid"))
.andExpect(status().isFound())
.andExpect(redirectedUrl("/login?error"));
// @formatter:on
}
@Test @Test
public void loginWhenFormLoginConfiguredThenHasDefaultSuccessUrl() throws Exception { public void loginWhenFormLoginConfiguredThenHasDefaultSuccessUrl() throws Exception {
this.spring.register(FormLoginConfig.class).autowire(); this.spring.register(FormLoginConfig.class).autowire();
@@ -531,37 +519,6 @@ public class FormLoginConfigurerTests {
} }
// gh-19128
@Configuration
@EnableWebSecurity
@EnableWebMvc
static class FormLoginBuilderBeanConfig {
@Bean
PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() {
PathPatternRequestMatcherBuilderFactoryBean bean = new PathPatternRequestMatcherBuilderFactoryBean();
bean.setBasePath("/spring");
return bean;
}
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((requests) -> requests
.anyRequest().authenticated())
.formLogin(withDefaults());
// @formatter:on
return http.build();
}
@Bean
UserDetailsService userDetailsService() {
return new InMemoryUserDetailsManager(PasswordEncodedUser.user());
}
}
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
static class FormLoginInLambdaConfig { static class FormLoginInLambdaConfig {
@@ -125,34 +125,6 @@ public class HttpSecuritySecurityMatchersTests {
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
} }
@Test
public void securityMatcherWhenBuilderBeanWithBasePathThenHonorsBasePath() throws Exception {
loadConfig(SecurityMatcherBuilderBeanConfig.class);
this.request.setServletPath("/spring");
this.request.setRequestURI("/spring/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setServletPath("");
this.request.setRequestURI("/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
}
@Test
public void securityMatchersWhenBuilderBeanWithBasePathAndRawStringsThenHonorsBasePath() throws Exception {
loadConfig(SecurityMatchersBuilderBeanConfig.class);
this.request.setServletPath("/spring");
this.request.setRequestURI("/spring/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setServletPath("");
this.request.setRequestURI("/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
}
@Test @Test
public void securityMatchersWhenMultiMvcMatcherInLambdaThenAllPathsAreDenied() throws Exception { public void securityMatchersWhenMultiMvcMatcherInLambdaThenAllPathsAreDenied() throws Exception {
loadConfig(MultiMvcMatcherInLambdaConfig.class); loadConfig(MultiMvcMatcherInLambdaConfig.class);
@@ -458,83 +430,6 @@ public class HttpSecuritySecurityMatchersTests {
} }
@EnableWebSecurity
@Configuration
@EnableWebMvc
@Import(UsersConfig.class)
static class SecurityMatcherBuilderBeanConfig {
@Bean
PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() {
PathPatternRequestMatcherBuilderFactoryBean bean = new PathPatternRequestMatcherBuilderFactoryBean();
bean.setBasePath("/spring");
return bean;
}
@Bean
SecurityFilterChain appSecurity(HttpSecurity http) throws Exception {
// @formatter:off
http
.securityMatcher("/path")
.httpBasic(withDefaults())
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().denyAll());
// @formatter:on
return http.build();
}
@RestController
static class PathController {
@RequestMapping("/path")
String path() {
return "path";
}
}
}
@EnableWebSecurity
@Configuration
@EnableWebMvc
@Import(UsersConfig.class)
static class SecurityMatchersBuilderBeanConfig {
@Bean
PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() {
PathPatternRequestMatcherBuilderFactoryBean bean = new PathPatternRequestMatcherBuilderFactoryBean();
bean.setBasePath("/spring");
return bean;
}
@Bean
SecurityFilterChain appSecurity(HttpSecurity http) throws Exception {
// @formatter:off
http
.securityMatchers((matchers) -> matchers
.requestMatchers("/path")
)
.httpBasic(withDefaults())
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().denyAll()
);
// @formatter:on
return http.build();
}
@RestController
static class PathController {
@RequestMapping("/path")
String path() {
return "path";
}
}
}
@Configuration @Configuration
static class UsersConfig { static class UsersConfig {
@@ -35,7 +35,6 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.config.web.PathPatternRequestMatcherBuilderFactoryBean;
import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.core.userdetails.PasswordEncodedUser; import org.springframework.security.core.userdetails.PasswordEncodedUser;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
@@ -128,17 +127,6 @@ public class LogoutConfigurerTests {
// @formatter:on // @formatter:on
} }
// gh-19128
@Test
public void logoutWhenBuilderBeanWithBasePathThenLogoutUrlIgnoresBasePath() throws Exception {
this.spring.register(LogoutBuilderBeanConfig.class).autowire();
// @formatter:off
this.mvc.perform(post("/logout").with(csrf()))
.andExpect(status().isFound())
.andExpect(redirectedUrl("/login?logout"));
// @formatter:on
}
// SEC-2311 // SEC-2311
@Test @Test
public void logoutWhenGetRequestAndCsrfDisabledThenRedirectsToLogin() throws Exception { public void logoutWhenGetRequestAndCsrfDisabledThenRedirectsToLogin() throws Exception {
@@ -536,29 +524,6 @@ public class LogoutConfigurerTests {
} }
// gh-19128
@Configuration
@EnableWebSecurity
static class LogoutBuilderBeanConfig {
@Bean
PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() {
PathPatternRequestMatcherBuilderFactoryBean bean = new PathPatternRequestMatcherBuilderFactoryBean();
bean.setBasePath("/spring");
return bean;
}
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.logout(withDefaults());
// @formatter:on
return http.build();
}
}
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
static class CsrfDisabledConfig { static class CsrfDisabledConfig {
@@ -26,7 +26,6 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.config.web.PathPatternRequestMatcherBuilderFactoryBean;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
@@ -67,16 +66,6 @@ public class PasswordManagementConfigurerTests {
.andExpect(redirectedUrl("/custom-change-password-page")); .andExpect(redirectedUrl("/custom-change-password-page"));
} }
// gh-19128
@Test
public void changePasswordWhenBuilderBeanWithBasePathThenChangePasswordUrlIgnoresBasePath() throws Exception {
this.spring.register(PasswordManagementBuilderBeanConfig.class).autowire();
this.mvc.perform(get("/.well-known/change-password"))
.andExpect(status().isFound())
.andExpect(redirectedUrl("/change-password"));
}
@Test @Test
public void whenSettingNullChangePasswordPage() { public void whenSettingNullChangePasswordPage() {
PasswordManagementConfigurer configurer = new PasswordManagementConfigurer(); PasswordManagementConfigurer configurer = new PasswordManagementConfigurer();
@@ -113,29 +102,6 @@ public class PasswordManagementConfigurerTests {
} }
// gh-19128
@Configuration
@EnableWebSecurity
static class PasswordManagementBuilderBeanConfig {
@Bean
PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() {
PathPatternRequestMatcherBuilderFactoryBean bean = new PathPatternRequestMatcherBuilderFactoryBean();
bean.setBasePath("/spring");
return bean;
}
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
return http
.passwordManagement(withDefaults())
.build();
// @formatter:on
}
}
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
static class PasswordManagementWithCustomChangePasswordPageConfig { static class PasswordManagementWithCustomChangePasswordPageConfig {
@@ -34,7 +34,6 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.config.web.PathPatternRequestMatcherBuilderFactoryBean;
import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.User;
import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.test.web.servlet.RequestCacheResultMatcher; import org.springframework.security.test.web.servlet.RequestCacheResultMatcher;
@@ -186,21 +185,6 @@ public class RequestCacheConfigurerTests {
this.mvc.perform(formLogin(session)).andExpect(redirectedUrl("/")); this.mvc.perform(formLogin(session)).andExpect(redirectedUrl("/"));
} }
// gh-19128
@Test
public void getWhenBuilderBeanWithBasePathThenSavedRequestMatcherIgnoresBasePath() throws Exception {
this.spring.register(RequestCacheBuilderBeanConfig.class, DefaultSecurityConfig.class).autowire();
MockHttpServletRequestBuilder request = get("/messages").header(HttpHeaders.ACCEPT, MediaType.TEXT_HTML);
// @formatter:off
MockHttpSession session = (MockHttpSession) this.mvc.perform(request)
.andExpect(redirectedUrl("/login"))
.andReturn()
.getRequest()
.getSession();
// @formatter:on
this.mvc.perform(formLogin(session)).andExpect(RequestCacheResultMatcher.redirectToCachedRequest());
}
@Test @Test
public void getWhenBookmarkedRequestIsAllMediaTypeThenPostAuthenticationRemembers() throws Exception { public void getWhenBookmarkedRequestIsAllMediaTypeThenPostAuthenticationRemembers() throws Exception {
this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire(); this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire();
@@ -417,31 +401,6 @@ public class RequestCacheConfigurerTests {
} }
// gh-19128
@Configuration
@EnableWebSecurity
static class RequestCacheBuilderBeanConfig {
@Bean
PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() {
PathPatternRequestMatcherBuilderFactoryBean bean = new PathPatternRequestMatcherBuilderFactoryBean();
bean.setBasePath("/spring");
return bean;
}
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((requests) -> requests
.anyRequest().authenticated())
.formLogin(withDefaults());
// @formatter:on
return http.build();
}
}
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
static class RequestCacheDisabledConfig { static class RequestCacheDisabledConfig {
@@ -19,13 +19,10 @@ package org.springframework.security.config.annotation.web.configurers;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.List; import java.util.List;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpOutputMessage; import org.springframework.http.HttpOutputMessage;
@@ -43,16 +40,8 @@ import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.ui.DefaultResourcesFilter; import org.springframework.security.web.authentication.ui.DefaultResourcesFilter;
import org.springframework.security.web.webauthn.api.Bytes;
import org.springframework.security.web.webauthn.api.ImmutablePublicKeyCredentialUserEntity;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions; import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions;
import org.springframework.security.web.webauthn.api.TestCredentialRecords;
import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialCreationOptions; import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialCreationOptions;
import org.springframework.security.web.webauthn.authentication.WebAuthnAuthenticationFilter;
import org.springframework.security.web.webauthn.management.MapPublicKeyCredentialUserEntityRepository;
import org.springframework.security.web.webauthn.management.MapUserCredentialRepository;
import org.springframework.security.web.webauthn.management.PublicKeyCredentialUserEntityRepository;
import org.springframework.security.web.webauthn.management.UserCredentialRepository;
import org.springframework.security.web.webauthn.management.WebAuthnRelyingPartyOperations; import org.springframework.security.web.webauthn.management.WebAuthnRelyingPartyOperations;
import org.springframework.security.web.webauthn.registration.HttpSessionPublicKeyCredentialCreationOptionsRepository; import org.springframework.security.web.webauthn.registration.HttpSessionPublicKeyCredentialCreationOptionsRepository;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
@@ -63,9 +52,6 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.willAnswer; import static org.mockito.BDDMockito.willAnswer;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
@@ -102,14 +88,6 @@ public class WebAuthnConfigurerTests {
.andExpect(content().string(containsString("body {"))); .andExpect(content().string(containsString("body {")));
} }
// gh-18128
@Test
public void webAuthnAuthenticationFilterIsPostProcessed() throws Exception {
this.spring.register(DefaultWebauthnConfiguration.class, PostProcessorConfiguration.class).autowire();
PostProcessorConfiguration postProcess = this.spring.getContext().getBean(PostProcessorConfiguration.class);
assertThat(postProcess.webauthnFilter).isNotNull();
}
@Test @Test
public void webauthnWhenNoFormLoginAndDefaultRegistrationPageConfiguredThenServesJavascript() throws Exception { public void webauthnWhenNoFormLoginAndDefaultRegistrationPageConfiguredThenServesJavascript() throws Exception {
this.spring.register(NoFormLoginAndDefaultRegistrationPageConfiguration.class).autowire(); this.spring.register(NoFormLoginAndDefaultRegistrationPageConfiguration.class).autowire();
@@ -149,42 +127,6 @@ public class WebAuthnConfigurerTests {
.hasSize(1); .hasSize(1);
} }
@Test
void webauthnWhenConfiguredDefaultsRpNameToRpId() throws Exception {
ObjectMapper mapper = new ObjectMapper();
this.spring.register(DefaultWebauthnConfiguration.class).autowire();
String response = this.mvc
.perform(post("/webauthn/register/options").with(csrf())
.with(authentication(new TestingAuthenticationToken("test", "ignored", "ROLE_user"))))
.andExpect(status().is2xxSuccessful())
.andReturn()
.getResponse()
.getContentAsString();
JsonNode parsedResponse = mapper.readTree(response);
assertThat(parsedResponse.get("rp").get("id").asText()).isEqualTo("example.com");
assertThat(parsedResponse.get("rp").get("name").asText()).isEqualTo("example.com");
}
@Test
void webauthnWhenRpNameConfiguredUsesRpName() throws Exception {
ObjectMapper mapper = new ObjectMapper();
this.spring.register(CustomRpNameWebauthnConfiguration.class).autowire();
String response = this.mvc
.perform(post("/webauthn/register/options").with(csrf())
.with(authentication(new TestingAuthenticationToken("test", "ignored", "ROLE_user"))))
.andExpect(status().is2xxSuccessful())
.andReturn()
.getResponse()
.getContentAsString();
JsonNode parsedResponse = mapper.readTree(response);
assertThat(parsedResponse.get("rp").get("id").asText()).isEqualTo("example.com");
assertThat(parsedResponse.get("rp").get("name").asText()).isEqualTo("Test RP Name");
}
@Test @Test
public void webauthnWhenConfiguredAndFormLoginThenDoesServesJavascript() throws Exception { public void webauthnWhenConfiguredAndFormLoginThenDoesServesJavascript() throws Exception {
this.spring.register(FormLoginAndNoDefaultRegistrationPageConfiguration.class).autowire(); this.spring.register(FormLoginAndNoDefaultRegistrationPageConfiguration.class).autowire();
@@ -265,24 +207,6 @@ public class WebAuthnConfigurerTests {
.andExpect(content().string(expectedBody)); .andExpect(content().string(expectedBody));
} }
@Test
void webauthnWhenDeleteAndCredentialBelongsToUserThenNoContent() throws Exception {
this.spring.register(DeleteCredentialConfiguration.class).autowire();
this.mvc
.perform(delete("/webauthn/register/" + DeleteCredentialConfiguration.CREDENTIAL_ID_BASE64URL)
.with(authentication(new TestingAuthenticationToken("user", "password", "ROLE_USER"))))
.andExpect(status().isNoContent());
}
@Test
void webauthnWhenDeleteAndCredentialBelongsToDifferentUserThenForbidden() throws Exception {
this.spring.register(DeleteCredentialConfiguration.class).autowire();
this.mvc
.perform(delete("/webauthn/register/" + DeleteCredentialConfiguration.CREDENTIAL_ID_BASE64URL)
.with(authentication(new TestingAuthenticationToken("other-user", "password", "ROLE_USER"))))
.andExpect(status().isForbidden());
}
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
static class ConfigCredentialCreationOptionsRepository { static class ConfigCredentialCreationOptionsRepository {
@@ -365,26 +289,6 @@ public class WebAuthnConfigurerTests {
} }
@Configuration(proxyBeanMethods = false)
static class PostProcessorConfiguration {
WebAuthnAuthenticationFilter webauthnFilter;
@Bean
BeanPostProcessor beanPostProcessor() {
return new BeanPostProcessor() {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean instanceof WebAuthnAuthenticationFilter filter) {
PostProcessorConfiguration.this.webauthnFilter = filter;
}
return bean;
}
};
}
}
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
static class DefaultWebauthnConfiguration { static class DefaultWebauthnConfiguration {
@@ -400,7 +304,8 @@ public class WebAuthnConfigurerTests {
http http
.formLogin(Customizer.withDefaults()) .formLogin(Customizer.withDefaults())
.webAuthn((authn) -> authn .webAuthn((authn) -> authn
.rpId("example.com") .rpId("spring.io")
.rpName("spring")
); );
// @formatter:on // @formatter:on
return http.build(); return http.build();
@@ -408,24 +313,6 @@ public class WebAuthnConfigurerTests {
} }
@Configuration
@EnableWebSecurity
static class CustomRpNameWebauthnConfiguration {
@Bean
UserDetailsService userDetailsService() {
return new InMemoryUserDetailsManager();
}
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http.formLogin(Customizer.withDefaults())
.webAuthn((webauthn) -> webauthn.rpId("example.com").rpName("Test RP Name"))
.build();
}
}
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
static class NoFormLoginAndDefaultRegistrationPageConfiguration { static class NoFormLoginAndDefaultRegistrationPageConfiguration {
@@ -501,47 +388,4 @@ public class WebAuthnConfigurerTests {
} }
@Configuration
@EnableWebSecurity
static class DeleteCredentialConfiguration {
static final String CREDENTIAL_ID_BASE64URL = "NauGCN7bZ5jEBwThcde51g";
static final Bytes USER_ENTITY_ID = Bytes.fromBase64("vKBFhsWT3gQnn-gHdT4VXIvjDkVXVYg5w8CLGHPunMM");
@Bean
UserDetailsService userDetailsService() {
return new InMemoryUserDetailsManager();
}
@Bean
WebAuthnRelyingPartyOperations webAuthnRelyingPartyOperations() {
return mock(WebAuthnRelyingPartyOperations.class);
}
@Bean
UserCredentialRepository userCredentialRepository() {
MapUserCredentialRepository repository = new MapUserCredentialRepository();
repository.save(TestCredentialRecords.userCredential().build());
return repository;
}
@Bean
PublicKeyCredentialUserEntityRepository userEntityRepository() {
MapPublicKeyCredentialUserEntityRepository repository = new MapPublicKeyCredentialUserEntityRepository();
repository.save(ImmutablePublicKeyCredentialUserEntity.builder()
.name("user")
.id(USER_ENTITY_ID)
.displayName("User")
.build());
return repository;
}
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http.csrf(AbstractHttpConfigurer::disable).webAuthn(Customizer.withDefaults()).build();
}
}
} }
@@ -56,7 +56,6 @@ import org.springframework.security.config.annotation.web.configurers.oauth2.cli
import org.springframework.security.config.oauth2.client.CommonOAuth2Provider; import org.springframework.security.config.oauth2.client.CommonOAuth2Provider;
import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.config.web.PathPatternRequestMatcherBuilderFactoryBean;
import org.springframework.security.context.DelegatingApplicationListener; import org.springframework.security.context.DelegatingApplicationListener;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
@@ -441,18 +440,6 @@ public class OAuth2LoginConfigurerTests {
then(redirectStrategy).should().sendRedirect(any(), any(), anyString()); then(redirectStrategy).should().sendRedirect(any(), any(), anyString());
} }
// gh-19128
@Test
public void oauth2LoginWhenBuilderBeanWithBasePathThenLoginProcessingUrlIgnoresBasePath() throws Exception {
loadConfig(OAuth2LoginBuilderBeanConfig.class);
String requestUri = "/login/oauth2/code/google";
this.request = get(requestUri).build();
this.request.setParameter("code", "code123");
this.request.setParameter("state", "state123");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain);
assertThat(this.response.getRedirectedUrl()).endsWith("/login?error");
}
// gh-5347 // gh-5347
@Test @Test
public void oauth2LoginWithOneClientConfiguredThenRedirectForAuthorization() throws Exception { public void oauth2LoginWithOneClientConfiguredThenRedirectForAuthorization() throws Exception {
@@ -801,31 +788,6 @@ public class OAuth2LoginConfigurerTests {
} }
// gh-19128
@Configuration
@EnableWebSecurity
static class OAuth2LoginBuilderBeanConfig extends CommonSecurityFilterChainConfig {
@Bean
PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() {
PathPatternRequestMatcherBuilderFactoryBean bean = new PathPatternRequestMatcherBuilderFactoryBean();
bean.setBasePath("/spring");
return bean;
}
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.oauth2Login((login) -> login
.clientRegistrationRepository(
new InMemoryClientRegistrationRepository(GOOGLE_CLIENT_REGISTRATION)));
// @formatter:on
return super.configureFilterChain(http);
}
}
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
static class OAuth2LoginConfigFormLogin extends CommonSecurityFilterChainConfig { static class OAuth2LoginConfigFormLogin extends CommonSecurityFilterChainConfig {
@@ -307,8 +307,8 @@ public class OAuth2AuthorizationCodeGrantTests {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
this.mvc this.mvc
.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI) .perform(
.queryParams(getAuthorizationRequestParameters(registeredClient))) get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).params(getAuthorizationRequestParameters(registeredClient)))
.andExpect(status().isBadRequest()) .andExpect(status().isBadRequest())
.andReturn(); .andReturn();
} }
@@ -851,31 +851,21 @@ public class OAuth2AuthorizationCodeGrantTests {
this.spring.register(AuthorizationServerConfigurationCustomAuthorizationEndpoint.class).autowire(); this.spring.register(AuthorizationServerConfigurationCustomAuthorizationEndpoint.class).autowire();
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
this.registeredClientRepository.save(registeredClient);
TestingAuthenticationToken principal = new TestingAuthenticationToken("principalName", "password"); TestingAuthenticationToken principal = new TestingAuthenticationToken("principalName", "password");
Map<String, Object> additionalParameters = new HashMap<>();
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, S256_CODE_CHALLENGE);
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256");
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication = new OAuth2AuthorizationCodeRequestAuthenticationToken(
"https://provider.com/oauth2/authorize", registeredClient.getClientId(), principal,
registeredClient.getRedirectUris().iterator().next(), STATE_URL_UNENCODED, registeredClient.getScopes(),
additionalParameters);
OAuth2AuthorizationCode authorizationCode = new OAuth2AuthorizationCode("code", Instant.now(), OAuth2AuthorizationCode authorizationCode = new OAuth2AuthorizationCode("code", Instant.now(),
Instant.now().plus(5, ChronoUnit.MINUTES)); Instant.now().plus(5, ChronoUnit.MINUTES));
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult = new OAuth2AuthorizationCodeRequestAuthenticationToken( OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult = new OAuth2AuthorizationCodeRequestAuthenticationToken(
"https://provider.com/oauth2/authorize", registeredClient.getClientId(), principal, authorizationCode, "https://provider.com/oauth2/authorize", registeredClient.getClientId(), principal, authorizationCode,
registeredClient.getRedirectUris().iterator().next(), STATE_URL_UNENCODED, registeredClient.getRedirectUris().iterator().next(), STATE_URL_UNENCODED,
registeredClient.getScopes()); registeredClient.getScopes());
given(authorizationRequestConverter.convert(any())).willReturn(authorizationCodeRequestAuthentication); given(authorizationRequestConverter.convert(any())).willReturn(authorizationCodeRequestAuthenticationResult);
given(authorizationRequestAuthenticationProvider given(authorizationRequestAuthenticationProvider
.supports(eq(OAuth2AuthorizationCodeRequestAuthenticationToken.class))).willReturn(true); .supports(eq(OAuth2AuthorizationCodeRequestAuthenticationToken.class))).willReturn(true);
given(authorizationRequestAuthenticationProvider.authenticate(any())) given(authorizationRequestAuthenticationProvider.authenticate(any()))
.willReturn(authorizationCodeRequestAuthenticationResult); .willReturn(authorizationCodeRequestAuthenticationResult);
this.mvc this.mvc
.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI) .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).params(getAuthorizationRequestParameters(registeredClient))
.queryParams(getAuthorizationRequestParameters(registeredClient))
.with(user("user"))) .with(user("user")))
.andExpect(status().isOk()); .andExpect(status().isOk());
@@ -890,7 +880,8 @@ public class OAuth2AuthorizationCodeGrantTests {
|| converter instanceof OAuth2AuthorizationCodeRequestAuthenticationConverter || converter instanceof OAuth2AuthorizationCodeRequestAuthenticationConverter
|| converter instanceof OAuth2AuthorizationConsentAuthenticationConverter); || converter instanceof OAuth2AuthorizationConsentAuthenticationConverter);
verify(authorizationRequestAuthenticationProvider).authenticate(eq(authorizationCodeRequestAuthentication)); verify(authorizationRequestAuthenticationProvider)
.authenticate(eq(authorizationCodeRequestAuthenticationResult));
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
ArgumentCaptor<List<AuthenticationProvider>> authenticationProvidersCaptor = ArgumentCaptor ArgumentCaptor<List<AuthenticationProvider>> authenticationProvidersCaptor = ArgumentCaptor
@@ -45,7 +45,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Primary;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
@@ -159,8 +158,6 @@ public class OAuth2ClientCredentialsGrantTests {
private static AuthenticationFailureHandler authenticationFailureHandler; private static AuthenticationFailureHandler authenticationFailureHandler;
private static PasswordEncoder passwordEncoder;
public final SpringTestContext spring = new SpringTestContext(this); public final SpringTestContext spring = new SpringTestContext(this);
@Autowired @Autowired
@@ -186,9 +183,6 @@ public class OAuth2ClientCredentialsGrantTests {
authenticationProvidersConsumer = mock(Consumer.class); authenticationProvidersConsumer = mock(Consumer.class);
authenticationSuccessHandler = mock(AuthenticationSuccessHandler.class); authenticationSuccessHandler = mock(AuthenticationSuccessHandler.class);
authenticationFailureHandler = mock(AuthenticationFailureHandler.class); authenticationFailureHandler = mock(AuthenticationFailureHandler.class);
passwordEncoder = mock(PasswordEncoder.class);
given(passwordEncoder.matches(any(), any())).willReturn(true);
given(passwordEncoder.upgradeEncoding(any())).willReturn(false);
db = new EmbeddedDatabaseBuilder().generateUniqueName(true) db = new EmbeddedDatabaseBuilder().generateUniqueName(true)
.setType(EmbeddedDatabaseType.HSQL) .setType(EmbeddedDatabaseType.HSQL)
.setScriptEncoding("UTF-8") .setScriptEncoding("UTF-8")
@@ -502,26 +496,6 @@ public class OAuth2ClientCredentialsGrantTests {
.andExpect(jsonPath("$.token_type").value(OAuth2AccessToken.TokenType.DPOP.getValue())); .andExpect(jsonPath("$.token_type").value(OAuth2AccessToken.TokenType.DPOP.getValue()));
} }
@Test
public void requestWhenTokenRequestWithMultiplePasswordEncodersThenPrimaryPasswordEncoderUsed() throws Exception {
this.spring.register(AuthorizationServerConfigurationWithMultiplePasswordEncoders.class).autowire();
RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build();
this.registeredClientRepository.save(registeredClient);
this.mvc
.perform(post(DEFAULT_TOKEN_ENDPOINT_URI)
.param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
.param(OAuth2ParameterNames.SCOPE, "scope1 scope2")
.header(HttpHeaders.AUTHORIZATION,
"Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret())))
.andExpect(status().isOk())
.andExpect(jsonPath("$.access_token").isNotEmpty())
.andExpect(jsonPath("$.scope").value("scope1 scope2"));
verify(passwordEncoder).matches(any(), any());
}
private static String generateDPoPProof(String tokenEndpointUri) { private static String generateDPoPProof(String tokenEndpointUri) {
// @formatter:off // @formatter:off
Map<String, Object> publicJwk = TestJwks.DEFAULT_EC_JWK Map<String, Object> publicJwk = TestJwks.DEFAULT_EC_JWK
@@ -684,16 +658,4 @@ public class OAuth2ClientCredentialsGrantTests {
} }
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
static class AuthorizationServerConfigurationWithMultiplePasswordEncoders extends AuthorizationServerConfiguration {
@Primary
@Bean
PasswordEncoder primaryPasswordEncoder() {
return passwordEncoder;
}
}
} }
@@ -79,7 +79,6 @@ import org.springframework.security.oauth2.server.authorization.OAuth2Authorizat
import org.springframework.security.oauth2.server.authorization.OAuth2ClientRegistration; import org.springframework.security.oauth2.server.authorization.OAuth2ClientRegistration;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientRegistrationAuthenticationProvider; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientRegistrationAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientRegistrationAuthenticationToken; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientRegistrationAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientRegistrationAuthenticationValidator;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientParametersMapper; import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientParametersMapper;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
@@ -412,102 +411,6 @@ public class OAuth2ClientRegistrationTests {
.isCloseTo(expectedSecretExpiryDate, allowedDelta); .isCloseTo(expectedSecretExpiryDate, allowedDelta);
} }
@Test
public void requestWhenProtocolRelativeRedirectUriThenBadRequest() throws Exception {
this.spring.register(DefaultValidatorConfiguration.class).autowire();
assertThat(requestWhenInvalidClientMetadataThenBadRequest("""
{
"client_name": "client-name",
"redirect_uris": ["//client.example.com/path"],
"grant_types": ["authorization_code"]
}
""")).isEqualTo(HttpStatus.BAD_REQUEST.value());
}
@Test
public void requestWhenJavascriptSchemeRedirectUriThenBadRequest() throws Exception {
this.spring.register(DefaultValidatorConfiguration.class).autowire();
assertThat(requestWhenInvalidClientMetadataThenBadRequest("""
{
"client_name": "client-name",
"redirect_uris": ["javascript:alert(document.cookie)"],
"grant_types": ["authorization_code"]
}
""")).isEqualTo(HttpStatus.BAD_REQUEST.value());
}
@Test
public void requestWhenDataSchemeRedirectUriThenBadRequest() throws Exception {
this.spring.register(DefaultValidatorConfiguration.class).autowire();
assertThat(requestWhenInvalidClientMetadataThenBadRequest("""
{
"client_name": "client-name",
"redirect_uris": ["data:text/html,<h1>content</h1>"],
"grant_types": ["authorization_code"]
}
""")).isEqualTo(HttpStatus.BAD_REQUEST.value());
}
@Test
public void requestWhenHttpJwkSetUriThenBadRequest() throws Exception {
this.spring.register(DefaultValidatorConfiguration.class).autowire();
assertThat(requestWhenInvalidClientMetadataThenBadRequest("""
{
"client_name": "client-name",
"redirect_uris": ["https://client.example.com"],
"grant_types": ["authorization_code"],
"jwks_uri": "http://169.254.169.254/keys",
"token_endpoint_auth_method": "private_key_jwt"
}
""")).isEqualTo(HttpStatus.BAD_REQUEST.value());
}
@Test
public void requestWhenArbitraryScopeThenBadRequest() throws Exception {
this.spring.register(DefaultValidatorConfiguration.class).autowire();
assertThat(requestWhenInvalidClientMetadataThenBadRequest("""
{
"client_name": "client-name",
"redirect_uris": ["https://client.example.com"],
"grant_types": ["client_credentials"],
"scope": "read write"
}
""")).isEqualTo(HttpStatus.BAD_REQUEST.value());
}
private int requestWhenInvalidClientMetadataThenBadRequest(String json) throws Exception {
String clientRegistrationScope = "client.create";
// @formatter:off
RegisteredClient clientRegistrar = RegisteredClient.withId("client-registrar-" + System.nanoTime())
.clientId("client-registrar-" + System.nanoTime())
.clientSecret("{noop}secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.scope(clientRegistrationScope)
.build();
// @formatter:on
this.registeredClientRepository.save(clientRegistrar);
MvcResult tokenResult = this.mvc
.perform(post(ISSUER.concat(DEFAULT_TOKEN_ENDPOINT_URI))
.param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
.param(OAuth2ParameterNames.SCOPE, clientRegistrationScope)
.with(httpBasic(clientRegistrar.getClientId(), "secret")))
.andExpect(status().isOk())
.andReturn();
OAuth2AccessToken accessToken = readAccessTokenResponse(tokenResult.getResponse()).getAccessToken();
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setBearerAuth(accessToken.getTokenValue());
MvcResult registerResult = this.mvc
.perform(post(ISSUER.concat(DEFAULT_OAUTH2_CLIENT_REGISTRATION_ENDPOINT_URI)).headers(httpHeaders)
.contentType(MediaType.APPLICATION_JSON)
.content(json))
.andReturn();
return registerResult.getResponse().getStatus();
}
private OAuth2ClientRegistration registerClient(OAuth2ClientRegistration clientRegistration) throws Exception { private OAuth2ClientRegistration registerClient(OAuth2ClientRegistration clientRegistration) throws Exception {
// ***** (1) Obtain the "initial" access token used for registering the client // ***** (1) Obtain the "initial" access token used for registering the client
@@ -593,17 +496,6 @@ public class OAuth2ClientRegistrationTests {
return clientRegistrationHttpMessageConverter.read(OAuth2ClientRegistration.class, httpResponse); return clientRegistrationHttpMessageConverter.read(OAuth2ClientRegistration.class, httpResponse);
} }
private static Consumer<List<AuthenticationProvider>> scopePermissiveValidatorCustomizer() {
return (authenticationProviders) -> authenticationProviders.forEach((authenticationProvider) -> {
if (authenticationProvider instanceof OAuth2ClientRegistrationAuthenticationProvider provider) {
provider.setAuthenticationValidator(
OAuth2ClientRegistrationAuthenticationValidator.DEFAULT_REDIRECT_URI_VALIDATOR
.andThen(OAuth2ClientRegistrationAuthenticationValidator.DEFAULT_JWK_SET_URI_VALIDATOR)
.andThen(OAuth2ClientRegistrationAuthenticationValidator.SIMPLE_SCOPE_VALIDATOR));
}
});
}
@EnableWebSecurity @EnableWebSecurity
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
static class CustomClientRegistrationConfiguration extends AuthorizationServerConfiguration { static class CustomClientRegistrationConfiguration extends AuthorizationServerConfiguration {
@@ -620,7 +512,7 @@ public class OAuth2ClientRegistrationTests {
.clientRegistrationRequestConverter(authenticationConverter) .clientRegistrationRequestConverter(authenticationConverter)
.clientRegistrationRequestConverters(authenticationConvertersConsumer) .clientRegistrationRequestConverters(authenticationConvertersConsumer)
.authenticationProvider(authenticationProvider) .authenticationProvider(authenticationProvider)
.authenticationProviders(scopePermissiveValidatorCustomizer().andThen(authenticationProvidersConsumer)) .authenticationProviders(authenticationProvidersConsumer)
.clientRegistrationResponseHandler(authenticationSuccessHandler) .clientRegistrationResponseHandler(authenticationSuccessHandler)
.errorResponseHandler(authenticationFailureHandler) .errorResponseHandler(authenticationFailureHandler)
) )
@@ -647,7 +539,7 @@ public class OAuth2ClientRegistrationTests {
authorizationServer authorizationServer
.clientRegistrationEndpoint((clientRegistration) -> .clientRegistrationEndpoint((clientRegistration) ->
clientRegistration clientRegistration
.authenticationProviders(scopePermissiveValidatorCustomizer().andThen(configureClientRegistrationConverters())) .authenticationProviders(configureClientRegistrationConverters())
) )
) )
.authorizeHttpRequests((authorize) -> .authorizeHttpRequests((authorize) ->
@@ -685,7 +577,7 @@ public class OAuth2ClientRegistrationTests {
authorizationServer authorizationServer
.clientRegistrationEndpoint((clientRegistration) -> .clientRegistrationEndpoint((clientRegistration) ->
clientRegistration clientRegistration
.authenticationProviders(scopePermissiveValidatorCustomizer().andThen(configureClientRegistrationConverters())) .authenticationProviders(configureClientRegistrationConverters())
) )
) )
.authorizeHttpRequests((authorize) -> .authorizeHttpRequests((authorize) ->
@@ -722,7 +614,6 @@ public class OAuth2ClientRegistrationTests {
.clientRegistrationEndpoint((clientRegistration) -> .clientRegistrationEndpoint((clientRegistration) ->
clientRegistration clientRegistration
.openRegistrationAllowed(true) .openRegistrationAllowed(true)
.authenticationProviders(scopePermissiveValidatorCustomizer())
) )
) )
.authorizeHttpRequests((authorize) -> .authorizeHttpRequests((authorize) ->
@@ -736,30 +627,6 @@ public class OAuth2ClientRegistrationTests {
} }
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
static class DefaultValidatorConfiguration extends AuthorizationServerConfiguration {
// Override with Customizer.withDefaults() so the default (strict)
// OAuth2ClientRegistrationAuthenticationValidator is in effect.
// @formatter:off
@Bean
@Override
SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
http
.oauth2AuthorizationServer((authorizationServer) ->
authorizationServer
.clientRegistrationEndpoint(Customizer.withDefaults())
)
.authorizeHttpRequests((authorize) ->
authorize.anyRequest().authenticated()
);
return http.build();
}
// @formatter:on
}
@EnableWebSecurity @EnableWebSecurity
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
static class AuthorizationServerConfiguration { static class AuthorizationServerConfiguration {
@@ -770,10 +637,7 @@ public class OAuth2ClientRegistrationTests {
http http
.oauth2AuthorizationServer((authorizationServer) -> .oauth2AuthorizationServer((authorizationServer) ->
authorizationServer authorizationServer
.clientRegistrationEndpoint((clientRegistration) -> .clientRegistrationEndpoint(Customizer.withDefaults())
clientRegistration
.authenticationProviders(scopePermissiveValidatorCustomizer())
)
) )
.authorizeHttpRequests((authorize) -> .authorizeHttpRequests((authorize) ->
authorize.anyRequest().authenticated() authorize.anyRequest().authenticated()
@@ -359,7 +359,7 @@ public class OAuth2DeviceCodeGrantTests {
} }
@Test @Test
public void requestWhenDeviceAuthorizationConsentRequestUnauthenticatedThenUnauthorized() throws Exception { public void requestWhenDeviceAuthorizationConsentRequestUnauthenticatedThenBadRequest() throws Exception {
this.spring.register(AuthorizationServerConfiguration.class).autowire(); this.spring.register(AuthorizationServerConfiguration.class).autowire();
// @formatter:off // @formatter:off
@@ -392,7 +392,7 @@ public class OAuth2DeviceCodeGrantTests {
// @formatter:off // @formatter:off
this.mvc.perform(post(DEFAULT_DEVICE_VERIFICATION_ENDPOINT_URI) this.mvc.perform(post(DEFAULT_DEVICE_VERIFICATION_ENDPOINT_URI)
.params(parameters)) .params(parameters))
.andExpect(status().isUnauthorized()); .andExpect(status().isBadRequest());
// @formatter:on // @formatter:on
} }
@@ -97,7 +97,6 @@ import org.springframework.security.oauth2.server.authorization.oidc.OidcClientR
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientConfigurationAuthenticationProvider; import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientConfigurationAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationProvider; import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationToken; import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationValidator;
import org.springframework.security.oauth2.server.authorization.oidc.converter.OidcClientRegistrationRegisteredClientConverter; import org.springframework.security.oauth2.server.authorization.oidc.converter.OidcClientRegistrationRegisteredClientConverter;
import org.springframework.security.oauth2.server.authorization.oidc.converter.RegisteredClientOidcClientRegistrationConverter; import org.springframework.security.oauth2.server.authorization.oidc.converter.RegisteredClientOidcClientRegistrationConverter;
import org.springframework.security.oauth2.server.authorization.oidc.http.converter.OidcClientRegistrationHttpMessageConverter; import org.springframework.security.oauth2.server.authorization.oidc.http.converter.OidcClientRegistrationHttpMessageConverter;
@@ -546,129 +545,6 @@ public class OidcClientRegistrationTests {
.isCloseTo(expectedSecretExpiryDate, allowedDelta); .isCloseTo(expectedSecretExpiryDate, allowedDelta);
} }
@Test
public void requestWhenProtocolRelativeRedirectUriThenBadRequest() throws Exception {
this.spring.register(DefaultValidatorConfiguration.class).autowire();
assertThat(requestWhenInvalidClientMetadataThenBadRequest("""
{
"client_name": "client-name",
"redirect_uris": ["//client.example.com/path"],
"grant_types": ["authorization_code"]
}
""")).isEqualTo(HttpStatus.BAD_REQUEST.value());
}
@Test
public void requestWhenJavascriptSchemeRedirectUriThenBadRequest() throws Exception {
this.spring.register(DefaultValidatorConfiguration.class).autowire();
assertThat(requestWhenInvalidClientMetadataThenBadRequest("""
{
"client_name": "client-name",
"redirect_uris": ["javascript:alert(document.cookie)"],
"grant_types": ["authorization_code"]
}
""")).isEqualTo(HttpStatus.BAD_REQUEST.value());
}
@Test
public void requestWhenDataSchemeRedirectUriThenBadRequest() throws Exception {
this.spring.register(DefaultValidatorConfiguration.class).autowire();
assertThat(requestWhenInvalidClientMetadataThenBadRequest("""
{
"client_name": "client-name",
"redirect_uris": ["data:text/html,<h1>content</h1>"],
"grant_types": ["authorization_code"]
}
""")).isEqualTo(HttpStatus.BAD_REQUEST.value());
}
@Test
public void requestWhenJavascriptSchemePostLogoutRedirectUriThenBadRequest() throws Exception {
this.spring.register(DefaultValidatorConfiguration.class).autowire();
assertThat(requestWhenInvalidClientMetadataThenBadRequest("""
{
"client_name": "client-name",
"redirect_uris": ["https://client.example.com"],
"post_logout_redirect_uris": ["javascript:alert(document.cookie)"],
"grant_types": ["authorization_code"]
}
""")).isEqualTo(HttpStatus.BAD_REQUEST.value());
}
@Test
public void requestWhenDataSchemePostLogoutRedirectUriThenBadRequest() throws Exception {
this.spring.register(DefaultValidatorConfiguration.class).autowire();
assertThat(requestWhenInvalidClientMetadataThenBadRequest("""
{
"client_name": "client-name",
"redirect_uris": ["https://client.example.com"],
"post_logout_redirect_uris": ["data:text/html,<h1>content</h1>"],
"grant_types": ["authorization_code"]
}
""")).isEqualTo(HttpStatus.BAD_REQUEST.value());
}
@Test
public void requestWhenHttpJwkSetUriThenBadRequest() throws Exception {
this.spring.register(DefaultValidatorConfiguration.class).autowire();
assertThat(requestWhenInvalidClientMetadataThenBadRequest("""
{
"client_name": "client-name",
"redirect_uris": ["https://client.example.com"],
"grant_types": ["authorization_code"],
"jwks_uri": "http://169.254.169.254/keys",
"token_endpoint_auth_method": "private_key_jwt"
}
""")).isEqualTo(HttpStatus.BAD_REQUEST.value());
}
@Test
public void requestWhenArbitraryScopeThenBadRequest() throws Exception {
this.spring.register(DefaultValidatorConfiguration.class).autowire();
assertThat(requestWhenInvalidClientMetadataThenBadRequest("""
{
"client_name": "client-name",
"redirect_uris": ["https://client.example.com"],
"grant_types": ["authorization_code"],
"scope": "read write"
}
""")).isEqualTo(HttpStatus.BAD_REQUEST.value());
}
private int requestWhenInvalidClientMetadataThenBadRequest(String json) throws Exception {
String clientRegistrationScope = "client.create";
String clientId = "client-registrar-" + System.nanoTime();
// @formatter:off
RegisteredClient clientRegistrar = RegisteredClient.withId(clientId)
.clientId(clientId)
.clientSecret("{noop}secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.scope(clientRegistrationScope)
.build();
// @formatter:on
this.registeredClientRepository.save(clientRegistrar);
MvcResult tokenResult = this.mvc
.perform(post(ISSUER.concat(DEFAULT_TOKEN_ENDPOINT_URI))
.param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
.param(OAuth2ParameterNames.SCOPE, clientRegistrationScope)
.with(httpBasic(clientId, "secret")))
.andExpect(status().isOk())
.andReturn();
OAuth2AccessToken accessToken = readAccessTokenResponse(tokenResult.getResponse()).getAccessToken();
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setBearerAuth(accessToken.getTokenValue());
MvcResult registerResult = this.mvc
.perform(post(ISSUER.concat(DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI)).headers(httpHeaders)
.contentType(MediaType.APPLICATION_JSON)
.content(json))
.andReturn();
return registerResult.getResponse().getStatus();
}
private OidcClientRegistration registerClient(OidcClientRegistration clientRegistration) throws Exception { private OidcClientRegistration registerClient(OidcClientRegistration clientRegistration) throws Exception {
// ***** (1) Obtain the "initial" access token used for registering the client // ***** (1) Obtain the "initial" access token used for registering the client
@@ -766,18 +642,6 @@ public class OidcClientRegistrationTests {
return clientRegistrationHttpMessageConverter.read(OidcClientRegistration.class, httpResponse); return clientRegistrationHttpMessageConverter.read(OidcClientRegistration.class, httpResponse);
} }
private static Consumer<List<AuthenticationProvider>> scopePermissiveValidatorCustomizer() {
return (authenticationProviders) -> authenticationProviders.forEach((authenticationProvider) -> {
if (authenticationProvider instanceof OidcClientRegistrationAuthenticationProvider provider) {
provider.setAuthenticationValidator(
OidcClientRegistrationAuthenticationValidator.DEFAULT_REDIRECT_URI_VALIDATOR.andThen(
OidcClientRegistrationAuthenticationValidator.DEFAULT_POST_LOGOUT_REDIRECT_URI_VALIDATOR)
.andThen(OidcClientRegistrationAuthenticationValidator.DEFAULT_JWK_SET_URI_VALIDATOR)
.andThen(OidcClientRegistrationAuthenticationValidator.SIMPLE_SCOPE_VALIDATOR));
}
});
}
@EnableWebSecurity @EnableWebSecurity
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
static class CustomClientRegistrationConfiguration extends AuthorizationServerConfiguration { static class CustomClientRegistrationConfiguration extends AuthorizationServerConfiguration {
@@ -796,7 +660,7 @@ public class OidcClientRegistrationTests {
.clientRegistrationRequestConverter(authenticationConverter) .clientRegistrationRequestConverter(authenticationConverter)
.clientRegistrationRequestConverters(authenticationConvertersConsumer) .clientRegistrationRequestConverters(authenticationConvertersConsumer)
.authenticationProvider(authenticationProvider) .authenticationProvider(authenticationProvider)
.authenticationProviders(scopePermissiveValidatorCustomizer().andThen(authenticationProvidersConsumer)) .authenticationProviders(authenticationProvidersConsumer)
.clientRegistrationResponseHandler(authenticationSuccessHandler) .clientRegistrationResponseHandler(authenticationSuccessHandler)
.errorResponseHandler(authenticationFailureHandler) .errorResponseHandler(authenticationFailureHandler)
) )
@@ -826,7 +690,7 @@ public class OidcClientRegistrationTests {
oidc oidc
.clientRegistrationEndpoint((clientRegistration) -> .clientRegistrationEndpoint((clientRegistration) ->
clientRegistration clientRegistration
.authenticationProviders(scopePermissiveValidatorCustomizer().andThen(configureClientRegistrationConverters())) .authenticationProviders(configureClientRegistrationConverters())
) )
) )
) )
@@ -867,7 +731,7 @@ public class OidcClientRegistrationTests {
oidc oidc
.clientRegistrationEndpoint((clientRegistration) -> .clientRegistrationEndpoint((clientRegistration) ->
clientRegistration clientRegistration
.authenticationProviders(scopePermissiveValidatorCustomizer().andThen(configureClientRegistrationConverters())) .authenticationProviders(configureClientRegistrationConverters())
) )
) )
) )
@@ -891,33 +755,6 @@ public class OidcClientRegistrationTests {
} }
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
static class DefaultValidatorConfiguration extends AuthorizationServerConfiguration {
// Override with Customizer.withDefaults() so the default (strict)
// OidcClientRegistrationAuthenticationValidator is in effect.
// @formatter:off
@Bean
@Override
SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
http
.oauth2AuthorizationServer((authorizationServer) ->
authorizationServer
.oidc((oidc) ->
oidc
.clientRegistrationEndpoint(Customizer.withDefaults())
)
)
.authorizeHttpRequests((authorize) ->
authorize.anyRequest().authenticated()
);
return http.build();
}
// @formatter:on
}
@EnableWebSecurity @EnableWebSecurity
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
static class AuthorizationServerConfiguration { static class AuthorizationServerConfiguration {
@@ -930,10 +767,7 @@ public class OidcClientRegistrationTests {
authorizationServer authorizationServer
.oidc((oidc) -> .oidc((oidc) ->
oidc oidc
.clientRegistrationEndpoint((clientRegistration) -> .clientRegistrationEndpoint(Customizer.withDefaults())
clientRegistration
.authenticationProviders(scopePermissiveValidatorCustomizer())
)
) )
) )
.authorizeHttpRequests((authorize) -> .authorizeHttpRequests((authorize) ->
@@ -60,7 +60,6 @@ import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.FactorGrantedAuthority;
import org.springframework.security.core.session.SessionRegistry; import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl; import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.NoOpPasswordEncoder;
@@ -211,8 +210,7 @@ public class OidcTests {
registeredClient); registeredClient);
MvcResult mvcResult = this.mvc MvcResult mvcResult = this.mvc
.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters) .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters)
.with(user("user").roles("A", "B") .with(user("user").roles("A", "B")))
.authorities(FactorGrantedAuthority.fromAuthority(FactorGrantedAuthority.PASSWORD_AUTHORITY))))
.andExpect(status().is3xxRedirection()) .andExpect(status().is3xxRedirection())
.andReturn(); .andReturn();
String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); String redirectedUrl = mvcResult.getResponse().getRedirectedUrl();
@@ -272,8 +270,7 @@ public class OidcTests {
registeredClient); registeredClient);
MvcResult mvcResult = this.mvc MvcResult mvcResult = this.mvc
.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters) .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters)
.with(user("user").roles("A", "B") .with(user("user").roles("A", "B")))
.authorities(FactorGrantedAuthority.fromAuthority(FactorGrantedAuthority.PASSWORD_AUTHORITY))))
.andExpect(status().is3xxRedirection()) .andExpect(status().is3xxRedirection())
.andReturn(); .andReturn();
String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); String redirectedUrl = mvcResult.getResponse().getRedirectedUrl();
@@ -338,8 +335,7 @@ public class OidcTests {
registeredClient); registeredClient);
MvcResult mvcResult = this.mvc MvcResult mvcResult = this.mvc
.perform(get(issuer.concat(DEFAULT_AUTHORIZATION_ENDPOINT_URI)).queryParams(authorizationRequestParameters) .perform(get(issuer.concat(DEFAULT_AUTHORIZATION_ENDPOINT_URI)).queryParams(authorizationRequestParameters)
.with(user("user") .with(user("user")))
.authorities(FactorGrantedAuthority.fromAuthority(FactorGrantedAuthority.PASSWORD_AUTHORITY))))
.andExpect(status().is3xxRedirection()) .andExpect(status().is3xxRedirection())
.andReturn(); .andReturn();
@@ -392,8 +388,7 @@ public class OidcTests {
registeredClient1); registeredClient1);
MvcResult mvcResult = this.mvc MvcResult mvcResult = this.mvc
.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters) .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters)
.with(user("user1") .with(user("user1")))
.authorities(FactorGrantedAuthority.fromAuthority(FactorGrantedAuthority.PASSWORD_AUTHORITY))))
.andExpect(status().is3xxRedirection()) .andExpect(status().is3xxRedirection())
.andReturn(); .andReturn();
@@ -429,8 +424,7 @@ public class OidcTests {
authorizationRequestParameters = getAuthorizationRequestParameters(registeredClient2); authorizationRequestParameters = getAuthorizationRequestParameters(registeredClient2);
mvcResult = this.mvc mvcResult = this.mvc
.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters) .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters)
.with(user("user2") .with(user("user2")))
.authorities(FactorGrantedAuthority.fromAuthority(FactorGrantedAuthority.PASSWORD_AUTHORITY))))
.andExpect(status().is3xxRedirection()) .andExpect(status().is3xxRedirection())
.andReturn(); .andReturn();
@@ -503,8 +497,7 @@ public class OidcTests {
registeredClient); registeredClient);
MvcResult mvcResult = this.mvc MvcResult mvcResult = this.mvc
.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters) .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters)
.with(user("user") .with(user("user")))
.authorities(FactorGrantedAuthority.fromAuthority(FactorGrantedAuthority.PASSWORD_AUTHORITY))))
.andExpect(status().is3xxRedirection()) .andExpect(status().is3xxRedirection())
.andReturn(); .andReturn();
String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); String redirectedUrl = mvcResult.getResponse().getRedirectedUrl();
@@ -544,8 +537,7 @@ public class OidcTests {
registeredClient); registeredClient);
MvcResult mvcResult = this.mvc MvcResult mvcResult = this.mvc
.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters) .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters)
.with(user("user") .with(user("user")))
.authorities(FactorGrantedAuthority.fromAuthority(FactorGrantedAuthority.PASSWORD_AUTHORITY))))
.andExpect(status().is3xxRedirection()) .andExpect(status().is3xxRedirection())
.andReturn(); .andReturn();
String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); String redirectedUrl = mvcResult.getResponse().getRedirectedUrl();

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