1
0
mirror of synced 2026-09-03 07:49:56 +00:00

Compare commits

..

22 Commits

Author SHA1 Message Date
Josh Cummings b512ea6352 Release 7.2.0-M1
Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-08-13 16:39:08 -06:00
Josh Cummings 076ef2cc74 Update to Spring Data 2026.1.0-M1
Closes gh-19531

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-08-13 16:39:08 -06:00
Josh Cummings 3cedc1a2e4 Update to Micrometer 1.18.0-M1
Closes gh-19530

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-08-13 16:39:07 -06:00
Josh Cummings d32b23e497 Update to Reactor 2026.0.0-M1
Closes gh-19529

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-08-13 16:39:07 -06:00
Josh Cummings b58eef0d5d Update to Spring Framework 7.1.0-M1
Closes gh-19528

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-08-13 16:39:07 -06:00
Josh Cummings 1202f08f3e Configure Build for Commercial Repositories
Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-08-13 16:39:07 -06:00
Josh Cummings 8e315eae11 Configure with Commercial Workflows
Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-08-13 16:39:07 -06:00
Josh Cummings 03a504b4ff Remove OSS CI configuration 2026-08-13 16:39:07 -06:00
Josh Cummings 3c458bc661 Use jspecify Nullable in DPoPProofReplayValidator
org.springframework.lang.Nullable is banned by the
bannedNullabilityImports checkstyle rule on this line; the embargoed
commit that introduced this file predates that rule's adoption here.
Switch to org.jspecify.annotations.Nullable to match the rest of the
codebase (already used correctly elsewhere in this same file).

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-08-13 16:34:32 -06:00
Josh Cummings 1bb9ed906e Align Null Behavior
Previously, ExternalInetAddressMatcher treated a null InetAddress
as external by negating InternalInetAddressMatcher's result.

This commit makes ExternalInetAddressMatcher return false for a
null address, so that neither the internal nor the external
matcher classifies an unknown address as a match.

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-08-13 16:34:32 -06:00
Josh Cummings fc9a3f95ec Deprecate AesBytesEncryptor
This commit separates AesBytesEncryptor into two separate
implememtations, allowing for a migration away from default
arrangements that used a null IV

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-08-13 16:34:32 -06:00
Josh Cummings 7f1700d043 Improve Equivalence Tests
This commit adds equals and hashCode implementations as well
as a readResolve implementation to ensure that deserialization
mechanisms can correctly assess the equality of a constnat
and a corresponding deserialized instance. For defense-in-depth
reasons, this commit also favors .equals over == for these
constants.

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-08-13 16:34:32 -06:00
Joe Grandja 56456601ab Bind UnboundIdContainer to loopback address 2026-08-13 16:34:32 -06:00
Joe Grandja 249ea55494 Apply html encoding in DefaultConsentPage 2026-08-13 16:34:32 -06:00
Joe Grandja 43f42d8e32 Provide ability to configure DPoP proof replay 2026-08-13 16:34:32 -06:00
Josh Cummings 734d989c32 Use Constant-Time Comparison
This commit updates password encoders to use a
constant-time comparison method to defend against
timing attacks

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-08-13 16:34:32 -06:00
Josh Cummings 2f572278d4 Use Utf8#isEqual
Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-08-13 16:34:32 -06:00
Josh Cummings 816066ab60 Use Constant-Time Equals
This commit updates the filter to use constant-time
equals for sensitive material

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-08-13 16:34:32 -06:00
Josh Cummings fb3f864d27 Use Constant-Time Equals
This commit updates the filter to use constant-time
equals for sensitive material

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-08-13 16:34:32 -06:00
Josh Cummings ab29661a68 Add Utf8#isEqual
This commit adds a constant-time equals method,
useful for comparing password hashes or other
sensitive material

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-08-13 16:34:32 -06:00
Josh Cummings 35abf87e2e Use MessageDigest#isEqual
This commit favors constant-time comparison
to mitigate timing attacks

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-08-13 16:34:32 -06:00
Josh Cummings 4e3811cb29 Add Tests for User and Device Code
Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-08-13 16:34:31 -06:00
46 changed files with 561 additions and 925 deletions
@@ -0,0 +1,7 @@
name: Build Release
runs:
using: composite
steps:
- name: Build Release
shell: bash
run: ./gradlew -PdeploymentRepository=$(pwd)/deployment-repository publishAllPublicationsToDeploymentRepository
@@ -0,0 +1,7 @@
name: Test Release
runs:
using: composite
steps:
- name: Test Release
shell: bash
run: ./gradlew build
+17
View File
@@ -0,0 +1,17 @@
workflow:
generator:
project:
java:
versions:
primary: 25
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
@@ -1,16 +0,0 @@
name: Merge Dependabot PR
on:
pull_request:
branches:
- main
- 'docs-build'
run-name: Merge Dependabot PR ${{ github.ref_name }}
jobs:
merge-dependabot-pr:
permissions: write-all
uses: spring-io/spring-github-workflows/.github/workflows/spring-merge-dependabot-pr.yml@0d3f15bb384839966a1ff5c4383731a2b747f24b # v7
with:
mergeArguments: --auto --rebase
-36
View File
@@ -1,36 +0,0 @@
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@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15
strategy:
matrix:
include:
- java-version: 25
toolchain: 25
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.0.+ -PreactorVersion=2025.+ -PspringDataVersion=2025.+ -PmicrometerVersion=1.+ --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@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15
with:
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
@@ -1,23 +0,0 @@
name: Clean build artifacts
on:
schedule:
- cron: '0 10 * * *' # Once per day at 10am UTC
permissions:
contents: read
jobs:
main:
runs-on: ubuntu-latest
if: ${{ github.repository == 'spring-projects/spring-security' }}
permissions:
contents: none
steps:
- name: Delete artifacts in cron job
env:
GH_ACTIONS_REPO_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
run: |
echo "Running clean build artifacts logic"
output=$(curl -X GET -H "Authorization: token $GH_ACTIONS_REPO_TOKEN" https://api.github.com/repos/spring-projects/spring-security/actions/artifacts | grep '"id"' | cut -d : -f2 | sed 's/,*$//g')
echo Output is $output
for id in $output; do curl -X DELETE -H "Authorization: token $GH_ACTIONS_REPO_TOKEN" https://api.github.com/repos/spring-projects/spring-security/actions/artifacts/$id; done;
-17
View File
@@ -1,17 +0,0 @@
name: "CodeQL Advanced"
on:
push:
pull_request:
workflow_dispatch:
schedule:
# https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#schedule
- cron: '0 5 * * *'
permissions: read-all
jobs:
codeql-analysis-call:
permissions:
actions: read
contents: read
security-events: write
uses: spring-io/github-actions/.github/workflows/codeql-analysis.yml@e415dadd0910c901e7a7fabd67bbb355b2324500 # 1
@@ -8,45 +8,42 @@ on:
- cron: '0 10 * * *' # Once per day at 10am UTC
workflow_dispatch: # Manual trigger
env:
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
permissions:
contents: read
jobs:
build:
name: Build
uses: spring-io/spring-security-release-tools/.github/workflows/build.yml@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15
strategy:
matrix:
os: [ ubuntu-latest, windows-latest ]
jdk: [ 25 ]
uses: spring-projects/spring-security-commercial/.github/workflows/build.yml@workflows/v1
with:
runs-on: ${{ matrix.os }}
java-version: ${{ matrix.jdk }}
distribution: temurin
java-version: '25'
secrets: inherit
test:
name: Test Against Snapshots
uses: spring-projects/spring-security-commercial/.github/workflows/test.yml@workflows/v1
with:
java-version: '25'
test-args: --refresh-dependencies -PforceMavenRepositories=snapshot -PisOverrideVersionCatalog -PtestToolchain=25 -PspringFrameworkVersion=7.0.+ -PreactorVersion=2025.0.+ -PspringDataVersion=2026.0.+ -PmicrometerVersion=1.17.+ --stacktrace
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:
name: Deploy Artifacts
needs: [ build ]
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-artifacts.yml@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15
with:
should-deploy-artifacts: ${{ needs.build.outputs.should-deploy-artifacts }}
default-publish-milestones-central: true
java-version: 25
secrets: inherit
deploy-schema:
name: Deploy Schema
needs: [ build ]
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-schema.yml@3f6cc7ffc137ca160061749d5f34dc30d5f36986 # v1.0.17
with:
should-deploy-schema: ${{ needs.build.outputs.should-deploy-artifacts }}
java-version: 25
needs: [ build, test, compute-version ]
if: needs.compute-version.outputs.snapshot == 'true'
uses: spring-projects/spring-security-commercial/.github/workflows/deploy-artifacts.yml@workflows/v1
secrets: inherit
send-notification:
name: Send Notification
needs: [ deploy-artifacts, deploy-schema ]
needs: [ deploy-artifacts ]
if: ${{ !success() }}
runs-on: ubuntu-latest
steps:
-76
View File
@@ -1,76 +0,0 @@
name: Defer Issues
on:
workflow_dispatch:
permissions:
contents: read
jobs:
defer-issues:
name: Defer Issues
runs-on: ubuntu-latest
if: github.repository_owner == 'spring-projects'
permissions:
issues: write
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Compute Version
id: compute-version
uses: spring-io/spring-release-actions/compute-version@a1f321783a0769dd2aea4fad6c2ae2f95a52b885 # 0.0.5
- name: Get Today's Release Version
id: todays-release
uses: spring-io/spring-release-actions/get-todays-release-version@a1f321783a0769dd2aea4fad6c2ae2f95a52b885 # 0.0.5
with:
snapshot-version: ${{ steps.compute-version.outputs.version }}
milestone-repository: ${{ github.repository }}
milestone-token: ${{ secrets.GITHUB_TOKEN }}
- name: Compute Next Version
id: next-version
uses: spring-io/spring-release-actions/compute-next-version@1b8671612c3eb3d9b9763e2d7b66f1a80d00ee95 # 0.0.6
with:
version: ${{ steps.todays-release.outputs.release-version }}
- name: Schedule Next Milestone
uses: spring-io/spring-release-actions/schedule-milestone@a1f321783a0769dd2aea4fad6c2ae2f95a52b885 # 0.0.5
with:
version: ${{ steps.next-version.outputs.version }}
version-date: ${{ steps.next-version.outputs.version-date }}
repository: ${{ github.repository }}
token: ${{ secrets.GITHUB_TOKEN }}
- name: Move Open Issues to Next Milestone
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CURRENT_MILESTONE: ${{ steps.todays-release.outputs.release-version }}
NEXT_MILESTONE: ${{ steps.next-version.outputs.version }}
run: |
current_milestone_number=$(gh api repos/${{ github.repository }}/milestones \
--jq ".[] | select(.title == \"$CURRENT_MILESTONE\") | .number")
if [ -z "$current_milestone_number" ]; then
echo "No milestone found for $CURRENT_MILESTONE"
exit 0
fi
next_milestone_number=$(gh api repos/${{ github.repository }}/milestones \
--jq ".[] | select(.title == \"$NEXT_MILESTONE\") | .number")
if [ -z "$next_milestone_number" ]; then
echo "No milestone found for $NEXT_MILESTONE"
exit 1
fi
echo "Moving open issues from milestone '$CURRENT_MILESTONE' (#$current_milestone_number) to '$NEXT_MILESTONE' (#$next_milestone_number)"
page=1
while true; do
issues=$(gh api "repos/${{ github.repository }}/issues?milestone=$current_milestone_number&state=open&per_page=100&page=$page" \
--jq '.[].number')
if [ -z "$issues" ]; then
break
fi
for issue in $issues; do
echo "Moving issue/PR #$issue to milestone $NEXT_MILESTONE"
gh api repos/${{ github.repository }}/issues/$issue \
--method PATCH \
--field milestone=$next_milestone_number \
--silent
done
page=$((page + 1))
done
echo "Done."
@@ -0,0 +1,10 @@
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
@@ -1,33 +0,0 @@
name: Deploy Docs
on:
push:
branches-ignore:
- "gh-pages"
- "dependabot/**"
tags: '**'
repository_dispatch:
types: request-build-reference # legacy
#schedule:
#- cron: '0 10 * * *' # Once per day at 10am UTC
workflow_dispatch:
permissions: read-all
jobs:
build:
runs-on: ubuntu-latest
if: github.repository_owner == 'spring-projects'
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: docs-build
fetch-depth: 1
- name: Dispatch (partial build)
if: github.ref_type == 'branch'
env:
GH_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
run: gh workflow run deploy-docs.yml -r $(git rev-parse --abbrev-ref HEAD) -f build-refname=${{ github.ref_name }}
- name: Dispatch (full build)
if: github.ref_type == 'tag'
env:
GH_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
run: gh workflow run deploy-docs.yml -r $(git rev-parse --abbrev-ref HEAD)
@@ -1,34 +0,0 @@
name: Execute Gradle Wrapper Upgrade
on:
schedule:
- cron: '0 2 * * *' # 2am UTC
workflow_dispatch:
permissions:
pull-requests: write
jobs:
upgrade_wrapper:
name: Execution
if: ${{ github.repository == 'spring-projects/spring-security' }}
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up JDK 25
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: '25'
distribution: 'temurin'
- name: Set up Gradle
uses: gradle/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1
- 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
@@ -0,0 +1,63 @@
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 }}
+3 -41
View File
@@ -8,44 +8,6 @@ permissions:
jobs:
build:
name: Build
runs-on: ubuntu-latest
if: ${{ github.repository == 'spring-projects/spring-security' }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@c8668747d7c264864c8c7f7026d0d277d14a78dc # v2.0.6
with:
java-version: '25'
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@c8668747d7c264864c8c7f7026d0d277d14a78dc # v2.0.6
with:
java-version: '25'
distribution: 'temurin'
- name: Run Antora
run: ./gradlew -PbuildSrc.skipTests=true :spring-security-docs:antora
- name: Upload Docs
id: upload
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
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@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15
with:
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
uses: spring-projects/spring-security-commercial/.github/workflows/build-pull-request.yml@workflows/v1
if: ${{ github.actor != 'dependabot[bot]' }}
secrets: inherit
@@ -1,30 +0,0 @@
name: Release Announcements - Publish
on:
workflow_dispatch:
inputs:
version:
description: The version to publish
required: true
type: string
permissions:
actions: write
contents: read
issues: write
jobs:
deploy-schema:
name: Deploy Schema
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-schema.yml@3f6cc7ffc137ca160061749d5f34dc30d5f36986 # v1.0.17
with:
should-deploy-schema: true
secrets: inherit
publish-release-announcements:
name: Publish Release Announcements
needs: [ deploy-schema ]
if: ${{ !github.event.repository.fork }}
uses: spring-io/spring-security-release-tools/.github/workflows/release-announcements-publish.yml@d6c65d3013c0888e2c9cbae9f4beda610994776c # v1.0.16
with:
version: ${{ inputs.version }}
secrets: inherit
@@ -1,24 +0,0 @@
name: Release Announcements - Stage
on:
push:
tags:
- '[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
if: ${{ !github.event.repository.fork }}
uses: spring-io/spring-security-release-tools/.github/workflows/release-announcements-stage.yml@3f6cc7ffc137ca160061749d5f34dc30d5f36986 # v1.0.17
with:
version: ${{ inputs.version || github.ref_name }}
secrets: inherit
+92
View File
@@ -0,0 +1,92 @@
# 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: "25"
- 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
@@ -0,0 +1,55 @@
# 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
@@ -0,0 +1,46 @@
# 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
@@ -0,0 +1,47 @@
# 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
@@ -0,0 +1,34 @@
# 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
@@ -0,0 +1,83 @@
# 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: "25"
- 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"
@@ -1,37 +0,0 @@
name: Update Antora UI Spring
on:
schedule:
- cron: '0 10 * * *' # Once per day at 10am UTC
workflow_dispatch:
permissions:
pull-requests: write
issues: write
contents: write
jobs:
update-antora-ui-spring:
name: Update on Supported Branches
if: ${{ github.repository == 'spring-projects/spring-security' }}
runs-on: ubuntu-latest
strategy:
matrix:
branch: [ '6.5.x', '7.0.x', '7.1.x', 'main' ]
steps:
- uses: spring-io/spring-doc-actions/update-antora-spring-ui@415e2b11a766ba64799fffb5c97a4f7e17f677cf # v0.0.22
name: Update
with:
docs-branch: ${{ matrix.branch }}
token: ${{ secrets.GITHUB_TOKEN }}
antora-file-path: 'docs/antora-playbook.yml'
update-antora-ui-spring-docs-build:
name: Update on docs-build
if: ${{ github.repository == 'spring-projects/spring-security' }}
runs-on: ubuntu-latest
steps:
- uses: spring-io/spring-doc-actions/update-antora-spring-ui@415e2b11a766ba64799fffb5c97a4f7e17f677cf # v0.0.22
name: Update
with:
docs-branch: 'docs-build'
token: ${{ secrets.GITHUB_TOKEN }}
+5 -8
View File
@@ -27,6 +27,7 @@ apply plugin: 'org.jetbrains.kotlin.jvm'
apply plugin: 'org.springframework.security.versions.verify-dependencies-versions'
apply plugin: 'org.springframework.security.check-expected-branch-version'
apply plugin: 'io.spring.security.release'
apply from: 'commercial-settings.gradle'
group = 'org.springframework.security'
description = 'Spring Security'
@@ -41,10 +42,12 @@ repositories {
}
springRelease {
repositoryOwner = "spring-projects"
repositoryName = "spring-security-commercial"
weekOfMonth = 3
dayOfWeek = 1
referenceDocUrl = "https://docs.spring.io/spring-security/reference/{version}/index.html"
apiDocUrl = "https://docs.spring.io/spring-security/reference/{version}/api/java/index.html"
referenceDocUrl = "https://docs.spring.vmware.com/spring-security/reference/{version}/index.html"
apiDocUrl = "https://docs.spring.vmware.com/spring-security/reference/{version}/api/java/index.html"
replaceSnapshotVersionInReferenceDocUrl = true
}
@@ -72,12 +75,6 @@ allprojects {
}
}
develocity {
buildScan {
termsOfUseUrl = 'https://gradle.com/help/legal-terms-of-use'
termsOfUseAgree = 'yes'
}
}
nohttp {
source.exclude "buildSrc/build/**", "**/build/**", "**/target/**", "javascript/.gradle/**", "javascript/package-lock.json", "javascript/node_modules/**", "javascript/build/**", "javascript/dist/**"
+10 -1
View File
@@ -13,7 +13,16 @@ java {
repositories {
gradlePluginPortal()
mavenCentral()
maven { url = 'https://repo.spring.io/snapshot' }
if (project.hasProperty("artifactoryUsername") && project.hasProperty("artifactoryPassword")) {
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"
+27
View File
@@ -0,0 +1,27 @@
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
}
}
}
}
}
}
@@ -297,7 +297,6 @@ import org.springframework.web.util.pattern.PathPatternParser;
* @author Ankur Pathak
* @author Alexey Nesterov
* @author Yanming Zhou
* @author Iain Henderson
* @since 5.0
*/
public class ServerHttpSecurity {
@@ -4139,8 +4138,6 @@ public class ServerHttpSecurity {
private ServerAuthenticationFailureHandler authenticationFailureHandler;
private ServerAuthenticationSuccessHandler authenticationSuccessHandler;
private ServerAccessDeniedHandler accessDeniedHandler = new BearerTokenServerAccessDeniedHandler();
private ServerAuthenticationConverter bearerTokenConverter = new ServerBearerTokenAuthenticationConverter();
@@ -4189,20 +4186,6 @@ public class ServerHttpSecurity {
return this;
}
/**
* Configures the {@link ServerAuthenticationSuccessHandler} to use. The default
* is {@link WebFilterChainServerAuthenticationSuccessHandler}
* @param authenticationSuccessHandler the
* {@link ServerAuthenticationSuccessHandler} to use
* @return the {@link OAuth2ClientSpec} to customize
* @since 7.2
*/
public OAuth2ResourceServerSpec authenticationSuccessHandler(
ServerAuthenticationSuccessHandler authenticationSuccessHandler) {
this.authenticationSuccessHandler = authenticationSuccessHandler;
return this;
}
/**
* Configures the {@link ServerAuthenticationConverter} to use for requests
* authenticating with
@@ -4271,7 +4254,6 @@ public class ServerHttpSecurity {
AuthenticationWebFilter oauth2 = new AuthenticationWebFilter(this.authenticationManagerResolver);
oauth2.setServerAuthenticationConverter(this.bearerTokenConverter);
oauth2.setAuthenticationFailureHandler(authenticationFailureHandler());
oauth2.setAuthenticationSuccessHandler(authenticationSuccessHandler());
http.addFilterAt(oauth2, SecurityWebFiltersOrder.AUTHENTICATION);
}
else if (this.jwt != null) {
@@ -4331,13 +4313,6 @@ public class ServerHttpSecurity {
return new ServerAuthenticationEntryPointFailureHandler(this.entryPoint);
}
private ServerAuthenticationSuccessHandler authenticationSuccessHandler() {
if (this.authenticationSuccessHandler != null) {
return this.authenticationSuccessHandler;
}
return new WebFilterChainServerAuthenticationSuccessHandler();
}
/**
* Configures JWT Resource Server Support
*/
@@ -4412,7 +4387,6 @@ public class ServerHttpSecurity {
AuthenticationWebFilter oauth2 = new AuthenticationWebFilter(authenticationManager);
oauth2.setServerAuthenticationConverter(OAuth2ResourceServerSpec.this.bearerTokenConverter);
oauth2.setAuthenticationFailureHandler(authenticationFailureHandler());
oauth2.setAuthenticationSuccessHandler(authenticationSuccessHandler());
http.addFilterAt(oauth2, SecurityWebFiltersOrder.AUTHENTICATION);
}
@@ -4545,7 +4519,6 @@ public class ServerHttpSecurity {
AuthenticationWebFilter oauth2 = new AuthenticationWebFilter(authenticationManager);
oauth2.setServerAuthenticationConverter(OAuth2ResourceServerSpec.this.bearerTokenConverter);
oauth2.setAuthenticationFailureHandler(authenticationFailureHandler());
oauth2.setAuthenticationSuccessHandler(authenticationSuccessHandler());
http.addFilterAt(oauth2, SecurityWebFiltersOrder.AUTHENTICATION);
}
@@ -20,7 +20,6 @@ import org.springframework.security.authentication.ReactiveAuthenticationManager
import org.springframework.security.web.server.ServerAuthenticationEntryPoint
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter
import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler
import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler
import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler
import org.springframework.web.server.ServerWebExchange
@@ -36,8 +35,6 @@ import org.springframework.web.server.ServerWebExchange
* @property bearerTokenConverter the [ServerAuthenticationConverter] to use for requests authenticating with
* Bearer Tokens.
* @property authenticationManagerResolver the [ReactiveAuthenticationManagerResolver] to use.
* @property authenticationSuccessHandler the [ServerAuthenticationSuccessHandler] to use after
* authentication success.
*/
@ServerSecurityMarker
class ServerOAuth2ResourceServerDsl {
@@ -46,7 +43,6 @@ class ServerOAuth2ResourceServerDsl {
var authenticationEntryPoint: ServerAuthenticationEntryPoint? = null
var bearerTokenConverter: ServerAuthenticationConverter? = null
var authenticationManagerResolver: ReactiveAuthenticationManagerResolver<ServerWebExchange>? = null
var authenticationSuccessHandler: ServerAuthenticationSuccessHandler? = null
private var jwt: ((ServerHttpSecurity.OAuth2ResourceServerSpec.JwtSpec) -> Unit)? = null
private var opaqueToken: ((ServerHttpSecurity.OAuth2ResourceServerSpec.OpaqueTokenSpec) -> Unit)? = null
@@ -119,7 +115,6 @@ class ServerOAuth2ResourceServerDsl {
authenticationEntryPoint?.also { oauth2ResourceServer.authenticationEntryPoint(authenticationEntryPoint) }
bearerTokenConverter?.also { oauth2ResourceServer.bearerTokenConverter(bearerTokenConverter) }
authenticationManagerResolver?.also { oauth2ResourceServer.authenticationManagerResolver(authenticationManagerResolver!!) }
authenticationSuccessHandler?.also { oauth2ResourceServer.authenticationSuccessHandler(authenticationSuccessHandler) }
jwt?.also { oauth2ResourceServer.jwt(jwt) }
opaqueToken?.also { oauth2ResourceServer.opaqueToken(opaqueToken) }
}
@@ -25,12 +25,12 @@ public class MockSecurityContextHolderStrategy implements SecurityContextHolderS
private SecurityContext context;
@Override
public synchronized void clearContext() {
public void clearContext() {
this.context = null;
}
@Override
public synchronized SecurityContext getContext() {
public SecurityContext getContext() {
if (this.context == null) {
this.context = createEmptyContext();
}
@@ -38,7 +38,7 @@ public class MockSecurityContextHolderStrategy implements SecurityContextHolderS
}
@Override
public synchronized void setContext(SecurityContext context) {
public void setContext(SecurityContext context) {
this.context = context;
}
@@ -73,11 +73,9 @@ import org.springframework.security.oauth2.server.resource.authentication.Reacti
import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter;
import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenAuthenticationConverter;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.WebFilterExchange;
import org.springframework.security.web.server.authentication.HttpStatusServerEntryPoint;
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter;
import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler;
import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;
import org.springframework.security.web.server.authorization.HttpStatusServerAccessDeniedHandler;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.bind.annotation.GetMapping;
@@ -373,79 +371,6 @@ public class OAuth2ResourceServerSpecTests {
verify(handler).onAuthenticationFailure(any(), any());
}
@Test
public void getWhenUsingCustomAuthenticationSuccessHandlerThenUsesIsAccordingly() {
this.spring.register(CustomAuthenticationSuccessHandlerAuthenticationManagerResolverConfig.class).autowire();
ServerAuthenticationSuccessHandler handler = this.spring.getContext()
.getBean(ServerAuthenticationSuccessHandler.class);
ReactiveAuthenticationManager authenticationManager = this.spring.getContext()
.getBean(ReactiveAuthenticationManager.class);
given(authenticationManager.authenticate(any()))
.willAnswer((input) -> Mono.just(input.getArgument(0, Authentication.class)));
given(handler.onAuthenticationSuccess(any(), any())).willAnswer((input) -> {
WebFilterExchange webFilterExchange = input.getArgument(0, WebFilterExchange.class);
return webFilterExchange.getChain().filter(webFilterExchange.getExchange());
});
// @formatter:off
this.client.get()
.headers((headers) -> headers.setBearerAuth(this.messageReadToken))
.exchange()
.expectStatus().isUnauthorized();
// @formatter:on
verify(handler).onAuthenticationSuccess(any(), any());
}
@Test
public void getWhenUsingCustomAuthenticationSuccessHandlerWithJwtThenUsesIsAccordingly() {
this.spring.register(CustomAuthenticationSuccessHandlerJwtConfig.class).autowire();
ServerAuthenticationSuccessHandler handler = this.spring.getContext()
.getBean(ServerAuthenticationSuccessHandler.class);
ReactiveAuthenticationManager authenticationManager = this.spring.getContext()
.getBean(ReactiveAuthenticationManager.class);
given(authenticationManager.authenticate(any()))
.willAnswer((input) -> Mono.just(input.getArgument(0, Authentication.class)));
given(handler.onAuthenticationSuccess(any(), any())).willAnswer((input) -> {
WebFilterExchange webFilterExchange = input.getArgument(0, WebFilterExchange.class);
return webFilterExchange.getChain().filter(webFilterExchange.getExchange());
});
// @formatter:off
this.client.get()
.headers((headers) -> headers.setBearerAuth(this.messageReadToken))
.exchange()
.expectStatus().isUnauthorized();
// @formatter:on
verify(handler).onAuthenticationSuccess(any(), any());
}
@Test
public void getWhenUsingCustomAuthenticationSuccessHandlerWIthOpaqueTokenThenUsesIsAccordingly() {
this.spring.register(CustomAuthenticationSuccessHandlerOpaqueTokenConfig.class, RootController.class)
.autowire();
this.spring.getContext()
.getBean(MockWebServer.class)
.setDispatcher(requiresAuth(this.clientId, this.clientSecret, this.active));
ServerAuthenticationSuccessHandler handler = this.spring.getContext()
.getBean(ServerAuthenticationSuccessHandler.class);
ReactiveAuthenticationManager authenticationManager = this.spring.getContext()
.getBean(ReactiveAuthenticationManager.class);
given(authenticationManager.authenticate(any()))
.willAnswer((input) -> Mono.just(input.getArgument(0, Authentication.class)));
given(handler.onAuthenticationSuccess(any(), any())).willAnswer((input) -> {
WebFilterExchange webFilterExchange = input.getArgument(0, WebFilterExchange.class);
return webFilterExchange.getChain().filter(webFilterExchange.getExchange());
});
// @formatter:off
this.client.get()
.headers((headers) -> headers
.setBearerAuth(this.messageReadToken)
)
.exchange()
.expectStatus().isOk();
// @formatter:on
verify(handler).onAuthenticationSuccess(any(), any());
}
@Test
public void postWhenSignedThenReturnsOk() {
this.spring.register(PublicKeyConfig.class, RootController.class).autowire();
@@ -1025,111 +950,6 @@ public class OAuth2ResourceServerSpecTests {
}
@Configuration
@EnableWebFlux
@EnableWebFluxSecurity
static class CustomAuthenticationSuccessHandlerAuthenticationManagerResolverConfig {
@Bean
SecurityWebFilterChain springSecurity(ServerHttpSecurity http) {
// @formatter:off
http
.authorizeExchange((authorize) -> authorize.anyExchange().authenticated())
.oauth2ResourceServer((oauth2) -> oauth2
.authenticationSuccessHandler(authenticationSuccessHandler())
.authenticationManagerResolver((exchange) -> Mono.just(authenticationManager()))
);
// @formatter:on
return http.build();
}
@Bean
ReactiveAuthenticationManager authenticationManager() {
return mock(ReactiveAuthenticationManager.class);
}
@Bean
ServerAuthenticationSuccessHandler authenticationSuccessHandler() {
return mock(ServerAuthenticationSuccessHandler.class);
}
}
@Configuration
@EnableWebFlux
@EnableWebFluxSecurity
static class CustomAuthenticationSuccessHandlerJwtConfig {
@Bean
SecurityWebFilterChain springSecurity(ServerHttpSecurity http) {
// @formatter:off
http
.authorizeExchange((authorize) -> authorize.anyExchange().authenticated())
.oauth2ResourceServer((oauth2) -> oauth2
.authenticationSuccessHandler(authenticationSuccessHandler())
.jwt((jwt) -> jwt.authenticationManager(authenticationManager()))
);
// @formatter:on
return http.build();
}
@Bean
ReactiveAuthenticationManager authenticationManager() {
return mock(ReactiveAuthenticationManager.class);
}
@Bean
ServerAuthenticationSuccessHandler authenticationSuccessHandler() {
return mock(ServerAuthenticationSuccessHandler.class);
}
}
@Configuration
@EnableWebFlux
@EnableWebFluxSecurity
static class CustomAuthenticationSuccessHandlerOpaqueTokenConfig {
private MockWebServer mockWebServer = new MockWebServer();
@Bean
SecurityWebFilterChain springSecurity(ServerHttpSecurity http) {
String introspectionUri = mockWebServer().url("/introspect").toString();
// @formatter:off
http
.authorizeExchange((authorize) -> authorize.anyExchange().authenticated())
.oauth2ResourceServer((oauth2) -> oauth2
.authenticationSuccessHandler(authenticationSuccessHandler())
.opaqueToken((opaqueToken) -> opaqueToken
.introspectionUri(introspectionUri)
.introspectionClientCredentials("client", "secret"))
);
// @formatter:on
return http.build();
}
@Bean
ReactiveAuthenticationManager authenticationManager() {
return mock(ReactiveAuthenticationManager.class);
}
@Bean
ServerAuthenticationSuccessHandler authenticationSuccessHandler() {
return mock(ServerAuthenticationSuccessHandler.class);
}
@Bean
MockWebServer mockWebServer() {
return this.mockWebServer;
}
@PreDestroy
void shutdown() throws IOException {
this.mockWebServer.shutdown();
}
}
@EnableWebFlux
@EnableWebFluxSecurity
static class CustomBearerTokenServerAuthenticationConverter {
@@ -17,7 +17,6 @@
package org.springframework.security.config.web.server
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkObject
import io.mockk.verify
import org.junit.jupiter.api.Test
@@ -38,7 +37,6 @@ import org.springframework.security.web.server.SecurityWebFilterChain
import org.springframework.security.web.server.WebFilterExchange
import org.springframework.security.web.server.authentication.HttpStatusServerEntryPoint
import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler
import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler
import org.springframework.security.web.server.authorization.HttpStatusServerAccessDeniedHandler
import org.springframework.test.web.reactive.server.WebTestClient
import org.springframework.web.reactive.config.EnableWebFlux
@@ -185,46 +183,6 @@ class ServerOAuth2ResourceServerDslTests {
}
@Test
fun `request when custom authentication success handler then success handler used`() {
this.spring.register(AuthenticationSuccessHandlerConfig::class.java).autowire()
every {
AuthenticationSuccessHandlerConfig.SUCCESS_HANDLER.onAuthenticationSuccess(any(), any())
} returns Mono.empty()
this.client.get()
.uri("/")
.headers { it.setBearerAuth(validJwt) }
.exchange()
verify(exactly = 1) { AuthenticationSuccessHandlerConfig.SUCCESS_HANDLER.onAuthenticationSuccess(any(), any()) }
}
@Configuration
@EnableWebFluxSecurity
@EnableWebFlux
open class AuthenticationSuccessHandlerConfig {
companion object {
val SUCCESS_HANDLER: ServerAuthenticationSuccessHandler = mockk()
}
@Bean
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
authorizeExchange {
authorize(anyExchange, authenticated)
}
oauth2ResourceServer {
authenticationSuccessHandler = SUCCESS_HANDLER
jwt {
publicKey = publicKey()
}
}
}
}
}
@Test
fun `request when custom bearer token converter configured then custom converter used`() {
this.spring.register(BearerTokenConverterConfig::class.java).autowire()
@@ -32,7 +32,6 @@ import org.springframework.util.Assert;
* {@link java.util.ServiceLoader} mechanism when context-propagation is on the classpath.
*
* @author Steve Riesenberg
* @author Tadaya Tsuyukubo
* @since 6.5
* @see io.micrometer.context.ContextRegistry
*/
@@ -54,9 +53,7 @@ public final class SecurityContextHolderThreadLocalAccessor implements ThreadLoc
@Override
public void setValue(SecurityContext securityContext) {
Assert.notNull(securityContext, "securityContext cannot be null");
SecurityContext newContext = SecurityContextHolder.createEmptyContext();
newContext.setAuthentication(securityContext.getAuthentication());
SecurityContextHolder.setContext(newContext);
SecurityContextHolder.setContext(securityContext);
}
@Override
@@ -259,9 +259,7 @@ public final class InetAddressMatchers {
if (address.isLoopbackAddress() || address.isLinkLocalAddress() || address.isSiteLocalAddress()) {
return true;
}
if (address.isAnyLocalAddress()) {
return true;
}
byte[] rawAddress = address.getAddress();
if (rawAddress.length == 16) {
@@ -16,18 +16,11 @@
package org.springframework.security.core.context;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.core.task.support.ContextPropagatingTaskDecorator;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
@@ -36,7 +29,6 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
* Tests for {@link SecurityContextHolderThreadLocalAccessor}.
*
* @author Steve Riesenberg
* @author Tadaya Tsuyukubo
*/
public class SecurityContextHolderThreadLocalAccessorTests {
@@ -73,11 +65,9 @@ public class SecurityContextHolderThreadLocalAccessorTests {
@Test
public void setValueWhenSecurityContextThenSetsSecurityContextHolder() {
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
Authentication authentication = new TestingAuthenticationToken("user", "password");
securityContext.setAuthentication(authentication);
securityContext.setAuthentication(new TestingAuthenticationToken("user", "password"));
this.threadLocalAccessor.setValue(securityContext);
assertThat(SecurityContextHolder.getContext()).isNotSameAs(securityContext);
assertThat(SecurityContextHolder.getContext().getAuthentication()).isSameAs(authentication);
assertThat(SecurityContextHolder.getContext()).isSameAs(securityContext);
}
@Test
@@ -100,41 +90,4 @@ public class SecurityContextHolderThreadLocalAccessorTests {
assertThat(SecurityContextHolder.getContext()).isEqualTo(emptyContext);
}
@Test
public void newSecurityContextInDifferentThread() throws Exception {
Authentication authA = new TestingAuthenticationToken("foo", "password");
Authentication authB = new TestingAuthenticationToken("bar", "password");
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
securityContext.setAuthentication(authA);
SecurityContextHolder.setContext(securityContext);
CountDownLatch latch = new CountDownLatch(1);
AtomicReference<SecurityContext> contextHolder = new AtomicReference<>();
AtomicReference<Authentication> authHolder = new AtomicReference<>();
Runnable runnable = () -> {
SecurityContext context = SecurityContextHolder.getContext();
contextHolder.set(context);
authHolder.set(context.getAuthentication());
context.setAuthentication(authB);
latch.countDown();
};
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setTaskDecorator(new ContextPropagatingTaskDecorator());
executor.afterPropertiesSet();
executor.execute(runnable);
boolean finished = latch.await(10, TimeUnit.SECONDS);
assertThat(finished).isTrue();
assertThat(contextHolder.get()).isNotSameAs(securityContext);
assertThat(authHolder.get()).isSameAs(authA);
SecurityContext current = SecurityContextHolder.getContext();
assertThat(current).isSameAs(securityContext);
assertThat(current.getAuthentication()).isSameAs(authA);
}
}
@@ -276,17 +276,16 @@ class InetAddressMatchersTests {
}
@ParameterizedTest
@ValueSource(strings = { "127.0.0.1", "127.0.0.255", "0.0.0.0" })
@ValueSource(strings = { "127.0.0.1", "127.0.0.255" })
void matchesWhenIpv4LoopbackThenReturnsTrue(String address) throws Exception {
InetAddressMatcher matcher = InetAddressMatchers.matchInternal().build();
assertThat(matcher.matches(InetAddress.getByName(address))).isTrue();
}
@ParameterizedTest
@ValueSource(strings = { "::1", "::" })
void matchesWhenIpv6LoopbackThenReturnsTrue(String address) throws Exception {
@Test
void matchesWhenIpv6LoopbackThenReturnsTrue() throws Exception {
InetAddressMatcher matcher = InetAddressMatchers.matchInternal().build();
assertThat(matcher.matches(InetAddress.getByName(address))).isTrue();
assertThat(matcher.matches(InetAddress.getByName("::1"))).isTrue();
}
@ParameterizedTest
-5
View File
@@ -8,8 +8,3 @@
== Web
* Since Spring Framework's `HttpMethod#valueOf` now normalizes casing, `StrictServerWebExchangeFirewall` no longer detects a non-canonical-case HTTP method (for example, `get` instead of `GET`) as a distinct value; such requests are processed as the canonical method instead of being rejected. Applications with a customized `ServerExchangeRejectedHandler` should be aware it is no longer invoked for this case.
== OAuth 2.0
* https://github.com/spring-projects/spring-security/pull/18895[gh-18895] - Add `authenticationSuccessHandler` to the Reactive Resource Server DSL
+1 -1
View File
@@ -14,7 +14,7 @@
# limitations under the License.
#
springBootVersion=4.2.0-SNAPSHOT
version=7.2.0-SNAPSHOT
version=7.2.0-M1
samplesBranch=main
org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError
org.gradle.parallel=true
+5 -5
View File
@@ -18,8 +18,8 @@ org-springframework = "7.1.0-M1"
com-password4j = "1.8.4"
[libraries]
ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.6.3"
com-fasterxml-jackson-jackson-bom = "com.fasterxml.jackson:jackson-bom:2.22.2"
ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.6.1"
com-fasterxml-jackson-jackson-bom = "com.fasterxml.jackson:jackson-bom:2.22.1"
com-google-inject-guice = "com.google.inject:guice:3.0"
com-netflix-nebula-nebula-project-plugin = "com.netflix.nebula:nebula-project-plugin:8.2.0"
com-nimbusds-nimbus-jose-jwt = "com.nimbusds:nimbus-jose-jwt:10.9.1"
@@ -39,7 +39,7 @@ io-spring-javaformat-spring-javaformat-checkstyle = { module = "io.spring.javafo
io-spring-javaformat-spring-javaformat-gradle-plugin = { module = "io.spring.javaformat:spring-javaformat-gradle-plugin", version.ref = "io-spring-javaformat" }
io-spring-nohttp-nohttp-checkstyle = { module = "io.spring.nohttp:nohttp-checkstyle", version.ref = "io-spring-nohttp" }
io-spring-nohttp-nohttp-gradle = { module = "io.spring.nohttp:nohttp-gradle", version.ref = "io-spring-nohttp" }
io-spring-security-release-plugin = "io.spring.gradle:spring-security-release-plugin:1.0.17"
io-spring-security-release-plugin = "io.spring.gradle:spring-security-release-plugin:1.0.15"
jakarta-annotation-jakarta-annotation-api = "jakarta.annotation:jakarta.annotation-api:3.0.0"
jakarta-inject-jakarta-inject-api = "jakarta.inject:jakarta.inject-api:2.0.1"
jakarta-persistence-jakarta-persistence-api = "jakarta.persistence:jakarta.persistence-api:3.2.0"
@@ -86,10 +86,10 @@ org-skyscreamer-jsonassert = "org.skyscreamer:jsonassert:1.5.3"
org-slf4j-log4j-over-slf4j = "org.slf4j:log4j-over-slf4j:1.7.36"
org-slf4j-slf4j-api = "org.slf4j:slf4j-api:2.0.18"
org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2026.1.0-M1"
org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:4.1.1"
org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:4.1.0"
org-springframework-spring-framework-bom = { module = "org.springframework:spring-framework-bom", version.ref = "org-springframework" }
org-synchronoss-cloud-nio-multipart-parser = "org.synchronoss.cloud:nio-multipart-parser:1.1.0"
tools-jackson-jackson-bom = "tools.jackson:jackson-bom:3.2.2"
tools-jackson-jackson-bom = "tools.jackson:jackson-bom:3.2.1"
com-google-code-gson-gson = "com.google.code.gson:gson:2.14.0"
com-thaiopensource-trag = "com.thaiopensource:trang:20091111"
@@ -24,12 +24,9 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
import reactor.core.publisher.Mono;
import org.springframework.core.log.LogMessage;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
@@ -82,7 +79,7 @@ public final class RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler
.map((c) -> c.get(ServerWebExchange.class));
// @formatter:on
private ServerSecurityContextRepository serverSecurityContextRepository = new NonRotatingWebSessionServerSecurityContextRepository();
private ServerSecurityContextRepository serverSecurityContextRepository = new WebSessionServerSecurityContextRepository();
private ReactiveJwtDecoderFactory<ClientRegistration> jwtDecoderFactory = new ReactiveOidcIdTokenDecoderFactory();
@@ -144,7 +141,8 @@ public final class RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler
/**
* Sets a {@link ServerSecurityContextRepository} to use for refreshing a
* {@link SecurityContext}.
* {@link SecurityContext}, defaults to
* {@link WebSessionServerSecurityContextRepository}.
* @param serverSecurityContextRepository the {@link ServerSecurityContextRepository}
* to use
*/
@@ -318,28 +316,4 @@ public final class RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler
return this.serverSecurityContextRepository.save(exchange, securityContext);
}
private static final class NonRotatingWebSessionServerSecurityContextRepository
implements ServerSecurityContextRepository {
private static final Log logger = LogFactory.getLog(NonRotatingWebSessionServerSecurityContextRepository.class);
@Override
public Mono<SecurityContext> load(ServerWebExchange exchange) {
return Mono.empty();
}
@Override
public Mono<Void> save(ServerWebExchange exchange, @Nullable SecurityContext context) {
Assert.notNull(context, "context cannot be null");
// Save SecurityContext in WebSession without rotating session id.
return exchange.getSession().doOnNext((session) -> {
session.getAttributes()
.put(WebSessionServerSecurityContextRepository.DEFAULT_SPRING_SECURITY_CONTEXT_ATTR_NAME, context);
logger.debug(LogMessage.format("Saved SecurityContext '%s' in WebSession: '%s'", context, session));
}).then();
}
}
}
@@ -511,6 +511,9 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement
}
HttpServletRequest servletRequest = getRequest(attrs);
HttpServletResponse servletResponse = getResponse(attrs);
if (servletRequest == null || servletResponse == null) {
return Mono.empty();
}
OAuth2AuthorizeRequest.Builder builder = OAuth2AuthorizeRequest.withClientRegistrationId(clientRegistrationId)
.principal(authentication);
builder.attributes((attributes) -> addToAttributes(attributes, servletRequest, servletResponse));
@@ -535,6 +538,9 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement
}
HttpServletRequest servletRequest = getRequest(attrs);
HttpServletResponse servletResponse = getResponse(attrs);
if (servletRequest == null || servletResponse == null) {
return Mono.just(authorizedClient);
}
OAuth2AuthorizeRequest.Builder builder = OAuth2AuthorizeRequest.withAuthorizedClient(authorizedClient)
.principal(authentication);
builder.attributes((attributes) -> addToAttributes(attributes, servletRequest, servletResponse));
@@ -546,8 +552,8 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement
.subscribeOn(Schedulers.boundedElastic());
}
private void addToAttributes(Map<String, Object> attributes, @Nullable HttpServletRequest servletRequest,
@Nullable HttpServletResponse servletResponse) {
private void addToAttributes(Map<String, Object> attributes, HttpServletRequest servletRequest,
HttpServletResponse servletResponse) {
if (servletRequest != null) {
attributes.put(HTTP_SERVLET_REQUEST_ATTR_NAME, servletRequest);
}
@@ -50,9 +50,7 @@ import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoderFactory;
import org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebSession;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
@@ -142,51 +140,6 @@ class RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandlerTests {
.verifyComplete();
}
// gh-19424
@Test
void onAuthorizationSuccessWhenDefaultServerSecurityContextRepositoryThenWebSessionIdNotChanged() {
ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build();
Instant authTime = Instant.now();
DefaultOidcUser principal = createOidcUser(authTime);
OAuth2AuthenticationToken authenticationToken = new OAuth2AuthenticationToken(principal,
principal.getAuthorities(), clientRegistration.getRegistrationId());
OAuth2AccessToken accessToken = createAccessToken();
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(clientRegistration, principal.getName(),
accessToken, null);
MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/").build());
Map<String, Object> attributes = Map.of(ServerWebExchange.class.getName(), exchange,
OidcParameterNames.ID_TOKEN, "id-token-1234");
Map<String, Object> claims = new HashMap<>();
claims.put("iss", principal.getIssuer());
claims.put("sub", principal.getSubject());
claims.put("aud", principal.getAudience());
claims.put("auth_time", authTime);
claims.put("nonce", principal.getNonce());
Jwt jwt = mock(Jwt.class);
given(jwt.getTokenValue()).willReturn("id-token-1234");
given(jwt.getIssuedAt()).willReturn(principal.getIssuedAt());
given(jwt.getClaims()).willReturn(claims);
ReactiveJwtDecoder jwtDecoder = mock(ReactiveJwtDecoder.class);
given(jwtDecoder.decode(any())).willReturn(Mono.just(jwt));
ReactiveJwtDecoderFactory<ClientRegistration> reactiveJwtDecoderFactory = mock(ReactiveJwtDecoderFactory.class);
given(reactiveJwtDecoderFactory.createDecoder(any())).willReturn(jwtDecoder);
ReactiveOAuth2UserService<OidcUserRequest, OidcUser> userService = mock(ReactiveOAuth2UserService.class);
given(userService.loadUser(any())).willReturn(Mono.just(principal));
RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler handler = new RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler();
handler.setJwtDecoderFactory(reactiveJwtDecoderFactory);
handler.setUserService(userService);
String originalSessionId = exchange.getSession().map(WebSession::getId).block();
StepVerifier.create(handler.onAuthorizationSuccess(authorizedClient, authenticationToken, attributes))
.verifyComplete();
StepVerifier.create(exchange.getSession())
.assertNext((session) -> assertThat(session.getId()).isEqualTo(originalSessionId))
.verifyComplete();
WebSessionServerSecurityContextRepository securityContextRepository = new WebSessionServerSecurityContextRepository();
StepVerifier.create(securityContextRepository.load(exchange).mapNotNull(SecurityContext::getAuthentication))
.expectNext(authenticationToken)
.verifyComplete();
}
@Test
void onAuthorizationSuccessWhenIdTokenIssuerNotSameThenException() {
ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build();
@@ -35,7 +35,6 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import reactor.util.context.Context;
import org.springframework.http.HttpHeaders;
@@ -78,7 +77,6 @@ import org.springframework.web.reactive.function.client.ExchangeFunction;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClientResponseException;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebSession;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -322,8 +320,6 @@ public class ServerOAuth2AuthorizedClientExchangeFilterFunctionITests {
doReturn(Mono.just(authorizedClient)).when(this.authorizedClientRepository)
.loadAuthorizedClient(eq(clientRegistration.getRegistrationId()), eq(this.authentication),
eq(this.exchange));
// Capture the original session id.
String originalSessionId = this.exchange.getSession().map(WebSession::getId).block();
this.webClient.get()
.uri(this.serverUrl)
.attributes(ServletOAuth2AuthorizedClientExchangeFilterFunction
@@ -360,10 +356,6 @@ public class ServerOAuth2AuthorizedClientExchangeFilterFunctionITests {
assertThat(oidcUser.getSubject()).isEqualTo("subject-1234");
assertThat(oidcUser.getName()).isEqualTo("refreshed-username");
});
// Verify that session id was not changed.
StepVerifier.create(this.exchange.getSession())
.assertNext((session) -> assertThat(session.getId()).isEqualTo(originalSessionId))
.verifyComplete();
}
@Test
@@ -40,10 +40,8 @@ import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.client.AuthorizedClientServiceOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.InMemoryOAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
@@ -168,67 +166,6 @@ public class ServletOAuth2AuthorizedClientExchangeFilterFunctionITests {
assertThat(authorizedClientCaptor.getValue().getClientRegistration()).isSameAs(clientRegistration);
}
// gh-19421
@Test
public void requestWhenNoServletRequestThenAuthorizeAndSendRequest() {
RequestContextHolder.resetRequestAttributes();
final OAuth2AuthorizedClientService delegate = new InMemoryOAuth2AuthorizedClientService(
this.clientRegistrationRepository);
OAuth2AuthorizedClientService authorizedClientService = spy(new OAuth2AuthorizedClientService() {
@Override
public <T extends OAuth2AuthorizedClient> T loadAuthorizedClient(String clientRegistrationId,
String principal) {
return delegate.loadAuthorizedClient(clientRegistrationId, principal);
}
@Override
public void saveAuthorizedClient(OAuth2AuthorizedClient authorizedClient, Authentication principal) {
delegate.saveAuthorizedClient(authorizedClient, principal);
}
@Override
public void removeAuthorizedClient(String clientRegistrationId, String principal) {
delegate.removeAuthorizedClient(clientRegistrationId, principal);
}
});
this.authorizedClientFilter = new ServletOAuth2AuthorizedClientExchangeFilterFunction(
new AuthorizedClientServiceOAuth2AuthorizedClientManager(this.clientRegistrationRepository,
authorizedClientService));
this.webClient = WebClient.builder().apply(this.authorizedClientFilter.oauth2Configuration()).build();
// @formatter:off
String accessTokenResponse = "{\n"
+ " \"access_token\": \"access-token-1234\",\n"
+ " \"token_type\": \"bearer\",\n"
+ " \"expires_in\": \"3600\",\n"
+ " \"scope\": \"read write\"\n"
+ "}\n";
String clientResponse = "{\n"
+ " \"attribute1\": \"value1\",\n"
+ " \"attribute2\": \"value2\"\n"
+ "}\n";
// @formatter:on
this.server.enqueue(jsonResponse(accessTokenResponse));
this.server.enqueue(jsonResponse(clientResponse));
ClientRegistration clientRegistration = TestClientRegistrations.clientCredentials()
.tokenUri(this.serverUrl)
.build();
given(this.clientRegistrationRepository.findByRegistrationId(eq(clientRegistration.getRegistrationId())))
.willReturn(clientRegistration);
this.webClient.get()
.uri(this.serverUrl)
.attributes(ServletOAuth2AuthorizedClientExchangeFilterFunction
.clientRegistrationId(clientRegistration.getRegistrationId()))
.retrieve()
.bodyToMono(String.class)
.block();
assertThat(this.server.getRequestCount()).isEqualTo(2);
ArgumentCaptor<OAuth2AuthorizedClient> authorizedClientCaptor = ArgumentCaptor
.forClass(OAuth2AuthorizedClient.class);
verify(authorizedClientService).saveAuthorizedClient(authorizedClientCaptor.capture(), eq(this.authentication));
assertThat(authorizedClientCaptor.getValue().getClientRegistration()).isSameAs(clientRegistration);
}
@Test
public void requestWhenAuthorizedButExpiredThenRefreshAndSendRequest() {
// @formatter:off
@@ -67,14 +67,12 @@ import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.oauth2.client.AuthorizedClientServiceOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.ClientAuthorizationException;
import org.springframework.security.oauth2.client.JwtBearerOAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.OAuth2AuthorizationFailureHandler;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.RefreshTokenOAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.client.endpoint.JwtBearerGrantRequest;
@@ -136,9 +134,6 @@ public class ServletOAuth2AuthorizedClientExchangeFilterFunctionTests {
@Mock
private OAuth2AuthorizedClientRepository authorizedClientRepository;
@Mock
private OAuth2AuthorizedClientService authorizedClientService;
@Mock
private ClientRegistrationRepository clientRegistrationRepository;
@@ -666,43 +661,6 @@ public class ServletOAuth2AuthorizedClientExchangeFilterFunctionTests {
authentication, servletRequest);
}
// gh-19421
@Test
public void filterWhenServletRequestNullAndClientRegistrationIdFromAuthenticationAndCustomPrincipalResolverThenAuthorizedClientResolved() {
this.function = new ServletOAuth2AuthorizedClientExchangeFilterFunction(
new AuthorizedClientServiceOAuth2AuthorizedClientManager(this.clientRegistrationRepository,
this.authorizedClientService));
this.function.setDefaultOAuth2AuthorizedClient(true);
OAuth2User user = mock(OAuth2User.class);
List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("ROLE_USER");
OAuth2AuthenticationToken initialAuthentication = new OAuth2AuthenticationToken(user, authorities,
"initial-registration-id");
OAuth2AuthenticationToken authentication = new OAuth2AuthenticationToken(user, authorities,
this.registration.getRegistrationId());
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(this.registration, "principalName",
this.accessToken);
given(this.clientRegistrationRepository.findByRegistrationId(this.registration.getRegistrationId()))
.willReturn(this.registration);
given(this.authorizedClientService.loadAuthorizedClient(this.registration.getRegistrationId(),
initialAuthentication.getName()))
.willReturn(authorizedClient);
final ClientRequest clientRequest = ClientRequest.create(HttpMethod.GET, URI.create("https://example.com"))
.build();
this.function.setPrincipalResolver((request) -> authentication);
this.function.filter(clientRequest, this.exchange)
.contextWrite(context(null, null, initialAuthentication))
.block();
List<ClientRequest> requests = this.exchange.getRequests();
assertThat(requests).hasSize(1);
ClientRequest request = requests.get(0);
assertThat(request.headers().getFirst(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer token-0");
assertThat(request.url().toASCIIString()).isEqualTo("https://example.com");
assertThat(request.method()).isEqualTo(HttpMethod.GET);
assertThat(getBody(request)).isEmpty();
verify(this.authorizedClientService).loadAuthorizedClient(this.registration.getRegistrationId(),
authentication.getName());
}
@Test
public void filterWhenUnauthorizedThenInvokeFailureHandler() {
assertHttpStatusInvokesFailureHandler(HttpStatus.UNAUTHORIZED, OAuth2ErrorCodes.INVALID_TOKEN);
+2 -1
View File
@@ -1,5 +1,5 @@
changelog:
repository: spring-projects/spring-security
repository: spring-projects/spring-security-commercial
sections:
- title: ":rewind: Breaking Changes"
labels: ["type: breaks-passivity"]
@@ -17,5 +17,6 @@ changelog:
labels: ["type: task", "in: build"]
sort: "title"
issues:
generate_links: false
exclude:
labels: ["status: duplicate"]
+2 -2
View File
@@ -15,12 +15,12 @@ dependencyResolutionManagement {
}
}
rootProject.name = 'spring-security'
rootProject.name = 'spring-security-commercial'
FileTree buildFiles = fileTree(rootDir) {
List excludes = gradle.startParameter.projectProperties.get("excludeProjects")?.split(",")
include '**/*.gradle', '**/*.gradle.kts'
exclude 'build', '**/gradle', 'settings.gradle', 'buildSrc', '/build.gradle', '.*', 'out'
exclude 'build', '**/gradle', 'settings.gradle', 'commercial-settings.gradle', 'buildSrc', '/build.gradle', '.*', 'out'
exclude '**/grails3'
if(excludes) {
exclude excludes