Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 435d132c79 |
@@ -14,4 +14,4 @@ jobs:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
uses: spring-io/github-actions/.github/workflows/codeql-analysis.yml@e415dadd0910c901e7a7fabd67bbb355b2324500 # 1
|
||||
uses: spring-io/github-actions/.github/workflows/codeql-analysis.yml@1
|
||||
|
||||
@@ -17,7 +17,7 @@ permissions:
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
uses: spring-io/spring-security-release-tools/.github/workflows/build.yml@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15
|
||||
uses: spring-io/spring-security-release-tools/.github/workflows/build.yml@v1
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ ubuntu-latest, windows-latest ]
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
secrets: inherit
|
||||
test:
|
||||
name: Test Against Snapshots
|
||||
uses: spring-io/spring-security-release-tools/.github/workflows/test.yml@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15
|
||||
uses: spring-io/spring-security-release-tools/.github/workflows/test.yml@v1
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
@@ -39,16 +39,16 @@ jobs:
|
||||
toolchain: 17
|
||||
with:
|
||||
java-version: ${{ matrix.java-version }}
|
||||
test-args: --refresh-dependencies -PforceMavenRepositories=snapshot -PisOverrideVersionCatalog -PtestToolchain=${{ matrix.toolchain }} -PspringFrameworkVersion=6.2.+ -PreactorVersion=2023.0.+ -PspringDataVersion=2024.0.+ -PmicrometerVersion=1.15.+ --stacktrace
|
||||
test-args: --refresh-dependencies -PforceMavenRepositories=snapshot -PisOverrideVersionCatalog -PtestToolchain=${{ matrix.toolchain }} -PspringFrameworkVersion=6.2.+ -PreactorVersion=2023.0.+ -PspringDataVersion=2024.0.+ --stacktrace
|
||||
secrets: inherit
|
||||
check-samples:
|
||||
name: Check Samples
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository_owner == 'spring-projects' }}
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up gradle
|
||||
uses: spring-io/spring-gradle-build-action@c8668747d7c264864c8c7f7026d0d277d14a78dc # v2.0.6
|
||||
uses: spring-io/spring-gradle-build-action@v2
|
||||
with:
|
||||
java-version: 17
|
||||
distribution: temurin
|
||||
@@ -67,21 +67,21 @@ jobs:
|
||||
deploy-artifacts:
|
||||
name: Deploy Artifacts
|
||||
needs: [ build, test, check-samples ]
|
||||
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-artifacts.yml@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15
|
||||
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-artifacts.yml@v1
|
||||
with:
|
||||
should-deploy-artifacts: ${{ needs.build.outputs.should-deploy-artifacts }}
|
||||
secrets: inherit
|
||||
deploy-schema:
|
||||
name: Deploy Schema
|
||||
needs: [ build, test, check-samples ]
|
||||
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-schema.yml@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15
|
||||
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-schema ]
|
||||
uses: spring-io/spring-security-release-tools/.github/workflows/perform-release.yml@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15
|
||||
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 }}
|
||||
@@ -97,6 +97,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- 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:
|
||||
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
|
||||
|
||||
@@ -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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- 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@a1f321783a0769dd2aea4fad6c2ae2f95a52b885 # 0.0.5
|
||||
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."
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
if: github.repository_owner == 'spring-projects'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: docs-build
|
||||
fetch-depth: 1
|
||||
|
||||
@@ -19,14 +19,14 @@ jobs:
|
||||
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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
- name: Set up Gradle
|
||||
uses: gradle/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1
|
||||
uses: gradle/gradle-build-action@v2
|
||||
- name: Upgrade Wrappers
|
||||
run: ./gradlew clean upgradeGradleWrapperAll --continue -Porg.gradle.java.installations.auto-download=false
|
||||
env:
|
||||
|
||||
@@ -30,6 +30,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- 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:
|
||||
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
|
||||
|
||||
@@ -11,9 +11,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository == 'spring-projects/spring-security' }}
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up gradle
|
||||
uses: spring-io/spring-gradle-build-action@c8668747d7c264864c8c7f7026d0d277d14a78dc # v2.0.6
|
||||
uses: spring-io/spring-gradle-build-action@v2
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
@@ -24,9 +24,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository == 'spring-projects/spring-security' }}
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up gradle
|
||||
uses: spring-io/spring-gradle-build-action@c8668747d7c264864c8c7f7026d0d277d14a78dc # v2.0.6
|
||||
uses: spring-io/spring-gradle-build-action@v2
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
run: ./gradlew -PbuildSrc.skipTests=true :spring-security-docs:antora
|
||||
- name: Upload Docs
|
||||
id: upload
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: docs
|
||||
path: docs/build/site
|
||||
@@ -46,6 +46,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- 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:
|
||||
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- name: Dispatch
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
matrix:
|
||||
branch: [ '5.8.x', '6.2.x', '6.3.x', 'main' ]
|
||||
steps:
|
||||
- uses: spring-io/spring-doc-actions/update-antora-spring-ui@415e2b11a766ba64799fffb5c97a4f7e17f677cf # v0.0.22
|
||||
- uses: spring-io/spring-doc-actions/update-antora-spring-ui@e28269199d1d27975cf7f65e16d6095c555b3cd0
|
||||
name: Update
|
||||
with:
|
||||
docs-branch: ${{ matrix.branch }}
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
name: Update on docs-build
|
||||
steps:
|
||||
- uses: spring-io/spring-doc-actions/update-antora-spring-ui@415e2b11a766ba64799fffb5c97a4f7e17f677cf # v0.0.22
|
||||
- uses: spring-io/spring-doc-actions/update-antora-spring-ui@e28269199d1d27975cf7f65e16d6095c555b3cd0
|
||||
name: Update
|
||||
with:
|
||||
docs-branch: 'docs-build'
|
||||
|
||||
@@ -9,7 +9,7 @@ permissions:
|
||||
jobs:
|
||||
update-scheduled-release-version:
|
||||
name: Update Scheduled Release Version
|
||||
uses: spring-io/spring-security-release-tools/.github/workflows/update-scheduled-release-version.yml@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15
|
||||
uses: spring-io/spring-security-release-tools/.github/workflows/update-scheduled-release-version.yml@v1
|
||||
secrets: inherit
|
||||
send-notification:
|
||||
name: Send Notification
|
||||
@@ -18,6 +18,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Send Notification
|
||||
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15
|
||||
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@v1
|
||||
with:
|
||||
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
|
||||
-21
@@ -68,27 +68,6 @@ The https://github.com/spring-projects/spring-security/tree/docs-build[playbook
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
BIN
Binary file not shown.
@@ -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
|
||||
+240
@@ -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" "$@"
|
||||
Vendored
+91
@@ -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
|
||||
-3
@@ -36,7 +36,6 @@ import org.springframework.security.web.webauthn.api.PublicKeyCredentialRpEntity
|
||||
import org.springframework.security.web.webauthn.authentication.PublicKeyCredentialRequestOptionsFilter;
|
||||
import org.springframework.security.web.webauthn.authentication.WebAuthnAuthenticationFilter;
|
||||
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.MapUserCredentialRepository;
|
||||
import org.springframework.security.web.webauthn.management.PublicKeyCredentialUserEntityRepository;
|
||||
@@ -167,8 +166,6 @@ public class WebAuthnConfigurer<H extends HttpSecurityBuilder<H>>
|
||||
new ProviderManager(new WebAuthnAuthenticationProvider(rpOperations, userDetailsService)));
|
||||
WebAuthnRegistrationFilter webAuthnRegistrationFilter = new WebAuthnRegistrationFilter(userCredentials,
|
||||
rpOperations);
|
||||
webAuthnRegistrationFilter.setDeleteCredentialAuthorizationManager(
|
||||
new CredentialRecordOwnerAuthorizationManager(userCredentials, userEntities));
|
||||
PublicKeyCredentialCreationOptionsFilter creationOptionsFilter = new PublicKeyCredentialCreationOptionsFilter(
|
||||
rpOperations);
|
||||
if (creationOptionsRepository != null) {
|
||||
|
||||
-4
@@ -74,7 +74,6 @@ import org.springframework.security.web.context.RequestAttributeSecurityContextR
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Ngoc Nhan
|
||||
* @author Max Batischev
|
||||
* @since 3.2
|
||||
*/
|
||||
public final class X509Configurer<H extends HttpSecurityBuilder<H>>
|
||||
@@ -162,10 +161,7 @@ public final class X509Configurer<H extends HttpSecurityBuilder<H>>
|
||||
* @param subjectPrincipalRegex the regex to extract the user principal from the
|
||||
* certificate (i.e. "CN=(.*?)(?:,|$)").
|
||||
* @return the {@link X509Configurer} for further customizations
|
||||
* @deprecated Please use {@link #x509PrincipalExtractor(X509PrincipalExtractor)}
|
||||
* instead
|
||||
*/
|
||||
@Deprecated
|
||||
public X509Configurer<H> subjectPrincipalRegex(String subjectPrincipalRegex) {
|
||||
SubjectDnX509PrincipalExtractor principalExtractor = new SubjectDnX509PrincipalExtractor();
|
||||
principalExtractor.setSubjectDnRegex(subjectPrincipalRegex);
|
||||
|
||||
+3
-15
@@ -521,23 +521,11 @@ final class AuthenticationConfigBuilder {
|
||||
filterBuilder.addPropertyValue("authenticationManager", authManager);
|
||||
filterBuilder.addPropertyValue("securityContextHolderStrategy",
|
||||
authenticationFilterSecurityContextHolderStrategyRef);
|
||||
String principalExtractorRef = x509Elt.getAttribute("principal-extractor-ref");
|
||||
String subjectPrincipalRegex = x509Elt.getAttribute("subject-principal-regex");
|
||||
boolean hasPrincipalExtractorRef = StringUtils.hasText(principalExtractorRef);
|
||||
boolean hasSubjectPrincipalRegex = StringUtils.hasText(subjectPrincipalRegex);
|
||||
if (hasPrincipalExtractorRef && hasSubjectPrincipalRegex) {
|
||||
this.pc.getReaderContext()
|
||||
.error("The attribute 'principal-extractor-ref' cannot be used together with the 'subject-principal-regex' attribute within <"
|
||||
+ Elements.X509 + ">", this.pc.extractSource(x509Elt));
|
||||
}
|
||||
if (hasPrincipalExtractorRef) {
|
||||
RuntimeBeanReference principalExtractor = new RuntimeBeanReference(principalExtractorRef);
|
||||
filterBuilder.addPropertyValue("principalExtractor", principalExtractor);
|
||||
}
|
||||
if (hasSubjectPrincipalRegex) {
|
||||
String regex = x509Elt.getAttribute("subject-principal-regex");
|
||||
if (StringUtils.hasText(regex)) {
|
||||
BeanDefinitionBuilder extractor = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(SubjectDnX509PrincipalExtractor.class);
|
||||
extractor.addPropertyValue("subjectDnRegex", subjectPrincipalRegex);
|
||||
extractor.addPropertyValue("subjectDnRegex", regex);
|
||||
filterBuilder.addPropertyValue("principalExtractor", extractor.getBeanDefinition());
|
||||
}
|
||||
injectAuthenticationDetailsSource(x509Elt, filterBuilder);
|
||||
|
||||
+6
-6
@@ -119,7 +119,7 @@ import org.springframework.security.oauth2.server.resource.web.server.BearerToke
|
||||
import org.springframework.security.oauth2.server.resource.web.server.authentication.ServerBearerTokenAuthenticationConverter;
|
||||
import org.springframework.security.web.PortMapper;
|
||||
import org.springframework.security.web.authentication.logout.LogoutHandler;
|
||||
import org.springframework.security.web.authentication.preauth.x509.SubjectX500PrincipalExtractor;
|
||||
import org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor;
|
||||
import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor;
|
||||
import org.springframework.security.web.server.DefaultServerRedirectStrategy;
|
||||
import org.springframework.security.web.server.DelegatingServerAuthenticationEntryPoint;
|
||||
@@ -943,8 +943,8 @@ public class ServerHttpSecurity {
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* Note that if extractor is not specified, {@link SubjectX500PrincipalExtractor} will
|
||||
* be used. If authenticationManager is not specified,
|
||||
* Note that if extractor is not specified, {@link SubjectDnX509PrincipalExtractor}
|
||||
* will be used. If authenticationManager is not specified,
|
||||
* {@link ReactivePreAuthenticatedAuthenticationManager} will be used.
|
||||
* @return the {@link X509Spec} to customize
|
||||
* @since 5.2
|
||||
@@ -978,8 +978,8 @@ public class ServerHttpSecurity {
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* Note that if extractor is not specified, {@link SubjectX500PrincipalExtractor} will
|
||||
* be used. If authenticationManager is not specified,
|
||||
* Note that if extractor is not specified, {@link SubjectDnX509PrincipalExtractor}
|
||||
* will be used. If authenticationManager is not specified,
|
||||
* {@link ReactivePreAuthenticatedAuthenticationManager} will be used.
|
||||
* @param x509Customizer the {@link Customizer} to provide more options for the
|
||||
* {@link X509Spec}
|
||||
@@ -4180,7 +4180,7 @@ public class ServerHttpSecurity {
|
||||
if (this.principalExtractor != null) {
|
||||
return this.principalExtractor;
|
||||
}
|
||||
return new SubjectX500PrincipalExtractor();
|
||||
return new SubjectDnX509PrincipalExtractor();
|
||||
}
|
||||
|
||||
private ReactiveAuthenticationManager getAuthenticationManager() {
|
||||
|
||||
@@ -1050,9 +1050,6 @@ x509.attlist &=
|
||||
x509.attlist &=
|
||||
## Reference to an AuthenticationDetailsSource which will be used by the authentication filter
|
||||
attribute authentication-details-source-ref {xsd:token}?
|
||||
x509.attlist &=
|
||||
## Reference to an X509PrincipalExtractor which will be used by the authentication filter
|
||||
attribute principal-extractor-ref {xsd:token}?
|
||||
|
||||
jee =
|
||||
## Adds a J2eePreAuthenticatedProcessingFilter to the filter chain to provide integration with container authentication.
|
||||
|
||||
@@ -2911,12 +2911,6 @@
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="principal-extractor-ref" type="xs:token">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Reference to an X509PrincipalExtractor which will be used by the authentication filter
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:attributeGroup>
|
||||
<xs:element name="jee">
|
||||
<xs:annotation>
|
||||
|
||||
@@ -211,7 +211,6 @@ import org.springframework.security.web.webauthn.api.AuthenticationExtensionsCli
|
||||
import org.springframework.security.web.webauthn.api.AuthenticationExtensionsClientOutputs;
|
||||
import org.springframework.security.web.webauthn.api.AuthenticatorAssertionResponse;
|
||||
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.Bytes;
|
||||
import org.springframework.security.web.webauthn.api.CredProtectAuthenticationExtensionsClientInput;
|
||||
@@ -226,7 +225,6 @@ import org.springframework.security.web.webauthn.api.PublicKeyCredentialRequestO
|
||||
import org.springframework.security.web.webauthn.api.PublicKeyCredentialType;
|
||||
import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity;
|
||||
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.TestPublicKeyCredentialRequestOptions;
|
||||
import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialUserEntities;
|
||||
@@ -656,8 +654,6 @@ final class SerializationSamples {
|
||||
generatorByClassName.put(CredentialPropertiesOutput.class, (o) -> credentialOutput);
|
||||
generatorByClassName.put(ImmutableAuthenticationExtensionsClientOutputs.class, (o) -> outputs);
|
||||
generatorByClassName.put(AuthenticatorAssertionResponse.class, (r) -> response);
|
||||
generatorByClassName.put(AuthenticatorAttestationResponse.class,
|
||||
(r) -> TestAuthenticatorAttestationResponses.createAuthenticatorAttestationResponse().build());
|
||||
generatorByClassName.put(RelyingPartyAuthenticationRequest.class, (r) -> authRequest);
|
||||
generatorByClassName.put(PublicKeyCredential.class, (r) -> credential);
|
||||
generatorByClassName.put(WebAuthnAuthenticationRequestToken.class, (r) -> requestToken);
|
||||
|
||||
+5
-54
@@ -33,10 +33,10 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
@@ -207,7 +207,10 @@ class SpringSecurityCoreVersionSerializableTests {
|
||||
boolean hasSerialVersion = Stream.of(clazz.getDeclaredFields())
|
||||
.map(Field::getName)
|
||||
.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);
|
||||
continue;
|
||||
}
|
||||
@@ -246,58 +249,6 @@ class SpringSecurityCoreVersionSerializableTests {
|
||||
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() {
|
||||
String version = System.getProperty("springSecurityVersion");
|
||||
String[] parts = version.split("\\.");
|
||||
|
||||
-69
@@ -42,15 +42,8 @@ import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||
import org.springframework.security.web.FilterChainProxy;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
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.TestCredentialRecords;
|
||||
import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialCreationOptions;
|
||||
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.registration.HttpSessionPublicKeyCredentialCreationOptionsRepository;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
@@ -63,7 +56,6 @@ import static org.mockito.BDDMockito.willAnswer;
|
||||
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.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
@@ -255,24 +247,6 @@ public class WebAuthnConfigurerTests {
|
||||
.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
|
||||
@EnableWebSecurity
|
||||
static class ConfigCredentialCreationOptionsRepository {
|
||||
@@ -443,47 +417,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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-80
@@ -43,9 +43,7 @@ import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
|
||||
import org.springframework.security.web.authentication.preauth.x509.SubjectX500PrincipalExtractor;
|
||||
import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter;
|
||||
import org.springframework.security.web.authentication.preauth.x509.X509TestUtils;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -125,28 +123,6 @@ public class X509ConfigurerTests {
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void x509WhenSubjectX500PrincipalExtractor() throws Exception {
|
||||
this.spring.register(SubjectX500PrincipalExtractorConfig.class).autowire();
|
||||
X509Certificate certificate = loadCert("rod.cer");
|
||||
// @formatter:off
|
||||
this.mvc.perform(get("/").with(x509(certificate)))
|
||||
.andExpect((result) -> assertThat(result.getRequest().getSession(false)).isNull())
|
||||
.andExpect(authenticated().withUsername("rod"));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void x509WhenSubjectX500PrincipalExtractorBean() throws Exception {
|
||||
this.spring.register(SubjectX500PrincipalExtractorEmailConfig.class).autowire();
|
||||
X509Certificate certificate = X509TestUtils.buildTestCertificate();
|
||||
// @formatter:off
|
||||
this.mvc.perform(get("/").with(x509(certificate)))
|
||||
.andExpect((result) -> assertThat(result.getRequest().getSession(false)).isNull())
|
||||
.andExpect(authenticated().withUsername("luke@monkeymachine"));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void x509WhenUserDetailsServiceNotConfiguredThenUsesBean() throws Exception {
|
||||
this.spring.register(UserDetailsServiceBeanConfig.class).autowire();
|
||||
@@ -301,62 +277,6 @@ public class X509ConfigurerTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
static class SubjectX500PrincipalExtractorConfig {
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.x509((x509) -> x509
|
||||
.x509PrincipalExtractor(new SubjectX500PrincipalExtractor())
|
||||
);
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
UserDetailsService userDetailsService() {
|
||||
UserDetails user = User.withDefaultPasswordEncoder()
|
||||
.username("rod")
|
||||
.password("password")
|
||||
.roles("USER", "ADMIN")
|
||||
.build();
|
||||
return new InMemoryUserDetailsManager(user);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
static class SubjectX500PrincipalExtractorEmailConfig {
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
SubjectX500PrincipalExtractor principalExtractor = new SubjectX500PrincipalExtractor();
|
||||
principalExtractor.setExtractPrincipalNameFromEmail(true);
|
||||
// @formatter:off
|
||||
http
|
||||
.x509((x509) -> x509
|
||||
.x509PrincipalExtractor(principalExtractor)
|
||||
);
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
UserDetailsService userDetailsService() {
|
||||
UserDetails user = User.withDefaultPasswordEncoder()
|
||||
.username("luke@monkeymachine")
|
||||
.password("password")
|
||||
.roles("USER", "ADMIN")
|
||||
.build();
|
||||
return new InMemoryUserDetailsManager(user);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
static class UserDetailsServiceBeanConfig {
|
||||
|
||||
-25
@@ -28,17 +28,14 @@ import jakarta.servlet.http.HttpSession;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
|
||||
import org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException;
|
||||
import org.springframework.security.config.test.SpringTestContext;
|
||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.authentication.session.SessionLimit;
|
||||
import org.springframework.security.web.header.HeaderWriterFilter;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.ResultMatcher;
|
||||
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
|
||||
@@ -153,16 +150,6 @@ public class HttpHeadersConfigTests {
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenHeadersEagerlyConfiguredThenHeadersAreWritten() throws Exception {
|
||||
this.spring.configLocations(this.xml("HeadersEagerlyConfigured")).autowire();
|
||||
// @formatter:off
|
||||
this.mvc.perform(get("/").secure(true))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(includesDefaults());
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenFrameOptionsConfiguredThenIncludesHeader() throws Exception {
|
||||
Map<String, String> headers = new HashMap(defaultHeaders);
|
||||
@@ -968,18 +955,6 @@ public class HttpHeadersConfigTests {
|
||||
|
||||
}
|
||||
|
||||
public static class EagerHeadersBeanPostProcessor implements BeanPostProcessor {
|
||||
|
||||
@Override
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
||||
if (bean instanceof HeaderWriterFilter headerWriterFilter) {
|
||||
headerWriterFilter.setShouldWriteHeadersEagerly(true);
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class CustomSessionLimit implements SessionLimit {
|
||||
|
||||
@Override
|
||||
|
||||
-23
@@ -21,7 +21,6 @@ import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.security.AccessController;
|
||||
import java.security.Principal;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
@@ -92,7 +91,6 @@ import org.springframework.security.web.authentication.AnonymousAuthenticationFi
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.security.web.authentication.logout.LogoutFilter;
|
||||
import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter;
|
||||
import org.springframework.security.web.authentication.preauth.x509.X509TestUtils;
|
||||
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
|
||||
import org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter;
|
||||
import org.springframework.security.web.authentication.ui.DefaultResourcesFilter;
|
||||
@@ -400,27 +398,6 @@ public class MiscHttpConfigTests {
|
||||
.containsSubsequence(CsrfFilter.class, X509AuthenticationFilter.class, ExceptionTranslationFilter.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenUsingX509PrincipalExtractorRef() throws Exception {
|
||||
this.spring.configLocations(xml("X509PrincipalExtractorRef")).autowire();
|
||||
X509Certificate certificate = X509TestUtils.buildTestCertificate();
|
||||
RequestPostProcessor x509 = x509(certificate);
|
||||
// @formatter:off
|
||||
this.mvc.perform(get("/protected").with(x509))
|
||||
.andExpect(status().isOk());
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenUsingX509PrincipalExtractorRefAndSubjectPrincipalRegex() throws Exception {
|
||||
String xmlResourceName = "X509PrincipalExtractorRefAndSubjectPrincipalRegex";
|
||||
// @formatter:off
|
||||
assertThatExceptionOfType(BeanDefinitionParsingException.class)
|
||||
.isThrownBy(() -> this.spring.configLocations(xml(xmlResourceName)).autowire())
|
||||
.withMessage("Configuration problem: The attribute 'principal-extractor-ref' cannot be used together with the 'subject-principal-regex' attribute within <x509>\n" + "Offending resource: class path resource [org/springframework/security/config/http/MiscHttpConfigTests-X509PrincipalExtractorRefAndSubjectPrincipalRegex.xml]");
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenUsingX509AndPropertyPlaceholderThenSubjectPrincipalRegexIsConfigured() throws Exception {
|
||||
System.setProperty("subject_principal_regex", "OU=(.*?)(?:,|$)");
|
||||
|
||||
-37
@@ -1,37 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://www.springframework.org/schema/security"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/security
|
||||
https://www.springframework.org/schema/security/spring-security.xsd
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<http auto-config="true">
|
||||
<headers/>
|
||||
<intercept-url pattern="/**" access="permitAll"/>
|
||||
</http>
|
||||
|
||||
<b:bean class="org.springframework.security.config.http.HttpHeadersConfigTests.EagerHeadersBeanPostProcessor"/>
|
||||
|
||||
<b:bean name="simple" class="org.springframework.security.config.http.HttpHeadersConfigTests.SimpleController"/>
|
||||
|
||||
<b:import resource="userservice.xml"/>
|
||||
</b:beans>
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright 2002-2018 the original author or authors.
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
|
||||
xmlns:p="http://www.springframework.org/schema/p"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://www.springframework.org/schema/security"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/security
|
||||
https://www.springframework.org/schema/security/spring-security.xsd
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<http>
|
||||
<x509 principal-extractor-ref="principalExtractor"/>
|
||||
<intercept-url pattern="/**" access="authenticated"/>
|
||||
</http>
|
||||
|
||||
<user-service id="us">
|
||||
<user name="luke@monkeymachine" password="{noop}password" authorities="ROLE_USER"/>
|
||||
</user-service>
|
||||
|
||||
<b:bean name="principalExtractor" class="org.springframework.security.web.authentication.preauth.x509.SubjectX500PrincipalExtractor"
|
||||
p:extractPrincipalNameFromEmail="true"/>
|
||||
|
||||
<b:import resource="MiscHttpConfigTests-controllers.xml"/>
|
||||
</b:beans>
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright 2002-2018 the original author or authors.
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
|
||||
xmlns:p="http://www.springframework.org/schema/p"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://www.springframework.org/schema/security"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/security
|
||||
https://www.springframework.org/schema/security/spring-security.xsd
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<http>
|
||||
<x509 principal-extractor-ref="principalExtractor" subject-principal-regex="(.*)"/>
|
||||
<intercept-url pattern="/**" access="authenticated"/>
|
||||
</http>
|
||||
|
||||
<user-service id="us">
|
||||
<user name="luke@monkeymachine" password="{noop}password" authorities="ROLE_USER"/>
|
||||
</user-service>
|
||||
|
||||
<b:bean name="principalExtractor" class="org.springframework.security.web.authentication.preauth.x509.SubjectX500PrincipalExtractor"
|
||||
p:extractPrincipalNameFromEmail="true"/>
|
||||
|
||||
<b:import resource="MiscHttpConfigTests-controllers.xml"/>
|
||||
</b:beans>
|
||||
BIN
Binary file not shown.
+4
-39
@@ -92,8 +92,6 @@ public abstract class AbstractUserDetailsAuthenticationProvider
|
||||
|
||||
private UserDetailsChecker postAuthenticationChecks = new DefaultPostAuthenticationChecks();
|
||||
|
||||
private boolean alwaysPerformAdditionalChecksOnUser = true;
|
||||
|
||||
private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();
|
||||
|
||||
/**
|
||||
@@ -148,7 +146,8 @@ public abstract class AbstractUserDetailsAuthenticationProvider
|
||||
Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
|
||||
}
|
||||
try {
|
||||
performPreCheck(user, (UsernamePasswordAuthenticationToken) authentication);
|
||||
this.preAuthenticationChecks.check(user);
|
||||
additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
|
||||
}
|
||||
catch (AuthenticationException ex) {
|
||||
if (!cacheWasUsed) {
|
||||
@@ -158,7 +157,8 @@ public abstract class AbstractUserDetailsAuthenticationProvider
|
||||
// we're using latest data (i.e. not from the cache)
|
||||
cacheWasUsed = false;
|
||||
user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
|
||||
performPreCheck(user, (UsernamePasswordAuthenticationToken) authentication);
|
||||
this.preAuthenticationChecks.check(user);
|
||||
additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
|
||||
}
|
||||
this.postAuthenticationChecks.check(user);
|
||||
if (!cacheWasUsed) {
|
||||
@@ -171,25 +171,6 @@ public abstract class AbstractUserDetailsAuthenticationProvider
|
||||
return createSuccessAuthentication(principalToReturn, authentication, user);
|
||||
}
|
||||
|
||||
private void performPreCheck(UserDetails user, UsernamePasswordAuthenticationToken authentication) {
|
||||
try {
|
||||
this.preAuthenticationChecks.check(user);
|
||||
}
|
||||
catch (AuthenticationException ex) {
|
||||
if (!this.alwaysPerformAdditionalChecksOnUser) {
|
||||
throw ex;
|
||||
}
|
||||
try {
|
||||
additionalAuthenticationChecks(user, authentication);
|
||||
}
|
||||
catch (AuthenticationException ignored) {
|
||||
// preserve the original failed check
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
additionalAuthenticationChecks(user, authentication);
|
||||
}
|
||||
|
||||
private String determineUsername(Authentication authentication) {
|
||||
return (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName();
|
||||
}
|
||||
@@ -332,22 +313,6 @@ public abstract class AbstractUserDetailsAuthenticationProvider
|
||||
this.postAuthenticationChecks = postAuthenticationChecks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether to always perform the additional checks on the user, even if the
|
||||
* pre-authentication checks fail. This is useful to ensure that regardless of the
|
||||
* state of the user account, authentication takes the same amount of time to
|
||||
* complete.
|
||||
*
|
||||
* <p>
|
||||
* For applications that rely on the additional checks running only once should set
|
||||
* this value to {@code false}
|
||||
* @param alwaysPerformAdditionalChecksOnUser
|
||||
* @since 5.7.23
|
||||
*/
|
||||
public void setAlwaysPerformAdditionalChecksOnUser(boolean alwaysPerformAdditionalChecksOnUser) {
|
||||
this.alwaysPerformAdditionalChecksOnUser = alwaysPerformAdditionalChecksOnUser;
|
||||
}
|
||||
|
||||
public void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) {
|
||||
this.authoritiesMapper = authoritiesMapper;
|
||||
}
|
||||
|
||||
+3
-5
@@ -152,9 +152,7 @@ public final class JdbcOneTimeTokenService implements OneTimeTokenService, Dispo
|
||||
return null;
|
||||
}
|
||||
OneTimeToken token = tokens.get(0);
|
||||
if (deleteOneTimeToken(token) == 0) {
|
||||
return null;
|
||||
}
|
||||
deleteOneTimeToken(token);
|
||||
if (isExpired(token)) {
|
||||
return null;
|
||||
}
|
||||
@@ -172,11 +170,11 @@ public final class JdbcOneTimeTokenService implements OneTimeTokenService, Dispo
|
||||
return this.jdbcOperations.query(SELECT_ONE_TIME_TOKEN_SQL, pss, this.oneTimeTokenRowMapper);
|
||||
}
|
||||
|
||||
private int deleteOneTimeToken(OneTimeToken oneTimeToken) {
|
||||
private void deleteOneTimeToken(OneTimeToken oneTimeToken) {
|
||||
List<SqlParameterValue> parameters = List
|
||||
.of(new SqlParameterValue(Types.VARCHAR, oneTimeToken.getTokenValue()));
|
||||
PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters.toArray());
|
||||
return this.jdbcOperations.update(DELETE_ONE_TIME_TOKEN_SQL, pss);
|
||||
this.jdbcOperations.update(DELETE_ONE_TIME_TOKEN_SQL, pss);
|
||||
}
|
||||
|
||||
private ThreadPoolTaskScheduler createTaskScheduler(String cleanupCron) {
|
||||
|
||||
+1
-5
@@ -67,11 +67,7 @@ public final class AuthoritiesAuthorizationManager implements AuthorizationManag
|
||||
|
||||
private boolean isAuthorized(Authentication authentication, Collection<String> authorities) {
|
||||
for (GrantedAuthority grantedAuthority : getGrantedAuthorities(authentication)) {
|
||||
String authority = grantedAuthority.getAuthority();
|
||||
if (authority == null) {
|
||||
continue;
|
||||
}
|
||||
if (authorities.contains(authority)) {
|
||||
if (authorities.contains(grantedAuthority.getAuthority())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
-1
@@ -28,7 +28,6 @@ import org.springframework.security.authorization.AuthorizationDecision;
|
||||
* instead
|
||||
*/
|
||||
@Deprecated
|
||||
@SuppressWarnings("serial")
|
||||
public class ExpressionAttributeAuthorizationDecision extends AuthorizationDecision {
|
||||
|
||||
private final ExpressionAttribute expressionAttribute;
|
||||
|
||||
+6
-42
@@ -21,7 +21,6 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
|
||||
|
||||
import org.springframework.cache.Cache;
|
||||
import org.springframework.dao.DataRetrievalFailureException;
|
||||
@@ -43,7 +42,6 @@ import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.userdetails.PasswordEncodedUser;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsChecker;
|
||||
import org.springframework.security.core.userdetails.UserDetailsPasswordService;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
@@ -64,7 +62,6 @@ import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.isA;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.willThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
@@ -455,10 +452,12 @@ public class DaoAuthenticationProviderTests {
|
||||
assertThat(daoAuthenticationProvider.getPasswordEncoder()).isSameAs(NoOpPasswordEncoder.getInstance());
|
||||
}
|
||||
|
||||
// SEC-2056
|
||||
@Test
|
||||
@EnabledIfSystemProperty(named = "spring.security.timing-tests", matches = "true")
|
||||
public void testSec2056() {
|
||||
/**
|
||||
* This is an explicit test for SEC-2056. It is intentionally ignored since this test
|
||||
* is not deterministic and {@link #testUserNotFoundEncodesPassword()} ensures that
|
||||
* SEC-2056 is fixed.
|
||||
*/
|
||||
public void IGNOREtestSec2056() {
|
||||
UsernamePasswordAuthenticationToken foundUser = UsernamePasswordAuthenticationToken.unauthenticated("rod",
|
||||
"koala");
|
||||
UsernamePasswordAuthenticationToken notFoundUser = UsernamePasswordAuthenticationToken
|
||||
@@ -492,41 +491,6 @@ public class DaoAuthenticationProviderTests {
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
// related to SEC-2056
|
||||
@Test
|
||||
@EnabledIfSystemProperty(named = "spring.security.timing-tests", matches = "true")
|
||||
public void testDisabledUserTiming() {
|
||||
UsernamePasswordAuthenticationToken user = UsernamePasswordAuthenticationToken.unauthenticated("rod", "koala");
|
||||
PasswordEncoder encoder = new BCryptPasswordEncoder();
|
||||
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
|
||||
provider.setPasswordEncoder(encoder);
|
||||
MockUserDetailsServiceUserRod users = new MockUserDetailsServiceUserRod();
|
||||
users.password = encoder.encode((CharSequence) user.getCredentials());
|
||||
provider.setUserDetailsService(users);
|
||||
int sampleSize = 100;
|
||||
List<Long> enabledTimes = new ArrayList<>(sampleSize);
|
||||
for (int i = 0; i < sampleSize; i++) {
|
||||
long start = System.currentTimeMillis();
|
||||
provider.authenticate(user);
|
||||
enabledTimes.add(System.currentTimeMillis() - start);
|
||||
}
|
||||
UserDetailsChecker preChecks = mock(UserDetailsChecker.class);
|
||||
willThrow(new DisabledException("User is disabled")).given(preChecks).check(any(UserDetails.class));
|
||||
provider.setPreAuthenticationChecks(preChecks);
|
||||
List<Long> disabledTimes = new ArrayList<>(sampleSize);
|
||||
for (int i = 0; i < sampleSize; i++) {
|
||||
long start = System.currentTimeMillis();
|
||||
assertThatExceptionOfType(DisabledException.class).isThrownBy(() -> provider.authenticate(user));
|
||||
disabledTimes.add(System.currentTimeMillis() - start);
|
||||
}
|
||||
double enabledAvg = avg(enabledTimes);
|
||||
double disabledAvg = avg(disabledTimes);
|
||||
assertThat(Math.abs(disabledAvg - enabledAvg) <= 3)
|
||||
.withFailMessage("Disabled user average " + disabledAvg + " should be within 3ms of enabled user average "
|
||||
+ enabledAvg)
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
private double avg(List<Long> counts) {
|
||||
return counts.stream().mapToLong(Long::longValue).average().orElse(0);
|
||||
}
|
||||
|
||||
-26
@@ -21,24 +21,19 @@ import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentMatchers;
|
||||
|
||||
import org.springframework.jdbc.core.JdbcOperations;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.PreparedStatementSetter;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
@@ -150,27 +145,6 @@ class JdbcOneTimeTokenServiceTests {
|
||||
assertThat(consumedOneTimeToken).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void consumeWhenTokenIsDeletedConcurrentlyThenReturnNull() throws Exception {
|
||||
// Simulates a concurrent consume: SELECT finds the token but DELETE affects
|
||||
// 0 rows because another caller already consumed it.
|
||||
JdbcOperations jdbcOperations = mock(JdbcOperations.class);
|
||||
Instant notExpired = Instant.now().plus(5, ChronoUnit.MINUTES);
|
||||
OneTimeToken token = new DefaultOneTimeToken(TOKEN_VALUE, USERNAME, notExpired);
|
||||
given(jdbcOperations.query(any(String.class), any(PreparedStatementSetter.class),
|
||||
ArgumentMatchers.<RowMapper<OneTimeToken>>any()))
|
||||
.willReturn(List.of(token));
|
||||
given(jdbcOperations.update(any(String.class), any(PreparedStatementSetter.class))).willReturn(0);
|
||||
JdbcOneTimeTokenService service = new JdbcOneTimeTokenService(jdbcOperations);
|
||||
try {
|
||||
OneTimeToken consumed = service.consume(new OneTimeTokenAuthenticationToken(TOKEN_VALUE));
|
||||
assertThat(consumed).isNull();
|
||||
}
|
||||
finally {
|
||||
service.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void consumeWhenTokenIsExpiredThenReturnNull() {
|
||||
GenerateOneTimeTokenRequest request = new GenerateOneTimeTokenRequest(USERNAME);
|
||||
|
||||
-20
@@ -17,9 +17,7 @@
|
||||
package org.springframework.security.authorization;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -32,13 +30,11 @@ import org.springframework.security.core.Authentication;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.assertThatNullPointerException;
|
||||
|
||||
/**
|
||||
* Tests for {@link AuthoritiesAuthorizationManager}.
|
||||
*
|
||||
* @author Evgeniy Cheban
|
||||
* @author Khyojae
|
||||
*/
|
||||
class AuthoritiesAuthorizationManagerTests {
|
||||
|
||||
@@ -88,20 +84,4 @@ class AuthoritiesAuthorizationManagerTests {
|
||||
assertThat(manager.check(authentication, Collections.singleton("ROLE_USER")).isGranted()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
// gh-18543
|
||||
void authorizeWhenAuthorityIsNullThenDoesNotThrowNullPointerException() {
|
||||
AuthoritiesAuthorizationManager manager = new AuthoritiesAuthorizationManager();
|
||||
|
||||
Authentication authentication = new TestingAuthenticationToken("user", "password",
|
||||
Collections.singletonList(() -> null));
|
||||
|
||||
Collection<String> authoritiesContainsThrowsNPE = Set.of("ROLE_USER");
|
||||
|
||||
// must be Collection that throws NPE when .contains(null) is invoked
|
||||
// to replicate the issue in gh-18543
|
||||
assertThatNullPointerException().isThrownBy(() -> authoritiesContainsThrowsNPE.contains(null));
|
||||
assertThat(manager.authorize(() -> authentication, authoritiesContainsThrowsNPE).isGranted()).isFalse();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
plugins {
|
||||
id 'java-test-fixtures'
|
||||
}
|
||||
|
||||
apply plugin: 'io.spring.convention.spring-module'
|
||||
|
||||
dependencies {
|
||||
@@ -10,8 +6,6 @@ dependencies {
|
||||
optional 'org.springframework:spring-core'
|
||||
optional 'org.bouncycastle:bcpkix-jdk18on'
|
||||
|
||||
testFixturesImplementation "org.assertj:assertj-core"
|
||||
|
||||
testImplementation "org.assertj:assertj-core"
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-api"
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-params"
|
||||
|
||||
-47
@@ -1,47 +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.crypto.assertions;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
/**
|
||||
* Tests for {@link CryptoAssertions} and {@link CryptoStringAssert}.
|
||||
*/
|
||||
class CryptoAssertionsTests {
|
||||
|
||||
@Test
|
||||
void doesNotDecryptToPassesWhenSupplierThrows() {
|
||||
CryptoAssertions.assertThat((() -> {
|
||||
throw new RuntimeException("decrypt failed");
|
||||
})).doesNotDecryptTo("any");
|
||||
}
|
||||
|
||||
@Test
|
||||
void doesNotDecryptToPassesWhenResultDiffersFromExpected() {
|
||||
CryptoAssertions.assertThat(() -> "other").doesNotDecryptTo("plaintext");
|
||||
}
|
||||
|
||||
@Test
|
||||
void doesNotDecryptToFailsWhenResultEqualsExpected() {
|
||||
assertThatExceptionOfType(AssertionError.class)
|
||||
.isThrownBy(() -> CryptoAssertions.assertThat(() -> "plaintext").doesNotDecryptTo("plaintext"))
|
||||
.withMessageContaining("Expected supplier not to return <plaintext> but it did");
|
||||
}
|
||||
|
||||
}
|
||||
+4
-8
@@ -22,9 +22,8 @@ import java.security.interfaces.RSAPublicKey;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.security.crypto.assertions.CryptoAssertions;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
@@ -87,15 +86,13 @@ public class RsaSecretEncryptorTests {
|
||||
@Test
|
||||
public void roundTripWithMixedAlgorithm() {
|
||||
RsaSecretEncryptor oaep = new RsaSecretEncryptor(RsaAlgorithm.OAEP);
|
||||
CryptoAssertions.assertThat(() -> oaep.decrypt(this.encryptor.encrypt("encryptor")))
|
||||
.doesNotDecryptTo("encryptor");
|
||||
assertThatIllegalStateException().isThrownBy(() -> oaep.decrypt(this.encryptor.encrypt("encryptor")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void roundTripWithMixedSalt() {
|
||||
RsaSecretEncryptor other = new RsaSecretEncryptor(this.encryptor.getPublicKey(), RsaAlgorithm.DEFAULT, "salt");
|
||||
CryptoAssertions.assertThat(() -> this.encryptor.decrypt(other.encrypt("encryptor")))
|
||||
.doesNotDecryptTo("encryptor");
|
||||
assertThatIllegalStateException().isThrownBy(() -> this.encryptor.decrypt(other.encrypt("encryptor")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -109,8 +106,7 @@ public class RsaSecretEncryptorTests {
|
||||
public void publicKeyCannotDecrypt() {
|
||||
RsaSecretEncryptor encryptor = new RsaSecretEncryptor(this.encryptor.getPublicKey());
|
||||
assertThat(encryptor.canDecrypt()).as("Encryptor schould not be able to decrypt").isFalse();
|
||||
CryptoAssertions.assertThat(() -> encryptor.decrypt(encryptor.encrypt("encryptor")))
|
||||
.doesNotDecryptTo("encryptor");
|
||||
assertThatIllegalStateException().isThrownBy(() -> encryptor.decrypt(encryptor.encrypt("encryptor")));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
-44
@@ -1,44 +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.crypto.assertions;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* AssertJ entry point for crypto-related assertions. Use {@link #assertThat(Supplier)} to
|
||||
* assert on a {@link Supplier}<{@link String}> (e.g. a decryption lambda).
|
||||
* <p>
|
||||
* Example: <pre>
|
||||
* assertThat(() -> encryptor.decrypt(ciphertext)).doesNotDecryptTo("plaintext");
|
||||
* </pre>
|
||||
*/
|
||||
public final class CryptoAssertions {
|
||||
|
||||
private CryptoAssertions() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create assertions for the given supplier (e.g. a decryption expression).
|
||||
* @param actual the supplier to assert on
|
||||
* @return assertion object with methods like
|
||||
* {@link CryptoStringAssert#doesNotDecryptTo(String)}
|
||||
*/
|
||||
public static CryptoStringAssert assertThat(Supplier<String> actual) {
|
||||
return new CryptoStringAssert(actual);
|
||||
}
|
||||
|
||||
}
|
||||
-56
@@ -1,56 +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.crypto.assertions;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.assertj.core.api.AbstractObjectAssert;
|
||||
|
||||
/**
|
||||
* AssertJ assertion for {@link Supplier}<{@link String}>, supporting
|
||||
* decryption-related checks such as {@link #doesNotDecryptTo(String)}.
|
||||
*/
|
||||
public final class CryptoStringAssert extends AbstractObjectAssert<CryptoStringAssert, Supplier<String>> {
|
||||
|
||||
CryptoStringAssert(Supplier<String> actual) {
|
||||
super(actual, CryptoStringAssert.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that either the supplier throws an exception when invoked, or the value it
|
||||
* returns is not equal to the given string. Use this to assert that a decryption
|
||||
* attempt does not yield a specific plaintext (e.g. wrong key or tampered
|
||||
* ciphertext).
|
||||
* @param expected the value that the supplier must not return
|
||||
* @return this assertion for chaining
|
||||
*/
|
||||
public CryptoStringAssert doesNotDecryptTo(String expected) {
|
||||
isNotNull();
|
||||
try {
|
||||
String result = this.actual.get();
|
||||
if (Objects.equals(result, expected)) {
|
||||
failWithMessage("Expected supplier not to return <%s> but it did", expected);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Exception thrown: supplier does not "decrypt to" the expected value
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,16 +11,13 @@ dependencies {
|
||||
def springFrameworkVersion = project.property("springFrameworkVersion")
|
||||
def reactorVersion = project.property("reactorVersion")
|
||||
def springDataVersion = project.property("springDataVersion")
|
||||
def micrometerVersion = project.property("micrometerVersion")
|
||||
api platform("org.springframework:spring-framework-bom:$springFrameworkVersion")
|
||||
api platform("io.projectreactor:reactor-bom:$reactorVersion")
|
||||
api platform("org.springframework.data:spring-data-bom:$springDataVersion")
|
||||
api platform("io.micrometer:micrometer-bom:$micrometerVersion")
|
||||
} else {
|
||||
api platform(libs.org.springframework.spring.framework.bom)
|
||||
api platform(libs.io.projectreactor.reactor.bom)
|
||||
api platform(libs.org.springframework.data.spring.data.bom)
|
||||
api platform(libs.io.micrometer.micrometer.bom)
|
||||
}
|
||||
api platform(libs.io.rsocket.rsocket.bom)
|
||||
api platform(libs.org.junit.junit.bom)
|
||||
|
||||
@@ -31,7 +31,7 @@ urls:
|
||||
redirect_facility: httpd
|
||||
ui:
|
||||
bundle:
|
||||
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.26/ui-bundle.zip
|
||||
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.18/ui-bundle.zip
|
||||
snapshot: true
|
||||
runtime:
|
||||
log:
|
||||
|
||||
@@ -18,4 +18,3 @@ asciidoc:
|
||||
gh-url: "https://github.com/spring-projects/spring-security/tree/{gh-tag}"
|
||||
include-java: 'example$docs-src/test/java/org/springframework/security/docs'
|
||||
include-kotlin: 'example$docs-src/test/kotlin/org/springframework/security/kt/docs'
|
||||
include-xml: 'example$docs-src/test/resources/org/springframework/security/docs'
|
||||
|
||||
@@ -7,9 +7,7 @@
|
||||
** xref:migration-7/authorization.adoc[Authorization]
|
||||
** xref:migration-7/configuration.adoc[Configuration]
|
||||
** xref:migration-7/ldap.adoc[LDAP]
|
||||
** xref:migration-7/messaging.adoc[Messaging]
|
||||
** xref:migration-7/oauth2.adoc[OAuth 2.0]
|
||||
** xref:migration-7/saml2.adoc[SAML 2.0]
|
||||
** xref:migration-7/web.adoc[Web]
|
||||
* xref:migration/index.adoc[Migrating to 6]
|
||||
* xref:getting-spring-security.adoc[Getting Spring Security]
|
||||
|
||||
@@ -186,19 +186,6 @@ Xml::
|
||||
This will tell the Spring Security DSL to use `PathPatternRequestMatcher` for all request matchers that it constructs.
|
||||
|
||||
In the event that you are directly constructing an object (as opposed to having the DSL construct it) that has a `setRequestMatcher` method. you should also proactively specify a `PathPatternRequestMatcher` there as well.
|
||||
In XML, you do this by using `request-matcher="path"` instead of `request-matcher="ant"` or `request-matcher="mvc"` like so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http auto-config="true">
|
||||
<intercept-url pattern="/my/login/**" access="authenticated" request-matcher="path"/>
|
||||
</http>
|
||||
----
|
||||
======
|
||||
|
||||
=== Migrate `exitUserUrl` and `switchUserUrl` Request Matchers in `SwitchUserFilter`
|
||||
|
||||
@@ -414,22 +401,22 @@ Instead of taking this responsibility away from developers, now it is simpler to
|
||||
|
||||
[method,java]
|
||||
----
|
||||
PathPatternRequestMatcher.Builder servlet = PathPatternRequestMatcher.withDefaults().basePath("/mvc");
|
||||
PathPatternRequestParser.Builder servlet = PathPatternRequestParser.withDefaults().basePath("/mvc");
|
||||
http
|
||||
.authorizeHttpRequests((authorize) -> authorize
|
||||
.requestMatchers(servlet.matcher("/orders/**")).authenticated()
|
||||
.requestMatchers(servlet.pattern("/orders/**").matcher()).authenticated()
|
||||
)
|
||||
----
|
||||
|
||||
|
||||
For paths that belong to the default servlet, use `PathPatternRequestMatcher.withDefaults()` instead:
|
||||
For paths that belong to the default servlet, use `PathPatternRequestParser.withDefaults()` instead:
|
||||
|
||||
[method,java]
|
||||
----
|
||||
PathPatternRequestMatcher.Builder request = PathPatternRequestMatcher.withDefaults();
|
||||
PathPatternRequestParser.Builder request = PathPatternRequestParser.withDefaults();
|
||||
http
|
||||
.authorizeHttpRequests((authorize) -> authorize
|
||||
.requestMatchers(request.matcher("/js/**")).authenticated()
|
||||
.requestMatchers(request.pattern("/js/**").matcher()).authenticated()
|
||||
)
|
||||
----
|
||||
|
||||
|
||||
@@ -5,16 +5,98 @@ Similar to xref:servlet/authentication/x509.adoc#servlet-x509[Servlet X.509 auth
|
||||
|
||||
The following example shows a reactive x509 security configuration:
|
||||
|
||||
include-code::./DefaultX509Configuration[tag=springSecurity,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
|
||||
http
|
||||
.x509(withDefaults())
|
||||
.authorizeExchange(exchanges -> exchanges
|
||||
.anyExchange().permitAll()
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
----
|
||||
|
||||
In the preceding configuration, when neither `principalExtractor` nor `authenticationManager` is provided, defaults are used.
|
||||
The default principal extractor is `SubjectX500PrincipalExtractor`, which extracts the CN (common name) field from a certificate provided by a client.
|
||||
The default authentication manager is `ReactivePreAuthenticatedAuthenticationManager`, which performs user account validation, checking that a user account with a name extracted by `principalExtractor` exists and that it is not locked, disabled, or expired.
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
x509 { }
|
||||
authorizeExchange {
|
||||
authorize(anyExchange, authenticated)
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
In the preceding configuration, when neither `principalExtractor` nor `authenticationManager` is provided, defaults are used. The default principal extractor is `SubjectDnX509PrincipalExtractor`, which extracts the CN (common name) field from a certificate provided by a client. The default authentication manager is `ReactivePreAuthenticatedAuthenticationManager`, which performs user account validation, checking that a user account with a name extracted by `principalExtractor` exists and that it is not locked, disabled, or expired.
|
||||
|
||||
The following example demonstrates how these defaults can be overridden:
|
||||
|
||||
include-code::./CustomX509Configuration[tag=springSecurity,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
|
||||
SubjectDnX509PrincipalExtractor principalExtractor =
|
||||
new SubjectDnX509PrincipalExtractor();
|
||||
|
||||
In the previous example, a username is extracted from the `emailAddress` field of a client certificate instead of CN, and account lookup uses a custom `ReactiveAuthenticationManager` instance.
|
||||
principalExtractor.setSubjectDnRegex("OU=(.*?)(?:,|$)");
|
||||
|
||||
ReactiveAuthenticationManager authenticationManager = authentication -> {
|
||||
authentication.setAuthenticated("Trusted Org Unit".equals(authentication.getName()));
|
||||
return Mono.just(authentication);
|
||||
};
|
||||
|
||||
http
|
||||
.x509(x509 -> x509
|
||||
.principalExtractor(principalExtractor)
|
||||
.authenticationManager(authenticationManager)
|
||||
)
|
||||
.authorizeExchange(exchanges -> exchanges
|
||||
.anyExchange().authenticated()
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain? {
|
||||
val customPrincipalExtractor = SubjectDnX509PrincipalExtractor()
|
||||
customPrincipalExtractor.setSubjectDnRegex("OU=(.*?)(?:,|$)")
|
||||
val customAuthenticationManager = ReactiveAuthenticationManager { authentication: Authentication ->
|
||||
authentication.isAuthenticated = "Trusted Org Unit" == authentication.name
|
||||
Mono.just(authentication)
|
||||
}
|
||||
return http {
|
||||
x509 {
|
||||
principalExtractor = customPrincipalExtractor
|
||||
authenticationManager = customAuthenticationManager
|
||||
}
|
||||
authorizeExchange {
|
||||
authorize(anyExchange, authenticated)
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
In the previous example, a username is extracted from the OU field of a client certificate instead of CN, and account lookup using `ReactiveUserDetailsService` is not performed at all. Instead, if the provided certificate issued to an OU named "`Trusted Org Unit`", a request is authenticated.
|
||||
|
||||
For an example of configuring Netty and `WebClient` or `curl` command-line tool to use mutual TLS and enable X.509 authentication, see https://github.com/spring-projects/spring-security-samples/tree/main/servlet/java-configuration/authentication/x509.
|
||||
|
||||
@@ -39,18 +39,17 @@ This endpoint is referred to as a https://openid.net/specs/openid-connect-discov
|
||||
|
||||
When this property and these dependencies are used, Resource Server automatically configures itself to validate JWT-encoded Bearer Tokens.
|
||||
|
||||
It achieves this through a deterministic discovery process it launches at the first request containing a JWT:
|
||||
It achieves this through a deterministic startup process:
|
||||
|
||||
. Hit the Provider Configuration or Authorization Server Metadata endpoint, processing the response for the `jwks_url` property.
|
||||
. Configure the validation strategy to query `jwks_url` for valid public keys.
|
||||
. Configure the validation strategy to validate each JWT's `iss` claim against `https://idp.example.com`.
|
||||
|
||||
One benefit of deferring this process is that Resource Server startup is not coupled to the authorization server's availability.
|
||||
A consequence of this process is that the authorization server must be receiving requests in order for Resource Server to successfully start up.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
This deferral is managed by javadoc:org.springframework.security.oauth2.jwt.SupplierReactiveJwtDecoder[`SupplierReactiveJwtDecoder`].
|
||||
Consider wrapping any <<webflux-oauth2resourceserver-decoder-bean,`JwtDecoder` `@Bean`>> you declare in order to preserve this behavior.
|
||||
If the authorization server is down when Resource Server queries it (given appropriate timeouts), then startup fails.
|
||||
====
|
||||
|
||||
=== Runtime Expectations
|
||||
@@ -86,7 +85,7 @@ From here, consider jumping to:
|
||||
[[webflux-oauth2resourceserver-jwt-jwkseturi]]
|
||||
=== Specifying the Authorization Server JWK Set Uri Directly
|
||||
|
||||
If the authorization server does not support any configuration endpoints, or if Resource Server must be able to initialize independently from the authorization server, you can supply `jwk-set-uri` as well:
|
||||
If the authorization server does not support any configuration endpoints, or if Resource Server must be able to start up independently from the authorization server, you can supply `jwk-set-uri` as well:
|
||||
|
||||
[source,yaml]
|
||||
----
|
||||
@@ -376,9 +375,7 @@ Java::
|
||||
----
|
||||
@Bean
|
||||
public ReactiveJwtDecoder jwtDecoder() {
|
||||
NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withIssuerLocation(issuer).build();
|
||||
jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(issuer));
|
||||
return jwtDecoder;
|
||||
return NimbusReactiveJwtDecoder.withIssuerLocation(issuer).build();
|
||||
}
|
||||
----
|
||||
|
||||
@@ -388,9 +385,7 @@ Kotlin::
|
||||
----
|
||||
@Bean
|
||||
fun jwtDecoder(): ReactiveJwtDecoder {
|
||||
val jwtDecoder = NimbusReactiveJwtDecoder.withIssuerLocation(issuer).build()
|
||||
jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(issuer))
|
||||
return jwtDecoder
|
||||
return NimbusReactiveJwtDecoder.withIssuerLocation(issuer).build()
|
||||
}
|
||||
----
|
||||
======
|
||||
@@ -456,10 +451,8 @@ Java::
|
||||
----
|
||||
@Bean
|
||||
ReactiveJwtDecoder jwtDecoder() {
|
||||
NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withIssuerLocation(this.issuer)
|
||||
return NimbusReactiveJwtDecoder.withIssuerLocation(this.issuer)
|
||||
.jwsAlgorithm(RS512).build();
|
||||
jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(this.issuer));
|
||||
return jwtDecoder;
|
||||
}
|
||||
----
|
||||
|
||||
@@ -469,10 +462,8 @@ Kotlin::
|
||||
----
|
||||
@Bean
|
||||
fun jwtDecoder(): ReactiveJwtDecoder {
|
||||
val jwtDecoder = NimbusReactiveJwtDecoder.withIssuerLocation(this.issuer)
|
||||
return NimbusReactiveJwtDecoder.withIssuerLocation(this.issuer)
|
||||
.jwsAlgorithm(RS512).build()
|
||||
jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(this.issuer))
|
||||
return jwtDecoder
|
||||
}
|
||||
----
|
||||
======
|
||||
@@ -487,10 +478,8 @@ Java::
|
||||
----
|
||||
@Bean
|
||||
ReactiveJwtDecoder jwtDecoder() {
|
||||
NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withIssuerLocation(this.issuer)
|
||||
return NimbusReactiveJwtDecoder.withIssuerLocation(this.issuer)
|
||||
.jwsAlgorithm(RS512).jwsAlgorithm(ES512).build();
|
||||
jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(this.issuer));
|
||||
return jwtDecoder;
|
||||
}
|
||||
----
|
||||
|
||||
@@ -500,10 +489,8 @@ Kotlin::
|
||||
----
|
||||
@Bean
|
||||
fun jwtDecoder(): ReactiveJwtDecoder {
|
||||
val jwtDecoder = NimbusReactiveJwtDecoder.withIssuerLocation(this.issuer)
|
||||
return NimbusReactiveJwtDecoder.withIssuerLocation(this.issuer)
|
||||
.jwsAlgorithm(RS512).jwsAlgorithm(ES512).build()
|
||||
jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(this.issuer))
|
||||
return jwtDecoder
|
||||
}
|
||||
----
|
||||
======
|
||||
@@ -518,13 +505,11 @@ Java::
|
||||
----
|
||||
@Bean
|
||||
ReactiveJwtDecoder jwtDecoder() {
|
||||
NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withIssuerLocation(this.issuer)
|
||||
return NimbusReactiveJwtDecoder.withIssuerLocation(this.jwkSetUri)
|
||||
.jwsAlgorithms(algorithms -> {
|
||||
algorithms.add(RS512);
|
||||
algorithms.add(ES512);
|
||||
}).build();
|
||||
jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(this.issuer));
|
||||
return jwtDecoder;
|
||||
}
|
||||
----
|
||||
|
||||
@@ -534,14 +519,12 @@ Kotlin::
|
||||
----
|
||||
@Bean
|
||||
fun jwtDecoder(): ReactiveJwtDecoder {
|
||||
val jwtDecoder = NimbusReactiveJwtDecoder.withIssuerLocation(this.issuer)
|
||||
return NimbusReactiveJwtDecoder.withIssuerLocation(this.jwkSetUri)
|
||||
.jwsAlgorithms {
|
||||
it.add(RS512)
|
||||
it.add(ES512)
|
||||
}
|
||||
.build()
|
||||
jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(this.issuer))
|
||||
return jwtDecoder
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
@@ -319,7 +319,7 @@ If you have trouble working out where a session is being created, you can add so
|
||||
[[appendix-faq-forbidden-csrf]]
|
||||
=== I get a 403 Forbidden when performing a POST. What is wrong?
|
||||
|
||||
If an HTTP 403 Forbidden error is returned for HTTP POST, but it works for HTTP GET, the issue is most likely related to xref:features/exploits/csrf.adoc#csrf[CSRF]. Either provide the CSRF Token or disable CSRF protection (the latter is not recommended).
|
||||
If an HTTP 403 Forbidden error is returned for HTTP POST, but it works for HTTP GET, the issue is most likely related to https://docs.spring.io/spring-security/site/docs/3.2.x/reference/htmlsingle/#csrf[CSRF]. Either provide the CSRF Token or disable CSRF protection (the latter is not recommended).
|
||||
|
||||
[[appendix-faq-no-security-on-forward]]
|
||||
=== I am forwarding a request to another URL by using the RequestDispatcher, but my security constraints are not being applied.
|
||||
|
||||
@@ -2212,10 +2212,6 @@ A `PreAuthenticatedAuthenticationProvider` will also be created which delegates
|
||||
* **authentication-details-source-ref**
|
||||
A reference to an `AuthenticationDetailsSource`
|
||||
|
||||
[[nsa-x509-principal-extractor-ref]]
|
||||
* **principal-extractor-ref**
|
||||
Reference to an `X509PrincipalExtractor` which will be used by the authentication filter.
|
||||
|
||||
|
||||
[[nsa-x509-subject-principal-regex]]
|
||||
* **subject-principal-regex**
|
||||
@@ -2227,6 +2223,7 @@ Defines a regular expression which will be used to extract the username from the
|
||||
Allows a specific `UserDetailsService` to be used with X.509 in the case where multiple instances are configured.
|
||||
If not set, an attempt will be made to locate a suitable instance automatically and use that.
|
||||
|
||||
|
||||
[[nsa-filter-chain-map]]
|
||||
== <filter-chain-map>
|
||||
Used to explicitly configure a FilterChainProxy instance with a FilterChainMap
|
||||
|
||||
@@ -137,8 +137,7 @@ fun method(authentication: Authentication?): String {
|
||||
will always return "not anonymous", even for anonymous requests.
|
||||
The reason is that Spring MVC resolves the parameter using `HttpServletRequest#getPrincipal`, which is `null` when the request is anonymous.
|
||||
|
||||
If you'd like to obtain the `Authentication` in anonymous requests, use
|
||||
xref:servlet/integrations/mvc.adoc#mvc-current-security-context[`@CurrentSecurityContext`] instead:
|
||||
If you'd like to obtain the `Authentication` in anonymous requests, use `@CurrentSecurityContext` instead:
|
||||
|
||||
.Use CurrentSecurityContext for Anonymous requests
|
||||
[tabs]
|
||||
|
||||
@@ -97,11 +97,7 @@ val authorities = authentication.authorities
|
||||
----
|
||||
======
|
||||
|
||||
In Spring MVC, you can resolve the current principal with
|
||||
xref:servlet/integrations/mvc.adoc#mvc-authentication-principal[`@AuthenticationPrincipal`]
|
||||
and the full `SecurityContext` with
|
||||
xref:servlet/integrations/mvc.adoc#mvc-current-security-context[`@CurrentSecurityContext`].
|
||||
For Servlet API access, use `HttpServletRequest#getRemoteUser`.
|
||||
// FIXME: Add links to and relevant description of HttpServletRequest.getRemoteUser() and @CurrentSecurityContext @AuthenticationPrincipal
|
||||
|
||||
By default, `SecurityContextHolder` uses a `ThreadLocal` to store these details, which means that the `SecurityContext` is always available to methods in the same thread, even if the `SecurityContext` is not explicitly passed around as an argument to those methods.
|
||||
Using a `ThreadLocal` in this way is quite safe if you take care to clear the thread after the present principal's request is processed.
|
||||
|
||||
@@ -259,7 +259,7 @@ Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
HeaderWriterLogoutHandler clearSiteData = new HeaderWriterLogoutHandler(new ClearSiteDataHeaderWriter(Directive.ALL));
|
||||
HeaderWriterLogoutHandler clearSiteData = new HeaderWriterLogoutHandler(new ClearSiteDataHeaderWriter(Directives.ALL));
|
||||
http
|
||||
.logout((logout) -> logout.addLogoutHandler(clearSiteData))
|
||||
----
|
||||
@@ -268,7 +268,7 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val clearSiteData = HeaderWriterLogoutHandler(ClearSiteDataHeaderWriter(Directive.ALL))
|
||||
val clearSiteData = HeaderWriterLogoutHandler(ClearSiteDataHeaderWriter(Directives.ALL))
|
||||
http {
|
||||
logout {
|
||||
addLogoutHandler(clearSiteData)
|
||||
|
||||
@@ -3,10 +3,7 @@
|
||||
|
||||
Once you have got an application that is xref:servlet/authentication/index.adoc[authenticating requests], it is important to consider how that resulting authentication will be persisted and restored on future requests.
|
||||
|
||||
This is done automatically by default.
|
||||
If you have a custom filter or controller that is setting the security context, you will need to use a `SecurityContextRepository` to persist it across requests.
|
||||
|
||||
If you are upgrading from an older version, you may be interested in the `requireExplicitSave` setting that preserves Spring Security 5's default, though note that this is primarily for migration purposes.
|
||||
This is done automatically by default, so no additional code is necessary, though it is important to know what `requireExplicitSave` means in `HttpSecurity`.
|
||||
|
||||
If you like, <<how-it-works-requireexplicitsave,you can read more about what requireExplicitSave is doing>> or <<requireexplicitsave,why it's important>>. Otherwise, in most cases you are done with this section.
|
||||
|
||||
|
||||
@@ -14,27 +14,37 @@ You should get this working before trying it out with Spring Security.
|
||||
The Spring Security X.509 module extracts the certificate by using a filter.
|
||||
It maps the certificate to an application user and loads that user's set of granted authorities for use with the standard Spring Security infrastructure.
|
||||
|
||||
[[servlet-x509-config]]
|
||||
|
||||
== Adding X.509 Authentication to Your Web Application
|
||||
Enabling X.509 client authentication is very straightforward.
|
||||
To do so, add the `<x509/>` element to your http security namespace configuration:
|
||||
|
||||
Similar to xref:reactive/authentication/x509.adoc[Reactive X.509 authentication], the servlet x509 authentication filter allows extracting an authentication token from a certificate provided by a client.
|
||||
[source,xml]
|
||||
----
|
||||
<http>
|
||||
...
|
||||
<x509 subject-principal-regex="CN=(.*?)," user-service-ref="userService"/>;
|
||||
</http>
|
||||
----
|
||||
|
||||
The following example shows a reactive x509 security configuration:
|
||||
The element has two optional attributes:
|
||||
|
||||
include-code::./DefaultX509Configuration[tag=springSecurity,indent=0]
|
||||
|
||||
In the preceding configuration, when neither `principalExtractor` nor `authenticationManager` is provided, defaults are used.
|
||||
The default principal extractor is `SubjectX500PrincipalExtractor`, which extracts the CN (common name) field from a certificate provided by a client.
|
||||
The default authentication manager is `ReactivePreAuthenticatedAuthenticationManager`, which performs user account validation, checking that a user account with a name extracted by `principalExtractor` exists and that it is not locked, disabled, or expired.
|
||||
|
||||
The following example demonstrates how these defaults can be overridden:
|
||||
|
||||
include-code::./CustomX509Configuration[tag=springSecurity,indent=0]
|
||||
|
||||
In the previous example, a username is extracted from the `emailAddress` field of a client certificate instead of CN, and account lookup uses a custom `ReactiveAuthenticationManager` instance.
|
||||
|
||||
For an example of configuring Netty and `WebClient` or `curl` command-line tool to use mutual TLS and enable X.509 authentication, see https://github.com/spring-projects/spring-security-samples/tree/main/servlet/java-configuration/authentication/x509.
|
||||
* `subject-principal-regex`.
|
||||
The regular expression used to extract a username from the certificate's subject name.
|
||||
The default value is shown in the preceding listing.
|
||||
This is the username that is passed to the `UserDetailsService` to load the authorities for the user.
|
||||
* `user-service-ref`.
|
||||
This is the bean ID of the `UserDetailsService` to be used with X.509.
|
||||
It is not needed if there is only one defined in your application context.
|
||||
|
||||
The `subject-principal-regex` should contain a single group.
|
||||
For example, the default expression (`CN=(.*?)`) matches the common name field.
|
||||
So, if the subject name in the certificate is "CN=Jimi Hendrix, OU=...", this gives a user name of "Jimi Hendrix".
|
||||
The matches are case insensitive.
|
||||
So "emailAddress=(+.*?+)," matches "EMAILADDRESS=jimi@hendrix.org,CN=...", giving a user name "jimi@hendrix.org".
|
||||
If the client presents a certificate and a valid username is successfully extracted, there should be a valid `Authentication` object in the security context.
|
||||
If no certificate is found or no corresponding user could be found, the security context remains empty.
|
||||
This means that you can use X.509 authentication with other options, such as a form-based login.
|
||||
|
||||
[[x509-ssl-config]]
|
||||
== Setting up SSL in Tomcat
|
||||
|
||||
@@ -1377,17 +1377,14 @@ Java::
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Component
|
||||
public class MyPreAuthorizeAuthorizationManager implements AuthorizationManager<MethodInvocation> {
|
||||
public class MyAuthorizationManager implements AuthorizationManager<MethodInvocation>, AuthorizationManager<MethodInvocationResult> {
|
||||
@Override
|
||||
public AuthorizationResult authorize(Supplier<Authentication> authentication, MethodInvocation invocation) {
|
||||
public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation invocation) {
|
||||
// ... authorization logic
|
||||
}
|
||||
}
|
||||
|
||||
@Component
|
||||
public class MyPostAuthorizeAuthorizationManager implements AuthorizationManager<MethodInvocationResult> {
|
||||
@Override
|
||||
public AuthorizationResult authorize(Supplier<Authentication> authentication, MethodInvocationResult invocation) {
|
||||
public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocationResult invocation) {
|
||||
// ... authorization logic
|
||||
}
|
||||
}
|
||||
@@ -1398,15 +1395,12 @@ Kotlin::
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Component
|
||||
class MyPreAuthorizeAuthorizationManager : AuthorizationManager<MethodInvocation> {
|
||||
override fun authorize(authentication: Supplier<Authentication>, invocation: MethodInvocation): AuthorizationResult {
|
||||
class MyAuthorizationManager : AuthorizationManager<MethodInvocation>, AuthorizationManager<MethodInvocationResult> {
|
||||
override fun check(authentication: Supplier<Authentication>, invocation: MethodInvocation): AuthorizationDecision {
|
||||
// ... authorization logic
|
||||
}
|
||||
}
|
||||
|
||||
@Component
|
||||
class MyPostAuthorizeAuthorizationManager : AuthorizationManager<MethodInvocationResult> {
|
||||
override fun authorize(authentication: Supplier<Authentication>, invocation: MethodInvocationResult): AuthorizationResult {
|
||||
override fun check(authentication: Supplier<Authentication>, invocation: MethodInvocationResult): AuthorizationDecision {
|
||||
// ... authorization logic
|
||||
}
|
||||
}
|
||||
@@ -1428,13 +1422,13 @@ Java::
|
||||
class MethodSecurityConfig {
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
Advisor preAuthorize(MyPreAuthorizeAuthorizationManager manager) {
|
||||
Advisor preAuthorize(MyAuthorizationManager manager) {
|
||||
return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(manager);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
Advisor postAuthorize(MyPostAuthorizeAuthorizationManager manager) {
|
||||
Advisor postAuthorize(MyAuthorizationManager manager) {
|
||||
return AuthorizationManagerAfterMethodInterceptor.postAuthorize(manager);
|
||||
}
|
||||
}
|
||||
@@ -1447,15 +1441,15 @@ Kotlin::
|
||||
@Configuration
|
||||
@EnableMethodSecurity(prePostEnabled = false)
|
||||
class MethodSecurityConfig {
|
||||
@Bean
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
fun preAuthorize(manager: MyPreAuthorizeAuthorizationManager): Advisor {
|
||||
fun preAuthorize(manager: MyAuthorizationManager) : Advisor {
|
||||
return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(manager)
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
fun postAuthorize(manager: MyPostAuthorizeAuthorizationManager): Advisor {
|
||||
fun postAuthorize(manager: MyAuthorizationManager) : Advisor {
|
||||
return AuthorizationManagerAfterMethodInterceptor.postAuthorize(manager)
|
||||
}
|
||||
}
|
||||
@@ -1472,13 +1466,13 @@ Xml::
|
||||
<bean id="preAuthorize"
|
||||
class="org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor"
|
||||
factory-method="preAuthorize">
|
||||
<constructor-arg ref="myPreAuthorizeAuthorizationManager"/>
|
||||
<constructor-arg ref="myAuthorizationManager"/>
|
||||
</bean>
|
||||
|
||||
<bean id="postAuthorize"
|
||||
class="org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor"
|
||||
factory-method="postAuthorize">
|
||||
<constructor-arg ref="myPostAuthorizeAuthorizationManager"/>
|
||||
<constructor-arg ref="myAuthorizationManager"/>
|
||||
</bean>
|
||||
----
|
||||
======
|
||||
@@ -1488,8 +1482,6 @@ Xml::
|
||||
You can place your interceptor in between Spring Security method interceptors using the order constants specified in `AuthorizationInterceptorsOrder`.
|
||||
====
|
||||
|
||||
You can also implement `MethodAuthorizationDeniedHandler` in the same manager class to override the default exception-handling behavior.
|
||||
|
||||
[[customizing-expression-handling]]
|
||||
=== Customizing Expression Handling
|
||||
|
||||
|
||||
@@ -63,9 +63,7 @@ To use this tag, you must also have an instance of `WebInvocationPrivilegeEvalua
|
||||
If you are using the namespace, one is automatically registered.
|
||||
This is an instance of `DefaultWebInvocationPrivilegeEvaluator`, which creates a dummy web request for the supplied URL and invokes the security interceptor to see whether the request would succeed or fail.
|
||||
This lets you delegate to the access-control setup you defined by using `intercept-url` declarations within the `<http>` namespace configuration and saves having to duplicate the information (such as the required roles) within your JSPs.
|
||||
|
||||
If you have xref:servlet/authorization/authorize-http-requests.adoc#match-by-httpmethod[method-based authorization rules], you should combine this approach with the `method` attribute (supplying the HTTP method, such as `POST`) to activate the intended method-based rule.
|
||||
For example, if you have a rule `.requestMatchers(POST, "/admin").hasRole("ADMIN")`, then you should do `<sec:authorize method="POST" url="/admin">` to match.
|
||||
You can also combine this approach with a `method` attribute (supplying the HTTP method, such as `POST`) for a more specific match.
|
||||
|
||||
You can store the Boolean result of evaluating the tag (whether it grants or denies access) in a page context scope variable by setting the `var` attribute to the variable name, avoiding the need for duplicating and re-evaluating the condition at other points in the page.
|
||||
|
||||
|
||||
@@ -375,7 +375,7 @@ Java::
|
||||
----
|
||||
@Bean
|
||||
public AnnotationTemplateExpressionDefaults templateDefaults() {
|
||||
return new AnnotationTemplateExpressionDefaults();
|
||||
return new AnnotationTemplateExpressionDeafults();
|
||||
}
|
||||
----
|
||||
|
||||
@@ -385,7 +385,7 @@ Kotlin::
|
||||
----
|
||||
@Bean
|
||||
fun templateDefaults(): AnnotationTemplateExpressionDefaults {
|
||||
return AnnotationTemplateExpressionDefaults()
|
||||
return AnnotationTemplateExpressionDeafults()
|
||||
}
|
||||
----
|
||||
|
||||
@@ -453,72 +453,6 @@ open fun findMessagesForUser(@CurrentUser("user_id") userId: String?): ModelAndV
|
||||
----
|
||||
======
|
||||
|
||||
[[mvc-current-security-context]]
|
||||
== @CurrentSecurityContext
|
||||
|
||||
Spring Security provides `CurrentSecurityContextArgumentResolver`, which can automatically resolve the current `SecurityContext` for Spring MVC arguments.
|
||||
By using `@EnableWebSecurity`, you automatically have this added to your Spring MVC configuration.
|
||||
If you use XML-based configuration, you must add this yourself:
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<mvc:annotation-driven>
|
||||
<mvc:argument-resolvers>
|
||||
<bean class="org.springframework.security.web.method.annotation.CurrentSecurityContextArgumentResolver" />
|
||||
</mvc:argument-resolvers>
|
||||
</mvc:annotation-driven>
|
||||
----
|
||||
|
||||
Once `CurrentSecurityContextArgumentResolver` is configured, you can access the `SecurityContext` directly:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@GetMapping("/me")
|
||||
public String me(@CurrentSecurityContext SecurityContext context) {
|
||||
return context.getAuthentication().getName();
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@GetMapping("/me")
|
||||
fun me(@CurrentSecurityContext context: SecurityContext): String {
|
||||
return context.authentication.name
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
You can also use a SpEL expression that is rooted at the `SecurityContext`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@GetMapping("/me")
|
||||
public String me(@CurrentSecurityContext(expression = "authentication") Authentication authentication) {
|
||||
return authentication.getName();
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@GetMapping("/me")
|
||||
fun me(@CurrentSecurityContext(expression = "authentication") authentication: Authentication): String {
|
||||
return authentication.name
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[[mvc-async]]
|
||||
== Spring MVC Async Integration
|
||||
|
||||
|
||||
@@ -40,20 +40,17 @@ And that's it!
|
||||
|
||||
When this property and these dependencies are used, Resource Server will automatically configure itself to validate JWT-encoded Bearer Tokens.
|
||||
|
||||
It achieves this through a deterministic discovery process it launches at the first request containing a JWT:
|
||||
It achieves this through a deterministic startup process:
|
||||
|
||||
1. Query the Provider Configuration or Authorization Server Metadata endpoint for the `jwks_url` property
|
||||
2. Query the `jwks_url` endpoint for supported algorithms
|
||||
3. Configure the validation strategy to query `jwks_url` for valid public keys of the algorithms found
|
||||
4. Configure the validation strategy to validate each JWTs `iss` claim against `https://idp.example.com`.
|
||||
|
||||
One benefit of deferring this process is that Resource Server startup is not coupled to the authorization server's availability.
|
||||
A consequence of this process is that the authorization server must be up and receiving requests in order for Resource Server to successfully start up.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
This deferral is managed by javadoc:org.springframework.security.oauth2.jwt.SupplierJwtDecoder[`SupplierJwtDecoder`].
|
||||
Consider wrapping any <<oauth2resourceserver-jwt-decoder,`JwtDecoder` `@Bean`>> you declare in order to preserve this behavior.
|
||||
====
|
||||
If the authorization server is down when Resource Server queries it (given appropriate timeouts), then startup will fail.
|
||||
|
||||
=== Runtime Expectations
|
||||
|
||||
@@ -69,7 +66,7 @@ So long as this scheme is indicated, Resource Server will attempt to process the
|
||||
|
||||
Given a well-formed JWT, Resource Server will:
|
||||
|
||||
1. Validate its signature against a public key obtained from the `jwks_url` endpoint during startup or on first request, depending on configuration, and matched against the JWT
|
||||
1. Validate its signature against a public key obtained from the `jwks_url` endpoint during startup and matched against the JWT
|
||||
2. Validate the JWT's `exp` and `nbf` timestamps and the JWT's `iss` claim, and
|
||||
3. Map each scope to an authority with the prefix `SCOPE_`.
|
||||
|
||||
@@ -114,7 +111,7 @@ Ultimately, the returned `JwtAuthenticationToken` will be set on the xref:servle
|
||||
[[oauth2resourceserver-jwt-jwkseturi]]
|
||||
== Specifying the Authorization Server JWK Set Uri Directly
|
||||
|
||||
If the authorization server doesn't support any configuration endpoints, or if Resource Server must be able to initialize independently from the authorization server, then the `jwk-set-uri` can be supplied as well:
|
||||
If the authorization server doesn't support any configuration endpoints, or if Resource Server must be able to start up independently from the authorization server, then the `jwk-set-uri` can be supplied as well:
|
||||
|
||||
[source,yaml]
|
||||
----
|
||||
@@ -519,9 +516,7 @@ Java::
|
||||
----
|
||||
@Bean
|
||||
public JwtDecoder jwtDecoder() {
|
||||
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuer).build();
|
||||
jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(issuer));
|
||||
return jwtDecoder;
|
||||
return NimbusJwtDecoder.withIssuerLocation(issuer).build();
|
||||
}
|
||||
----
|
||||
|
||||
@@ -531,9 +526,7 @@ Kotlin::
|
||||
----
|
||||
@Bean
|
||||
fun jwtDecoder(): JwtDecoder {
|
||||
val jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuer).build()
|
||||
jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(issuer))
|
||||
return jwtDecoder
|
||||
return NimbusJwtDecoder.withIssuerLocation(issuer).build()
|
||||
}
|
||||
----
|
||||
======
|
||||
@@ -599,10 +592,8 @@ Java::
|
||||
----
|
||||
@Bean
|
||||
JwtDecoder jwtDecoder() {
|
||||
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(this.issuer)
|
||||
return NimbusJwtDecoder.withIssuerLocation(this.issuer)
|
||||
.jwsAlgorithm(RS512).build();
|
||||
jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(this.issuer));
|
||||
return jwtDecoder;
|
||||
}
|
||||
----
|
||||
|
||||
@@ -612,10 +603,8 @@ Kotlin::
|
||||
----
|
||||
@Bean
|
||||
fun jwtDecoder(): JwtDecoder {
|
||||
val jwtDecoder = NimbusJwtDecoder.withIssuerLocation(this.issuer)
|
||||
return NimbusJwtDecoder.withIssuerLocation(this.issuer)
|
||||
.jwsAlgorithm(RS512).build()
|
||||
jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(this.issuer))
|
||||
return jwtDecoder
|
||||
}
|
||||
----
|
||||
======
|
||||
@@ -630,10 +619,8 @@ Java::
|
||||
----
|
||||
@Bean
|
||||
JwtDecoder jwtDecoder() {
|
||||
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(this.issuer)
|
||||
return NimbusJwtDecoder.withIssuerLocation(this.issuer)
|
||||
.jwsAlgorithm(RS512).jwsAlgorithm(ES512).build();
|
||||
jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(this.issuer));
|
||||
return jwtDecoder;
|
||||
}
|
||||
----
|
||||
|
||||
@@ -643,10 +630,8 @@ Kotlin::
|
||||
----
|
||||
@Bean
|
||||
fun jwtDecoder(): JwtDecoder {
|
||||
val jwtDecoder = NimbusJwtDecoder.withIssuerLocation(this.issuer)
|
||||
return NimbusJwtDecoder.withIssuerLocation(this.issuer)
|
||||
.jwsAlgorithm(RS512).jwsAlgorithm(ES512).build()
|
||||
jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(this.issuer))
|
||||
return jwtDecoder
|
||||
}
|
||||
----
|
||||
======
|
||||
@@ -661,13 +646,11 @@ Java::
|
||||
----
|
||||
@Bean
|
||||
JwtDecoder jwtDecoder() {
|
||||
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(this.issuer)
|
||||
return NimbusJwtDecoder.withIssuerLocation(this.issuer)
|
||||
.jwsAlgorithms(algorithms -> {
|
||||
algorithms.add(RS512);
|
||||
algorithms.add(ES512);
|
||||
}).build();
|
||||
jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(this.issuer));
|
||||
return jwtDecoder;
|
||||
}
|
||||
----
|
||||
|
||||
@@ -677,13 +660,11 @@ Kotlin::
|
||||
----
|
||||
@Bean
|
||||
fun jwtDecoder(): JwtDecoder {
|
||||
val jwtDecoder = NimbusJwtDecoder.withIssuerLocation(this.issuer)
|
||||
return NimbusJwtDecoder.withIssuerLocation(this.issuer)
|
||||
.jwsAlgorithms {
|
||||
it.add(RS512)
|
||||
it.add(ES512)
|
||||
}.build()
|
||||
jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(this.issuer))
|
||||
return jwtDecoder
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
+5
-5
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"antora": "3.2.0-alpha.12",
|
||||
"@antora/atlas-extension": "1.0.0-alpha.5",
|
||||
"@antora/collector-extension": "1.0.3",
|
||||
"antora": "3.2.0-alpha.8",
|
||||
"@antora/atlas-extension": "1.0.0-alpha.2",
|
||||
"@antora/collector-extension": "1.0.1",
|
||||
"@asciidoctor/tabs": "1.0.0-beta.6",
|
||||
"@springio/antora-extensions": "1.14.12",
|
||||
"@springio/asciidoctor-extensions": "1.0.0-alpha.18"
|
||||
"@springio/antora-extensions": "1.14.4",
|
||||
"@springio/asciidoctor-extensions": "1.0.0-alpha.17"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,10 +10,6 @@ plugins {
|
||||
apply plugin: 'io.spring.convention.docs'
|
||||
apply plugin: 'java'
|
||||
|
||||
node {
|
||||
version = "24.15.0"
|
||||
}
|
||||
|
||||
antora {
|
||||
options = [clean: true, fetch: !project.gradle.startParameter.offline, stacktrace: true]
|
||||
environment = [
|
||||
|
||||
-74
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.reactive.authentication.reactivex509;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.ReactiveAuthenticationManager;
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
|
||||
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.web.authentication.preauth.x509.SubjectX500PrincipalExtractor;
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
import org.springframework.security.web.server.authentication.ReactivePreAuthenticatedAuthenticationManager;
|
||||
import org.springframework.web.reactive.config.EnableWebFlux;
|
||||
|
||||
/**
|
||||
* Demonstrates custom configuration for x509 reactive configuration.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
public class CustomX509Configuration {
|
||||
|
||||
// tag::springSecurity[]
|
||||
@Bean
|
||||
SecurityWebFilterChain springSecurity(ServerHttpSecurity http) {
|
||||
SubjectX500PrincipalExtractor principalExtractor = new SubjectX500PrincipalExtractor();
|
||||
principalExtractor.setExtractPrincipalNameFromEmail(true);
|
||||
|
||||
// @formatter:off
|
||||
UserDetails user = User
|
||||
.withUsername("luke@monkeymachine")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build();
|
||||
// @formatter:on
|
||||
|
||||
ReactiveUserDetailsService users = new MapReactiveUserDetailsService(user);
|
||||
ReactiveAuthenticationManager authenticationManager = new ReactivePreAuthenticatedAuthenticationManager(users);
|
||||
|
||||
// @formatter:off
|
||||
http
|
||||
.x509(x509 -> x509
|
||||
.principalExtractor(principalExtractor)
|
||||
.authenticationManager(authenticationManager)
|
||||
)
|
||||
.authorizeExchange(exchanges -> exchanges
|
||||
.anyExchange().authenticated()
|
||||
);
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
// end::springSecurity[]
|
||||
|
||||
}
|
||||
-67
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.reactive.authentication.reactivex509;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
|
||||
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
import org.springframework.web.reactive.config.EnableWebFlux;
|
||||
|
||||
/**
|
||||
* Demonstrates custom configuration for x509 reactive configuration.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
public class DefaultX509Configuration {
|
||||
|
||||
// tag::springSecurity[]
|
||||
@Bean
|
||||
SecurityWebFilterChain springSecurity(ServerHttpSecurity http) {
|
||||
// @formatter:off
|
||||
http
|
||||
.x509(Customizer.withDefaults())
|
||||
.authorizeExchange(exchanges -> exchanges
|
||||
.anyExchange().authenticated()
|
||||
);
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
// end::springSecurity[]
|
||||
|
||||
@Bean
|
||||
ReactiveUserDetailsService userDetailsService() {
|
||||
// @formatter:off
|
||||
UserDetails user = User
|
||||
.withUsername("rod")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build();
|
||||
// @formatter:on
|
||||
|
||||
return new MapReactiveUserDetailsService(user);
|
||||
}
|
||||
}
|
||||
-148
@@ -1,148 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.reactive.authentication.reactivex509;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.http.client.reactive.ClientHttpConnector;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.SslInfo;
|
||||
import org.springframework.security.config.test.SpringTestContext;
|
||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||
import org.springframework.security.test.web.reactive.server.WebTestClientBuilder;
|
||||
import org.springframework.security.web.authentication.preauth.x509.X509TestUtils;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import org.springframework.test.web.reactive.server.WebTestClientConfigurer;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebFilter;
|
||||
import org.springframework.web.server.WebFilterChain;
|
||||
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
|
||||
|
||||
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.springSecurity;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.x509;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
|
||||
/**
|
||||
* Tests {@link CustomX509Configuration}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@ExtendWith(SpringTestContextExtension.class)
|
||||
public class X509ConfigurationTests {
|
||||
|
||||
public final SpringTestContext spring = new SpringTestContext(this);
|
||||
|
||||
WebTestClient client;
|
||||
|
||||
@Autowired
|
||||
void setSpringSecurityFilterChain(WebFilter springSecurityFilterChain) {
|
||||
this.client = WebTestClient.bindToController(WebTestClientBuilder.Http200RestController.class)
|
||||
.webFilter(springSecurityFilterChain)
|
||||
.apply(springSecurity())
|
||||
.configureClient()
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
void x509WhenDefaultX509Configuration() throws Exception {
|
||||
this.spring.register(DefaultX509Configuration.class).autowire();
|
||||
X509Certificate certificate = loadCert("rod.cer");
|
||||
// @formatter:off
|
||||
this.client
|
||||
.mutateWith(x509(certificate))
|
||||
.get()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectStatus().isOk();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
void x509WhenCustomX509Configuration() throws Exception {
|
||||
this.spring.register(CustomX509Configuration.class).autowire();
|
||||
X509Certificate certificate = X509TestUtils.buildTestCertificate();
|
||||
// @formatter:off
|
||||
this.client
|
||||
.mutateWith(x509(certificate))
|
||||
.get()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectStatus().isOk();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
private static @NotNull WebTestClientConfigurer x509(X509Certificate certificate) {
|
||||
return (builder, httpHandlerBuilder, connector) -> {
|
||||
builder.apply(new WebTestClientConfigurer() {
|
||||
@Override
|
||||
public void afterConfigurerAdded(WebTestClient.Builder builder,
|
||||
@Nullable WebHttpHandlerBuilder httpHandlerBuilder,
|
||||
@Nullable ClientHttpConnector connector) {
|
||||
SslInfo sslInfo = new SslInfo() {
|
||||
@Override
|
||||
public @Nullable String getSessionId() {
|
||||
return "sessionId";
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate @Nullable [] getPeerCertificates() {
|
||||
return new X509Certificate[] { certificate };
|
||||
}
|
||||
};
|
||||
httpHandlerBuilder.filters((filters) -> filters.add(0, new SslInfoOverrideWebFilter(sslInfo)));
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
private static class SslInfoOverrideWebFilter implements WebFilter {
|
||||
private final SslInfo sslInfo;
|
||||
|
||||
private SslInfoOverrideWebFilter(SslInfo sslInfo) {
|
||||
this.sslInfo = sslInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
|
||||
ServerHttpRequest sslInfoRequest = exchange.getRequest().mutate().sslInfo(sslInfo)
|
||||
.build();
|
||||
ServerWebExchange sslInfoExchange = exchange.mutate().request(sslInfoRequest).build();
|
||||
return chain.filter(sslInfoExchange);
|
||||
}
|
||||
}
|
||||
|
||||
private <T extends Certificate> T loadCert(String location) {
|
||||
try (InputStream is = new ClassPathResource(location).getInputStream()) {
|
||||
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
|
||||
return (T) certFactory.generateCertificate(is);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalArgumentException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
-75
@@ -1,75 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.authentication.servletx509config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
|
||||
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||
import org.springframework.security.web.DefaultSecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.preauth.x509.SubjectX500PrincipalExtractor;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
|
||||
/**
|
||||
* Demonstrates custom configuration for x509 reactive configuration.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@EnableWebMvc
|
||||
@EnableWebSecurity
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class CustomX509Configuration {
|
||||
|
||||
// tag::springSecurity[]
|
||||
@Bean
|
||||
DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
|
||||
SubjectX500PrincipalExtractor principalExtractor = new SubjectX500PrincipalExtractor();
|
||||
principalExtractor.setExtractPrincipalNameFromEmail(true);
|
||||
|
||||
|
||||
// @formatter:off
|
||||
http
|
||||
.x509((x509) -> x509
|
||||
.x509PrincipalExtractor(principalExtractor)
|
||||
)
|
||||
.authorizeHttpRequests((exchanges) -> exchanges
|
||||
.anyRequest().authenticated()
|
||||
);
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
// end::springSecurity[]
|
||||
|
||||
@Bean
|
||||
UserDetailsService userDetailsService() {
|
||||
// @formatter:off
|
||||
UserDetails user = User
|
||||
.withUsername("luke@monkeymachine")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build();
|
||||
// @formatter:on
|
||||
|
||||
return new InMemoryUserDetailsManager(user);
|
||||
}
|
||||
}
|
||||
-67
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.authentication.servletx509config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||
import org.springframework.security.web.DefaultSecurityFilterChain;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
|
||||
/**
|
||||
* Demonstrates custom configuration for x509 reactive configuration.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@EnableWebMvc
|
||||
@EnableWebSecurity
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class DefaultX509Configuration {
|
||||
|
||||
// tag::springSecurity[]
|
||||
@Bean
|
||||
DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.x509(Customizer.withDefaults())
|
||||
.authorizeHttpRequests(exchanges -> exchanges
|
||||
.anyRequest().authenticated()
|
||||
);
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
// end::springSecurity[]
|
||||
|
||||
@Bean
|
||||
UserDetailsService userDetailsService() {
|
||||
// @formatter:off
|
||||
UserDetails user = User
|
||||
.withUsername("rod")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build();
|
||||
// @formatter:on
|
||||
|
||||
return new InMemoryUserDetailsManager(user);
|
||||
}
|
||||
}
|
||||
-103
@@ -1,103 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.authentication.servletx509config;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.http.client.reactive.ClientHttpConnector;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.SslInfo;
|
||||
import org.springframework.security.config.test.SpringTestContext;
|
||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||
import org.springframework.security.web.authentication.preauth.x509.X509TestUtils;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import org.springframework.test.web.reactive.server.WebTestClientConfigurer;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebFilter;
|
||||
import org.springframework.web.server.WebFilterChain;
|
||||
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
|
||||
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.x509;
|
||||
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
* Tests {@link CustomX509Configuration}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@ExtendWith(SpringTestContextExtension.class)
|
||||
public class X509ConfigurationTests {
|
||||
|
||||
public final SpringTestContext spring = new SpringTestContext(this);
|
||||
|
||||
@Autowired
|
||||
MockMvc mockMvc;
|
||||
|
||||
@Test
|
||||
void x509WhenDefaultX509Configuration() throws Exception {
|
||||
this.spring.register(DefaultX509Configuration.class, Http200Controller.class).autowire();
|
||||
// @formatter:off
|
||||
this.mockMvc.perform(get("/").with(x509("rod.cer")))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(authenticated().withUsername("rod"));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
void x509WhenDefaultX509ConfigurationXml() throws Exception {
|
||||
this.spring.testConfigLocations("DefaultX509Configuration.xml").autowire();
|
||||
// @formatter:off
|
||||
this.mockMvc.perform(get("/").with(x509("rod.cer")))
|
||||
.andExpect(authenticated().withUsername("rod"));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
void x509WhenCustomX509Configuration() throws Exception {
|
||||
this.spring.register(CustomX509Configuration.class, Http200Controller.class).autowire();
|
||||
X509Certificate certificate = X509TestUtils.buildTestCertificate();
|
||||
// @formatter:off
|
||||
this.mockMvc.perform(get("/").with(x509(certificate)))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(authenticated().withUsername("luke@monkeymachine"));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@RestController
|
||||
static class Http200Controller {
|
||||
@GetMapping("/**")
|
||||
String ok() {
|
||||
return "ok";
|
||||
}
|
||||
}
|
||||
}
|
||||
-74
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.reactive.authentication.reactivex509
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.authentication.ReactiveAuthenticationManager
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
|
||||
import org.springframework.security.config.web.server.invoke
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity.http
|
||||
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService
|
||||
import org.springframework.security.core.userdetails.ReactiveUserDetailsService
|
||||
import org.springframework.security.core.userdetails.User
|
||||
import org.springframework.security.web.authentication.preauth.x509.SubjectX500PrincipalExtractor
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain
|
||||
import org.springframework.security.web.server.authentication.ReactivePreAuthenticatedAuthenticationManager
|
||||
import org.springframework.web.reactive.config.EnableWebFlux
|
||||
|
||||
/**
|
||||
* Demonstrates custom configuration for x509 reactive configuration.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@EnableWebFlux
|
||||
@EnableWebFluxSecurity
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
class CustomX509Configuration {
|
||||
|
||||
// tag::springSecurity[]
|
||||
@Bean
|
||||
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
val extractor = SubjectX500PrincipalExtractor()
|
||||
extractor.setExtractPrincipalNameFromEmail(true)
|
||||
|
||||
// @formatter:off
|
||||
val user = User
|
||||
.withUsername("luke@monkeymachine")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build()
|
||||
// @formatter:on
|
||||
|
||||
val users: ReactiveUserDetailsService = MapReactiveUserDetailsService(user)
|
||||
val authentication: ReactiveAuthenticationManager = ReactivePreAuthenticatedAuthenticationManager(users)
|
||||
|
||||
return http {
|
||||
x509 {
|
||||
principalExtractor = extractor
|
||||
authenticationManager = authentication
|
||||
}
|
||||
authorizeExchange {
|
||||
authorize(anyExchange, authenticated)
|
||||
}
|
||||
}
|
||||
}
|
||||
// end::springSecurity[]
|
||||
|
||||
|
||||
}
|
||||
-64
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.reactive.authentication.reactivex509
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity
|
||||
import org.springframework.security.config.web.server.invoke
|
||||
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService
|
||||
import org.springframework.security.core.userdetails.User
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain
|
||||
import org.springframework.web.reactive.config.EnableWebFlux
|
||||
|
||||
/**
|
||||
* Demonstrates custom configuration for x509 reactive configuration.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@EnableWebFlux
|
||||
@EnableWebFluxSecurity
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
class DefaultX509Configuration {
|
||||
|
||||
// tag::springSecurity[]
|
||||
@Bean
|
||||
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
x509 { }
|
||||
authorizeExchange {
|
||||
authorize(anyExchange, authenticated)
|
||||
}
|
||||
}
|
||||
}
|
||||
// end::springSecurity[]
|
||||
|
||||
@Bean
|
||||
fun userDetailsService(): MapReactiveUserDetailsService {
|
||||
// @formatter:off
|
||||
val user = User
|
||||
.withUsername("rod")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build()
|
||||
// @formatter:on
|
||||
|
||||
return MapReactiveUserDetailsService(user)
|
||||
}
|
||||
|
||||
}
|
||||
-131
@@ -1,131 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.kt.docs.reactive.authentication.reactivex509
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.core.io.ClassPathResource
|
||||
import org.springframework.http.client.reactive.ClientHttpConnector
|
||||
import org.springframework.http.server.reactive.SslInfo
|
||||
import org.springframework.security.config.test.SpringTestContext
|
||||
import org.springframework.security.config.test.SpringTestContextExtension
|
||||
import org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers
|
||||
import org.springframework.security.test.web.reactive.server.WebTestClientBuilder.Http200RestController
|
||||
import org.springframework.security.web.authentication.preauth.x509.X509TestUtils
|
||||
import org.springframework.test.web.reactive.server.WebTestClient
|
||||
import org.springframework.test.web.reactive.server.WebTestClientConfigurer
|
||||
import org.springframework.web.server.ServerWebExchange
|
||||
import org.springframework.web.server.WebFilter
|
||||
import org.springframework.web.server.WebFilterChain
|
||||
import org.springframework.web.server.adapter.WebHttpHandlerBuilder
|
||||
import reactor.core.publisher.Mono
|
||||
import java.security.cert.Certificate
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.function.Consumer
|
||||
|
||||
/**
|
||||
* Tests [CustomX509Configuration].
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@ExtendWith(SpringTestContextExtension::class)
|
||||
class X509ConfigurationTests {
|
||||
@JvmField
|
||||
val spring: SpringTestContext = SpringTestContext(this)
|
||||
|
||||
var client: WebTestClient? = null
|
||||
|
||||
@Autowired
|
||||
fun setSpringSecurityFilterChain(springSecurityFilterChain: WebFilter) {
|
||||
this.client = WebTestClient
|
||||
.bindToController(Http200RestController::class.java)
|
||||
.webFilter<WebTestClient.ControllerSpec>(springSecurityFilterChain)
|
||||
.apply<WebTestClient.ControllerSpec>(SecurityMockServerConfigurers.springSecurity())
|
||||
.configureClient()
|
||||
.build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun x509WhenDefaultX509Configuration() {
|
||||
this.spring.register(DefaultX509Configuration::class.java).autowire()
|
||||
val certificate = loadCert<X509Certificate>("rod.cer")
|
||||
// @formatter:off
|
||||
this.client!!.mutateWith(x509(certificate))
|
||||
.get()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
fun x509WhenCustomX509Configuration() {
|
||||
this.spring.register(CustomX509Configuration::class.java).autowire()
|
||||
val certificate = X509TestUtils.buildTestCertificate()
|
||||
// @formatter:off
|
||||
this.client!!.mutateWith(x509(certificate))
|
||||
.get()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
private class SslInfoOverrideWebFilter(private val sslInfo: SslInfo) : WebFilter {
|
||||
override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
|
||||
val sslInfoRequest = exchange.getRequest().mutate().sslInfo(sslInfo)
|
||||
.build()
|
||||
val sslInfoExchange = exchange.mutate().request(sslInfoRequest).build()
|
||||
return chain.filter(sslInfoExchange)
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T : Certificate?> loadCert(location: String): T {
|
||||
try {
|
||||
ClassPathResource(location).getInputStream().use { `is` ->
|
||||
val certFactory = CertificateFactory.getInstance("X.509")
|
||||
return certFactory.generateCertificate(`is`) as T
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
throw IllegalArgumentException(ex)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private fun x509(certificate: X509Certificate): WebTestClientConfigurer {
|
||||
return WebTestClientConfigurer { builder: WebTestClient.Builder, httpHandlerBuilder: WebHttpHandlerBuilder?, connector: ClientHttpConnector? ->
|
||||
|
||||
val sslInfo: SslInfo = object : SslInfo {
|
||||
override fun getSessionId(): String {
|
||||
return "sessionId"
|
||||
}
|
||||
|
||||
override fun getPeerCertificates(): Array<X509Certificate?> {
|
||||
return arrayOf(certificate)
|
||||
}
|
||||
}
|
||||
httpHandlerBuilder?.filters(Consumer { filters: MutableList<WebFilter> ->
|
||||
filters.add(
|
||||
0,
|
||||
SslInfoOverrideWebFilter(sslInfo)
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-69
@@ -1,69 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.kt.docs.servlet.authentication.servlet509config
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
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.invoke
|
||||
import org.springframework.security.core.userdetails.User
|
||||
import org.springframework.security.core.userdetails.UserDetailsService
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager
|
||||
import org.springframework.security.web.DefaultSecurityFilterChain
|
||||
import org.springframework.security.web.authentication.preauth.x509.SubjectX500PrincipalExtractor
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc
|
||||
|
||||
/**
|
||||
* Demonstrates custom configuration for x509 reactive configuration.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@EnableWebMvc
|
||||
@EnableWebSecurity
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
class CustomX509Configuration {
|
||||
// tag::springSecurity[]
|
||||
@Bean
|
||||
fun springSecurity(http: HttpSecurity): DefaultSecurityFilterChain? {
|
||||
val principalExtractor = SubjectX500PrincipalExtractor()
|
||||
principalExtractor.setExtractPrincipalNameFromEmail(true)
|
||||
|
||||
// @formatter:off
|
||||
http {
|
||||
authorizeHttpRequests {
|
||||
authorize(anyRequest, authenticated)
|
||||
}
|
||||
x509 {
|
||||
x509PrincipalExtractor = principalExtractor
|
||||
}
|
||||
}
|
||||
return http.build()
|
||||
}
|
||||
// end::springSecurity[]
|
||||
|
||||
@Bean
|
||||
fun userDetailsService(): UserDetailsService {
|
||||
// @formatter:off
|
||||
val user = User
|
||||
.withUsername("luke@monkeymachine")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build()
|
||||
// @formatter:on
|
||||
return InMemoryUserDetailsManager(user)
|
||||
}
|
||||
}
|
||||
-64
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.kt.docs.servlet.authentication.servlet509config
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
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.invoke
|
||||
import org.springframework.security.core.userdetails.User
|
||||
import org.springframework.security.core.userdetails.UserDetailsService
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager
|
||||
import org.springframework.security.web.DefaultSecurityFilterChain
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc
|
||||
|
||||
/**
|
||||
* Demonstrates custom configuration for x509 reactive configuration.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@EnableWebMvc
|
||||
@EnableWebSecurity
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
class DefaultX509Configuration {
|
||||
// tag::springSecurity[]
|
||||
@Bean
|
||||
fun springSecurity(http: HttpSecurity): DefaultSecurityFilterChain? {
|
||||
// @formatter:off
|
||||
http {
|
||||
authorizeHttpRequests {
|
||||
authorize(anyRequest, authenticated)
|
||||
}
|
||||
x509 { }
|
||||
}
|
||||
// @formatter:on
|
||||
return http.build()
|
||||
}
|
||||
// end::springSecurity[]
|
||||
|
||||
@Bean
|
||||
fun userDetailsService(): UserDetailsService {
|
||||
// @formatter:off
|
||||
val user = User
|
||||
.withUsername("rod")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build()
|
||||
// @formatter:on
|
||||
return InMemoryUserDetailsManager(user)
|
||||
}
|
||||
}
|
||||
-75
@@ -1,75 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.kt.docs.servlet.authentication.servlet509config
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.security.config.test.SpringTestContext
|
||||
import org.springframework.security.config.test.SpringTestContextExtension
|
||||
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors
|
||||
import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated
|
||||
import org.springframework.security.web.authentication.preauth.x509.X509TestUtils
|
||||
import org.springframework.test.web.servlet.MockMvc
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
|
||||
/**
|
||||
* Tests [CustomX509Configuration].
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@ExtendWith(SpringTestContextExtension::class)
|
||||
class X509ConfigurationTests {
|
||||
@JvmField
|
||||
val spring: SpringTestContext = SpringTestContext(this)
|
||||
|
||||
@Autowired
|
||||
var mockMvc: MockMvc? = null
|
||||
|
||||
@Test
|
||||
@Throws(Exception::class)
|
||||
fun x509WhenDefaultX509Configuration() {
|
||||
this.spring.register(DefaultX509Configuration::class.java, Http200Controller::class.java).autowire()
|
||||
// @formatter:off
|
||||
this.mockMvc!!.perform(get("/").with(SecurityMockMvcRequestPostProcessors.x509("rod.cer")))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(authenticated().withUsername("rod"))
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
@Throws(Exception::class)
|
||||
fun x509WhenCustomX509Configuration() {
|
||||
this.spring.register(CustomX509Configuration::class.java, Http200Controller::class.java).autowire()
|
||||
val certificate = X509TestUtils.buildTestCertificate()
|
||||
// @formatter:off
|
||||
this.mockMvc!!.perform(get("/").with(SecurityMockMvcRequestPostProcessors.x509(certificate)))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(authenticated().withUsername("luke@monkeymachine"))
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@RestController
|
||||
internal class Http200Controller {
|
||||
@GetMapping("/**")
|
||||
fun ok(): String {
|
||||
return "ok"
|
||||
}
|
||||
}
|
||||
}
|
||||
-45
@@ -1,45 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright 2002-2018 the original author or authors.
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
|
||||
xmlns:p="http://www.springframework.org/schema/p"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://www.springframework.org/schema/security"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/security
|
||||
https://www.springframework.org/schema/security/spring-security.xsd
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<!-- tag::springSecurity[] -->
|
||||
<http>
|
||||
<intercept-url pattern="/**" access="authenticated"/>
|
||||
<x509 principal-extractor-ref="principalExtractor"/>
|
||||
</http>
|
||||
<b:bean id="principalExtractor"
|
||||
class="org.springframework.security.web.authentication.preauth.x509.SubjectX500PrincipalExtractor"
|
||||
p:extractPrincipalNameFromEmail="true"/>
|
||||
<!-- end::springSecurity[] -->
|
||||
|
||||
<user-service id="us">
|
||||
<user name="luke@monkeymachine" password="{noop}password" authorities="ROLE_USER"/>
|
||||
</user-service>
|
||||
|
||||
|
||||
|
||||
<b:import resource="MiscHttpConfigTests-controllers.xml"/>
|
||||
</b:beans>
|
||||
-39
@@ -1,39 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright 2002-2018 the original author or authors.
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
|
||||
xmlns:p="http://www.springframework.org/schema/p"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://www.springframework.org/schema/security"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/security
|
||||
https://www.springframework.org/schema/security/spring-security.xsd
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<!-- tag::springSecurity[] -->
|
||||
<http>
|
||||
<intercept-url pattern="/**" access="authenticated"/>
|
||||
<x509 />
|
||||
</http>
|
||||
<!-- end::springSecurity[] -->
|
||||
|
||||
<user-service>
|
||||
<user name="rod" password="{noop}password" authorities="ROLE_USER"/>
|
||||
</user-service>
|
||||
|
||||
</b:beans>
|
||||
+1
-1
@@ -14,7 +14,7 @@
|
||||
# limitations under the License.
|
||||
#
|
||||
springBootVersion=3.3.3
|
||||
version=6.5.12-SNAPSHOT
|
||||
version=6.5.8-SNAPSHOT
|
||||
samplesBranch=6.5.x
|
||||
org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError
|
||||
org.gradle.parallel=true
|
||||
|
||||
+18
-20
@@ -1,25 +1,24 @@
|
||||
[versions]
|
||||
com-squareup-okhttp3 = "3.14.9"
|
||||
io-micrometer = "1.15.12"
|
||||
io-rsocket = "1.1.5"
|
||||
io-spring-javaformat = "0.0.47"
|
||||
io-spring-nohttp = "0.0.11"
|
||||
jakarta-websocket = "2.2.0"
|
||||
org-apache-directory-server = "1.5.5"
|
||||
org-apache-maven-resolver = "1.9.27"
|
||||
org-aspectj = "1.9.25.1"
|
||||
org-bouncycastle = "1.80.2"
|
||||
org-apache-maven-resolver = "1.9.24"
|
||||
org-aspectj = "1.9.25"
|
||||
org-bouncycastle = "1.80"
|
||||
org-eclipse-jetty = "11.0.26"
|
||||
org-jetbrains-kotlin = "1.9.25"
|
||||
org-jetbrains-kotlinx = "1.10.2"
|
||||
org-mockito = "5.17.0"
|
||||
org-opensaml = "4.3.2"
|
||||
org-opensaml5 = "5.1.2"
|
||||
org-springframework = "6.2.19"
|
||||
org-springframework = "6.2.14"
|
||||
|
||||
[libraries]
|
||||
ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.5.34"
|
||||
com-fasterxml-jackson-jackson-bom = "com.fasterxml.jackson:jackson-bom:2.18.8"
|
||||
ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.5.21"
|
||||
com-fasterxml-jackson-jackson-bom = "com.fasterxml.jackson:jackson-bom:2.18.5"
|
||||
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:9.37.4"
|
||||
@@ -29,17 +28,16 @@ com-squareup-okhttp3-okhttp = { module = "com.squareup.okhttp3:okhttp", version.
|
||||
com-unboundid-unboundid-ldapsdk = "com.unboundid:unboundid-ldapsdk:6.0.11"
|
||||
com-unboundid-unboundid-ldapsdk7 = "com.unboundid:unboundid-ldapsdk:7.0.1"
|
||||
commons-collections = "commons-collections:commons-collections:3.2.2"
|
||||
io-micrometer-context-propagation = "io.micrometer:context-propagation:1.1.4"
|
||||
io-micrometer-micrometer-bom = { module = "io.micrometer:micrometer-bom", version.ref = "io-micrometer" }
|
||||
io-micrometer-micrometer-observation = { module = "io.micrometer:micrometer-observation", version.ref = "io-micrometer" }
|
||||
io-mockk = "io.mockk:mockk:1.14.7"
|
||||
io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2024.0.18"
|
||||
io-micrometer-context-propagation = "io.micrometer:context-propagation:1.1.3"
|
||||
io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.14.13"
|
||||
io-mockk = "io.mockk:mockk:1.14.6"
|
||||
io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2024.0.12"
|
||||
io-rsocket-rsocket-bom = { module = "io.rsocket:rsocket-bom", version.ref = "io-rsocket" }
|
||||
io-spring-javaformat-spring-javaformat-checkstyle = { module = "io.spring.javaformat:spring-javaformat-checkstyle", version.ref = "io-spring-javaformat" }
|
||||
io-spring-javaformat-spring-javaformat-gradle-plugin = { module = "io.spring.javaformat:spring-javaformat-gradle-plugin", version.ref = "io-spring-javaformat" }
|
||||
io-spring-nohttp-nohttp-checkstyle = { module = "io.spring.nohttp:nohttp-checkstyle", version.ref = "io-spring-nohttp" }
|
||||
io-spring-nohttp-nohttp-gradle = { module = "io.spring.nohttp:nohttp-gradle", version.ref = "io-spring-nohttp" }
|
||||
io-spring-security-release-plugin = "io.spring.gradle:spring-security-release-plugin:1.0.15"
|
||||
io-spring-security-release-plugin = "io.spring.gradle:spring-security-release-plugin:1.0.13"
|
||||
jakarta-annotation-jakarta-annotation-api = "jakarta.annotation:jakarta.annotation-api:2.1.1"
|
||||
jakarta-inject-jakarta-inject-api = "jakarta.inject:jakarta.inject-api:2.0.1"
|
||||
jakarta-persistence-jakarta-persistence-api = "jakarta.persistence:jakarta.persistence-api:3.1.0"
|
||||
@@ -48,7 +46,7 @@ jakarta-servlet-jsp-jakarta-servlet-jsp-api = "jakarta.servlet.jsp:jakarta.servl
|
||||
jakarta-servlet-jsp-jstl-jakarta-servlet-jsp-jstl-api = "jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api:3.0.2"
|
||||
jakarta-websocket-jakarta-websocket-api = { module = "jakarta.websocket:jakarta.websocket-api", version.ref = "jakarta-websocket" }
|
||||
jakarta-websocket-jakarta-websocket-client-api = { module = "jakarta.websocket:jakarta.websocket-client-api", version.ref = "jakarta-websocket" }
|
||||
jakarta-xml-bind-jakarta-xml-bind-api = "jakarta.xml.bind:jakarta.xml.bind-api:4.0.5"
|
||||
jakarta-xml-bind-jakarta-xml-bind-api = "jakarta.xml.bind:jakarta.xml.bind-api:4.0.4"
|
||||
ldapsdk = "ldapsdk:ldapsdk:4.1"
|
||||
net-sourceforge-htmlunit = "net.sourceforge.htmlunit:htmlunit:2.70.0"
|
||||
org-htmlunit-htmlunit = "org.htmlunit:htmlunit:4.11.1"
|
||||
@@ -59,7 +57,7 @@ org-apache-directory-server-apacheds-protocol-shared = { module = "org.apache.di
|
||||
org-apache-directory-server-apacheds-server-jndi = { module = "org.apache.directory.server:apacheds-server-jndi", version.ref = "org-apache-directory-server" }
|
||||
org-apache-directory-shared-shared-ldap = "org.apache.directory.shared:shared-ldap:0.9.15"
|
||||
org-apache-httpcomponents-httpclient = "org.apache.httpcomponents:httpclient:4.5.14"
|
||||
org-apache-maven-maven-resolver-provider = "org.apache.maven:maven-resolver-provider:3.9.16"
|
||||
org-apache-maven-maven-resolver-provider = "org.apache.maven:maven-resolver-provider:3.9.11"
|
||||
org-apache-maven-resolver-maven-resolver-connector-basic = { module = "org.apache.maven.resolver:maven-resolver-connector-basic", version.ref = "org-apache-maven-resolver" }
|
||||
org-apache-maven-resolver-maven-resolver-impl = { module = "org.apache.maven.resolver:maven-resolver-impl", version.ref = "org-apache-maven-resolver" }
|
||||
org-apache-maven-resolver-maven-resolver-transport-http = { module = "org.apache.maven.resolver:maven-resolver-transport-http", version.ref = "org-apache-maven-resolver" }
|
||||
@@ -67,13 +65,13 @@ org-apereo-cas-client-cas-client-core = "org.apereo.cas.client:cas-client-core:4
|
||||
io-freefair-gradle-aspectj-plugin = "io.freefair.gradle:aspectj-plugin:8.13.1"
|
||||
org-aspectj-aspectjrt = { module = "org.aspectj:aspectjrt", version.ref = "org-aspectj" }
|
||||
org-aspectj-aspectjweaver = { module = "org.aspectj:aspectjweaver", version.ref = "org-aspectj" }
|
||||
org-assertj-assertj-core = "org.assertj:assertj-core:3.27.7"
|
||||
org-assertj-assertj-core = "org.assertj:assertj-core:3.27.6"
|
||||
org-bouncycastle-bcpkix-jdk15on = { module = "org.bouncycastle:bcpkix-jdk18on", version.ref = "org-bouncycastle" }
|
||||
org-bouncycastle-bcprov-jdk15on = { module = "org.bouncycastle:bcprov-jdk18on", version.ref = "org-bouncycastle" }
|
||||
org-eclipse-jetty-jetty-server = { module = "org.eclipse.jetty:jetty-server", version.ref = "org-eclipse-jetty" }
|
||||
org-eclipse-jetty-jetty-servlet = { module = "org.eclipse.jetty:jetty-servlet", version.ref = "org-eclipse-jetty" }
|
||||
org-hamcrest = "org.hamcrest:hamcrest:2.2"
|
||||
org-hibernate-orm-hibernate-core = "org.hibernate.orm:hibernate-core:6.6.53.Final"
|
||||
org-hibernate-orm-hibernate-core = "org.hibernate.orm:hibernate-core:6.6.36.Final"
|
||||
org-hsqldb = "org.hsqldb:hsqldb:2.7.4"
|
||||
org-jetbrains-kotlin-kotlin-bom = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "org-jetbrains-kotlin" }
|
||||
org-jetbrains-kotlin-kotlin-gradle-plugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25"
|
||||
@@ -90,9 +88,9 @@ org-seleniumhq-selenium-selenium-java = "org.seleniumhq.selenium:selenium-java:4
|
||||
org-seleniumhq-selenium-selenium-support = "org.seleniumhq.selenium:selenium-support:3.141.59"
|
||||
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:2024.1.13"
|
||||
org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:3.2.16"
|
||||
org-slf4j-slf4j-api = "org.slf4j:slf4j-api:2.0.17"
|
||||
org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2024.1.12"
|
||||
org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:3.2.15"
|
||||
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"
|
||||
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionSha256Sum=6f74b601422d6d6fc4e1f9a1ab6522f642c2fdcbc15ae33ebd30ba3d7198e854
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.5-bin.zip
|
||||
distributionSha256Sum=61ad310d3c7d3e5da131b76bbf22b5a4c0786e9d892dae8c1658d4b484de3caa
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
Vendored
+1
-1
@@ -1,5 +1,5 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@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.
|
||||
|
||||
+1
-5
@@ -300,11 +300,7 @@ public final class OidcAuthorizedClientRefreshedEventListener
|
||||
return;
|
||||
}
|
||||
|
||||
// The auth_time claim MUST represent the time of the original authentication OR
|
||||
// the most recent time when the end-user reauthenticated when "prompt=login" is
|
||||
// passed in the authentication request
|
||||
if (!idToken.getAuthenticatedAt().equals(existingOidcUser.getIdToken().getAuthenticatedAt())
|
||||
&& !idToken.getAuthenticatedAt().isAfter(existingOidcUser.getIdToken().getAuthenticatedAt())) {
|
||||
if (!idToken.getAuthenticatedAt().equals(existingOidcUser.getIdToken().getAuthenticatedAt())) {
|
||||
OAuth2Error oauth2Error = new OAuth2Error(INVALID_ID_TOKEN_ERROR_CODE, "Invalid authenticated at time",
|
||||
REFRESH_TOKEN_RESPONSE_ERROR_URI);
|
||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||
|
||||
-1
@@ -269,7 +269,6 @@ public class OAuth2AuthorizationRequestRedirectFilter extends OncePerRequestFilt
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private static final class OAuth2AuthorizationRequestException extends AuthenticationException {
|
||||
|
||||
OAuth2AuthorizationRequestException(Throwable cause) {
|
||||
|
||||
+3
-21
@@ -419,8 +419,8 @@ public class OidcAuthorizedClientRefreshedEventListenerTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onApplicationEventWhenIdTokenAuthenticatedAtBeforeCurrentAuthenticatedAtThenThrowsOAuth2AuthenticationException() {
|
||||
Instant authTime = this.oidcUser.getAuthenticatedAt().minus(5, ChronoUnit.MINUTES);
|
||||
public void onApplicationEventWhenIdTokenAuthenticatedAtDoesNotMatchThenThrowsOAuth2AuthenticationException() {
|
||||
Instant authTime = this.oidcUser.getAuthenticatedAt().plus(5, ChronoUnit.SECONDS);
|
||||
Jwt jwt = createJwt().claim(IdTokenClaimNames.AUTH_TIME, authTime).build();
|
||||
SecurityContextImpl securityContext = new SecurityContextImpl(this.authentication);
|
||||
given(this.securityContextHolderStrategy.getContext()).willReturn(securityContext);
|
||||
@@ -440,26 +440,8 @@ public class OidcAuthorizedClientRefreshedEventListenerTests {
|
||||
verifyNoInteractions(this.userService, this.applicationEventPublisher);
|
||||
}
|
||||
|
||||
// gh-18839
|
||||
@Test
|
||||
public void onApplicationEventWhenIdTokenAuthenticatedAtAfterCurrentAuthenticatedAtThenOidcUserRefreshedEventPublished() {
|
||||
Instant authTime = this.oidcUser.getAuthenticatedAt().plus(5, ChronoUnit.MINUTES);
|
||||
Jwt jwt = createJwt().claim(IdTokenClaimNames.AUTH_TIME, authTime).build();
|
||||
SecurityContextImpl securityContext = new SecurityContextImpl(this.authentication);
|
||||
given(this.securityContextHolderStrategy.getContext()).willReturn(securityContext);
|
||||
given(this.jwtDecoder.decode(anyString())).willReturn(jwt);
|
||||
given(this.userService.loadUser(any(OidcUserRequest.class))).willReturn(this.oidcUser);
|
||||
|
||||
OAuth2AuthorizedClientRefreshedEvent authorizedClientRefreshedEvent = new OAuth2AuthorizedClientRefreshedEvent(
|
||||
this.accessTokenResponse, this.authorizedClient);
|
||||
this.eventListener.onApplicationEvent(authorizedClientRefreshedEvent);
|
||||
|
||||
verify(this.applicationEventPublisher).publishEvent(any(OidcUserRefreshedEvent.class));
|
||||
verifyNoMoreInteractions(this.applicationEventPublisher);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onApplicationEventWhenIdTokenAuthenticatedAtMatchesCurrentAuthenticatedAtThenOidcUserRefreshedEventPublished() {
|
||||
public void onApplicationEventWhenIdTokenAuthenticatedAtMatchesThenOidcUserRefreshedEventPublished() {
|
||||
Instant authTime = this.authentication.getPrincipal().getAttribute(IdTokenClaimNames.AUTH_TIME);
|
||||
Jwt jwt = createJwt().claim(IdTokenClaimNames.AUTH_TIME, authTime).build();
|
||||
SecurityContextImpl securityContext = new SecurityContextImpl(this.authentication);
|
||||
|
||||
-35
@@ -19,7 +19,6 @@ package org.springframework.security.oauth2.core.oidc.user;
|
||||
import java.io.Serial;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
|
||||
@@ -115,38 +114,4 @@ public class DefaultOidcUser extends DefaultOAuth2User implements OidcUser {
|
||||
return this.userInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || this.getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
DefaultOidcUser that = (DefaultOidcUser) obj;
|
||||
if (!this.getName().equals(that.getName())) {
|
||||
return false;
|
||||
}
|
||||
if (!this.getAuthorities().equals(that.getAuthorities())) {
|
||||
return false;
|
||||
}
|
||||
if (this.getIdToken().getIssuer() == null || that.getIdToken().getIssuer() == null) {
|
||||
return false;
|
||||
}
|
||||
return Objects.equals(this.getIdToken().getIssuer().toExternalForm(),
|
||||
that.getIdToken().getIssuer().toExternalForm())
|
||||
&& Objects.equals(this.getIdToken().getSubject(), that.getIdToken().getSubject());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = this.getName().hashCode();
|
||||
result = 31 * result + this.getAuthorities().hashCode();
|
||||
result = 31 * result + ((this.getIdToken().getIssuer() != null)
|
||||
? this.getIdToken().getIssuer().toExternalForm().hashCode() : 0);
|
||||
result = 31 * result
|
||||
+ ((this.getIdToken().getSubject() != null) ? this.getIdToken().getSubject().hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-28
@@ -17,7 +17,6 @@
|
||||
package org.springframework.security.oauth2.core.oidc.user;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -148,31 +147,4 @@ public class DefaultOidcUserTests {
|
||||
StandardClaimNames.NAME, StandardClaimNames.EMAIL);
|
||||
}
|
||||
|
||||
// gh-18622
|
||||
@Test
|
||||
public void equalsWhenOidcUserPrincipalSameThenTrue() {
|
||||
String issuer = "https://example.com";
|
||||
String subject = "subject-1";
|
||||
|
||||
// @formatter:off
|
||||
OidcIdToken idToken1 = OidcIdToken.withTokenValue("id-token-value-1")
|
||||
.issuer(issuer)
|
||||
.subject(subject)
|
||||
.issuedAt(Instant.now())
|
||||
.expiresAt(Instant.now().plus(30, ChronoUnit.MINUTES))
|
||||
.build();
|
||||
|
||||
OidcIdToken idToken2 = OidcIdToken.withTokenValue("id-token-value-2")
|
||||
.issuer(issuer)
|
||||
.subject(subject)
|
||||
.issuedAt(Instant.now())
|
||||
.expiresAt(Instant.now().plus(30, ChronoUnit.MINUTES))
|
||||
.build();
|
||||
// @formatter:on
|
||||
|
||||
DefaultOidcUser user1 = new DefaultOidcUser(AUTHORITIES, idToken1, USER_INFO);
|
||||
DefaultOidcUser user2 = new DefaultOidcUser(AUTHORITIES, idToken2, USER_INFO);
|
||||
assertThat(user1).isEqualTo(user2);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-1
@@ -185,7 +185,6 @@ public final class DPoPProofJwtDecoderFactory implements JwtDecoderFactory<DPoPP
|
||||
return Base64.getUrlEncoder().withoutPadding().encodeToString(digest);
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private static final class JtiCache extends LinkedHashMap<String, Long> {
|
||||
|
||||
private static final int MAX_SIZE = 1000;
|
||||
|
||||
+2
-13
@@ -230,8 +230,7 @@ public final class NimbusJwtDecoder implements JwtDecoder {
|
||||
.getConfigurationForIssuerLocation(issuer, rest);
|
||||
JwtDecoderProviderConfigurationUtils.validateIssuer(configuration, issuer);
|
||||
return configuration.get("jwks_uri").toString();
|
||||
}, JwtDecoderProviderConfigurationUtils::getJWSAlgorithms)
|
||||
.validator(JwtValidators.createDefaultWithIssuer(issuer));
|
||||
}, JwtDecoderProviderConfigurationUtils::getJWSAlgorithms);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -290,8 +289,6 @@ public final class NimbusJwtDecoder implements JwtDecoder {
|
||||
|
||||
private Consumer<ConfigurableJWTProcessor<SecurityContext>> jwtProcessorCustomizer;
|
||||
|
||||
private OAuth2TokenValidator<Jwt> validator = JwtValidators.createDefault();
|
||||
|
||||
private JwkSetUriJwtDecoderBuilder(String jwkSetUri) {
|
||||
Assert.hasText(jwkSetUri, "jwkSetUri cannot be empty");
|
||||
this.jwkSetUri = (rest) -> jwkSetUri;
|
||||
@@ -426,12 +423,6 @@ public final class NimbusJwtDecoder implements JwtDecoder {
|
||||
return this;
|
||||
}
|
||||
|
||||
JwkSetUriJwtDecoderBuilder validator(OAuth2TokenValidator<Jwt> validator) {
|
||||
Assert.notNull(validator, "validator cannot be null");
|
||||
this.validator = validator;
|
||||
return this;
|
||||
}
|
||||
|
||||
JWSKeySelector<SecurityContext> jwsKeySelector(JWKSource<SecurityContext> jwkSource) {
|
||||
if (this.signatureAlgorithms.isEmpty()) {
|
||||
return new JWSVerificationKeySelector<>(this.defaultAlgorithms.apply(jwkSource), jwkSource);
|
||||
@@ -470,9 +461,7 @@ public final class NimbusJwtDecoder implements JwtDecoder {
|
||||
* @return the configured {@link NimbusJwtDecoder}
|
||||
*/
|
||||
public NimbusJwtDecoder build() {
|
||||
NimbusJwtDecoder decoder = new NimbusJwtDecoder(processor());
|
||||
decoder.setJwtValidator(this.validator);
|
||||
return decoder;
|
||||
return new NimbusJwtDecoder(processor());
|
||||
}
|
||||
|
||||
private static final class SpringJWKSource<C extends SecurityContext> implements JWKSetSource<C> {
|
||||
|
||||
+2
-12
@@ -241,8 +241,7 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
|
||||
}
|
||||
return Mono.just(configuration.get("jwks_uri").toString());
|
||||
}),
|
||||
ReactiveJwtDecoderProviderConfigurationUtils::getJWSAlgorithms)
|
||||
.validator(JwtValidators.createDefaultWithIssuer(issuer));
|
||||
ReactiveJwtDecoderProviderConfigurationUtils::getJWSAlgorithms);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -333,8 +332,6 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
|
||||
|
||||
private BiFunction<ReactiveRemoteJWKSource, ConfigurableJWTProcessor<JWKSecurityContext>, Mono<ConfigurableJWTProcessor<JWKSecurityContext>>> jwtProcessorCustomizer;
|
||||
|
||||
private OAuth2TokenValidator<Jwt> validator = JwtValidators.createDefault();
|
||||
|
||||
private JwkSetUriReactiveJwtDecoderBuilder(String jwkSetUri) {
|
||||
Assert.hasText(jwkSetUri, "jwkSetUri cannot be empty");
|
||||
this.jwkSetUri = (web) -> Mono.just(jwkSetUri);
|
||||
@@ -459,11 +456,6 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
|
||||
return this;
|
||||
}
|
||||
|
||||
JwkSetUriReactiveJwtDecoderBuilder validator(OAuth2TokenValidator<Jwt> validator) {
|
||||
this.validator = validator;
|
||||
return this;
|
||||
}
|
||||
|
||||
JwkSetUriReactiveJwtDecoderBuilder jwtProcessorCustomizer(
|
||||
BiFunction<ReactiveRemoteJWKSource, ConfigurableJWTProcessor<JWKSecurityContext>, Mono<ConfigurableJWTProcessor<JWKSecurityContext>>> jwtProcessorCustomizer) {
|
||||
Assert.notNull(jwtProcessorCustomizer, "jwtProcessorCustomizer cannot be null");
|
||||
@@ -476,9 +468,7 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
|
||||
* @return the configured {@link NimbusReactiveJwtDecoder}
|
||||
*/
|
||||
public NimbusReactiveJwtDecoder build() {
|
||||
NimbusReactiveJwtDecoder decoder = new NimbusReactiveJwtDecoder(processor());
|
||||
decoder.setJwtValidator(this.validator);
|
||||
return decoder;
|
||||
return new NimbusReactiveJwtDecoder(processor());
|
||||
}
|
||||
|
||||
Mono<JWSKeySelector<JWKSecurityContext>> jwsKeySelector(ReactiveRemoteJWKSource source) {
|
||||
|
||||
+1
-16
@@ -328,26 +328,11 @@ public class NimbusJwtDecoderTests {
|
||||
.willReturn(new ResponseEntity<>(Map.of("issuer", issuer, "jwks_uri", issuer + "/jwks"), HttpStatus.OK));
|
||||
given(restOperations.exchange(any(RequestEntity.class), eq(String.class)))
|
||||
.willReturn(new ResponseEntity<>(JWK_SET, HttpStatus.OK));
|
||||
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuer)
|
||||
.restOperations(restOperations)
|
||||
.build();
|
||||
jwtDecoder.setJwtValidator(JwtValidators.createDefault());
|
||||
JwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuer).restOperations(restOperations).build();
|
||||
Jwt jwt = jwtDecoder.decode(SIGNED_JWT);
|
||||
assertThat(jwt.hasClaim(JwtClaimNames.EXP)).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decodeWhenIssuerLocationThenRejectsMismatchingIssuers() {
|
||||
String issuer = "https://example.org/wrong-issuer";
|
||||
RestOperations restOperations = mock(RestOperations.class);
|
||||
given(restOperations.exchange(any(RequestEntity.class), any(ParameterizedTypeReference.class)))
|
||||
.willReturn(new ResponseEntity<>(Map.of("issuer", issuer, "jwks_uri", issuer + "/jwks"), HttpStatus.OK));
|
||||
given(restOperations.exchange(any(RequestEntity.class), eq(String.class)))
|
||||
.willReturn(new ResponseEntity<>(JWK_SET, HttpStatus.OK));
|
||||
JwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuer).restOperations(restOperations).build();
|
||||
assertThatExceptionOfType(JwtValidationException.class).isThrownBy(() -> jwtDecoder.decode(SIGNED_JWT));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withJwkSetUriWhenNullOrEmptyThenThrowsException() {
|
||||
// @formatter:off
|
||||
|
||||
+2
-22
@@ -617,31 +617,11 @@ public class NimbusReactiveJwtDecoderTests {
|
||||
given(responseSpec.bodyToMono(any(ParameterizedTypeReference.class)))
|
||||
.willReturn(Mono.just(Map.of("issuer", issuer, "jwks_uri", issuer + "/jwks")));
|
||||
given(spec.retrieve()).willReturn(responseSpec);
|
||||
NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withIssuerLocation(issuer)
|
||||
.webClient(webClient)
|
||||
.build();
|
||||
jwtDecoder.setJwtValidator(JwtValidators.createDefault());
|
||||
Jwt jwt = jwtDecoder.decode(this.messageReadToken).block();
|
||||
assertThat(jwt.hasClaim(JwtClaimNames.EXP)).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decodeWhenIssuerLocationThenRejectsMismatchingIssuers() {
|
||||
String issuer = "https://example.org/wrong-issuer";
|
||||
WebClient real = WebClient.builder().build();
|
||||
WebClient.RequestHeadersUriSpec spec = spy(real.get());
|
||||
WebClient webClient = spy(WebClient.class);
|
||||
given(webClient.get()).willReturn(spec);
|
||||
WebClient.ResponseSpec responseSpec = mock(WebClient.ResponseSpec.class);
|
||||
given(responseSpec.bodyToMono(String.class)).willReturn(Mono.just(this.jwkSet));
|
||||
given(responseSpec.bodyToMono(any(ParameterizedTypeReference.class)))
|
||||
.willReturn(Mono.just(Map.of("issuer", issuer, "jwks_uri", issuer + "/jwks")));
|
||||
given(spec.retrieve()).willReturn(responseSpec);
|
||||
ReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withIssuerLocation(issuer)
|
||||
.webClient(webClient)
|
||||
.build();
|
||||
assertThatExceptionOfType(JwtValidationException.class)
|
||||
.isThrownBy(() -> jwtDecoder.decode(this.messageReadToken).block());
|
||||
Jwt jwt = jwtDecoder.decode(this.messageReadToken).block();
|
||||
assertThat(jwt.hasClaim(JwtClaimNames.EXP)).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
-17
@@ -30,23 +30,6 @@ import org.springframework.util.Assert;
|
||||
* A {@link Jwt} to {@link GrantedAuthority} {@link Converter} that is a composite of
|
||||
* converters.
|
||||
*
|
||||
* <p>
|
||||
* This is handy when needing to read authorities from multiple locations in a JWT; each
|
||||
* underlying converter is called in series and the results are aggregated into a single
|
||||
* collection of authorities.
|
||||
*
|
||||
* <p>
|
||||
* For example, you might have a claim called "scope" and another called "roles". With
|
||||
* {@link DelegatingJwtGrantedAuthoritiesConverter}, you can do:
|
||||
*
|
||||
* <code>
|
||||
* JwtGrantedAuthoritiesConverter scopes = new JwtGrantedAuthoritiesConverter();
|
||||
* JwtGrantedAuthoritiesConverter roles = new JwtGrantedAUthoritiesConverter();
|
||||
* roles.setAuthoritiesClaimName("roles");
|
||||
* roles.setAuthorityPrefix("ROLE_");
|
||||
* return new DelegatingJwtGrantedAuthoritiesConverter(scopes, roles);
|
||||
* </code>
|
||||
*
|
||||
* @author Laszlo Stahorszki
|
||||
* @author Josh Cummings
|
||||
* @since 5.5
|
||||
|
||||
+3
@@ -36,6 +36,9 @@ import org.springframework.util.Assert;
|
||||
* Uses an expression for extracting the token claim value to use for mapping
|
||||
* {@link GrantedAuthority authorities}.
|
||||
*
|
||||
* Note this can be used in combination with a
|
||||
* {@link DelegatingJwtGrantedAuthoritiesConverter}.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
* @since 6.4
|
||||
*/
|
||||
|
||||
-2
@@ -361,8 +361,6 @@ public class SpringOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
|
||||
* @since 6.5
|
||||
*/
|
||||
public SpringOpaqueTokenIntrospector build() {
|
||||
Assert.notNull(this.clientId, "clientId cannot be null");
|
||||
Assert.notNull(this.clientSecret, "clientSecret cannot be null");
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
restTemplate.getInterceptors().add(new BasicAuthenticationInterceptor(this.clientId, this.clientSecret));
|
||||
return new SpringOpaqueTokenIntrospector(this.introspectionUri, restTemplate);
|
||||
|
||||
-2
@@ -314,8 +314,6 @@ public class SpringReactiveOpaqueTokenIntrospector implements ReactiveOpaqueToke
|
||||
* @since 6.5
|
||||
*/
|
||||
public SpringReactiveOpaqueTokenIntrospector build() {
|
||||
Assert.notNull(this.clientId, "clientId cannot be null");
|
||||
Assert.notNull(this.clientSecret, "clientSecret cannot be null");
|
||||
WebClient webClient = WebClient.builder()
|
||||
.defaultHeaders((h) -> h.setBasicAuth(this.clientId, this.clientSecret))
|
||||
.build();
|
||||
|
||||
-7
@@ -383,13 +383,6 @@ public class SpringOpaqueTokenIntrospectorTests {
|
||||
}
|
||||
}
|
||||
|
||||
// gh-19201
|
||||
@Test
|
||||
public void builderWhenMissingClientCredentialsThenThrowsException() {
|
||||
assertThatExceptionOfType(IllegalArgumentException.class)
|
||||
.isThrownBy(() -> SpringOpaqueTokenIntrospector.withIntrospectionUri(INTROSPECTION_URL).build());
|
||||
}
|
||||
|
||||
private static ResponseEntity<Map<String, Object>> response(String content) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
|
||||
-7
@@ -308,13 +308,6 @@ public class SpringReactiveOpaqueTokenIntrospectorTests {
|
||||
}
|
||||
}
|
||||
|
||||
// gh-19201
|
||||
@Test
|
||||
public void builderWhenMissingClientCredentialsThenThrowsException() {
|
||||
assertThatExceptionOfType(IllegalArgumentException.class)
|
||||
.isThrownBy(() -> SpringReactiveOpaqueTokenIntrospector.withIntrospectionUri(INTROSPECTION_URL).build());
|
||||
}
|
||||
|
||||
private WebClient mockResponse(String response) {
|
||||
return mockResponse(toMap(response));
|
||||
}
|
||||
|
||||
+1
-25
@@ -18,7 +18,6 @@ package org.springframework.security.saml2.internal;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
@@ -65,7 +64,7 @@ final class Saml2Utils {
|
||||
static String samlInflate(byte[] b) {
|
||||
try {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
InflaterOutputStream iout = new InflaterOutputStream(new CappedOutputStream(out), new Inflater(true));
|
||||
InflaterOutputStream iout = new InflaterOutputStream(out, new Inflater(true));
|
||||
iout.write(b);
|
||||
iout.finish();
|
||||
return new String(out.toByteArray(), StandardCharsets.UTF_8);
|
||||
@@ -194,27 +193,4 @@ final class Saml2Utils {
|
||||
|
||||
}
|
||||
|
||||
static class CappedOutputStream extends OutputStream {
|
||||
|
||||
private static final long MAX_SIZE = 1024 * 1024;
|
||||
|
||||
private final OutputStream delegate;
|
||||
|
||||
private int size;
|
||||
|
||||
CappedOutputStream(OutputStream delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
if (this.size >= MAX_SIZE) {
|
||||
throw new IOException("SAML payload exceeded maximum size of " + MAX_SIZE);
|
||||
}
|
||||
this.delegate.write(b);
|
||||
this.size++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+24
-37
@@ -326,75 +326,62 @@ class BaseOpenSamlAuthenticationProvider implements AuthenticationProvider {
|
||||
boolean responseSigned = response.isSigned();
|
||||
|
||||
ResponseToken responseToken = new ResponseToken(response, token);
|
||||
Collection<Saml2Error> responseSignatureErrors = this.responseSignatureValidator.convert(responseToken)
|
||||
.getErrors();
|
||||
if (!responseSignatureErrors.isEmpty()) {
|
||||
reportErrors(response, responseSignatureErrors);
|
||||
return;
|
||||
}
|
||||
|
||||
Collection<Saml2Error> errors = new ArrayList<>();
|
||||
Saml2ResponseValidatorResult result = this.responseSignatureValidator.convert(responseToken);
|
||||
if (responseSigned) {
|
||||
this.responseElementsDecrypter.accept(responseToken);
|
||||
}
|
||||
else if (!response.getEncryptedAssertions().isEmpty()) {
|
||||
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE,
|
||||
result = result.concat(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE,
|
||||
"Did not decrypt response [" + response.getID() + "] since it is not signed"));
|
||||
}
|
||||
if (!this.validateResponseAfterAssertions) {
|
||||
errors.addAll(this.responseValidator.convert(responseToken).getErrors());
|
||||
result = result.concat(this.responseValidator.convert(responseToken));
|
||||
}
|
||||
boolean allAssertionsSigned = true;
|
||||
for (Assertion assertion : response.getAssertions()) {
|
||||
AssertionToken assertionToken = new AssertionToken(assertion, token);
|
||||
Collection<Saml2Error> assertionSignatureErrors = this.assertionSignatureValidator.convert(assertionToken)
|
||||
.getErrors();
|
||||
errors.addAll(assertionSignatureErrors);
|
||||
result = result.concat(this.assertionSignatureValidator.convert(assertionToken));
|
||||
allAssertionsSigned = allAssertionsSigned && assertion.isSigned();
|
||||
if (!assertionSignatureErrors.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
if (responseSigned || assertion.isSigned()) {
|
||||
this.assertionElementsDecrypter.accept(new AssertionToken(assertion, token));
|
||||
}
|
||||
errors.addAll(this.assertionValidator.convert(assertionToken).getErrors());
|
||||
result = result.concat(this.assertionValidator.convert(assertionToken));
|
||||
}
|
||||
if (!responseSigned && !allAssertionsSigned) {
|
||||
String description = "Either the response or one of the assertions is unsigned. "
|
||||
+ "Please either sign the response or all of the assertions.";
|
||||
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, description));
|
||||
result = result.concat(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, description));
|
||||
}
|
||||
if (this.validateResponseAfterAssertions) {
|
||||
errors.addAll(this.responseValidator.convert(responseToken).getErrors());
|
||||
result = result.concat(this.responseValidator.convert(responseToken));
|
||||
}
|
||||
else {
|
||||
Assertion firstAssertion = CollectionUtils.firstElement(response.getAssertions());
|
||||
if (firstAssertion != null && !hasName(firstAssertion)) {
|
||||
errors.add(new Saml2Error(Saml2ErrorCodes.SUBJECT_NOT_FOUND,
|
||||
"Assertion [" + firstAssertion.getID() + "] is missing a subject"));
|
||||
Saml2Error error = new Saml2Error(Saml2ErrorCodes.SUBJECT_NOT_FOUND,
|
||||
"Assertion [" + firstAssertion.getID() + "] is missing a subject");
|
||||
result = result.concat(error);
|
||||
}
|
||||
}
|
||||
|
||||
reportErrors(response, errors);
|
||||
}
|
||||
|
||||
private void reportErrors(Response response, Collection<Saml2Error> errors) {
|
||||
if (errors.isEmpty()) {
|
||||
if (result.hasErrors()) {
|
||||
Collection<Saml2Error> errors = result.getErrors();
|
||||
if (this.logger.isTraceEnabled()) {
|
||||
this.logger.trace("Found " + errors.size() + " validation errors in SAML response [" + response.getID()
|
||||
+ "]: " + errors);
|
||||
}
|
||||
else if (this.logger.isDebugEnabled()) {
|
||||
this.logger
|
||||
.debug("Found " + errors.size() + " validation errors in SAML response [" + response.getID() + "]");
|
||||
}
|
||||
Saml2Error first = errors.iterator().next();
|
||||
throw createAuthenticationException(first.getErrorCode(), first.getDescription(), null);
|
||||
}
|
||||
else {
|
||||
if (this.logger.isDebugEnabled()) {
|
||||
this.logger.debug("Successfully processed SAML Response [" + response.getID() + "]");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (this.logger.isTraceEnabled()) {
|
||||
this.logger.debug("Found " + errors.size() + " validation errors in SAML response [" + response.getID()
|
||||
+ "]: " + errors);
|
||||
}
|
||||
else if (this.logger.isDebugEnabled()) {
|
||||
this.logger
|
||||
.debug("Found " + errors.size() + " validation errors in SAML response [" + response.getID() + "]");
|
||||
}
|
||||
Saml2Error first = errors.iterator().next();
|
||||
throw createAuthenticationException(first.getErrorCode(), first.getDescription(), null);
|
||||
}
|
||||
|
||||
private Converter<ResponseToken, Saml2ResponseValidatorResult> createDefaultResponseSignatureValidator() {
|
||||
|
||||
-4
@@ -16,7 +16,6 @@
|
||||
|
||||
package org.springframework.security.saml2.provider.service.authentication;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
@@ -34,9 +33,6 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public class Saml2AuthenticationToken extends AbstractAuthenticationToken {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 5225098478444036532L;
|
||||
|
||||
private final RelyingPartyRegistration relyingPartyRegistration;
|
||||
|
||||
private final String saml2Response;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user