Compare commits
182 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ada3337104 | |||
| 0bd7daf899 | |||
| c6461d61ba | |||
| 4405cf18f3 | |||
| 7eb8a58244 | |||
| 415ee4d823 | |||
| 4d47d513b5 | |||
| 7983c695e2 | |||
| 15b3744dcf | |||
| 20b94c4a26 | |||
| 69a13dfcd4 | |||
| 89afd4c487 | |||
| 35e769d90b | |||
| d9e1906281 | |||
| e6f28b9c02 | |||
| 65230b8ee2 | |||
| 57d94ce816 | |||
| c967d6569d | |||
| 10f7a968c1 | |||
| 344bad34be | |||
| 9792e2a0fa | |||
| 44508df940 | |||
| 35ac1dd71e | |||
| 1460d53b2f | |||
| 040a28a8c9 | |||
| 0ae6e6e767 | |||
| 2f25c03609 | |||
| 7271499e74 | |||
| f2fc2a593f | |||
| f491cc8938 | |||
| 22a39545b9 | |||
| 36d57b597e | |||
| 02c6b04320 | |||
| 2c3522e290 | |||
| 01e37feb0d | |||
| 6403cba2f5 | |||
| d655deb718 | |||
| 6156f29c3b | |||
| 25363ac13e | |||
| 15d902322f | |||
| 070b67a9a1 | |||
| 304ef2302c | |||
| a09f6e15ad | |||
| 7e0302be5c | |||
| 7554ee8fec | |||
| 893b651aea | |||
| ca353d6781 | |||
| a763382c3e | |||
| 60595f2801 | |||
| ba810e468f | |||
| 40dfe8f259 | |||
| b448954f43 | |||
| b0c7d7703b | |||
| 5bc0537f1b | |||
| 355bf8ddee | |||
| 05585a5f13 | |||
| 00d350608a | |||
| 906c520fa0 | |||
| ca19601f8b | |||
| bdd4d05ce2 | |||
| 0eec932917 | |||
| af5c1761e8 | |||
| 491989c692 | |||
| 731069a69a | |||
| 47b8860681 | |||
| 828cac8889 | |||
| aefd2d497c | |||
| 775bf91c77 | |||
| 2bf7a5ae80 | |||
| c495ff8759 | |||
| b7a12e1b80 | |||
| 670d10dadd | |||
| 72db6a20c9 | |||
| 00f4033b9b | |||
| ddb59d439f | |||
| e4a76b0ec9 | |||
| 78e8507af7 | |||
| 13e7c9f58d | |||
| 14e6d8ad49 | |||
| d1a27676df | |||
| 844879b84d | |||
| d5efbd34b5 | |||
| b9ef11d228 | |||
| d9b1df8b65 | |||
| 020dd90566 | |||
| fafde0910f | |||
| b2db2bdb2a | |||
| cce1c16555 | |||
| e969c363c0 | |||
| 764e696026 | |||
| 6e86fab19d | |||
| 1a3e80506c | |||
| eb0597154d | |||
| 97dfabe92e | |||
| 944463e19a | |||
| 816e847af2 | |||
| 1f919bc791 | |||
| cae8990046 | |||
| c9917b3cd0 | |||
| 1653b7848d | |||
| c55f1f8bea | |||
| 670cf99258 | |||
| 41feec4766 | |||
| 34e04f642c | |||
| bdc3feaa6d | |||
| 134f8b0c5d | |||
| 835ee559d7 | |||
| cfe48556d4 | |||
| c7b73d7b3b | |||
| 618edc2e12 | |||
| f7fef100c0 | |||
| 9be97e76dc | |||
| 603e0a4140 | |||
| b081627f52 | |||
| 10a0bda63f | |||
| 7db5149163 | |||
| b206670245 | |||
| b067aa4653 | |||
| ba16d91971 | |||
| dfebd6d9d4 | |||
| bd703ff4ac | |||
| 7abaefda10 | |||
| 4440020217 | |||
| c2b9c0856d | |||
| a06f47fac3 | |||
| 8c0103498a | |||
| e269e5cb91 | |||
| ed8b08fa24 | |||
| 208e327629 | |||
| 51dfae1b91 | |||
| 2fd94752c9 | |||
| 61284ce22d | |||
| fdd017d935 | |||
| bfeb6bd756 | |||
| e41360b4a3 | |||
| e2e06539fc | |||
| 93f59a228b | |||
| b2bb014492 | |||
| 062910c2c4 | |||
| 40fdb5a60d | |||
| 2fa0da5cde | |||
| c5f4ae5c0a | |||
| c68c1c55ff | |||
| be2595bc66 | |||
| dddd71439f | |||
| 95a1ccb52c | |||
| 4060f83261 | |||
| f9a7ae0a6f | |||
| 425d008f1d | |||
| b73104c9d1 | |||
| 895ef6cf76 | |||
| cb4bb463da | |||
| 5fd81eeaf1 | |||
| a332e2a728 | |||
| 9daf058a6e | |||
| 839cc5e851 | |||
| 0087128be8 | |||
| bfc9c5e18f | |||
| 6fbd038111 | |||
| 700bda68b7 | |||
| 53870ab3de | |||
| fe13b488a1 | |||
| c38495bfaf | |||
| c4b210b580 | |||
| 1a28b4f849 | |||
| 63cd52d1a6 | |||
| 895ae0ac55 | |||
| e6c268add0 | |||
| 82d471a592 | |||
| f48a006034 | |||
| b7a0959ede | |||
| 29f4193529 | |||
| ab59adbfd2 | |||
| de4b3a4310 | |||
| e691906dbc | |||
| 5a06fbd984 | |||
| 34b55242fe | |||
| 26e34368c0 | |||
| 0fb7c24cbb | |||
| 0351883dfa | |||
| 95132d9fe3 | |||
| bf9f1de6e1 |
+1
-1
@@ -1,5 +1,5 @@
|
||||
# EditorConfig for Spring Security
|
||||
# see https://github.com/spring-projects/spring-security/blob/master/CONTRIBUTING.md#mind-the-whitespace
|
||||
# see https://github.com/spring-projects/spring-security/blob/master/CONTRIBUTING.adoc#mind-the-whitespace
|
||||
|
||||
root = true
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
# Normalize line endings to auto.
|
||||
* text auto
|
||||
|
||||
# Ensure that line endings for DOS batch files are not modified.
|
||||
*.bat -text
|
||||
|
||||
# Ensure the following are treated as binary.
|
||||
*.cer binary
|
||||
*.graffle binary
|
||||
|
||||
@@ -2,8 +2,6 @@ name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
schedule:
|
||||
- cron: '0 10 * * *' # Once per day at 10am UTC
|
||||
workflow_dispatch: # Manual trigger
|
||||
@@ -26,16 +24,6 @@ jobs:
|
||||
outputs:
|
||||
runjobs: ${{ steps.continue.outputs.runjobs }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Initiate error tracking
|
||||
uses: spring-projects/track-build-errors-action@v1
|
||||
with:
|
||||
job-name: "prerequisites"
|
||||
- name: Export errors file
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: errors
|
||||
path: job-prerequisites.txt
|
||||
- id: continue
|
||||
name: Determine if should continue
|
||||
if: env.RUN_JOBS == 'true'
|
||||
@@ -43,7 +31,10 @@ jobs:
|
||||
build_jdk_11:
|
||||
name: Build JDK 11
|
||||
needs: [prerequisites]
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
if: needs.prerequisites.outputs.runjobs
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
@@ -54,66 +45,18 @@ jobs:
|
||||
- name: Setup gradle user name
|
||||
run: |
|
||||
mkdir -p ~/.gradle
|
||||
echo 'systemProp.user.name=spring-builds' >> ~/.gradle/gradle.properties
|
||||
echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties
|
||||
- name: Cache Gradle packages
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.gradle/caches
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
|
||||
- name: Build with Gradle
|
||||
run: |
|
||||
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
|
||||
export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD"
|
||||
export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY"
|
||||
./gradlew clean build --continue -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD"
|
||||
- name: Track error step
|
||||
uses: spring-projects/track-build-errors-action@v1
|
||||
if: ${{ failure() }}
|
||||
with:
|
||||
job-name: ${{ github.job }}
|
||||
- name: Export errors file
|
||||
uses: actions/upload-artifact@v2
|
||||
if: ${{ failure() }}
|
||||
with:
|
||||
name: errors
|
||||
path: job-${{ github.job }}.txt
|
||||
build_windows:
|
||||
name: Build Windows
|
||||
needs: [prerequisites]
|
||||
runs-on: windows-latest
|
||||
if: needs.prerequisites.outputs.runjobs
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: '11'
|
||||
- name: Setup gradle user name
|
||||
run: |
|
||||
mkdir -p ~/.gradle
|
||||
echo 'systemProp.user.name=spring-builds' >> ~/.gradle/gradle.properties
|
||||
- name: Cache Gradle packages
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.gradle/caches
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
|
||||
- name: Build with Gradle
|
||||
run: |
|
||||
set GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
|
||||
set GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD"
|
||||
set GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY"
|
||||
./gradlew clean build --continue -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD"
|
||||
- name: Track error step
|
||||
uses: spring-projects/track-build-errors-action@v1
|
||||
if: ${{ failure() }}
|
||||
with:
|
||||
job-name: ${{ github.job }}
|
||||
- name: Export errors file
|
||||
uses: actions/upload-artifact@v2
|
||||
if: ${{ failure() }}
|
||||
with:
|
||||
name: errors
|
||||
path: job-${{ github.job }}.txt
|
||||
env:
|
||||
GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }}
|
||||
GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }}
|
||||
GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
|
||||
run: ./gradlew clean build --continue -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD"
|
||||
snapshot_tests:
|
||||
name: Test against snapshots
|
||||
needs: [prerequisites]
|
||||
@@ -128,71 +71,16 @@ jobs:
|
||||
- name: Setup gradle user name
|
||||
run: |
|
||||
mkdir -p ~/.gradle
|
||||
echo 'systemProp.user.name=spring-builds' >> ~/.gradle/gradle.properties
|
||||
echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties
|
||||
- name: Snapshot Tests
|
||||
run: |
|
||||
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
|
||||
export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD"
|
||||
export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY"
|
||||
./gradlew test --refresh-dependencies -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" -PforceMavenRepositories=snapshot -PspringVersion='5.+' -PreactorVersion='20+' -PspringDataVersion='Neumann-BUILD-SNAPSHOT' -PrsocketVersion=1.1.0-SNAPSHOT -PspringBootVersion=2.4.0-SNAPSHOT -PlocksDisabled --stacktrace
|
||||
- name: Track error step
|
||||
uses: spring-projects/track-build-errors-action@v1
|
||||
if: ${{ failure() }}
|
||||
with:
|
||||
job-name: ${{ github.job }}
|
||||
- name: Export errors file
|
||||
uses: actions/upload-artifact@v2
|
||||
if: ${{ failure() }}
|
||||
with:
|
||||
name: errors
|
||||
path: job-${{ github.job }}.txt
|
||||
sonar_analysis:
|
||||
name: Static Code Analysis
|
||||
needs: [prerequisites]
|
||||
runs-on: ubuntu-latest
|
||||
if: needs.prerequisites.outputs.runjobs
|
||||
env:
|
||||
SONAR_URL: ${{ secrets.SONAR_URL }}
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: '11'
|
||||
- name: Setup gradle user name
|
||||
run: |
|
||||
mkdir -p ~/.gradle
|
||||
echo 'systemProp.user.name=spring-builds' >> ~/.gradle/gradle.properties
|
||||
- name: Run Sonar on given (non-main) branch
|
||||
if: ${{ github.ref != 'refs/heads/main' }}
|
||||
run: |
|
||||
export BRANCH=${GITHUB_REF#refs/heads/}
|
||||
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
|
||||
export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD"
|
||||
export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY"
|
||||
./gradlew sonarqube -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" -PexcludeProjects='**/samples/**' -Dsonar.projectKey="spring-security-${GITHUB_REF#refs/heads/}" -Dsonar.projectName="spring-security-${GITHUB_REF#refs/heads/}" -Dsonar.host.url="$SONAR_URL" -Dsonar.login="$SONAR_TOKEN" --stacktrace
|
||||
- name: Run Sonar on main
|
||||
if: ${{ github.ref == 'refs/heads/main' }}
|
||||
run: |
|
||||
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
|
||||
export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD"
|
||||
export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY"
|
||||
./gradlew sonarqube -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" -PexcludeProjects='**/samples/**' -Dsonar.host.url="$SONAR_URL" -Dsonar.login="$SONAR_TOKEN" --stacktrace
|
||||
- name: Track error step
|
||||
uses: spring-projects/track-build-errors-action@v1
|
||||
if: ${{ failure() }}
|
||||
with:
|
||||
job-name: ${{ github.job }}
|
||||
- name: Export errors file
|
||||
uses: actions/upload-artifact@v2
|
||||
if: ${{ failure() }}
|
||||
with:
|
||||
name: errors
|
||||
path: job-${{ github.job }}.txt
|
||||
deploy_artifacts:
|
||||
name: Deploy Artifacts
|
||||
needs: [build_jdk_11, build_windows, snapshot_tests, sonar_analysis]
|
||||
needs: [build_jdk_11, snapshot_tests]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
@@ -203,37 +91,23 @@ jobs:
|
||||
- name: Setup gradle user name
|
||||
run: |
|
||||
mkdir -p ~/.gradle
|
||||
echo 'systemProp.user.name=spring-builds' >> ~/.gradle/gradle.properties
|
||||
echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties
|
||||
- name: Deploy artifacts
|
||||
run: |
|
||||
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
|
||||
export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD"
|
||||
export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY"
|
||||
export VERSION_HEADER=$'Version: GnuPG v2\n\n'
|
||||
export ORG_GRADLE_PROJECT_signingKey=${GPG_PRIVATE_KEY_NO_HEADER#"$VERSION_HEADER"}
|
||||
export ORG_GRADLE_PROJECT_signingPassword="$GPG_PASSPHRASE"
|
||||
./gradlew publishArtifacts finalizeDeployArtifacts -PossrhUsername="$OSSRH_TOKEN_USERNAME" -PossrhPassword="$OSSRH_TOKEN_PASSWORD" -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" --stacktrace
|
||||
env:
|
||||
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
|
||||
ORG_GRADLE_PROJECT_signingKey: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.GPG_PASSPHRASE }}
|
||||
OSSRH_TOKEN_USERNAME: ${{ secrets.OSSRH_S01_TOKEN_USERNAME }}
|
||||
OSSRH_TOKEN_PASSWORD: ${{ secrets.OSSRH_S01_TOKEN_PASSWORD }}
|
||||
ARTIFACTORY_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
ARTIFACTORY_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
- name: Track error step
|
||||
uses: spring-projects/track-build-errors-action@v1
|
||||
if: ${{ failure() }}
|
||||
with:
|
||||
job-name: ${{ github.job }}
|
||||
- name: Export errors file
|
||||
uses: actions/upload-artifact@v2
|
||||
if: ${{ failure() }}
|
||||
with:
|
||||
name: errors
|
||||
path: job-${{ github.job }}.txt
|
||||
deploy_docs:
|
||||
name: Deploy Docs
|
||||
needs: [build_jdk_11, build_windows, snapshot_tests, sonar_analysis]
|
||||
needs: [build_jdk_11, snapshot_tests]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
@@ -244,7 +118,7 @@ jobs:
|
||||
- name: Setup gradle user name
|
||||
run: |
|
||||
mkdir -p ~/.gradle
|
||||
echo 'systemProp.user.name=spring-builds' >> ~/.gradle/gradle.properties
|
||||
echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties
|
||||
- name: Deploy Docs
|
||||
run: |
|
||||
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
|
||||
@@ -255,20 +129,9 @@ jobs:
|
||||
DOCS_USERNAME: ${{ secrets.DOCS_USERNAME }}
|
||||
DOCS_SSH_KEY: ${{ secrets.DOCS_SSH_KEY }}
|
||||
DOCS_HOST: ${{ secrets.DOCS_HOST }}
|
||||
- name: Track error step
|
||||
uses: spring-projects/track-build-errors-action@v1
|
||||
if: ${{ failure() }}
|
||||
with:
|
||||
job-name: ${{ github.job }}
|
||||
- name: Export errors file
|
||||
uses: actions/upload-artifact@v2
|
||||
if: ${{ failure() }}
|
||||
with:
|
||||
name: errors
|
||||
path: job-${{ github.job }}.txt
|
||||
deploy_schema:
|
||||
name: Deploy Schema
|
||||
needs: [build_jdk_11, build_windows, snapshot_tests, sonar_analysis]
|
||||
needs: [build_jdk_11, snapshot_tests]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
@@ -279,7 +142,7 @@ jobs:
|
||||
- name: Setup gradle user name
|
||||
run: |
|
||||
mkdir -p ~/.gradle
|
||||
echo 'systemProp.user.name=spring-builds' >> ~/.gradle/gradle.properties
|
||||
echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties
|
||||
- name: Deploy Schema
|
||||
run: |
|
||||
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
|
||||
@@ -290,34 +153,16 @@ jobs:
|
||||
DOCS_USERNAME: ${{ secrets.DOCS_USERNAME }}
|
||||
DOCS_SSH_KEY: ${{ secrets.DOCS_SSH_KEY }}
|
||||
DOCS_HOST: ${{ secrets.DOCS_HOST }}
|
||||
- name: Track error step
|
||||
uses: spring-projects/track-build-errors-action@v1
|
||||
if: ${{ failure() }}
|
||||
with:
|
||||
job-name: ${{ github.job }}
|
||||
- name: Export errors file
|
||||
uses: actions/upload-artifact@v2
|
||||
if: ${{ failure() }}
|
||||
with:
|
||||
name: errors
|
||||
path: job-${{ github.job }}.txt
|
||||
notify_result:
|
||||
name: Check for failures
|
||||
needs: [build_jdk_11, build_windows, snapshot_tests, sonar_analysis, deploy_artifacts, deploy_docs, deploy_schema]
|
||||
if: always()
|
||||
needs: [build_jdk_11, snapshot_tests, deploy_artifacts, deploy_docs, deploy_schema]
|
||||
if: failure()
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Download errors folder
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: errors
|
||||
- name: Send Slack message
|
||||
uses: spring-projects/notify-slack-errors-action@v1
|
||||
uses: Gamesight/slack-workflow-status@v1.0.1
|
||||
with:
|
||||
slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
branch-name: ${{ github.ref }}
|
||||
commit-sha: ${{ github.sha }}
|
||||
commit-owner: ${{ github.actor }}
|
||||
repo-name: ${{ github.repository }}
|
||||
run-id: ${{ github.run_id }}
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
slack_webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
channel: '#spring-security-ci'
|
||||
name: 'CI Notifier'
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# Use sdkman to run "sdk env" to initialize with correct JDK version
|
||||
# Enable auto-env through the sdkman_auto_env config
|
||||
# See https://sdkman.io/usage#config
|
||||
# A summary is to add the following to ~/.sdkman/etc/config
|
||||
# sdkman_auto_env=true
|
||||
java=11.0.14-tem
|
||||
+2
-2
@@ -22,7 +22,7 @@ Be sure to read the https://docs.spring.io/spring-security/site/docs/current/ref
|
||||
Extensive JavaDoc for the Spring Security code is also available in the https://docs.spring.io/spring-security/site/docs/current/api/[Spring Security API Documentation].
|
||||
|
||||
== Quick Start
|
||||
We recommend you visit https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/[Spring Security Reference] and read the "Getting Started" page.
|
||||
See https://docs.spring.io/spring-security/site/docs/5.5.x/reference/html5/#servlet-hello[Hello Spring Security] to get started with a "Hello, World" application.
|
||||
|
||||
== Building from Source
|
||||
Spring Security uses a https://gradle.org[Gradle]-based build system.
|
||||
@@ -43,7 +43,7 @@ git clone git@github.com:spring-projects/spring-security.git
|
||||
=== Install all spring-\* jars into your local Maven cache
|
||||
[indent=0]
|
||||
----
|
||||
./gradlew install
|
||||
./gradlew publishToMavenLocal
|
||||
----
|
||||
|
||||
=== Compile and test; build all jars, distribution zips, and docs
|
||||
|
||||
+1
-1
@@ -143,7 +143,7 @@ Generate the Release Notes replacing:
|
||||
* <next-version> - Replace with the milestone you are releasing now (i.e. 5.5.0-RC1)
|
||||
|
||||
----
|
||||
$ ./gradlew generateChangelog -P<next-version>
|
||||
$ ./gradlew generateChangelog -PnextVersion=<next-version>
|
||||
----
|
||||
|
||||
* Copy the release notes to your clipboard (your mileage may vary with
|
||||
|
||||
+11
-7
@@ -1,7 +1,7 @@
|
||||
buildscript {
|
||||
dependencies {
|
||||
classpath "io.spring.javaformat:spring-javaformat-gradle-plugin:$springJavaformatVersion"
|
||||
classpath 'io.spring.nohttp:nohttp-gradle:0.0.8'
|
||||
classpath 'io.spring.nohttp:nohttp-gradle:0.0.10'
|
||||
classpath "io.freefair.gradle:aspectj-plugin:5.3.3.3"
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
|
||||
classpath "com.netflix.nebula:nebula-project-plugin:8.0.0"
|
||||
@@ -68,12 +68,6 @@ updateDependenciesSettings {
|
||||
milestoneVersions()
|
||||
snapshotVersions()
|
||||
addRule { components ->
|
||||
components.withModule("commons-codec:commons-codec") { selection ->
|
||||
ModuleComponentIdentifier candidate = selection.getCandidate();
|
||||
if (!candidate.getVersion().equals(selection.getCurrentVersion())) {
|
||||
selection.reject("commons-codec updates break saml tests");
|
||||
}
|
||||
}
|
||||
components.withModule("org.python:jython") { selection ->
|
||||
ModuleComponentIdentifier candidate = selection.getCandidate();
|
||||
if (!candidate.getVersion().equals(selection.getCurrentVersion())) {
|
||||
@@ -93,6 +87,14 @@ updateDependenciesSettings {
|
||||
if (candidate.getGroup().equals("org.opensaml")) {
|
||||
selection.reject("org.opensaml maintains two different versions, so it must be updated manually");
|
||||
}
|
||||
if (candidate.getGroup().equals("org.jetbrains.kotlin")) {
|
||||
if (candidate.getVersion().endsWith("-RC")) {
|
||||
selection.reject("On a maintenance branch, we should not take RC versions")
|
||||
}
|
||||
if (candidate.getVersion().contains("-M")) {
|
||||
selection.reject("On a maintenance branch, we should not take milestone versions")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -104,6 +106,8 @@ subprojects {
|
||||
}
|
||||
tasks.withType(JavaCompile) {
|
||||
options.encoding = "UTF-8"
|
||||
options.compilerArgs.add("-parameters")
|
||||
options.release = 8
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -72,14 +72,15 @@ dependencies {
|
||||
implementation localGroovy()
|
||||
|
||||
implementation 'io.github.gradle-nexus:publish-plugin:1.1.0'
|
||||
implementation 'io.projectreactor:reactor-core:3.4.6'
|
||||
implementation 'io.projectreactor:reactor-core:3.4.18'
|
||||
implementation 'gradle.plugin.org.gretty:gretty:3.0.1'
|
||||
implementation 'com.apollographql.apollo:apollo-runtime:2.4.5'
|
||||
implementation 'com.github.ben-manes:gradle-versions-plugin:0.38.0'
|
||||
implementation 'io.spring.gradle:docbook-reference-plugin:0.3.1'
|
||||
implementation 'io.spring.gradle:propdeps-plugin:0.0.10.RELEASE'
|
||||
implementation 'io.spring.javaformat:spring-javaformat-gradle-plugin:0.0.15'
|
||||
implementation 'io.spring.nohttp:nohttp-gradle:0.0.8'
|
||||
implementation 'io.spring.nohttp:nohttp-gradle:0.0.10'
|
||||
implementation 'org.aim42:htmlSanityCheck:1.1.6'
|
||||
implementation 'org.asciidoctor:asciidoctor-gradle-jvm:3.1.0'
|
||||
implementation 'org.asciidoctor:asciidoctor-gradle-jvm-pdf:3.1.0'
|
||||
implementation 'org.hidetake:gradle-ssh-plugin:2.10.1'
|
||||
|
||||
@@ -36,6 +36,9 @@ class ArtifactoryPlugin implements Plugin<Project> {
|
||||
password = artifactoryPassword
|
||||
}
|
||||
}
|
||||
defaults {
|
||||
publications('mavenJava')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ class CheckstylePlugin implements Plugin<Project> {
|
||||
if (checkstyleDir.exists() && checkstyleDir.directory) {
|
||||
project.getPluginManager().apply('checkstyle')
|
||||
project.dependencies.add('checkstyle', 'io.spring.javaformat:spring-javaformat-checkstyle:0.0.15')
|
||||
project.dependencies.add('checkstyle', 'io.spring.nohttp:nohttp-checkstyle:0.0.8')
|
||||
project.dependencies.add('checkstyle', 'io.spring.nohttp:nohttp-checkstyle:0.0.10')
|
||||
|
||||
project.checkstyle {
|
||||
configDirectory = checkstyleDir
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
package io.spring.gradle.convention
|
||||
|
||||
import org.aim42.htmlsanitycheck.HtmlSanityCheckPlugin
|
||||
import org.aim42.htmlsanitycheck.HtmlSanityCheckTask
|
||||
import org.aim42.htmlsanitycheck.check.BrokenHttpLinksChecker
|
||||
import org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask
|
||||
import org.gradle.api.Action
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.Task
|
||||
import org.gradle.api.artifacts.Configuration
|
||||
import org.gradle.api.artifacts.DependencySet
|
||||
import org.gradle.api.plugins.PluginManager
|
||||
import org.gradle.api.tasks.Sync
|
||||
import org.gradle.api.tasks.bundling.Zip
|
||||
|
||||
/**
|
||||
@@ -25,6 +24,7 @@ public class DocsPlugin implements Plugin<Project> {
|
||||
pluginManager.apply(AsciidoctorConventionPlugin);
|
||||
pluginManager.apply(DeployDocsPlugin);
|
||||
pluginManager.apply(JavadocApiPlugin);
|
||||
pluginManager.apply(HtmlSanityCheckPlugin)
|
||||
|
||||
String projectName = Utils.getProjectName(project);
|
||||
String pdfFilename = projectName + "-reference.pdf";
|
||||
@@ -38,6 +38,17 @@ public class DocsPlugin implements Plugin<Project> {
|
||||
}
|
||||
}
|
||||
|
||||
project.tasks.withType(HtmlSanityCheckTask) { HtmlSanityCheckTask t ->
|
||||
project.configure(t) {
|
||||
t.dependsOn 'asciidoctor'
|
||||
t.checkerClasses = [BrokenHttpLinksChecker]
|
||||
t.checkingResultsDir = new File(project.getBuildDir(), "/report/htmlchecks")
|
||||
t.failOnErrors = false
|
||||
t.httpConnectionTimeout = 3000
|
||||
t.sourceDir = new File(project.getBuildDir(), "/docs/asciidoc/")
|
||||
t.sourceDocuments = project.files(new File(project.getBuildDir(), "/docs/asciidoc/index.html"))
|
||||
}
|
||||
}
|
||||
|
||||
Task docsZip = project.tasks.create('docsZip', Zip) {
|
||||
dependsOn 'api', 'asciidoctor'
|
||||
|
||||
@@ -40,9 +40,9 @@ import org.jasig.cas.client.authentication.AttributePrincipal;
|
||||
* </pre>
|
||||
*
|
||||
* @author Jitendra Singh
|
||||
* @since 4.2
|
||||
* @see CasJackson2Module
|
||||
* @see org.springframework.security.jackson2.SecurityJackson2Modules
|
||||
* @since 4.2
|
||||
*/
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
|
||||
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
|
||||
|
||||
+1
-1
@@ -37,9 +37,9 @@ import org.jasig.cas.client.proxy.ProxyRetriever;
|
||||
* </pre>
|
||||
*
|
||||
* @author Jitendra Singh
|
||||
* @since 4.2
|
||||
* @see CasJackson2Module
|
||||
* @see org.springframework.security.jackson2.SecurityJackson2Modules
|
||||
* @since 4.2
|
||||
*/
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
|
||||
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
|
||||
|
||||
+1
-1
@@ -47,9 +47,9 @@ import org.springframework.security.core.userdetails.UserDetails;
|
||||
* </pre>
|
||||
*
|
||||
* @author Jitendra Singh
|
||||
* @since 4.2
|
||||
* @see CasJackson2Module
|
||||
* @see org.springframework.security.jackson2.SecurityJackson2Modules
|
||||
* @since 4.2
|
||||
*/
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
|
||||
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, isGetterVisibility = JsonAutoDetect.Visibility.NONE,
|
||||
|
||||
@@ -100,10 +100,10 @@ rncToXsd {
|
||||
|
||||
tasks.withType(KotlinCompile).configureEach {
|
||||
kotlinOptions {
|
||||
apiVersion = "1.4"
|
||||
languageVersion = "1.3"
|
||||
apiVersion = "1.3"
|
||||
freeCompilerArgs = ["-Xjsr305=strict", "-Xsuppress-version-warnings"]
|
||||
jvmTarget = "1.8"
|
||||
languageVersion = "1.4"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,8 +19,8 @@ package org.springframework.security.config;
|
||||
/**
|
||||
* Callback interface that accepts a single input argument and returns no result.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
* @param <T> the type of the input to the operation
|
||||
* @author Eleftheria Stein
|
||||
* @since 5.2
|
||||
*/
|
||||
@FunctionalInterface
|
||||
|
||||
+2
-2
@@ -93,7 +93,7 @@ public final class SecurityNamespaceHandler implements NamespaceHandler {
|
||||
if (!namespaceMatchesVersion(element)) {
|
||||
pc.getReaderContext().fatal("You cannot use a spring-security-2.0.xsd or spring-security-3.0.xsd or "
|
||||
+ "spring-security-3.1.xsd schema or spring-security-3.2.xsd schema or spring-security-4.0.xsd schema "
|
||||
+ "with Spring Security 5.4. Please update your schema declarations to the 5.4 schema.", element);
|
||||
+ "with Spring Security 5.5. Please update your schema declarations to the 5.5 schema.", element);
|
||||
}
|
||||
String name = pc.getDelegate().getLocalName(element);
|
||||
BeanDefinitionParser parser = this.parsers.get(name);
|
||||
@@ -213,7 +213,7 @@ public final class SecurityNamespaceHandler implements NamespaceHandler {
|
||||
|
||||
private boolean matchesVersionInternal(Element element) {
|
||||
String schemaLocation = element.getAttributeNS("http://www.w3.org/2001/XMLSchema-instance", "schemaLocation");
|
||||
return schemaLocation.matches("(?m).*spring-security-5\\.4.*.xsd.*")
|
||||
return schemaLocation.matches("(?m).*spring-security-5\\.5.*.xsd.*")
|
||||
|| schemaLocation.matches("(?m).*spring-security.xsd.*")
|
||||
|| !schemaLocation.matches("(?m).*spring-security.*");
|
||||
}
|
||||
|
||||
+1
-1
@@ -27,8 +27,8 @@ import org.springframework.security.config.annotation.authentication.builders.Au
|
||||
* {@link AuthenticationConfiguration} to configure the global
|
||||
* {@link AuthenticationManagerBuilder}.
|
||||
*
|
||||
* @since 5.0
|
||||
* @author Rob Winch
|
||||
* @since 5.0
|
||||
*/
|
||||
@Order(100)
|
||||
public abstract class GlobalAuthenticationConfigurerAdapter
|
||||
|
||||
+2
-2
@@ -30,10 +30,10 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
|
||||
* class is not intended to be imported manually rather it is imported automatically when
|
||||
* using {@link EnableWebSecurity} or {@link EnableGlobalMethodSecurity}.
|
||||
*
|
||||
* @see EnableWebSecurity
|
||||
* @see EnableGlobalMethodSecurity
|
||||
* @author Rob Winch
|
||||
* @since 3.2
|
||||
* @see EnableWebSecurity
|
||||
* @see EnableGlobalMethodSecurity
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
|
||||
+12
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -114,8 +114,18 @@ final class FilterOrderRegistration {
|
||||
put(SwitchUserFilter.class, order.next());
|
||||
}
|
||||
|
||||
private void put(Class<? extends Filter> filter, int position) {
|
||||
/**
|
||||
* Register a {@link Filter} with its specific position. If the {@link Filter} was
|
||||
* already registered before, the position previously defined is not going to be
|
||||
* overriden
|
||||
* @param filter the {@link Filter} to register
|
||||
* @param position the position to associate with the {@link Filter}
|
||||
*/
|
||||
void put(Class<? extends Filter> filter, int position) {
|
||||
String className = filter.getName();
|
||||
if (this.filterToOrder.containsKey(className)) {
|
||||
return;
|
||||
}
|
||||
this.filterToOrder.put(className, position);
|
||||
}
|
||||
|
||||
|
||||
+15
-8
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -2653,6 +2653,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
|
||||
private HttpSecurity addFilterAtOffsetOf(Filter filter, int offset, Class<? extends Filter> registeredFilter) {
|
||||
int order = this.filterOrders.getOrder(registeredFilter) + offset;
|
||||
this.filters.add(new OrderedFilter(filter, order));
|
||||
this.filterOrders.put(filter.getClass(), order);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -3007,20 +3008,26 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
|
||||
*/
|
||||
public final class MvcMatchersRequestMatcherConfigurer extends RequestMatcherConfigurer {
|
||||
|
||||
private final List<MvcRequestMatcher> mvcMatchers;
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
* @param context the {@link ApplicationContext} to use
|
||||
* @param matchers the {@link MvcRequestMatcher} instances to set the servlet path
|
||||
* on if {@link #servletPath(String)} is set.
|
||||
* @param mvcMatchers the {@link MvcRequestMatcher} instances to set the servlet
|
||||
* path on if {@link #servletPath(String)} is set.
|
||||
* @param allMatchers the {@link RequestMatcher} instances to continue the
|
||||
* configuration
|
||||
*/
|
||||
private MvcMatchersRequestMatcherConfigurer(ApplicationContext context, List<MvcRequestMatcher> matchers) {
|
||||
private MvcMatchersRequestMatcherConfigurer(ApplicationContext context, List<MvcRequestMatcher> mvcMatchers,
|
||||
List<RequestMatcher> allMatchers) {
|
||||
super(context);
|
||||
this.matchers = new ArrayList<>(matchers);
|
||||
this.mvcMatchers = new ArrayList<>(mvcMatchers);
|
||||
this.matchers = allMatchers;
|
||||
}
|
||||
|
||||
public RequestMatcherConfigurer servletPath(String servletPath) {
|
||||
for (RequestMatcher matcher : this.matchers) {
|
||||
((MvcRequestMatcher) matcher).setServletPath(servletPath);
|
||||
for (MvcRequestMatcher matcher : this.mvcMatchers) {
|
||||
matcher.setServletPath(servletPath);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@@ -3045,7 +3052,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
|
||||
public MvcMatchersRequestMatcherConfigurer mvcMatchers(HttpMethod method, String... mvcPatterns) {
|
||||
List<MvcRequestMatcher> mvcMatchers = createMvcMatchers(method, mvcPatterns);
|
||||
setMatchers(mvcMatchers);
|
||||
return new MvcMatchersRequestMatcherConfigurer(getContext(), mvcMatchers);
|
||||
return new MvcMatchersRequestMatcherConfigurer(getContext(), mvcMatchers, this.matchers);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+56
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -20,6 +20,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
@@ -33,6 +34,7 @@ import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.access.PermissionEvaluator;
|
||||
import org.springframework.security.access.expression.SecurityExpressionHandler;
|
||||
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
|
||||
import org.springframework.security.authorization.AuthorizationManager;
|
||||
import org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder;
|
||||
import org.springframework.security.config.annotation.ObjectPostProcessor;
|
||||
import org.springframework.security.config.annotation.SecurityBuilder;
|
||||
@@ -47,9 +49,12 @@ import org.springframework.security.web.DefaultSecurityFilterChain;
|
||||
import org.springframework.security.web.FilterChainProxy;
|
||||
import org.springframework.security.web.FilterInvocation;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.access.AuthorizationManagerWebInvocationPrivilegeEvaluator;
|
||||
import org.springframework.security.web.access.DefaultWebInvocationPrivilegeEvaluator;
|
||||
import org.springframework.security.web.access.RequestMatcherDelegatingWebInvocationPrivilegeEvaluator;
|
||||
import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
|
||||
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
|
||||
import org.springframework.security.web.access.intercept.AuthorizationFilter;
|
||||
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
|
||||
import org.springframework.security.web.debug.DebugFilter;
|
||||
import org.springframework.security.web.firewall.HttpFirewall;
|
||||
@@ -57,7 +62,9 @@ import org.springframework.security.web.firewall.RequestRejectedHandler;
|
||||
import org.springframework.security.web.firewall.StrictHttpFirewall;
|
||||
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcherEntry;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.context.ServletContextAware;
|
||||
import org.springframework.web.filter.DelegatingFilterProxy;
|
||||
|
||||
/**
|
||||
@@ -81,7 +88,7 @@ import org.springframework.web.filter.DelegatingFilterProxy;
|
||||
* @see WebSecurityConfiguration
|
||||
*/
|
||||
public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter, WebSecurity>
|
||||
implements SecurityBuilder<Filter>, ApplicationContextAware {
|
||||
implements SecurityBuilder<Filter>, ApplicationContextAware, ServletContextAware {
|
||||
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
@@ -108,6 +115,8 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
|
||||
private Runnable postBuildAction = () -> {
|
||||
};
|
||||
|
||||
private ServletContext servletContext;
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
* @param objectPostProcessor the {@link ObjectPostProcessor} to use
|
||||
@@ -252,6 +261,8 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
|
||||
* {@link WebSecurityConfigurerAdapter}.
|
||||
* @param securityInterceptor the {@link FilterSecurityInterceptor} to use
|
||||
* @return the {@link WebSecurity} for further customizations
|
||||
* @deprecated Use {@link #privilegeEvaluator(WebInvocationPrivilegeEvaluator)}
|
||||
* instead
|
||||
*/
|
||||
public WebSecurity securityInterceptor(FilterSecurityInterceptor securityInterceptor) {
|
||||
this.filterSecurityInterceptor = securityInterceptor;
|
||||
@@ -278,11 +289,24 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
|
||||
+ ".addSecurityFilterChainBuilder directly");
|
||||
int chainSize = this.ignoredRequests.size() + this.securityFilterChainBuilders.size();
|
||||
List<SecurityFilterChain> securityFilterChains = new ArrayList<>(chainSize);
|
||||
List<RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>>> requestMatcherPrivilegeEvaluatorsEntries = new ArrayList<>();
|
||||
for (RequestMatcher ignoredRequest : this.ignoredRequests) {
|
||||
securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
|
||||
WebSecurity.this.logger.warn("You are asking Spring Security to ignore " + ignoredRequest
|
||||
+ ". This is not recommended -- please use permitAll via HttpSecurity#authorizeHttpRequests instead.");
|
||||
SecurityFilterChain securityFilterChain = new DefaultSecurityFilterChain(ignoredRequest);
|
||||
securityFilterChains.add(securityFilterChain);
|
||||
requestMatcherPrivilegeEvaluatorsEntries
|
||||
.add(getRequestMatcherPrivilegeEvaluatorsEntry(securityFilterChain));
|
||||
}
|
||||
for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : this.securityFilterChainBuilders) {
|
||||
securityFilterChains.add(securityFilterChainBuilder.build());
|
||||
SecurityFilterChain securityFilterChain = securityFilterChainBuilder.build();
|
||||
securityFilterChains.add(securityFilterChain);
|
||||
requestMatcherPrivilegeEvaluatorsEntries
|
||||
.add(getRequestMatcherPrivilegeEvaluatorsEntry(securityFilterChain));
|
||||
}
|
||||
if (this.privilegeEvaluator == null) {
|
||||
this.privilegeEvaluator = new RequestMatcherDelegatingWebInvocationPrivilegeEvaluator(
|
||||
requestMatcherPrivilegeEvaluatorsEntries);
|
||||
}
|
||||
FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
|
||||
if (this.httpFirewall != null) {
|
||||
@@ -306,6 +330,29 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
|
||||
return result;
|
||||
}
|
||||
|
||||
private RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>> getRequestMatcherPrivilegeEvaluatorsEntry(
|
||||
SecurityFilterChain securityFilterChain) {
|
||||
List<WebInvocationPrivilegeEvaluator> privilegeEvaluators = new ArrayList<>();
|
||||
for (Filter filter : securityFilterChain.getFilters()) {
|
||||
if (filter instanceof FilterSecurityInterceptor) {
|
||||
DefaultWebInvocationPrivilegeEvaluator defaultWebInvocationPrivilegeEvaluator = new DefaultWebInvocationPrivilegeEvaluator(
|
||||
(FilterSecurityInterceptor) filter);
|
||||
defaultWebInvocationPrivilegeEvaluator.setServletContext(this.servletContext);
|
||||
privilegeEvaluators.add(defaultWebInvocationPrivilegeEvaluator);
|
||||
continue;
|
||||
}
|
||||
if (filter instanceof AuthorizationFilter) {
|
||||
AuthorizationManager<HttpServletRequest> authorizationManager = ((AuthorizationFilter) filter)
|
||||
.getAuthorizationManager();
|
||||
AuthorizationManagerWebInvocationPrivilegeEvaluator evaluator = new AuthorizationManagerWebInvocationPrivilegeEvaluator(
|
||||
authorizationManager);
|
||||
evaluator.setServletContext(this.servletContext);
|
||||
privilegeEvaluators.add(evaluator);
|
||||
}
|
||||
}
|
||||
return new RequestMatcherEntry<>(securityFilterChain::matches, privilegeEvaluators);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.defaultWebSecurityExpressionHandler.setApplicationContext(applicationContext);
|
||||
@@ -333,6 +380,11 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setServletContext(ServletContext servletContext) {
|
||||
this.servletContext = servletContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* An {@link IgnoredRequestConfigurer} that allows optionally configuring the
|
||||
* {@link MvcRequestMatcher#setMethod(HttpMethod)}
|
||||
|
||||
+1
-1
@@ -94,8 +94,8 @@ class HttpSecurityConfiguration {
|
||||
.requestCache(withDefaults())
|
||||
.anonymous(withDefaults())
|
||||
.servletApi(withDefaults())
|
||||
.logout(withDefaults())
|
||||
.apply(new DefaultLoginPageConfigurer<>());
|
||||
http.logout(withDefaults());
|
||||
// @formatter:on
|
||||
return http;
|
||||
}
|
||||
|
||||
+4
-4
@@ -60,11 +60,11 @@ import org.springframework.util.Assert;
|
||||
* {@link WebSecurityConfigurer} and exposing it as a {@link Configuration}. This
|
||||
* configuration is imported when using {@link EnableWebSecurity}.
|
||||
*
|
||||
* @see EnableWebSecurity
|
||||
* @see WebSecurity
|
||||
* @author Rob Winch
|
||||
* @author Keesun Baik
|
||||
* @since 3.2
|
||||
* @see EnableWebSecurity
|
||||
* @see WebSecurity
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
|
||||
@@ -128,8 +128,8 @@ public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAwa
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the {@link WebInvocationPrivilegeEvaluator} that is necessary for the JSP
|
||||
* tag support.
|
||||
* Creates the {@link WebInvocationPrivilegeEvaluator} that is necessary to evaluate
|
||||
* privileges for a given web URI
|
||||
* @return the {@link WebInvocationPrivilegeEvaluator}
|
||||
*/
|
||||
@Bean
|
||||
|
||||
+1
-1
@@ -87,8 +87,8 @@ import org.springframework.web.accept.HeaderContentNegotiationStrategy;
|
||||
* org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer = sample.MyClassThatExtendsAbstractHttpConfigurer, sample.OtherThatExtendsAbstractHttpConfigurer
|
||||
* </pre>
|
||||
*
|
||||
* @see EnableWebSecurity
|
||||
* @author Rob Winch
|
||||
* @see EnableWebSecurity
|
||||
*/
|
||||
@Order(100)
|
||||
public abstract class WebSecurityConfigurerAdapter implements WebSecurityConfigurer<WebSecurity> {
|
||||
|
||||
+5
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -97,7 +97,10 @@ public final class DefaultLoginPageConfigurer<H extends HttpSecurityBuilder<H>>
|
||||
if (this.loginPageGeneratingFilter.isEnabled() && authenticationEntryPoint == null) {
|
||||
this.loginPageGeneratingFilter = postProcess(this.loginPageGeneratingFilter);
|
||||
http.addFilter(this.loginPageGeneratingFilter);
|
||||
http.addFilter(this.logoutPageGeneratingFilter);
|
||||
LogoutConfigurer<H> logoutConfigurer = http.getConfigurer(LogoutConfigurer.class);
|
||||
if (logoutConfigurer != null) {
|
||||
http.addFilter(this.logoutPageGeneratingFilter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -490,8 +490,8 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
|
||||
* @return the {@link FeaturePolicyConfig} for additional configuration
|
||||
* @throws IllegalArgumentException if policyDirectives is {@code null} or empty
|
||||
* @since 5.1
|
||||
* @see FeaturePolicyHeaderWriter
|
||||
* @deprecated Use {@link #permissionsPolicy(Customizer)} instead.
|
||||
* @seeObjectPostProcessorConfiguration FeaturePolicyHeaderWriter
|
||||
*/
|
||||
@Deprecated
|
||||
public FeaturePolicyConfig featurePolicy(String policyDirectives) {
|
||||
|
||||
+4
-4
@@ -49,15 +49,15 @@ import org.springframework.util.Assert;
|
||||
* <li>{@link ClientRegistrationRepository}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
* @see OAuth2AuthorizationRequestRedirectFilter
|
||||
* @see ClientRegistrationRepository
|
||||
* @deprecated It is not recommended to use the implicit flow due to the inherent risks of
|
||||
* returning access tokens in an HTTP redirect without any confirmation that it has been
|
||||
* received by the client. See reference
|
||||
* <a target="_blank" href="https://oauth.net/2/grant-types/implicit/">OAuth 2.0 Implicit
|
||||
* Grant</a>.
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
* @see OAuth2AuthorizationRequestRedirectFilter
|
||||
* @see ClientRegistrationRepository
|
||||
*/
|
||||
@Deprecated
|
||||
public final class ImplicitGrantConfigurer<B extends HttpSecurityBuilder<B>>
|
||||
|
||||
+1
-1
@@ -683,10 +683,10 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
||||
/**
|
||||
* Sets a custom {@link OAuth2User} type and associates it to the provided client
|
||||
* {@link ClientRegistration#getRegistrationId() registration identifier}.
|
||||
* @deprecated See {@link CustomUserTypesOAuth2UserService} for alternative usage.
|
||||
* @param customUserType a custom {@link OAuth2User} type
|
||||
* @param clientRegistrationId the client registration identifier
|
||||
* @return the {@link UserInfoEndpointConfig} for further configuration
|
||||
* @deprecated See {@link CustomUserTypesOAuth2UserService} for alternative usage.
|
||||
*/
|
||||
@Deprecated
|
||||
public UserInfoEndpointConfig customUserType(Class<? extends OAuth2User> customUserType,
|
||||
|
||||
+24
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -160,7 +160,7 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
||||
* @param repo the repository of relying parties
|
||||
* @return the {@link Saml2LoginConfigurer} for further configuration
|
||||
*/
|
||||
public Saml2LoginConfigurer relyingPartyRegistrationRepository(RelyingPartyRegistrationRepository repo) {
|
||||
public Saml2LoginConfigurer<B> relyingPartyRegistrationRepository(RelyingPartyRegistrationRepository repo) {
|
||||
this.relyingPartyRegistrationRepository = repo;
|
||||
return this;
|
||||
}
|
||||
@@ -172,10 +172,19 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the URL to validate the credentials. If specified a custom URL, consider
|
||||
* specifying a custom {@link AuthenticationConverter} via
|
||||
* {@link #authenticationConverter(AuthenticationConverter)}, since the default
|
||||
* {@link AuthenticationConverter} implementation relies on the
|
||||
* <code>{registrationId}</code> path variable to be present in the URL
|
||||
* @param loginProcessingUrl the URL to validate the credentials
|
||||
* @return the {@link Saml2LoginConfigurer} for additional customization
|
||||
* @see Saml2WebSsoAuthenticationFilter#DEFAULT_FILTER_PROCESSES_URI
|
||||
*/
|
||||
@Override
|
||||
public Saml2LoginConfigurer<B> loginProcessingUrl(String loginProcessingUrl) {
|
||||
Assert.hasText(loginProcessingUrl, "loginProcessingUrl cannot be empty");
|
||||
Assert.state(loginProcessingUrl.contains("{registrationId}"), "{registrationId} path variable is required");
|
||||
this.loginProcessingUrl = loginProcessingUrl;
|
||||
return this;
|
||||
}
|
||||
@@ -254,14 +263,24 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
||||
|
||||
private AuthenticationConverter getAuthenticationConverter(B http) {
|
||||
if (this.authenticationConverter == null) {
|
||||
Assert.state(this.loginProcessingUrl.contains("{registrationId}"),
|
||||
"loginProcessingUrl must contain {registrationId} path variable");
|
||||
return new Saml2AuthenticationTokenConverter(
|
||||
new DefaultRelyingPartyRegistrationResolver(this.relyingPartyRegistrationRepository));
|
||||
}
|
||||
return this.authenticationConverter;
|
||||
}
|
||||
|
||||
private String version() {
|
||||
String version = Version.getVersion();
|
||||
if (version != null) {
|
||||
return version;
|
||||
}
|
||||
return Version.getVersion();
|
||||
}
|
||||
|
||||
private void registerDefaultAuthenticationProvider(B http) {
|
||||
if (Version.getVersion().startsWith("4")) {
|
||||
if (version().startsWith("4")) {
|
||||
http.authenticationProvider(postProcess(new OpenSaml4AuthenticationProvider()));
|
||||
}
|
||||
else {
|
||||
@@ -346,7 +365,7 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
||||
private Saml2AuthenticationRequestFactory getResolver(B http) {
|
||||
Saml2AuthenticationRequestFactory resolver = getSharedOrBean(http, Saml2AuthenticationRequestFactory.class);
|
||||
if (resolver == null) {
|
||||
if (Version.getVersion().startsWith("4")) {
|
||||
if (version().startsWith("4")) {
|
||||
return new OpenSaml4AuthenticationRequestFactory();
|
||||
}
|
||||
return new OpenSamlAuthenticationRequestFactory();
|
||||
|
||||
+1
-1
@@ -37,9 +37,9 @@ import org.springframework.web.servlet.support.RequestDataValueProcessor;
|
||||
* {@link AuthenticationPrincipalArgumentResolver} as a
|
||||
* {@link HandlerMethodArgumentResolver}.
|
||||
*
|
||||
* @deprecated This is applied internally using SpringWebMvcImportSelector
|
||||
* @author Rob Winch
|
||||
* @since 3.2
|
||||
* @deprecated This is applied internally using SpringWebMvcImportSelector
|
||||
*/
|
||||
@Deprecated
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
|
||||
+1
-1
@@ -79,8 +79,8 @@ import org.springframework.web.socket.sockjs.transport.TransportHandlingSockJsSe
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @since 4.0
|
||||
* @author Rob Winch
|
||||
* @since 4.0
|
||||
*/
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE + 100)
|
||||
@Import(ObjectPostProcessorConfiguration.class)
|
||||
|
||||
+12
-6
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -57,6 +57,8 @@ public class AuthenticationManagerBeanDefinitionParser implements BeanDefinition
|
||||
|
||||
private static final String ATT_ERASE_CREDENTIALS = "erase-credentials";
|
||||
|
||||
private static final String AUTHENTICATION_EVENT_PUBLISHER_BEAN_NAME = "defaultAuthenticationEventPublisher";
|
||||
|
||||
@Override
|
||||
public BeanDefinition parse(Element element, ParserContext pc) {
|
||||
String id = element.getAttribute("id");
|
||||
@@ -86,11 +88,15 @@ public class AuthenticationManagerBeanDefinitionParser implements BeanDefinition
|
||||
if ("false".equals(element.getAttribute(ATT_ERASE_CREDENTIALS))) {
|
||||
providerManagerBldr.addPropertyValue("eraseCredentialsAfterAuthentication", false);
|
||||
}
|
||||
// Add the default event publisher
|
||||
BeanDefinition publisher = new RootBeanDefinition(DefaultAuthenticationEventPublisher.class);
|
||||
String pubId = pc.getReaderContext().generateBeanName(publisher);
|
||||
pc.registerBeanComponent(new BeanComponentDefinition(publisher, pubId));
|
||||
providerManagerBldr.addPropertyReference("authenticationEventPublisher", pubId);
|
||||
|
||||
if (!pc.getRegistry().containsBeanDefinition(AUTHENTICATION_EVENT_PUBLISHER_BEAN_NAME)) {
|
||||
// Add the default event publisher to the context
|
||||
BeanDefinition publisher = new RootBeanDefinition(DefaultAuthenticationEventPublisher.class);
|
||||
pc.registerBeanComponent(new BeanComponentDefinition(publisher, AUTHENTICATION_EVENT_PUBLISHER_BEAN_NAME));
|
||||
}
|
||||
|
||||
providerManagerBldr.addPropertyReference("authenticationEventPublisher",
|
||||
AUTHENTICATION_EVENT_PUBLISHER_BEAN_NAME);
|
||||
pc.registerBeanComponent(new BeanComponentDefinition(providerManagerBldr.getBeanDefinition(), id));
|
||||
if (StringUtils.hasText(alias)) {
|
||||
pc.getRegistry().registerAlias(id, alias);
|
||||
|
||||
+4
-1
@@ -2223,7 +2223,10 @@ public class ServerHttpSecurity {
|
||||
}
|
||||
if (loginPage != null) {
|
||||
http.addFilterAt(loginPage, SecurityWebFiltersOrder.LOGIN_PAGE_GENERATING);
|
||||
http.addFilterAt(new LogoutPageGeneratingWebFilter(), SecurityWebFiltersOrder.LOGOUT_PAGE_GENERATING);
|
||||
if (http.logout != null) {
|
||||
http.addFilterAt(new LogoutPageGeneratingWebFilter(),
|
||||
SecurityWebFiltersOrder.LOGOUT_PAGE_GENERATING);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+9
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -27,6 +27,7 @@ import org.powermock.modules.junit4.PowerMockRunner;
|
||||
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.security.config.util.InMemoryXmlApplicationContext;
|
||||
import org.springframework.security.config.util.SpringSecurityVersions;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
@@ -147,4 +148,11 @@ public class SecurityNamespaceHandlerTests {
|
||||
// should load just fine since no websocket block
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configureWhenOldVersionThenErrorMessageContainsCorrectVersion() {
|
||||
assertThatExceptionOfType(BeanDefinitionParsingException.class)
|
||||
.isThrownBy(() -> new InMemoryXmlApplicationContext(XML_AUTHENTICATION_MANAGER, "3.0", null))
|
||||
.withMessageContaining(SpringSecurityVersions.getCurrentXsdVersionFromSpringSchemas());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.annotation.web.builders;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class FilterOrderRegistrationTests {
|
||||
|
||||
private final FilterOrderRegistration filterOrderRegistration = new FilterOrderRegistration();
|
||||
|
||||
@Test
|
||||
public void putWhenNewFilterThenInsertCorrect() {
|
||||
int position = 153;
|
||||
this.filterOrderRegistration.put(MyFilter.class, position);
|
||||
Integer order = this.filterOrderRegistration.getOrder(MyFilter.class);
|
||||
assertThat(order).isEqualTo(position);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void putWhenCustomFilterAlreadyExistsThenDoesNotOverride() {
|
||||
int position = 160;
|
||||
this.filterOrderRegistration.put(MyFilter.class, position);
|
||||
this.filterOrderRegistration.put(MyFilter.class, 173);
|
||||
Integer order = this.filterOrderRegistration.getOrder(MyFilter.class);
|
||||
assertThat(order).isEqualTo(position);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void putWhenPredefinedFilterThenDoesNotOverride() {
|
||||
int position = 100;
|
||||
Integer predefinedFilterOrderBefore = this.filterOrderRegistration.getOrder(ChannelProcessingFilter.class);
|
||||
this.filterOrderRegistration.put(MyFilter.class, position);
|
||||
Integer myFilterOrder = this.filterOrderRegistration.getOrder(MyFilter.class);
|
||||
Integer predefinedFilterOrderAfter = this.filterOrderRegistration.getOrder(ChannelProcessingFilter.class);
|
||||
assertThat(myFilterOrder).isEqualTo(position);
|
||||
assertThat(predefinedFilterOrderAfter).isEqualTo(predefinedFilterOrderBefore).isEqualTo(position);
|
||||
}
|
||||
|
||||
static class MyFilter implements Filter {
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
|
||||
throws IOException, ServletException {
|
||||
filterChain.doFilter(servletRequest, servletResponse);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+144
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -30,14 +30,18 @@ import org.assertj.core.api.ListAssert;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.config.test.SpringTestRule;
|
||||
import org.springframework.security.web.FilterChainProxy;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.access.ExceptionTranslationFilter;
|
||||
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
|
||||
import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;
|
||||
import org.springframework.security.web.header.HeaderWriterFilter;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@@ -70,6 +74,46 @@ public class HttpSecurityAddFilterTest {
|
||||
ExceptionTranslationFilter.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addFilterAfterWhenAfterCustomFilterThenOrderCorrect() {
|
||||
this.spring.register(MyOtherFilterRelativeToMyFilterAfterConfig.class).autowire();
|
||||
|
||||
assertThatFilters().containsSubsequence(WebAsyncManagerIntegrationFilter.class, MyFilter.class,
|
||||
MyOtherFilter.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addFilterBeforeWhenBeforeCustomFilterThenOrderCorrect() {
|
||||
this.spring.register(MyOtherFilterRelativeToMyFilterBeforeConfig.class).autowire();
|
||||
|
||||
assertThatFilters().containsSubsequence(MyOtherFilter.class, MyFilter.class,
|
||||
WebAsyncManagerIntegrationFilter.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addFilterAtWhenAtCustomFilterThenOrderCorrect() {
|
||||
this.spring.register(MyOtherFilterRelativeToMyFilterAtConfig.class).autowire();
|
||||
|
||||
assertThatFilters().containsSubsequence(WebAsyncManagerIntegrationFilter.class, MyFilter.class,
|
||||
MyOtherFilter.class, SecurityContextPersistenceFilter.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addFilterBeforeWhenCustomFilterDifferentPlacesThenOrderCorrect() {
|
||||
this.spring.register(MyOtherFilterBeforeToMyFilterMultipleAfterConfig.class).autowire();
|
||||
|
||||
assertThatFilters().containsSubsequence(WebAsyncManagerIntegrationFilter.class, MyOtherFilter.class,
|
||||
MyFilter.class, ExceptionTranslationFilter.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addFilterBeforeAndAfterWhenCustomFiltersDifferentPlacesThenOrderCorrect() {
|
||||
this.spring.register(MyAnotherFilterRelativeToMyCustomFiltersMultipleConfig.class).autowire();
|
||||
|
||||
assertThatFilters().containsSubsequence(HeaderWriterFilter.class, MyFilter.class, MyOtherFilter.class,
|
||||
MyOtherFilter.class, MyAnotherFilter.class, MyFilter.class, ExceptionTranslationFilter.class);
|
||||
}
|
||||
|
||||
private ListAssert<Class<?>> assertThatFilters() {
|
||||
FilterChainProxy filterChain = this.spring.getContext().getBean(FilterChainProxy.class);
|
||||
List<Class<?>> filters = filterChain.getFilters("/").stream().map(Object::getClass)
|
||||
@@ -87,6 +131,26 @@ public class HttpSecurityAddFilterTest {
|
||||
|
||||
}
|
||||
|
||||
static class MyOtherFilter implements Filter {
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
|
||||
throws IOException, ServletException {
|
||||
filterChain.doFilter(servletRequest, servletResponse);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class MyAnotherFilter implements Filter {
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
|
||||
throws IOException, ServletException {
|
||||
filterChain.doFilter(servletRequest, servletResponse);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class MyFilterMultipleAfterConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@@ -129,4 +193,83 @@ public class HttpSecurityAddFilterTest {
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class MyOtherFilterRelativeToMyFilterAfterConfig {
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.addFilterAfter(new MyFilter(), WebAsyncManagerIntegrationFilter.class)
|
||||
.addFilterAfter(new MyOtherFilter(), MyFilter.class);
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class MyOtherFilterRelativeToMyFilterBeforeConfig {
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.addFilterBefore(new MyFilter(), WebAsyncManagerIntegrationFilter.class)
|
||||
.addFilterBefore(new MyOtherFilter(), MyFilter.class);
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class MyOtherFilterRelativeToMyFilterAtConfig {
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.addFilterAt(new MyFilter(), WebAsyncManagerIntegrationFilter.class)
|
||||
.addFilterAt(new MyOtherFilter(), MyFilter.class);
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class MyOtherFilterBeforeToMyFilterMultipleAfterConfig {
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.addFilterAfter(new MyFilter(), WebAsyncManagerIntegrationFilter.class)
|
||||
.addFilterAfter(new MyFilter(), ExceptionTranslationFilter.class)
|
||||
.addFilterBefore(new MyOtherFilter(), MyFilter.class);
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class MyAnotherFilterRelativeToMyCustomFiltersMultipleConfig {
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.addFilterAfter(new MyFilter(), HeaderWriterFilter.class)
|
||||
.addFilterBefore(new MyOtherFilter(), ExceptionTranslationFilter.class)
|
||||
.addFilterAfter(new MyOtherFilter(), MyFilter.class)
|
||||
.addFilterAt(new MyAnotherFilter(), MyOtherFilter.class)
|
||||
.addFilterAfter(new MyFilter(), MyAnotherFilter.class);
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+12
@@ -187,6 +187,18 @@ public class HttpSecurityConfigurationTests {
|
||||
this.mockMvc.perform(get("/login")).andExpect(status().isOk());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loginWhenUsingDefaultsThenDefaultLoginFailurePageGenerated() throws Exception {
|
||||
this.spring.register(SecurityEnabledConfig.class).autowire();
|
||||
this.mockMvc.perform(get("/login?error")).andExpect(status().isOk());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loginWhenUsingDefaultsThenDefaultLogoutSuccessPageGenerated() throws Exception {
|
||||
this.spring.register(SecurityEnabledConfig.class).autowire();
|
||||
this.mockMvc.perform(get("/login?logout")).andExpect(status().isOk());
|
||||
}
|
||||
|
||||
@RestController
|
||||
static class NameController {
|
||||
|
||||
|
||||
+191
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -33,6 +33,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
@@ -61,7 +62,7 @@ import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.FilterChainProxy;
|
||||
import org.springframework.security.web.FilterInvocation;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.access.DefaultWebInvocationPrivilegeEvaluator;
|
||||
import org.springframework.security.web.access.RequestMatcherDelegatingWebInvocationPrivilegeEvaluator;
|
||||
import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
|
||||
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
@@ -83,6 +84,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
||||
* @author Rob Winch
|
||||
* @author Joe Grandja
|
||||
* @author Evgeniy Cheban
|
||||
* @author Marcus Da Coregio
|
||||
*/
|
||||
public class WebSecurityConfigurationTests {
|
||||
|
||||
@@ -218,10 +220,10 @@ public class WebSecurityConfigurationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadConfigWhenDefaultWebInvocationPrivilegeEvaluatorThenDefaultIsRegistered() {
|
||||
public void loadConfigWhenDefaultWebInvocationPrivilegeEvaluatorThenRequestMatcherIsRegistered() {
|
||||
this.spring.register(WebInvocationPrivilegeEvaluatorDefaultsConfig.class).autowire();
|
||||
assertThat(this.spring.getContext().getBean(WebInvocationPrivilegeEvaluator.class))
|
||||
.isInstanceOf(DefaultWebInvocationPrivilegeEvaluator.class);
|
||||
.isInstanceOf(RequestMatcherDelegatingWebInvocationPrivilegeEvaluator.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -229,7 +231,7 @@ public class WebSecurityConfigurationTests {
|
||||
this.spring.register(AuthorizeRequestsFilterChainConfig.class).autowire();
|
||||
|
||||
assertThat(this.spring.getContext().getBean(WebInvocationPrivilegeEvaluator.class))
|
||||
.isInstanceOf(DefaultWebInvocationPrivilegeEvaluator.class);
|
||||
.isInstanceOf(RequestMatcherDelegatingWebInvocationPrivilegeEvaluator.class);
|
||||
}
|
||||
|
||||
// SEC-2303
|
||||
@@ -375,6 +377,69 @@ public class WebSecurityConfigurationTests {
|
||||
assertThat(filterChains.get(1).matches(request)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadConfigWhenTwoSecurityFilterChainsThenRequestMatcherDelegatingWebInvocationPrivilegeEvaluator() {
|
||||
this.spring.register(TwoSecurityFilterChainConfig.class).autowire();
|
||||
assertThat(this.spring.getContext().getBean(WebInvocationPrivilegeEvaluator.class))
|
||||
.isInstanceOf(RequestMatcherDelegatingWebInvocationPrivilegeEvaluator.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadConfigWhenTwoSecurityFilterChainDebugThenRequestMatcherDelegatingWebInvocationPrivilegeEvaluator() {
|
||||
this.spring.register(TwoSecurityFilterChainConfig.class).autowire();
|
||||
assertThat(this.spring.getContext().getBean(WebInvocationPrivilegeEvaluator.class))
|
||||
.isInstanceOf(RequestMatcherDelegatingWebInvocationPrivilegeEvaluator.class);
|
||||
}
|
||||
|
||||
// gh-10554
|
||||
@Test
|
||||
public void loadConfigWhenMultipleSecurityFilterChainsThenWebInvocationPrivilegeEvaluatorApplySecurity() {
|
||||
this.spring.register(MultipleSecurityFilterChainConfig.class).autowire();
|
||||
WebInvocationPrivilegeEvaluator privilegeEvaluator = this.spring.getContext()
|
||||
.getBean(WebInvocationPrivilegeEvaluator.class);
|
||||
assertUserPermissions(privilegeEvaluator);
|
||||
assertAdminPermissions(privilegeEvaluator);
|
||||
assertAnotherUserPermission(privilegeEvaluator);
|
||||
}
|
||||
|
||||
// gh-10554
|
||||
@Test
|
||||
public void loadConfigWhenMultipleSecurityFilterChainAndIgnoringThenWebInvocationPrivilegeEvaluatorAcceptsNullAuthenticationOnIgnored() {
|
||||
this.spring.register(MultipleSecurityFilterChainIgnoringConfig.class).autowire();
|
||||
WebInvocationPrivilegeEvaluator privilegeEvaluator = this.spring.getContext()
|
||||
.getBean(WebInvocationPrivilegeEvaluator.class);
|
||||
assertUserPermissions(privilegeEvaluator);
|
||||
assertAdminPermissions(privilegeEvaluator);
|
||||
assertAnotherUserPermission(privilegeEvaluator);
|
||||
// null authentication
|
||||
assertThat(privilegeEvaluator.isAllowed("/user", null)).isFalse();
|
||||
assertThat(privilegeEvaluator.isAllowed("/admin", null)).isFalse();
|
||||
assertThat(privilegeEvaluator.isAllowed("/another", null)).isFalse();
|
||||
assertThat(privilegeEvaluator.isAllowed("/ignoring1", null)).isTrue();
|
||||
assertThat(privilegeEvaluator.isAllowed("/ignoring1/child", null)).isTrue();
|
||||
}
|
||||
|
||||
private void assertAnotherUserPermission(WebInvocationPrivilegeEvaluator privilegeEvaluator) {
|
||||
Authentication anotherUser = new TestingAuthenticationToken("anotherUser", "password", "ROLE_ANOTHER");
|
||||
assertThat(privilegeEvaluator.isAllowed("/user", anotherUser)).isFalse();
|
||||
assertThat(privilegeEvaluator.isAllowed("/admin", anotherUser)).isFalse();
|
||||
assertThat(privilegeEvaluator.isAllowed("/another", anotherUser)).isTrue();
|
||||
}
|
||||
|
||||
private void assertAdminPermissions(WebInvocationPrivilegeEvaluator privilegeEvaluator) {
|
||||
Authentication admin = new TestingAuthenticationToken("admin", "password", "ROLE_ADMIN");
|
||||
assertThat(privilegeEvaluator.isAllowed("/user", admin)).isFalse();
|
||||
assertThat(privilegeEvaluator.isAllowed("/admin", admin)).isTrue();
|
||||
assertThat(privilegeEvaluator.isAllowed("/another", admin)).isTrue();
|
||||
}
|
||||
|
||||
private void assertUserPermissions(WebInvocationPrivilegeEvaluator privilegeEvaluator) {
|
||||
Authentication user = new TestingAuthenticationToken("user", "password", "ROLE_USER");
|
||||
assertThat(privilegeEvaluator.isAllowed("/user", user)).isTrue();
|
||||
assertThat(privilegeEvaluator.isAllowed("/admin", user)).isFalse();
|
||||
assertThat(privilegeEvaluator.isAllowed("/another", user)).isTrue();
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@Import(AuthenticationTestConfiguration.class)
|
||||
static class SortedWebSecurityConfigurerAdaptersConfig {
|
||||
@@ -1008,4 +1073,125 @@ public class WebSecurityConfigurationTests {
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class TwoSecurityFilterChainConfig {
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
public SecurityFilterChain path1(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.requestMatchers((requests) -> requests.antMatchers("/path1/**"))
|
||||
.authorizeRequests((requests) -> requests.anyRequest().authenticated());
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.LOWEST_PRECEDENCE)
|
||||
public SecurityFilterChain permitAll(HttpSecurity http) throws Exception {
|
||||
http.authorizeRequests((requests) -> requests.anyRequest().permitAll());
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity(debug = true)
|
||||
static class TwoSecurityFilterChainDebugConfig {
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
public SecurityFilterChain path1(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.requestMatchers((requests) -> requests.antMatchers("/path1/**"))
|
||||
.authorizeRequests((requests) -> requests.anyRequest().authenticated());
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.LOWEST_PRECEDENCE)
|
||||
public SecurityFilterChain permitAll(HttpSecurity http) throws Exception {
|
||||
http.authorizeRequests((requests) -> requests.anyRequest().permitAll());
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@Import(AuthenticationTestConfiguration.class)
|
||||
static class MultipleSecurityFilterChainConfig {
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
public SecurityFilterChain notAuthorized(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.requestMatchers((requests) -> requests.antMatchers("/user"))
|
||||
.authorizeRequests((requests) -> requests.anyRequest().hasRole("USER"));
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE + 1)
|
||||
public SecurityFilterChain path1(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.requestMatchers((requests) -> requests.antMatchers("/admin"))
|
||||
.authorizeRequests((requests) -> requests.anyRequest().hasRole("ADMIN"));
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.LOWEST_PRECEDENCE)
|
||||
public SecurityFilterChain permitAll(HttpSecurity http) throws Exception {
|
||||
http.authorizeRequests((requests) -> requests.anyRequest().permitAll());
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@Import(AuthenticationTestConfiguration.class)
|
||||
static class MultipleSecurityFilterChainIgnoringConfig {
|
||||
|
||||
@Bean
|
||||
public WebSecurityCustomizer webSecurityCustomizer() {
|
||||
return (web) -> web.ignoring().antMatchers("/ignoring1/**");
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
public SecurityFilterChain notAuthorized(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.requestMatchers((requests) -> requests.antMatchers("/user"))
|
||||
.authorizeRequests((requests) -> requests.anyRequest().hasRole("USER"));
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE + 1)
|
||||
public SecurityFilterChain admin(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.requestMatchers((requests) -> requests.antMatchers("/admin"))
|
||||
.authorizeRequests((requests) -> requests.anyRequest().hasRole("ADMIN"));
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.LOWEST_PRECEDENCE)
|
||||
public SecurityFilterChain permitAll(HttpSecurity http) throws Exception {
|
||||
http.authorizeRequests((requests) -> requests.anyRequest().permitAll());
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+79
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -21,16 +21,21 @@ import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.security.config.annotation.ObjectPostProcessor;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.config.test.SpringTestRule;
|
||||
import org.springframework.security.web.PortMapperImpl;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.access.channel.ChannelDecisionManagerImpl;
|
||||
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
|
||||
import org.springframework.security.web.access.channel.InsecureChannelProcessor;
|
||||
import org.springframework.security.web.access.channel.SecureChannelProcessor;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.spy;
|
||||
@@ -92,6 +97,24 @@ public class ChannelSecurityConfigurerTests {
|
||||
this.mvc.perform(get("/")).andExpect(redirectedUrl("https://localhost/"));
|
||||
}
|
||||
|
||||
// gh-10956
|
||||
@Test
|
||||
public void requestWhenRequiresChannelWithMultiMvcMatchersThenRedirectsToHttps() throws Exception {
|
||||
this.spring.register(RequiresChannelMultiMvcMatchersConfig.class).autowire();
|
||||
this.mvc.perform(get("/test-1")).andExpect(redirectedUrl("https://localhost/test-1"));
|
||||
this.mvc.perform(get("/test-2")).andExpect(redirectedUrl("https://localhost/test-2"));
|
||||
this.mvc.perform(get("/test-3")).andExpect(redirectedUrl("https://localhost/test-3"));
|
||||
}
|
||||
|
||||
// gh-10956
|
||||
@Test
|
||||
public void requestWhenRequiresChannelWithMultiMvcMatchersInLambdaThenRedirectsToHttps() throws Exception {
|
||||
this.spring.register(RequiresChannelMultiMvcMatchersInLambdaConfig.class).autowire();
|
||||
this.mvc.perform(get("/test-1")).andExpect(redirectedUrl("https://localhost/test-1"));
|
||||
this.mvc.perform(get("/test-2")).andExpect(redirectedUrl("https://localhost/test-2"));
|
||||
this.mvc.perform(get("/test-3")).andExpect(redirectedUrl("https://localhost/test-3"));
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class ObjectPostProcessorConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@@ -154,4 +177,59 @@ public class ChannelSecurityConfigurerTests {
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@EnableWebMvc
|
||||
static class RequiresChannelMultiMvcMatchersConfig {
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.portMapper()
|
||||
.portMapper(new PortMapperImpl())
|
||||
.and()
|
||||
.requiresChannel()
|
||||
.mvcMatchers("/test-1")
|
||||
.requiresSecure()
|
||||
.mvcMatchers("/test-2")
|
||||
.requiresSecure()
|
||||
.mvcMatchers("/test-3")
|
||||
.requiresSecure()
|
||||
.anyRequest()
|
||||
.requiresInsecure();
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@EnableWebMvc
|
||||
static class RequiresChannelMultiMvcMatchersInLambdaConfig {
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.portMapper((port) -> port
|
||||
.portMapper(new PortMapperImpl())
|
||||
)
|
||||
.requiresChannel((channel) -> channel
|
||||
.mvcMatchers("/test-1")
|
||||
.requiresSecure()
|
||||
.mvcMatchers("/test-2")
|
||||
.requiresSecure()
|
||||
.mvcMatchers("/test-3")
|
||||
.requiresSecure()
|
||||
.anyRequest()
|
||||
.requiresInsecure()
|
||||
);
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+51
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -46,11 +46,14 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.springframework.security.config.Customizer.withDefaults;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
|
||||
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;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
* Tests for {@link DefaultLoginPageConfigurer}
|
||||
@@ -375,6 +378,18 @@ public class DefaultLoginPageConfigurerTests {
|
||||
.isZero();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void formLoginWhenLogoutEnabledThenCreatesDefaultLogoutPage() throws Exception {
|
||||
this.spring.register(DefaultLogoutPageConfig.class).autowire();
|
||||
this.mvc.perform(get("/logout").with(user("user"))).andExpect(status().isOk());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void formLoginWhenLogoutDisabledThenDefaultLogoutPageDoesNotExist() throws Exception {
|
||||
this.spring.register(LogoutDisabledConfig.class).autowire();
|
||||
this.mvc.perform(get("/logout").with(user("user"))).andExpect(status().isNotFound());
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class DefaultLoginPageConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@@ -533,6 +548,41 @@ public class DefaultLoginPageConfigurerTests {
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class DefaultLogoutPageConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.authorizeRequests((authorize) -> authorize
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.formLogin(withDefaults());
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class LogoutDisabledConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.authorizeRequests((authorize) -> authorize
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.formLogin(withDefaults())
|
||||
.logout((logout) -> logout
|
||||
.disable()
|
||||
);
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class ReflectingObjectPostProcessor implements ObjectPostProcessor<Object> {
|
||||
|
||||
@Override
|
||||
|
||||
+132
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -23,7 +23,10 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.mock.web.MockFilterChain;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
@@ -33,6 +36,7 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.web.FilterChainProxy;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
|
||||
@@ -167,6 +171,38 @@ public class HttpSecurityRequestMatchersTests {
|
||||
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestMatcherWhenMultiMvcMatcherInLambdaThenAllPathsAreDenied() throws Exception {
|
||||
loadConfig(MultiMvcMatcherInLambdaConfig.class);
|
||||
this.request.setRequestURI("/test-1");
|
||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
|
||||
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
setup();
|
||||
this.request.setRequestURI("/test-2");
|
||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
|
||||
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
setup();
|
||||
this.request.setRequestURI("/test-3");
|
||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
|
||||
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestMatcherWhenMultiMvcMatcherThenAllPathsAreDenied() throws Exception {
|
||||
loadConfig(MultiMvcMatcherConfig.class);
|
||||
this.request.setRequestURI("/test-1");
|
||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
|
||||
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
setup();
|
||||
this.request.setRequestURI("/test-2");
|
||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
|
||||
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
setup();
|
||||
this.request.setRequestURI("/test-3");
|
||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
|
||||
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
public void loadConfig(Class<?>... configs) {
|
||||
this.context = new AnnotationConfigWebApplicationContext();
|
||||
this.context.register(configs);
|
||||
@@ -175,6 +211,101 @@ public class HttpSecurityRequestMatchersTests {
|
||||
this.context.getAutowireCapableBeanFactory().autowireBean(this);
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
static class MultiMvcMatcherInLambdaConfig {
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
SecurityFilterChain first(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.requestMatchers((requests) -> requests
|
||||
.mvcMatchers("/test-1")
|
||||
.mvcMatchers("/test-2")
|
||||
.mvcMatchers("/test-3")
|
||||
)
|
||||
.authorizeRequests((authorize) -> authorize.anyRequest().denyAll())
|
||||
.httpBasic(withDefaults());
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain second(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.requestMatchers((requests) -> requests
|
||||
.mvcMatchers("/test-1")
|
||||
)
|
||||
.authorizeRequests((authorize) -> authorize
|
||||
.anyRequest().permitAll()
|
||||
);
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@RestController
|
||||
static class PathController {
|
||||
|
||||
@RequestMapping({ "/test-1", "/test-2", "/test-3" })
|
||||
String path() {
|
||||
return "path";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
static class MultiMvcMatcherConfig {
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
SecurityFilterChain first(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.requestMatchers()
|
||||
.mvcMatchers("/test-1")
|
||||
.mvcMatchers("/test-2")
|
||||
.mvcMatchers("/test-3")
|
||||
.and()
|
||||
.authorizeRequests()
|
||||
.anyRequest().denyAll()
|
||||
.and()
|
||||
.httpBasic(withDefaults());
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain second(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.requestMatchers()
|
||||
.mvcMatchers("/test-1")
|
||||
.and()
|
||||
.authorizeRequests()
|
||||
.anyRequest().permitAll();
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@RestController
|
||||
static class PathController {
|
||||
|
||||
@RequestMapping({ "/test-1", "/test-2", "/test-3" })
|
||||
String path() {
|
||||
return "path";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
|
||||
+5
-7
@@ -21,11 +21,13 @@ import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.bouncycastle.asn1.x500.X500Name;
|
||||
import org.bouncycastle.asn1.x500.style.BCStyle;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import sun.security.x509.X500Name;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -239,12 +241,8 @@ public class NamespaceHttpX509Tests {
|
||||
}
|
||||
|
||||
private String extractCommonName(X509Certificate certificate) {
|
||||
try {
|
||||
return ((X500Name) certificate.getSubjectDN()).getCommonName();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalArgumentException(ex);
|
||||
}
|
||||
X500Principal principal = certificate.getSubjectX500Principal();
|
||||
return new X500Name(principal.getName()).getRDNs(BCStyle.CN)[0].getFirst().getValue().toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+77
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.security.config.annotation.web.configurers;
|
||||
|
||||
import java.util.Base64;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.After;
|
||||
@@ -23,16 +25,24 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.mock.web.MockFilterChain;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.mock.web.MockServletContext;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
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.FilterChainProxy;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
|
||||
@@ -125,6 +135,35 @@ public class UrlAuthorizationConfigurerTests {
|
||||
loadConfig(AnonymousUrlAuthorizationConfig.class);
|
||||
}
|
||||
|
||||
// gh-10956
|
||||
@Test
|
||||
public void multiMvcMatchersConfig() throws Exception {
|
||||
loadConfig(MultiMvcMatcherConfig.class);
|
||||
this.request.addHeader("Authorization",
|
||||
"Basic " + new String(Base64.getEncoder().encode("user:password".getBytes())));
|
||||
this.request.setRequestURI("/test-1");
|
||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
|
||||
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN);
|
||||
setup();
|
||||
this.request.addHeader("Authorization",
|
||||
"Basic " + new String(Base64.getEncoder().encode("user:password".getBytes())));
|
||||
this.request.setRequestURI("/test-2");
|
||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
|
||||
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN);
|
||||
setup();
|
||||
this.request.addHeader("Authorization",
|
||||
"Basic " + new String(Base64.getEncoder().encode("user:password".getBytes())));
|
||||
this.request.setRequestURI("/test-3");
|
||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
|
||||
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN);
|
||||
setup();
|
||||
this.request.addHeader("Authorization",
|
||||
"Basic " + new String(Base64.getEncoder().encode("user:password".getBytes())));
|
||||
this.request.setRequestURI("/test-x");
|
||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
|
||||
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
|
||||
}
|
||||
|
||||
public void loadConfig(Class<?>... configs) {
|
||||
this.context = new AnnotationConfigWebApplicationContext();
|
||||
this.context.register(configs);
|
||||
@@ -228,4 +267,41 @@ public class UrlAuthorizationConfigurerTests {
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@EnableWebMvc
|
||||
static class MultiMvcMatcherConfig {
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain security(HttpSecurity http, ApplicationContext context) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.httpBasic(Customizer.withDefaults())
|
||||
.apply(new UrlAuthorizationConfigurer<>(context)).getRegistry()
|
||||
.mvcMatchers("/test-1").hasRole("ADMIN")
|
||||
.mvcMatchers("/test-2").hasRole("ADMIN")
|
||||
.mvcMatchers("/test-3").hasRole("ADMIN")
|
||||
.anyRequest().hasRole("USER");
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
UserDetailsService userDetailsService() {
|
||||
UserDetails user = User.withDefaultPasswordEncoder().username("user").password("password").roles("USER")
|
||||
.build();
|
||||
return new InMemoryUserDetailsManager(user);
|
||||
}
|
||||
|
||||
@RestController
|
||||
static class PathController {
|
||||
|
||||
@RequestMapping({ "/test-1", "/test-2", "/test-3", "/test-x" })
|
||||
String path() {
|
||||
return "path";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+100
-10
@@ -37,6 +37,7 @@ import org.mockito.ArgumentCaptor;
|
||||
import org.opensaml.saml.saml2.core.Assertion;
|
||||
import org.opensaml.saml.saml2.core.AuthnRequest;
|
||||
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -49,6 +50,7 @@ import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
||||
import org.springframework.security.authentication.ProviderManager;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.ObjectPostProcessor;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
@@ -77,7 +79,9 @@ import org.springframework.security.saml2.provider.service.registration.RelyingP
|
||||
import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations;
|
||||
import org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationFilter;
|
||||
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestContextResolver;
|
||||
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationTokenConverter;
|
||||
import org.springframework.security.web.FilterChainProxy;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.AuthenticationConverter;
|
||||
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
|
||||
import org.springframework.security.web.context.HttpRequestResponseHolder;
|
||||
@@ -91,6 +95,7 @@ import org.springframework.web.util.UriComponents;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
@@ -117,6 +122,8 @@ public class Saml2LoginConfigurerTests {
|
||||
|
||||
private static final String SIGNED_RESPONSE = "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c2FtbDJwOlJlc3BvbnNlIHhtbG5zOnNhbWwycD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIiBEZXN0aW5hdGlvbj0iaHR0cHM6Ly9ycC5leGFtcGxlLm9yZy9hY3MiIElEPSJfYzE3MzM2YTAtNTM1My00MTQ5LWI3MmMtMDNkOWY5YWYzMDdlIiBJc3N1ZUluc3RhbnQ9IjIwMjAtMDgtMDRUMjI6MDQ6NDUuMDE2WiIgVmVyc2lvbj0iMi4wIj48c2FtbDI6SXNzdWVyIHhtbG5zOnNhbWwyPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIj5hcC1lbnRpdHktaWQ8L3NhbWwyOklzc3Vlcj48ZHM6U2lnbmF0dXJlIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj4KPGRzOlNpZ25lZEluZm8+CjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+CjxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNyc2Etc2hhMjU2Ii8+CjxkczpSZWZlcmVuY2UgVVJJPSIjX2MxNzMzNmEwLTUzNTMtNDE0OS1iNzJjLTAzZDlmOWFmMzA3ZSI+CjxkczpUcmFuc2Zvcm1zPgo8ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI2VudmVsb3BlZC1zaWduYXR1cmUiLz4KPGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPgo8L2RzOlRyYW5zZm9ybXM+CjxkczpEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNzaGEyNTYiLz4KPGRzOkRpZ2VzdFZhbHVlPjYzTmlyenFzaDVVa0h1a3NuRWUrM0hWWU5aYWFsQW1OQXFMc1lGMlRuRDA9PC9kczpEaWdlc3RWYWx1ZT4KPC9kczpSZWZlcmVuY2U+CjwvZHM6U2lnbmVkSW5mbz4KPGRzOlNpZ25hdHVyZVZhbHVlPgpLMVlvWWJVUjBTclY4RTdVMkhxTTIvZUNTOTNoV25mOExnNnozeGZWMUlyalgzSXhWYkNvMVlYcnRBSGRwRVdvYTJKKzVOMmFNbFBHJiMxMzsKN2VpbDBZRC9xdUVRamRYbTNwQTBjZmEvY25pa2RuKzVhbnM0ZWQwanU1amo2dkpvZ2w2Smt4Q25LWUpwTU9HNzhtampmb0phengrWCYjMTM7CkM2NktQVStBYUdxeGVwUEQ1ZlhRdTFKSy9Jb3lBaitaa3k4Z2Jwc3VyZHFCSEJLRWxjdnVOWS92UGY0OGtBeFZBKzdtRGhNNUMvL1AmIzEzOwp0L084Y3NZYXB2UjZjdjZrdk45QXZ1N3FRdm9qVk1McHVxZWNJZDJwTUVYb0NSSnE2Nkd4MStNTUVPeHVpMWZZQlRoMEhhYjRmK3JyJiMxMzsKOEY2V1NFRC8xZllVeHliRkJqZ1Q4d2lEWHFBRU8wSVY4ZWRQeEE9PQo8L2RzOlNpZ25hdHVyZVZhbHVlPgo8L2RzOlNpZ25hdHVyZT48c2FtbDI6QXNzZXJ0aW9uIHhtbG5zOnNhbWwyPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIiBJRD0iQWUzZjQ5OGI4LTliMTctNDA3OC05ZDM1LTg2YTA4NDA4NDk5NSIgSXNzdWVJbnN0YW50PSIyMDIwLTA4LTA0VDIyOjA0OjQ1LjA3N1oiIFZlcnNpb249IjIuMCI+PHNhbWwyOklzc3Vlcj5hcC1lbnRpdHktaWQ8L3NhbWwyOklzc3Vlcj48c2FtbDI6U3ViamVjdD48c2FtbDI6TmFtZUlEPnRlc3RAc2FtbC51c2VyPC9zYW1sMjpOYW1lSUQ+PHNhbWwyOlN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj48c2FtbDI6U3ViamVjdENvbmZpcm1hdGlvbkRhdGEgTm90QmVmb3JlPSIyMDIwLTA4LTA0VDIxOjU5OjQ1LjA5MFoiIE5vdE9uT3JBZnRlcj0iMjA0MC0wNy0zMFQyMjowNTowNi4wODhaIiBSZWNpcGllbnQ9Imh0dHBzOi8vcnAuZXhhbXBsZS5vcmcvYWNzIi8+PC9zYW1sMjpTdWJqZWN0Q29uZmlybWF0aW9uPjwvc2FtbDI6U3ViamVjdD48c2FtbDI6Q29uZGl0aW9ucyBOb3RCZWZvcmU9IjIwMjAtMDgtMDRUMjE6NTk6NDUuMDgwWiIgTm90T25PckFmdGVyPSIyMDQwLTA3LTMwVDIyOjA1OjA2LjA4N1oiLz48L3NhbWwyOkFzc2VydGlvbj48L3NhbWwycDpSZXNwb25zZT4=";
|
||||
|
||||
private static final AuthenticationConverter AUTHENTICATION_CONVERTER = mock(AuthenticationConverter.class);
|
||||
|
||||
@Autowired
|
||||
private ConfigurableApplicationContext context;
|
||||
|
||||
@@ -216,7 +223,7 @@ public class Saml2LoginConfigurerTests {
|
||||
public void authenticateWithInvalidDeflatedSAMLResponseThenFailureHandlerUses() throws Exception {
|
||||
this.spring.register(CustomAuthenticationFailureHandler.class).autowire();
|
||||
byte[] invalidDeflated = "invalid".getBytes();
|
||||
String encoded = Saml2Utils.samlEncode(invalidDeflated);
|
||||
String encoded = Saml2Utils.samlEncodeNotRfc2045(invalidDeflated);
|
||||
MockHttpServletRequestBuilder request = get("/login/saml2/sso/registration-id").queryParam("SAMLResponse",
|
||||
encoded);
|
||||
this.mvc.perform(request);
|
||||
@@ -230,6 +237,33 @@ public class Saml2LoginConfigurerTests {
|
||||
assertThat(exception.getCause()).isInstanceOf(IOException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saml2LoginWhenLoginProcessingUrlWithoutRegistrationIdAndDefaultAuthenticationConverterThenValidates() {
|
||||
assertThatExceptionOfType(BeanCreationException.class)
|
||||
.isThrownBy(() -> this.spring.register(CustomLoginProcessingUrlDefaultAuthenticationConverter.class)
|
||||
.autowire())
|
||||
.havingRootCause().isInstanceOf(IllegalStateException.class)
|
||||
.withMessage("loginProcessingUrl must contain {registrationId} path variable");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenCustomLoginProcessingUrlAndCustomAuthenticationConverterThenAuthenticate()
|
||||
throws Exception {
|
||||
this.spring.register(CustomLoginProcessingUrlCustomAuthenticationConverter.class).autowire();
|
||||
RelyingPartyRegistration relyingPartyRegistration = TestRelyingPartyRegistrations.noCredentials()
|
||||
.assertingPartyDetails((party) -> party.verificationX509Credentials(
|
||||
(c) -> c.add(TestSaml2X509Credentials.relyingPartyVerifyingCredential())))
|
||||
.build();
|
||||
String response = new String(Saml2Utils.samlDecode(SIGNED_RESPONSE));
|
||||
given(AUTHENTICATION_CONVERTER.convert(any(HttpServletRequest.class)))
|
||||
.willReturn(new Saml2AuthenticationToken(relyingPartyRegistration, response));
|
||||
// @formatter:off
|
||||
MockHttpServletRequestBuilder request = post("/my/custom/url").param("SAMLResponse", SIGNED_RESPONSE);
|
||||
// @formatter:on
|
||||
this.mvc.perform(request).andExpect(redirectedUrl("/"));
|
||||
verify(AUTHENTICATION_CONVERTER).convert(any(HttpServletRequest.class));
|
||||
}
|
||||
|
||||
private void validateSaml2WebSsoAuthenticationFilterConfiguration() {
|
||||
// get the OpenSamlAuthenticationProvider
|
||||
Saml2WebSsoAuthenticationFilter filter = getSaml2SsoFilter(this.springSecurityFilterChain);
|
||||
@@ -337,10 +371,10 @@ public class Saml2LoginConfigurerTests {
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.authorizeRequests((authz) -> authz
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.saml2Login(withDefaults());
|
||||
.authorizeRequests((authz) -> authz
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.saml2Login(withDefaults());
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@@ -359,11 +393,11 @@ public class Saml2LoginConfigurerTests {
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.authorizeRequests((authz) -> authz
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.saml2Login((saml2) -> {
|
||||
});
|
||||
.authorizeRequests((authz) -> authz
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.saml2Login((saml2) -> {
|
||||
});
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@@ -395,6 +429,62 @@ public class Saml2LoginConfigurerTests {
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@Import(Saml2LoginConfigBeans.class)
|
||||
static class CustomAuthenticationConverterBean {
|
||||
|
||||
private final Saml2AuthenticationTokenConverter authenticationConverter = mock(
|
||||
Saml2AuthenticationTokenConverter.class);
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain app(HttpSecurity http) throws Exception {
|
||||
http.authorizeHttpRequests((authz) -> authz.anyRequest().authenticated())
|
||||
.saml2Login(Customizer.withDefaults());
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
Saml2AuthenticationTokenConverter authenticationConverter() {
|
||||
return this.authenticationConverter;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@Import(Saml2LoginConfigBeans.class)
|
||||
static class CustomLoginProcessingUrlDefaultAuthenticationConverter {
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.authorizeRequests((authz) -> authz.anyRequest().authenticated())
|
||||
.saml2Login((saml2) -> saml2.loginProcessingUrl("/my/custom/url"));
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@Import(Saml2LoginConfigBeans.class)
|
||||
static class CustomLoginProcessingUrlCustomAuthenticationConverter {
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.authorizeRequests((authz) -> authz.anyRequest().authenticated())
|
||||
.saml2Login((saml2) -> saml2
|
||||
.loginProcessingUrl("/my/custom/url")
|
||||
.authenticationConverter(AUTHENTICATION_CONVERTER)
|
||||
);
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class Saml2LoginConfigBeans {
|
||||
|
||||
@Bean
|
||||
|
||||
+25
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -25,6 +25,7 @@ import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.security.authentication.AuthenticationEventPublisher;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
|
||||
import org.springframework.security.authentication.ProviderManager;
|
||||
@@ -54,6 +55,17 @@ public class AuthenticationManagerBeanDefinitionParserTests {
|
||||
+ "</authentication-manager>";
|
||||
// @formatter:on
|
||||
|
||||
// Issue #7282
|
||||
// @formatter:off
|
||||
private static final String CONTEXT_MULTI = "<authentication-manager id='amSecondary'>"
|
||||
+ " <authentication-provider>"
|
||||
+ " <user-service>"
|
||||
+ " <user name='john' password='{noop}doe' authorities='ROLE_C,ROLE_D' />"
|
||||
+ " </user-service>"
|
||||
+ " </authentication-provider>"
|
||||
+ "</authentication-manager>";
|
||||
// @formatter:on
|
||||
|
||||
@Rule
|
||||
public final SpringTestRule spring = new SpringTestRule();
|
||||
|
||||
@@ -64,6 +76,18 @@ public class AuthenticationManagerBeanDefinitionParserTests {
|
||||
assertThat(context.getBeansOfType(AuthenticationProvider.class)).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void eventPublishersAreRegisteredAsTopLevelBeans() {
|
||||
ConfigurableApplicationContext context = this.spring.context(CONTEXT).getContext();
|
||||
assertThat(context.getBeansOfType(AuthenticationEventPublisher.class)).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onlyOneEventPublisherIsRegisteredForMultipleAuthenticationManagers() {
|
||||
ConfigurableApplicationContext context = this.spring.context(CONTEXT + '\n' + CONTEXT_MULTI).getContext();
|
||||
assertThat(context.getBeansOfType(AuthenticationEventPublisher.class)).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void eventsArePublishedByDefault() throws Exception {
|
||||
ConfigurableApplicationContext appContext = this.spring.context(CONTEXT).getContext();
|
||||
|
||||
+3
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2009-2020 the original author or authors.
|
||||
* Copyright 2009-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -25,6 +25,7 @@ import org.springframework.security.util.InMemoryResource;
|
||||
/**
|
||||
* @author Luke Taylor
|
||||
* @author Eddú Meléndez
|
||||
* @author Emil Sierżęga
|
||||
*/
|
||||
public class InMemoryXmlApplicationContext extends AbstractXmlApplicationContext {
|
||||
|
||||
@@ -42,7 +43,7 @@ public class InMemoryXmlApplicationContext extends AbstractXmlApplicationContext
|
||||
+ "http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context-2.5.xsd\n"
|
||||
+ "http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security-";
|
||||
static final String BEANS_CLOSE = "</b:beans>\n";
|
||||
static final String SPRING_SECURITY_VERSION = "5.4";
|
||||
static final String SPRING_SECURITY_VERSION = SpringSecurityVersions.getCurrentXsdVersionFromSpringSchemas();
|
||||
|
||||
Resource inMemoryXml;
|
||||
|
||||
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.security.core.SpringSecurityCoreVersion;
|
||||
|
||||
/**
|
||||
* For computing different Spring Security versions
|
||||
*/
|
||||
public final class SpringSecurityVersions {
|
||||
|
||||
static final Pattern SCHEMA_VERSION_PATTERN = Pattern.compile("\\d+\\.\\d+(\\.\\d+)?");
|
||||
|
||||
public static String getCurrentXsdVersionFromSpringSchemas() {
|
||||
Properties properties = new Properties();
|
||||
try (InputStream is = SpringSecurityCoreVersion.class.getClassLoader()
|
||||
.getResourceAsStream("META-INF/spring.schemas")) {
|
||||
properties.load(is);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new RuntimeException("Could not read 'META-INF/spring.schemas'", ex);
|
||||
}
|
||||
|
||||
String inPackageLocation = properties
|
||||
.getProperty("https://www.springframework.org/schema/security/spring-security.xsd");
|
||||
Matcher matcher = SCHEMA_VERSION_PATTERN.matcher(inPackageLocation);
|
||||
if (matcher.find()) {
|
||||
return matcher.group(0);
|
||||
}
|
||||
throw new IllegalStateException("Failed to find version");
|
||||
}
|
||||
|
||||
private SpringSecurityVersions() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+18
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -26,7 +26,10 @@ import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
import org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository;
|
||||
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.security.config.Customizer.withDefaults;
|
||||
|
||||
/**
|
||||
@@ -146,7 +149,7 @@ public class LogoutSpecTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void logoutWhenDisabledThenPostToLogoutDoesNothing() {
|
||||
public void logoutWhenDisabledThenDefaultLogoutPageDoesNotExist() {
|
||||
// @formatter:off
|
||||
SecurityWebFilterChain securityWebFilter = this.http
|
||||
.authorizeExchange()
|
||||
@@ -156,7 +159,7 @@ public class LogoutSpecTests {
|
||||
.logout().disable()
|
||||
.build();
|
||||
WebTestClient webTestClient = WebTestClientBuilder
|
||||
.bindToWebFilters(securityWebFilter)
|
||||
.bindToControllerAndWebFilters(HomeController.class, securityWebFilter)
|
||||
.build();
|
||||
WebDriver driver = WebTestClientHtmlUnitDriverBuilder
|
||||
.webTestClientSetup(webTestClient)
|
||||
@@ -171,8 +174,8 @@ public class LogoutSpecTests {
|
||||
.submit(FormLoginTests.HomePage.class);
|
||||
// @formatter:on
|
||||
homePage.assertAt();
|
||||
FormLoginTests.DefaultLogoutPage.to(driver).assertAt().logout();
|
||||
homePage.assertAt();
|
||||
FormLoginTests.DefaultLogoutPage.to(driver);
|
||||
assertThat(driver.getPageSource()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -208,4 +211,14 @@ public class LogoutSpecTests {
|
||||
FormLoginTests.HomePage.to(driver, FormLoginTests.DefaultLoginPage.class).assertAt();
|
||||
}
|
||||
|
||||
@RestController
|
||||
public static class HomeController {
|
||||
|
||||
@GetMapping("/")
|
||||
public String ok() {
|
||||
return "ok";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,23 +2,15 @@ import java.util.concurrent.Callable
|
||||
|
||||
apply plugin: 'io.spring.convention.spring-module'
|
||||
|
||||
def includeProject = project(':spring-security-crypto')
|
||||
|
||||
configurations {
|
||||
included
|
||||
api.extendsFrom included
|
||||
}
|
||||
|
||||
dependencies {
|
||||
management platform(project(":spring-security-dependencies"))
|
||||
api project(':spring-security-crypto')
|
||||
api 'org.springframework:spring-aop'
|
||||
api 'org.springframework:spring-beans'
|
||||
api 'org.springframework:spring-context'
|
||||
api 'org.springframework:spring-core'
|
||||
api 'org.springframework:spring-expression'
|
||||
|
||||
included includeProject
|
||||
|
||||
optional 'com.fasterxml.jackson.core:jackson-databind'
|
||||
optional 'io.projectreactor:reactor-core'
|
||||
optional 'javax.annotation:jsr250-api'
|
||||
@@ -49,12 +41,6 @@ tasks.processResources {
|
||||
}
|
||||
}
|
||||
|
||||
tasks.jar.from { includeProject.sourceSets.main.output }
|
||||
|
||||
project.tasks.matching { t -> t.name == "sourcesJar"}.configureEach {
|
||||
from {includeProject.sourceSets.main.java }
|
||||
}
|
||||
|
||||
configure(project.tasks.withType(Test)) {
|
||||
doFirst {
|
||||
systemProperties['springSecurityVersion'] = version
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2010-2016 the original author or authors.
|
||||
* Copyright 2010-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -266,7 +266,7 @@ public abstract class AbstractJaasAuthenticationProvider implements Authenticati
|
||||
* Publishes the {@link JaasAuthenticationFailedEvent}. Can be overridden by
|
||||
* subclasses for different functionality
|
||||
* @param token The authentication token being processed
|
||||
* @param ase The excetion that caused the authentication failure
|
||||
* @param ase The exception that caused the authentication failure
|
||||
*/
|
||||
protected void publishFailureEvent(UsernamePasswordAuthenticationToken token, AuthenticationException ase) {
|
||||
if (this.applicationEventPublisher != null) {
|
||||
|
||||
+1
-1
@@ -224,7 +224,7 @@ public class JaasAuthenticationProvider extends AbstractJaasAuthenticationProvid
|
||||
* Publishes the {@link JaasAuthenticationFailedEvent}. Can be overridden by
|
||||
* subclasses for different functionality
|
||||
* @param token The authentication token being processed
|
||||
* @param ase The excetion that caused the authentication failure
|
||||
* @param ase The exception that caused the authentication failure
|
||||
*/
|
||||
@Override
|
||||
protected void publishFailureEvent(UsernamePasswordAuthenticationToken token, AuthenticationException ase) {
|
||||
|
||||
+1
-1
@@ -40,9 +40,9 @@ import org.springframework.security.core.GrantedAuthority;
|
||||
* <i>Note: This class will save full class name into a property called @class</i>
|
||||
*
|
||||
* @author Jitendra Singh
|
||||
* @since 4.2
|
||||
* @see CoreJackson2Module
|
||||
* @see SecurityJackson2Modules
|
||||
* @since 4.2
|
||||
*/
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
|
||||
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, isGetterVisibility = JsonAutoDetect.Visibility.NONE,
|
||||
|
||||
+1
-1
@@ -36,8 +36,8 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
* called @class</i> <i>The cause and stackTrace are ignored in the serialization.</i>
|
||||
*
|
||||
* @author Yannick Lombardi
|
||||
* @see CoreJackson2Module
|
||||
* @since 5.0
|
||||
* @see CoreJackson2Module
|
||||
*/
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true, value = { "cause", "stackTrace" })
|
||||
|
||||
@@ -44,8 +44,8 @@ import org.springframework.security.core.userdetails.User;
|
||||
* of all security modules.</b>
|
||||
*
|
||||
* @author Jitendra Singh.
|
||||
* @see SecurityJackson2Modules
|
||||
* @since 4.2
|
||||
* @see SecurityJackson2Modules
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class CoreJackson2Module extends SimpleModule {
|
||||
|
||||
+1
-1
@@ -47,9 +47,9 @@ import org.springframework.security.core.GrantedAuthority;
|
||||
* called @class</i>
|
||||
*
|
||||
* @author Jitendra Singh
|
||||
* @since 4.2
|
||||
* @see CoreJackson2Module
|
||||
* @see SecurityJackson2Modules
|
||||
* @since 4.2
|
||||
*/
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
|
||||
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
|
||||
|
||||
+1
-1
@@ -32,9 +32,9 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
* </pre>
|
||||
*
|
||||
* @author Jitendra Singh
|
||||
* @since 4.2
|
||||
* @see CoreJackson2Module
|
||||
* @see SecurityJackson2Modules
|
||||
* @since 4.2
|
||||
*/
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
|
||||
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.NONE,
|
||||
|
||||
@@ -32,10 +32,10 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
* </pre>
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 5.0.2
|
||||
* @see UnmodifiableListDeserializer
|
||||
* @see CoreJackson2Module
|
||||
* @see SecurityJackson2Modules
|
||||
* @since 5.0.2
|
||||
*/
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
|
||||
@JsonDeserialize(using = UnmodifiableListDeserializer.class)
|
||||
|
||||
@@ -32,10 +32,10 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
* </pre>
|
||||
*
|
||||
* @author Jitendra Singh
|
||||
* @since 4.2
|
||||
* @see UnmodifiableSetDeserializer
|
||||
* @see CoreJackson2Module
|
||||
* @see SecurityJackson2Modules
|
||||
* @since 4.2
|
||||
*/
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
|
||||
@JsonDeserialize(using = UnmodifiableSetDeserializer.class)
|
||||
|
||||
@@ -37,10 +37,10 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
* </pre>
|
||||
*
|
||||
* @author Jitendra Singh
|
||||
* @since 4.2
|
||||
* @see UserDeserializer
|
||||
* @see CoreJackson2Module
|
||||
* @see SecurityJackson2Modules
|
||||
* @since 4.2
|
||||
*/
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
|
||||
@JsonDeserialize(using = UserDeserializer.class)
|
||||
|
||||
+1
-1
@@ -39,9 +39,9 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
* </pre>
|
||||
*
|
||||
* @author Jitendra Singh
|
||||
* @since 4.2
|
||||
* @see CoreJackson2Module
|
||||
* @see SecurityJackson2Modules
|
||||
* @since 4.2
|
||||
*/
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class")
|
||||
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
|
||||
|
||||
@@ -526,35 +526,47 @@ public class BCrypt {
|
||||
* @param safety bit 16 is set when the safety measure is requested
|
||||
* @return an array containing the binary hashed password
|
||||
*/
|
||||
private byte[] crypt_raw(byte password[], byte salt[], int log_rounds, boolean sign_ext_bug, int safety) {
|
||||
int rounds, i, j;
|
||||
private byte[] crypt_raw(byte password[], byte salt[], int log_rounds, boolean sign_ext_bug, int safety,
|
||||
boolean for_check) {
|
||||
int cdata[] = bf_crypt_ciphertext.clone();
|
||||
int clen = cdata.length;
|
||||
byte ret[];
|
||||
|
||||
long rounds;
|
||||
if (log_rounds < 4 || log_rounds > 31) {
|
||||
throw new IllegalArgumentException("Bad number of rounds");
|
||||
if (!for_check) {
|
||||
throw new IllegalArgumentException("Bad number of rounds");
|
||||
}
|
||||
if (log_rounds != 0) {
|
||||
throw new IllegalArgumentException("Bad number of rounds");
|
||||
}
|
||||
rounds = 0;
|
||||
}
|
||||
rounds = 1 << log_rounds;
|
||||
else {
|
||||
rounds = roundsForLogRounds(log_rounds);
|
||||
if (rounds < 16 || rounds > Integer.MAX_VALUE) {
|
||||
throw new IllegalArgumentException("Bad number of rounds");
|
||||
}
|
||||
}
|
||||
|
||||
if (salt.length != BCRYPT_SALT_LEN) {
|
||||
throw new IllegalArgumentException("Bad salt length");
|
||||
}
|
||||
|
||||
init_key();
|
||||
ekskey(salt, password, sign_ext_bug, safety);
|
||||
for (i = 0; i < rounds; i++) {
|
||||
for (int i = 0; i < rounds; i++) {
|
||||
key(password, sign_ext_bug, safety);
|
||||
key(salt, false, safety);
|
||||
}
|
||||
|
||||
for (i = 0; i < 64; i++) {
|
||||
for (j = 0; j < (clen >> 1); j++) {
|
||||
for (int i = 0; i < 64; i++) {
|
||||
for (int j = 0; j < (clen >> 1); j++) {
|
||||
encipher(cdata, j << 1);
|
||||
}
|
||||
}
|
||||
|
||||
ret = new byte[clen * 4];
|
||||
for (i = 0, j = 0; i < clen; i++) {
|
||||
byte[] ret = new byte[clen * 4];
|
||||
for (int i = 0, j = 0; i < clen; i++) {
|
||||
ret[j++] = (byte) ((cdata[i] >> 24) & 0xff);
|
||||
ret[j++] = (byte) ((cdata[i] >> 16) & 0xff);
|
||||
ret[j++] = (byte) ((cdata[i] >> 8) & 0xff);
|
||||
@@ -563,6 +575,10 @@ public class BCrypt {
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static String hashpwforcheck(byte[] passwordb, String salt) {
|
||||
return hashpw(passwordb, salt, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash a password using the OpenBSD bcrypt scheme
|
||||
* @param password the password to hash
|
||||
@@ -584,6 +600,10 @@ public class BCrypt {
|
||||
* @return the hashed password
|
||||
*/
|
||||
public static String hashpw(byte passwordb[], String salt) {
|
||||
return hashpw(passwordb, salt, false);
|
||||
}
|
||||
|
||||
private static String hashpw(byte passwordb[], String salt, boolean for_check) {
|
||||
BCrypt B;
|
||||
String real_salt;
|
||||
byte saltb[], hashed[];
|
||||
@@ -633,7 +653,7 @@ public class BCrypt {
|
||||
}
|
||||
|
||||
B = new BCrypt();
|
||||
hashed = B.crypt_raw(passwordb, saltb, rounds, minor == 'x', minor == 'a' ? 0x10000 : 0);
|
||||
hashed = B.crypt_raw(passwordb, saltb, rounds, minor == 'x', minor == 'a' ? 0x10000 : 0, for_check);
|
||||
|
||||
rs.append("$2");
|
||||
if (minor >= 'a') {
|
||||
@@ -740,7 +760,8 @@ public class BCrypt {
|
||||
* @return true if the passwords match, false otherwise
|
||||
*/
|
||||
public static boolean checkpw(String plaintext, String hashed) {
|
||||
return equalsNoEarlyReturn(hashed, hashpw(plaintext, hashed));
|
||||
byte[] passwordb = plaintext.getBytes(StandardCharsets.UTF_8);
|
||||
return equalsNoEarlyReturn(hashed, hashpwforcheck(passwordb, hashed));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -751,7 +772,7 @@ public class BCrypt {
|
||||
* @since 5.3
|
||||
*/
|
||||
public static boolean checkpw(byte[] passwordb, String hashed) {
|
||||
return equalsNoEarlyReturn(hashed, hashpw(passwordb, hashed));
|
||||
return equalsNoEarlyReturn(hashed, hashpwforcheck(passwordb, hashed));
|
||||
}
|
||||
|
||||
static boolean equalsNoEarlyReturn(String a, String b) {
|
||||
|
||||
+14
@@ -208,4 +208,18 @@ public class BCryptPasswordEncoderTests {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> encoder.matches(null, "does-not-matter"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void upgradeWhenNoRoundsThenTrue() {
|
||||
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
|
||||
assertThat(encoder.upgradeEncoding("$2a$00$9N8N35BVs5TLqGL3pspAte5OWWA2a2aZIs.EGp7At7txYakFERMue")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkWhenNoRoundsThenTrue() {
|
||||
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
|
||||
assertThat(encoder.matches("password", "$2a$00$9N8N35BVs5TLqGL3pspAte5OWWA2a2aZIs.EGp7At7txYakFERMue"))
|
||||
.isTrue();
|
||||
assertThat(encoder.matches("wrong", "$2a$00$9N8N35BVs5TLqGL3pspAte5OWWA2a2aZIs.EGp7At7txYakFERMue")).isFalse();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -456,4 +456,11 @@ public class BCryptTests {
|
||||
assertThat(BCrypt.equalsNoEarlyReturn("test", "pass")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkpwWhenZeroRoundsThenMatches() {
|
||||
String password = "$2a$00$9N8N35BVs5TLqGL3pspAte5OWWA2a2aZIs.EGp7At7txYakFERMue";
|
||||
assertThat(BCrypt.checkpw("password", password)).isTrue();
|
||||
assertThat(BCrypt.checkpw("wrong", password)).isFalse();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -129,7 +129,7 @@ public class DelegatingPasswordEncoderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesWhenNoClosingPrefixStringThenIllegalArgumentExcetion() {
|
||||
public void matchesWhenNoClosingPrefixStringThenIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.passwordEncoder.matches(this.rawPassword, "{bcrypt" + this.rawPassword))
|
||||
.withMessage("There is no PasswordEncoder mapped for the id \"null\"");
|
||||
|
||||
+18
-18
@@ -7,18 +7,18 @@ javaPlatform {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api platform("org.springframework:spring-framework-bom:5.3.7")
|
||||
api platform("io.projectreactor:reactor-bom:2020.0.7")
|
||||
api platform("io.rsocket:rsocket-bom:1.1.0")
|
||||
api platform("org.springframework.data:spring-data-bom:2021.0.1")
|
||||
api platform("org.springframework:spring-framework-bom:$springFrameworkVersion")
|
||||
api platform("io.projectreactor:reactor-bom:2020.0.19")
|
||||
api platform("io.rsocket:rsocket-bom:1.1.2")
|
||||
api platform("org.springframework.data:spring-data-bom:2021.0.11")
|
||||
api platform("org.jetbrains.kotlin:kotlin-bom:$kotlinVersion")
|
||||
api platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.5.0")
|
||||
api platform("com.fasterxml.jackson:jackson-bom:2.12.3")
|
||||
api platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.5.2")
|
||||
api platform("com.fasterxml.jackson:jackson-bom:2.12.6.20220326")
|
||||
constraints {
|
||||
api "ch.qos.logback:logback-classic:1.2.3"
|
||||
api "ch.qos.logback:logback-classic:1.2.11"
|
||||
api "com.google.inject:guice:3.0"
|
||||
api "com.nimbusds:nimbus-jose-jwt:9.8.1"
|
||||
api "com.nimbusds:oauth2-oidc-sdk:9.3.3"
|
||||
api "com.nimbusds:nimbus-jose-jwt:9.10.1"
|
||||
api "com.nimbusds:oauth2-oidc-sdk:9.9.1"
|
||||
api "com.squareup.okhttp3:mockwebserver:3.14.9"
|
||||
api "com.squareup.okhttp3:okhttp:3.14.9"
|
||||
api "com.unboundid:unboundid-ldapsdk:4.0.14"
|
||||
@@ -49,12 +49,12 @@ dependencies {
|
||||
api "org.assertj:assertj-core:3.19.0"
|
||||
api "org.bouncycastle:bcpkix-jdk15on:1.68"
|
||||
api "org.bouncycastle:bcprov-jdk15on:1.68"
|
||||
api "org.eclipse.jetty:jetty-server:9.4.40.v20210413"
|
||||
api "org.eclipse.jetty:jetty-servlet:9.4.40.v20210413"
|
||||
api "org.eclipse.jetty:jetty-server:9.4.46.v20220331"
|
||||
api "org.eclipse.jetty:jetty-servlet:9.4.46.v20220331"
|
||||
api "org.eclipse.persistence:javax.persistence:2.2.1"
|
||||
api "org.hibernate:hibernate-entitymanager:5.4.31.Final"
|
||||
api "org.hsqldb:hsqldb:2.6.0"
|
||||
api "org.jasig.cas.client:cas-client-core:3.6.2"
|
||||
api "org.hibernate:hibernate-entitymanager:5.4.33"
|
||||
api "org.hsqldb:hsqldb:2.6.1"
|
||||
api "org.jasig.cas.client:cas-client-core:3.6.4"
|
||||
api "org.mockito:mockito-core:3.9.0"
|
||||
api "org.openid4java:openid4java-nodeps:0.9.6"
|
||||
api "org.opensaml:opensaml-core:$openSamlVersion"
|
||||
@@ -71,10 +71,10 @@ dependencies {
|
||||
api "org.seleniumhq.selenium:selenium-java:3.141.59"
|
||||
api "org.seleniumhq.selenium:selenium-support:3.141.59"
|
||||
api "org.skyscreamer:jsonassert:1.5.0"
|
||||
api "org.slf4j:jcl-over-slf4j:1.7.30"
|
||||
api "org.slf4j:log4j-over-slf4j:1.7.30"
|
||||
api "org.slf4j:slf4j-api:1.7.30"
|
||||
api "org.springframework.ldap:spring-ldap-core:2.3.4.RELEASE"
|
||||
api "org.slf4j:jcl-over-slf4j:1.7.36"
|
||||
api "org.slf4j:log4j-over-slf4j:1.7.36"
|
||||
api "org.slf4j:slf4j-api:1.7.36"
|
||||
api "org.springframework.ldap:spring-ldap-core:2.3.7.RELEASE"
|
||||
api "org.synchronoss.cloud:nio-multipart-parser:1.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ asciidoctor {
|
||||
baseDir = file('src/docs/asciidoc')
|
||||
options eruby: 'erubis'
|
||||
|
||||
def ghSamplesUrl = "https://github.com/spring-projects/spring-security-samples/tree/$samplesBranch"
|
||||
attributes copycss : '',
|
||||
icons : 'font',
|
||||
'source-highlighter': 'prettify',
|
||||
@@ -15,9 +16,10 @@ asciidoctor {
|
||||
idseparator: '-',
|
||||
doctype: 'book',
|
||||
'spring-security-version' : project.version,
|
||||
'download-url' : getDownloadUrl(),
|
||||
'include-maven-repository' : getMavenRepositoryInclude(),
|
||||
revnumber : project.version
|
||||
revnumber : project.version,
|
||||
'gh-samples-url': ghSamplesUrl,
|
||||
'gh-old-samples-url': "https://github.com/spring-projects/spring-security/tree/5.4.x/samples"
|
||||
}
|
||||
|
||||
ext.spec = copySpec {
|
||||
@@ -27,11 +29,6 @@ ext.spec = copySpec {
|
||||
}
|
||||
}
|
||||
|
||||
def getDownloadUrl() {
|
||||
snapshotBuild ? "https://github.com/spring-projects/spring-security/archive/main.zip" : "https://github.com/spring-projects/spring-security/archive/${project.version}.zip"
|
||||
}
|
||||
|
||||
|
||||
def getMavenRepositoryInclude() {
|
||||
if(snapshotBuild) {
|
||||
return "_includes/maven-repository-snapshot.asc"
|
||||
|
||||
@@ -12,11 +12,16 @@ download-url: the URL to download the Spring Security distribution
|
||||
|
||||
== Setting up the sample
|
||||
|
||||
This section outlines how to setup a workspace within https://spring.io/tools/sts[Spring Tool Suite (STS)] so that you can follow along with this guide. The next section outlines generic steps for how to apply Spring Security to your existing application. While you could simply apply the steps to your existing application, we encourage you to follow along with this guide in order to reduce the complexity.
|
||||
This section outlines how to setup a workspace within https://spring.io/tools[Eclipse IDE with Spring Tools] so that you can follow along with this guide. The next section outlines generic steps for how to apply Spring Security to your existing application. While you could simply apply the steps to your existing application, we encourage you to follow along with this guide in order to reduce the complexity.
|
||||
|
||||
=== Obtaining the sample project
|
||||
|
||||
Extract the {download-url}[Spring Security Distribution] to a known location and remember it as _SPRING_SECURITY_HOME_.
|
||||
Clone the {gh-samples-url}[Spring Security Samples project] to a known location and remember it as _SPRING_SECURITY_HOME_.
|
||||
[NOTE]
|
||||
====
|
||||
These samples are being migrated to a separate project, however, you can still find
|
||||
the not migrated samples in an older branch of the {gh-old-samples-url}[Spring Security repository].
|
||||
====
|
||||
|
||||
=== Import the {starter-appname} sample application
|
||||
|
||||
|
||||
@@ -12,11 +12,16 @@ download-url: the URL to download the Spring Security distribution
|
||||
|
||||
== Setting up the sample
|
||||
|
||||
This section outlines how to setup a workspace within https://spring.io/tools/sts[Spring Tool Suite (STS)] so that you can follow along with this guide. The next section outlines generic steps for how to apply Spring Security to your existing application. While you could simply apply the steps to your existing application, we encourage you to follow along with this guide in order to reduce the complexity.
|
||||
This section outlines how to setup a workspace within https://spring.io/tools[Eclipse IDE with Spring Tools] so that you can follow along with this guide. The next section outlines generic steps for how to apply Spring Security to your existing application. While you could simply apply the steps to your existing application, we encourage you to follow along with this guide in order to reduce the complexity.
|
||||
|
||||
=== Obtaining the sample project
|
||||
|
||||
Extract the {download-url}[Spring Security Distribution] to a known location and remember it as _SPRING_SECURITY_HOME_.
|
||||
Clone the {gh-samples-url}[Spring Security Samples project] to a known location and remember it as _SPRING_SECURITY_HOME_.
|
||||
[NOTE]
|
||||
====
|
||||
These samples are being migrated to a separate project, however, you can still find
|
||||
the not migrated samples in an older branch of the {gh-old-samples-url}[Spring Security repository].
|
||||
====
|
||||
|
||||
=== Import the {starter-appname} sample application
|
||||
|
||||
|
||||
@@ -14,13 +14,26 @@ asciidoctor {
|
||||
}
|
||||
|
||||
asciidoctorj {
|
||||
def docsTag = snapshotBuild ? 'current' : project.version
|
||||
def ghTag = snapshotBuild ? 'main' : project.version
|
||||
def ghUrl = "https://github.com/spring-projects/spring-security/tree/$ghTag"
|
||||
def ghOldSamplesUrl = "https://github.com/spring-projects/spring-security/tree/5.4.x/samples"
|
||||
def ghSamplesUrl = "https://github.com/spring-projects/spring-security-samples/tree/$samplesBranch"
|
||||
def securityDocsUrl = "https://docs.spring.io/spring-security/site/docs/$docsTag"
|
||||
def securityApiUrl = "$securityDocsUrl/api/"
|
||||
def securityReferenceUrl = "$securityDocsUrl/reference/html5/"
|
||||
def springFrameworkApiUrl = "https://docs.spring.io/spring-framework/docs/$springFrameworkVersion/javadoc-api/"
|
||||
def springFrameworkReferenceUrl = "https://docs.spring.io/spring-framework/docs/$springFrameworkVersion/reference/html/"
|
||||
attributes 'spring-security-version' : project.version,
|
||||
'spring-boot-version' : springBootVersion,
|
||||
revnumber : project.version,
|
||||
'gh-url': ghUrl,
|
||||
'gh-samples-url': "$ghUrl/samples"
|
||||
'gh-samples-url': ghSamplesUrl,
|
||||
'gh-old-samples-url': ghOldSamplesUrl,
|
||||
'security-api-url': securityApiUrl,
|
||||
'security-reference-url': securityReferenceUrl,
|
||||
'spring-framework-api-url': springFrameworkApiUrl,
|
||||
'spring-framework-reference-url': springFrameworkReferenceUrl
|
||||
attributeProvider resolvedVersions(project.configurations.testRuntimeClasspath)
|
||||
}
|
||||
|
||||
|
||||
@@ -463,7 +463,7 @@ You should instead migrate to using `DelegatingPasswordEncoder` to support secur
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
public static NoOpPasswordEncoder passwordEncoder() {
|
||||
public static PasswordEncoder passwordEncoder() {
|
||||
return NoOpPasswordEncoder.getInstance();
|
||||
}
|
||||
----
|
||||
|
||||
@@ -114,7 +114,7 @@ This means that requests with the HTTP method `GET`, `HEAD`, `OPTIONS`, and `TRA
|
||||
|
||||
[[csrf-protection-stp]]
|
||||
=== Synchronizer Token Pattern
|
||||
The predominant and most comprehensive way to protect against CSRF attacks is to use the https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet#General_Recommendation:_Synchronizer_Token_Pattern[Synchronizer Token Pattern].
|
||||
The predominant and most comprehensive way to protect against CSRF attacks is to use the https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#synchronizer-token-pattern[Synchronizer Token Pattern].
|
||||
This solution is to ensure that each HTTP request requires, in addition to our session cookie, a secure random generated value called a CSRF token must be present in the HTTP request.
|
||||
|
||||
When an HTTP request is submitted, the server must look up the expected CSRF token and compare it against the actual CSRF token in the HTTP request.
|
||||
@@ -348,7 +348,7 @@ This allows the expected CSRF token to outlive the session.
|
||||
One might ask why the expected CSRF token isn't stored in a cookie by default.
|
||||
This is because there are known exploits in which headers (for example, to specify the cookies) can be set by another domain.
|
||||
This is the same reason Ruby on Rails https://weblog.rubyonrails.org/2011/2/8/csrf-protection-bypass-in-ruby-on-rails/[no longer skips CSRF checks when the header X-Requested-With is present].
|
||||
See http://lists.webappsec.org/pipermail/websecurity_lists.webappsec.org/2011-February/007533.html[this webappsec.org thread] for details on how to perform the exploit.
|
||||
See https://web.archive.org/web/20210221120355/https://lists.webappsec.org/pipermail/websecurity_lists.webappsec.org/2011-February/007533.html[this webappsec.org thread] for details on how to perform the exploit.
|
||||
Another disadvantage is that by removing the state (that is, the timeout), you lose the ability to forcibly invalidate the token if it is compromised.
|
||||
|
||||
// FIXME: Document timeout with lengthy form expire. We do not want to automatically replay that request because it can lead to exploit
|
||||
|
||||
@@ -7,7 +7,7 @@ This portion of the documentation discusses the general topic of Security HTTP R
|
||||
Refer to the relevant sections for specific information on Security HTTP Response Headers <<servlet-headers,servlet>> and <<webflux-headers,WebFlux>> based applications.
|
||||
====
|
||||
|
||||
There are many https://www.owasp.org/index.php/OWASP_Secure_Headers_Project#tab=Headers[HTTP response headers] that can be used to increase the security of web applications.
|
||||
There are many https://owasp.org/www-project-secure-headers/#div-headers[HTTP response headers] that can be used to increase the security of web applications.
|
||||
This section is dedicated to the various HTTP response headers that Spring Security provides explicit support for.
|
||||
If necessary, Spring Security can also be configured to provide <<headers-custom,custom headers>>.
|
||||
|
||||
@@ -267,7 +267,7 @@ Content-Security-Policy: script-src https://trustedscripts.example.com; report-u
|
||||
----
|
||||
====
|
||||
|
||||
https://www.w3.org/TR/CSP2/#violation-reports[Violation reports] are standard JSON structures that can be captured either by the web application's own API or by a publicly hosted CSP violation reporting service, such as, https://report-uri.io/.
|
||||
https://www.w3.org/TR/CSP2/#violation-reports[Violation reports] are standard JSON structures that can be captured either by the web application's own API or by a publicly hosted CSP violation reporting service, such as, https://report-uri.com/.
|
||||
|
||||
The `Content-Security-Policy-Report-Only` header provides the capability for web application authors and administrators to monitor security policies, rather than enforce them.
|
||||
This header is typically used when experimenting and/or developing security policies for a site.
|
||||
|
||||
@@ -25,7 +25,7 @@ Without proper configuration, the application server will not know that the load
|
||||
|
||||
To fix this you can use https://tools.ietf.org/html/rfc7239[RFC 7239] to specify that a load balancer is being used.
|
||||
To make the application aware of this, you need to either configure your application server aware of the X-Forwarded headers.
|
||||
For example Tomcat uses the https://tomcat.apache.org/tomcat-8.0-doc/api/org/apache/catalina/valves/RemoteIpValve.html[RemoteIpValve] and Jetty uses https://download.eclipse.org/jetty/stable-9/apidocs/org/eclipse/jetty/server/ForwardedRequestCustomizer.html[ForwardedRequestCustomizer].
|
||||
For example Tomcat uses the https://tomcat.apache.org/tomcat-8.0-doc/api/org/apache/catalina/valves/RemoteIpValve.html[RemoteIpValve] and Jetty uses https://www.eclipse.org/jetty/javadoc/jetty-9/org/eclipse/jetty/server/ForwardedRequestCustomizer.html[ForwardedRequestCustomizer].
|
||||
Alternatively, Spring users can leverage https://github.com/spring-projects/spring-framework/blob/v4.3.3.RELEASE/spring-web/src/main/java/org/springframework/web/filter/ForwardedHeaderFilter.java[ForwardedHeaderFilter].
|
||||
|
||||
Spring Boot users may use the `server.use-forward-headers` property to configure the application.
|
||||
|
||||
@@ -24,7 +24,7 @@ The topics in this section provide detail on how to consume Spring Security when
|
||||
=== Spring Boot with Maven
|
||||
|
||||
Spring Boot provides a `spring-boot-starter-security` starter that aggregates Spring Security-related dependencies together.
|
||||
The simplest and preferred way to use the starter is to use https://docs.spring.io/initializr/docs/current/reference/htmlsingle/[Spring Initializr] by using an IDE integration (https://joshlong.com/jl/blogPost/tech_tip_geting_started_with_spring_boot.html[Eclipse], https://www.jetbrains.com/help/idea/spring-boot.html#d1489567e2[IntelliJ], https://github.com/AlexFalappa/nb-springboot/wiki/Quick-Tour[NetBeans]) or through https://start.spring.io.
|
||||
The simplest and preferred way to use the starter is to use https://docs.spring.io/initializr/docs/current/reference/html/[Spring Initializr] by using an IDE integration (https://joshlong.com/jl/blogPost/tech_tip_geting_started_with_spring_boot.html[Eclipse], https://www.jetbrains.com/help/idea/spring-boot.html#d1489567e2[IntelliJ], https://github.com/AlexFalappa/nb-springboot/wiki/Quick-Tour[NetBeans]) or through https://start.spring.io.
|
||||
|
||||
Alternatively, you can manually add the starter, as the following example shows:
|
||||
|
||||
@@ -53,7 +53,7 @@ If you wish to override the Spring Security version, you may do so by providing
|
||||
<properties>
|
||||
<!-- ... -->
|
||||
<spring-security.version>{spring-security-version}</spring-security.version>
|
||||
</dependencies>
|
||||
</properties>
|
||||
----
|
||||
====
|
||||
|
||||
@@ -68,7 +68,7 @@ You can do so by adding a Maven property, as the following example shows:
|
||||
<properties>
|
||||
<!-- ... -->
|
||||
<spring.version>{spring-core-version}</spring.version>
|
||||
</dependencies>
|
||||
</properties>
|
||||
----
|
||||
====
|
||||
|
||||
@@ -196,7 +196,7 @@ The following topics provide detail on how to consume Spring Security when using
|
||||
=== Spring Boot with Gradle
|
||||
|
||||
Spring Boot provides a `spring-boot-starter-security` starter that aggregates Spring Security related dependencies together.
|
||||
The simplest and preferred method to use the starter is to use https://docs.spring.io/initializr/docs/current/reference/htmlsingle/[Spring Initializr] by using an IDE integration (https://joshlong.com/jl/blogPost/tech_tip_geting_started_with_spring_boot.html[Eclipse], https://www.jetbrains.com/help/idea/spring-boot.html#d1489567e2[IntelliJ], https://github.com/AlexFalappa/nb-springboot/wiki/Quick-Tour[NetBeans]) or through https://start.spring.io.
|
||||
The simplest and preferred method to use the starter is to use https://docs.spring.io/initializr/docs/current/reference/html/[Spring Initializr] by using an IDE integration (https://joshlong.com/jl/blogPost/tech_tip_geting_started_with_spring_boot.html[Eclipse], https://www.jetbrains.com/help/idea/spring-boot.html#d1489567e2[IntelliJ], https://github.com/AlexFalappa/nb-springboot/wiki/Quick-Tour[NetBeans]) or through https://start.spring.io.
|
||||
|
||||
Alternatively, you can manually add the starter, as the following example shows:
|
||||
|
||||
|
||||
@@ -2,3 +2,9 @@
|
||||
= Samples
|
||||
|
||||
Spring Security includes many {gh-samples-url}[samples] applications.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
These samples are being migrated to a separate project, however, you can still find
|
||||
the not migrated samples in an older branch of the {gh-old-samples-url}[Spring Security repository].
|
||||
====
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
[[webflux-cors]]
|
||||
== CORS
|
||||
= CORS
|
||||
|
||||
Spring Framework provides https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-cors-intro[first class support for CORS].
|
||||
CORS must be processed before Spring Security because the pre-flight request will not contain any cookies (i.e. the `JSESSIONID`).
|
||||
|
||||
@@ -14,6 +14,8 @@ include::webclient.adoc[leveloffset=+1]
|
||||
|
||||
include::method.adoc[leveloffset=+1]
|
||||
|
||||
include::cors.adoc[leveloffset=+1]
|
||||
|
||||
include::test.adoc[leveloffset=+1]
|
||||
|
||||
include::rsocket.adoc[leveloffset=+1]
|
||||
|
||||
@@ -234,4 +234,4 @@ class SecurityConfig {
|
||||
----
|
||||
====
|
||||
|
||||
You can find a complete sample in {gh-samples-url}/javaconfig/hellowebflux-method[hellowebflux-method]
|
||||
You can find a complete sample in {gh-samples-url}/reactive/webflux/java/method[hellowebflux-method]
|
||||
|
||||
@@ -12,7 +12,7 @@ NOTE: OAuth 2.0 Login is implemented by using the *Authorization Code Grant*, as
|
||||
|
||||
Spring Boot 2.0 brings full auto-configuration capabilities for OAuth 2.0 Login.
|
||||
|
||||
This section shows how to configure the {gh-samples-url}/boot/oauth2login-webflux[*OAuth 2.0 Login WebFlux sample*] using _Google_ as the _Authentication Provider_ and covers the following topics:
|
||||
This section shows how to configure the {gh-samples-url}/reactive/webflux/java/oauth2/login[*OAuth 2.0 Login WebFlux sample*] using _Google_ as the _Authentication Provider_ and covers the following topics:
|
||||
|
||||
* <<webflux-oauth2-login-sample-setup,Initial setup>>
|
||||
* <<webflux-oauth2-login-sample-redirect,Setting the redirect URI>>
|
||||
|
||||
@@ -11,7 +11,7 @@ This authorization server can be consulted by resource servers to authorize requ
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
A complete working example for {gh-samples-url}/boot/oauth2resourceserver-webflux[*JWTs*] is available in the {gh-samples-url}[Spring Security repository].
|
||||
A complete working example for {gh-samples-url}/reactive/webflux/java/oauth2/resource-server[*JWTs*] is available in the {gh-samples-url}[Spring Security repository].
|
||||
====
|
||||
|
||||
[[webflux-oauth2resourceserver-jwt-minimaldependencies]]
|
||||
@@ -818,7 +818,7 @@ fun jwtDecoder(): ReactiveJwtDecoder {
|
||||
====
|
||||
|
||||
[NOTE]
|
||||
By default, Resource Server configures a clock skew of 30 seconds.
|
||||
By default, Resource Server configures a clock skew of 60 seconds.
|
||||
|
||||
[[webflux-oauth2resourceserver-validation-custom]]
|
||||
==== Configuring a Custom Validator
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ Spring Security allows resolving an access token using `@RegisteredOAuth2Authori
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
A working example can be found in {gh-samples-url}/boot/oauth2webclient-webflux[*OAuth 2.0 WebClient WebFlux sample*].
|
||||
A working example can be found in {gh-samples-url}/reactive/webflux/java/oauth2/webclient[*OAuth 2.0 WebClient WebFlux sample*].
|
||||
====
|
||||
|
||||
After configuring Spring Security for <<webflux-oauth2-login,OAuth2 Login>> or as an <<webflux-oauth2-client,OAuth2 Client>>, an `OAuth2AuthorizedClient` can be resolved using the following:
|
||||
|
||||
@@ -6,7 +6,7 @@ The main entry point into security is found in the `PayloadSocketAcceptorInterce
|
||||
|
||||
You can find a few sample applications that demonstrate the code below:
|
||||
|
||||
* Hello RSocket {gh-samples-url}/boot/hellorsocket[hellorsocket]
|
||||
* Hello RSocket {gh-samples-url}/reactive/rsocket/hello-security[hellorsocket]
|
||||
* https://github.com/rwinch/spring-flights/tree/security[Spring Flights]
|
||||
|
||||
|
||||
@@ -43,9 +43,8 @@ In a Spring Boot application this is done automatically using `RSocketSecurityAu
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
ServerRSocketFactoryProcessor springSecurityRSocketSecurity(
|
||||
SecuritySocketAcceptorInterceptor interceptor) {
|
||||
return builder -> builder.addSocketAcceptorPlugin(interceptor);
|
||||
RSocketServerCustomizer springSecurityRSocketSecurity(SecuritySocketAcceptorInterceptor interceptor) {
|
||||
return (server) -> server.interceptors((registry) -> registry.forSocketAcceptor(interceptor));
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
@@ -184,7 +184,7 @@ then Spring Security's test support can come in handy.
|
||||
Testing the method above with `WebTestClient` would require simulating some kind of grant flow with an authorization server.
|
||||
Certainly this would be a daunting task, which is why Spring Security ships with support for removing this boilerplate.
|
||||
|
||||
For example, we can tell Spring Security to include a default `OidcUser` using the `SecurityMockServerConfigurers#oidcLogin` method, like so:
|
||||
For example, we can tell Spring Security to include a default `OidcUser` using the `SecurityMockServerConfigurers#mockOidcLogin` method, like so:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@@ -311,7 +311,7 @@ public Mono<String> foo(@AuthenticationPrincipal OAuth2User oauth2User) {
|
||||
}
|
||||
----
|
||||
|
||||
In that case, we can tell Spring Security to include a default `OAuth2User` using the `SecurityMockServerConfigurers#oauth2User` method, like so:
|
||||
In that case, we can tell Spring Security to include a default `OAuth2User` using the `SecurityMockServerConfigurers#mockOAuth2Login` method, like so:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@@ -431,7 +431,7 @@ public Mono<String> foo(@RegisteredOAuth2AuthorizedClient("my-app") OAuth2Author
|
||||
----
|
||||
|
||||
Simulating this handshake with the authorization server could be cumbersome.
|
||||
Instead, you can use `SecurityMockServerConfigurers#oauth2Client` to add a `OAuth2AuthorizedClient` into a mock `ServerOAuth2AuthorizedClientRepository`:
|
||||
Instead, you can use `SecurityMockServerConfigurers#mockOAuth2Client` to add a `OAuth2AuthorizedClient` into a mock `ServerOAuth2AuthorizedClientRepository`:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@@ -541,7 +541,7 @@ We'll look at two of them now:
|
||||
==== `mockJwt() WebTestClientConfigurer`
|
||||
|
||||
The first way is via a `WebTestClientConfigurer`.
|
||||
The simplest of these would look something like this:
|
||||
The simplest of these would be to use the `SecurityMockServerConfigurers#mockJwt` method like the following:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@@ -664,7 +664,7 @@ public Mono<String> foo(BearerTokenAuthentication authentication) {
|
||||
}
|
||||
----
|
||||
|
||||
In that case, we can tell Spring Security to include a default `BearerTokenAuthentication` using the `SecurityMockServerConfigurers#opaqueToken` method, like so:
|
||||
In that case, we can tell Spring Security to include a default `BearerTokenAuthentication` using the `SecurityMockServerConfigurers#mockOpaqueToken` method, like so:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
Spring Security's WebFlux support relies on a `WebFilter` and works the same for Spring WebFlux and Spring WebFlux.Fn.
|
||||
You can find a few sample applications that demonstrate the code below:
|
||||
|
||||
* Hello WebFlux {gh-samples-url}/boot/hellowebflux[hellowebflux]
|
||||
* Hello WebFlux.Fn {gh-samples-url}/boot/hellowebfluxfn[hellowebfluxfn]
|
||||
* Hello WebFlux Method {gh-samples-url}/boot/hellowebflux-method[hellowebflux-method]
|
||||
* Hello WebFlux {gh-samples-url}/reactive/webflux/java/hello-security[hellowebflux]
|
||||
* Hello WebFlux.Fn {gh-samples-url}/reactive/webflux-fn/hello-security[hellowebfluxfn]
|
||||
* Hello WebFlux Method {gh-samples-url}/reactive/webflux/java/method[hellowebflux-method]
|
||||
|
||||
|
||||
== Minimal WebFlux Security Configuration
|
||||
|
||||
@@ -257,7 +257,7 @@ To get around this, you can disable session fixation protection, but in newer Se
|
||||
Note that switching between HTTP and HTTPS is not a good idea in general, as any application which uses HTTP at all is vulnerable to man-in-the-middle attacks.
|
||||
To be truly secure, the user should begin accessing your site in HTTPS and continue using it until they log out.
|
||||
Even clicking on an HTTPS link from a page accessed over HTTP is potentially risky.
|
||||
If you need more convincing, check out a tool like https://www.thoughtcrime.org/software/sslstrip/[sslstrip].
|
||||
If you need more convincing, check out a tool like https://github.com/moxie0/sslstrip/[sslstrip].
|
||||
|
||||
|
||||
==== I'm not switching between HTTP and HTTPS but my session is still getting lost
|
||||
@@ -378,7 +378,7 @@ For third-party jars the situation isn't always quite so obvious.
|
||||
A good starting point is to copy those from one of the pre-built sample applications WEB-INF/lib directories.
|
||||
For a basic application, you can start with the tutorial sample.
|
||||
If you want to use LDAP, with an embedded test server, then use the LDAP sample as a starting point.
|
||||
The reference manual also includes https://static.springsource.org/spring-security/site/docs/3.1.x/reference/springsecurity-single.html#appendix-dependencies[an appendix] listing the first-level dependencies for each Spring Security module with some information on whether they are optional and what they are required for.
|
||||
The reference manual also includes {security-reference-url}#modules[an appendix] listing the first-level dependencies for each Spring Security module with some information on whether they are optional and what they are required for.
|
||||
|
||||
If you are building your project with maven, then adding the appropriate Spring Security modules as dependencies to your pom.xml will automatically pull in the core jars that the framework requires.
|
||||
Any which are marked as "optional" in the Spring Security POM files will have to be added to your own pom.xml file if you need them.
|
||||
|
||||
@@ -7,7 +7,7 @@ Without proper configuration, the application server will not know that the load
|
||||
|
||||
To fix this you can use https://tools.ietf.org/html/rfc7239[RFC 7239] to specify that a load balancer is being used.
|
||||
To make the application aware of this, you need to either configure your application server aware of the X-Forwarded headers.
|
||||
For example Tomcat uses the https://tomcat.apache.org/tomcat-8.0-doc/api/org/apache/catalina/valves/RemoteIpValve.html[RemoteIpValve] and Jetty uses https://download.eclipse.org/jetty/stable-9/apidocs/org/eclipse/jetty/server/ForwardedRequestCustomizer.html[ForwardedRequestCustomizer].
|
||||
For example Tomcat uses the https://tomcat.apache.org/tomcat-8.0-doc/api/org/apache/catalina/valves/RemoteIpValve.html[RemoteIpValve] and Jetty uses https://www.eclipse.org/jetty/javadoc/jetty-9/org/eclipse/jetty/server/ForwardedRequestCustomizer.html[ForwardedRequestCustomizer].
|
||||
Alternatively, Spring 4.3+ users can leverage https://github.com/spring-projects/spring-framework/blob/v4.3.3.RELEASE/spring-web/src/main/java/org/springframework/web/filter/ForwardedHeaderFilter.java[ForwardedHeaderFilter].
|
||||
|
||||
Spring Boot users may use the `server.use-forward-headers` property to configure the application.
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[[servlet-delegatingfilterproxy]]
|
||||
= DelegatingFilterProxy
|
||||
|
||||
Spring provides a `Filter` implementation named {security-api-url}org/springframework/web/filter/DelegatingFilterProxy.html/[`DelegatingFilterProxy`] that allows bridging between the Servlet container's lifecycle and Spring's `ApplicationContext`.
|
||||
Spring provides a `Filter` implementation named {spring-framework-api-url}org/springframework/web/filter/DelegatingFilterProxy.html[`DelegatingFilterProxy`] that allows bridging between the Servlet container's lifecycle and Spring's `ApplicationContext`.
|
||||
The Servlet container allows registering ``Filter``s using its own standards, but it is not aware of Spring defined Beans.
|
||||
`DelegatingFilterProxy` can be registered via standard Servlet container mechanisms, but delegate all the work to a Spring Bean that implements `Filter`.
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ The picture below shows the typical layering of the handlers for a single HTTP r
|
||||
image::{figures}/filterchain.png[]
|
||||
|
||||
The client sends a request to the application, and the container creates a `FilterChain` which contains the ``Filter``s and `Servlet` that should process the `HttpServletRequest` based on the path of the request URI.
|
||||
In a Spring MVC application the `Servlet` is an instance of https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-servlet[`DispatcherServlet`].
|
||||
In a Spring MVC application the `Servlet` is an instance of {spring-framework-reference-url}web.html#mvc-servlet[`DispatcherServlet`].
|
||||
At most one `Servlet` can handle a single `HttpServletRequest` and `HttpServletResponse`.
|
||||
However, more than one `Filter` can be used to:
|
||||
|
||||
|
||||
@@ -100,3 +100,47 @@ This is an example of the use of the `AuthenticatedVoter` which we will see in t
|
||||
It uses an `AuthenticationTrustResolver` to process this particular configuration attribute and grant access to anonymous users.
|
||||
The `AuthenticatedVoter` approach is more powerful, since it allows you to differentiate between anonymous, remember-me and fully-authenticated users.
|
||||
If you don't need this functionality though, then you can stick with `ROLE_ANONYMOUS`, which will be processed by Spring Security's standard `RoleVoter`.
|
||||
|
||||
[[anonymous-auth-mvc-controller]]
|
||||
=== Getting Anonymous Authentications with Spring MVC
|
||||
|
||||
https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-arguments[Spring MVC resolves parameters of type `Principal`] using its own argument resolver.
|
||||
|
||||
This means that a construct like this one:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@GetMapping("/")
|
||||
public String method(Authentication authentication) {
|
||||
if (authentication instanceof AnonymousAuthenticationToken) {
|
||||
return "anonymous";
|
||||
} else {
|
||||
return "not anonymous";
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
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 `@CurrentSecurityContext` instead:
|
||||
|
||||
.Use CurrentSecurityContext for Anonymous requests
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@GetMapping("/")
|
||||
public String method(@CurrentSecurityContext SecurityContext context) {
|
||||
return context.getAuthentication().getName();
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@GetMapping("/")
|
||||
fun method(@CurrentSecurityContext context : SecurityContext) : String =
|
||||
context!!.authentication!!.name
|
||||
----
|
||||
====
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[[servlet-events]]
|
||||
== Authentication Events
|
||||
|
||||
For each authentication that succeeds or fails, a `AuthenticationSuccessEvent` or `AuthenticationFailureEvent` is fired, respectively.
|
||||
For each authentication that succeeds or fails, a `AuthenticationSuccessEvent` or `AbstractAuthenticationFailureEvent` is fired, respectively.
|
||||
|
||||
To listen for these events, you must first publish an `AuthenticationEventPublisher`.
|
||||
Spring Security's `DefaultAuthenticationEventPublisher` will probably do fine:
|
||||
@@ -42,7 +42,7 @@ public class AuthenticationEvents {
|
||||
}
|
||||
|
||||
@EventListener
|
||||
public void onFailure(AuthenticationFailureEvent failures) {
|
||||
public void onFailure(AbstractAuthenticationFailureEvent failures) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
@@ -70,7 +70,7 @@ While similar to `AuthenticationSuccessHandler` and `AuthenticationFailureHandle
|
||||
|
||||
=== Adding Exception Mappings
|
||||
|
||||
`DefaultAuthenticationEventPublisher` by default will publish an `AuthenticationFailureEvent` for the following events:
|
||||
`DefaultAuthenticationEventPublisher` by default will publish an `AbstractAuthenticationFailureEvent` for the following events:
|
||||
|
||||
|============
|
||||
| Exception | Event
|
||||
@@ -97,7 +97,7 @@ To that end, you may want to supply additional mappings to the publisher via the
|
||||
public AuthenticationEventPublisher authenticationEventPublisher
|
||||
(ApplicationEventPublisher applicationEventPublisher) {
|
||||
Map<Class<? extends AuthenticationException>,
|
||||
Class<? extends AuthenticationFailureEvent>> mapping =
|
||||
Class<? extends AbstractAuthenticationFailureEvent>> mapping =
|
||||
Collections.singletonMap(FooException.class, FooEvent.class);
|
||||
AuthenticationEventPublisher authenticationEventPublisher =
|
||||
new DefaultAuthenticationEventPublisher(applicationEventPublisher);
|
||||
|
||||
@@ -117,7 +117,7 @@ class="org.springframework.security.authentication.jaas.DefaultJaasAuthenticatio
|
||||
|
||||
[[jaas-jaasauthenticationprovider]]
|
||||
=== JaasAuthenticationProvider
|
||||
The `JaasAuthenticationProvider` assumes the default `Configuration` is an instance of https://download.oracle.com/javase/1.4.2/docs/guide/security/jaas/spec/com/sun/security/auth/login/ConfigFile.html[ ConfigFile].
|
||||
The `JaasAuthenticationProvider` assumes the default `Configuration` is an instance of https://docs.oracle.com/javase/8/docs/jre/api/security/jaas/spec/com/sun/security/auth/login/ConfigFile.html[ ConfigFile].
|
||||
This assumption is made in order to attempt to update the `Configuration`.
|
||||
The `JaasAuthenticationProvider` then uses the default `Configuration` to create the `LoginContext`.
|
||||
|
||||
|
||||
@@ -147,5 +147,4 @@ It's also assumed that you have added a `UserDetailsService` (called "userDetail
|
||||
The class `J2eePreAuthenticatedProcessingFilter` will extract the username from the `userPrincipal` property of the `HttpServletRequest`.
|
||||
Use of this filter would usually be combined with the use of Java EE roles as described above in <<j2ee-preauth-details>>.
|
||||
|
||||
There is a sample application in the codebase which uses this approach, so get hold of the code from github and have a look at the application context file if you are interested.
|
||||
The code is in the `samples/xml/preauth` directory.
|
||||
There is a {gh-old-samples-url}/xml/preauth[sample application] in the samples project which uses this approach, so get hold of the code from GitHub and have a look at the application context file if you are interested.
|
||||
|
||||
@@ -51,7 +51,7 @@ If you have more than one in your application context, you need to specify which
|
||||
|
||||
[[remember-me-persistent-token]]
|
||||
=== Persistent Token Approach
|
||||
This approach is based on the article http://jaspan.com/improved_persistent_login_cookie_best_practice[http://jaspan.com/improved_persistent_login_cookie_best_practice] with some minor modifications footnote:[Essentially, the username is not included in the cookie, to prevent exposing a valid login name unecessarily.
|
||||
This approach is based on the article https://web.archive.org/web/20180819014446/http://jaspan.com/improved_persistent_login_cookie_best_practice[http://jaspan.com/improved_persistent_login_cookie_best_practice] with some minor modifications footnote:[Essentially, the username is not included in the cookie, to prevent exposing a valid login name unecessarily.
|
||||
There is a discussion on this in the comments section of this article.].
|
||||
To use the this approach with namespace configuration, you would supply a datasource reference:
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ This means that you can easily use X.509 authentication with other options such
|
||||
|
||||
[[x509-ssl-config]]
|
||||
=== Setting up SSL in Tomcat
|
||||
There are some pre-generated certificates in the `samples/certificate` directory in the Spring Security project.
|
||||
There are some pre-generated certificates in the {gh-samples-url}/servlet/java-configuration/authentication/x509/server[Spring Security Samples repository].
|
||||
You can use these to enable SSL for testing if you don't want to generate your own.
|
||||
The file `server.jks` contains the server certificate, private key and the issuing certificate authority certificate.
|
||||
There are also some client certificate files for the users from the sample applications.
|
||||
|
||||
@@ -125,7 +125,7 @@ This should therefore work with all major databases.
|
||||
At the time of writing, the system had been successfully tested using Hypersonic SQL, PostgreSQL, Microsoft SQL Server and Oracle.
|
||||
|
||||
Two samples ship with Spring Security that demonstrate the ACL module.
|
||||
The first is the Contacts Sample, and the other is the Document Management System (DMS) Sample.
|
||||
The first is the {gh-samples-url}/servlet/xml/java/contacts[Contacts Sample], and the other is the {gh-samples-url}/servlet/xml/java/dms[Document Management System (DMS) Sample].
|
||||
We suggest taking a look over these for examples.
|
||||
|
||||
|
||||
|
||||
@@ -258,7 +258,7 @@ Their use is enabled through the `global-method-security` namespace element:
|
||||
|
||||
===== Access Control using @PreAuthorize and @PostAuthorize
|
||||
The most obviously useful annotation is `@PreAuthorize` which decides whether a method can actually be invoked or not.
|
||||
For example (from the "Contacts" sample application)
|
||||
For example (from the {gh-samples-url}/servlet/xml/java/contacts[Contacts] sample application)
|
||||
|
||||
====
|
||||
.Java
|
||||
@@ -492,7 +492,7 @@ This would look something like this:
|
||||
|
||||
Where `myPermissionEvaluator` is the bean which implements `PermissionEvaluator`.
|
||||
Usually this will be the implementation from the ACL module which is called `AclPermissionEvaluator`.
|
||||
See the "Contacts" sample application configuration for more details.
|
||||
See the {gh-samples-url}/servlet/xml/java/contacts[Contacts] sample application configuration for more details.
|
||||
|
||||
===== Method Security Meta Annotations
|
||||
|
||||
|
||||
@@ -198,10 +198,13 @@ The password package of the spring-security-crypto module provides support for e
|
||||
[source,java]
|
||||
----
|
||||
public interface PasswordEncoder {
|
||||
String encode(CharSequence rawPassword);
|
||||
|
||||
String encode(String rawPassword);
|
||||
boolean matches(CharSequence rawPassword, String encodedPassword);
|
||||
|
||||
boolean matches(String rawPassword, String encodedPassword);
|
||||
default boolean upgradeEncoding(String encodedPassword) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
@@ -375,7 +375,7 @@ For details, refer to the <<servlet-csrf-configure-custom-repository>> section.
|
||||
|
||||
If a token does expire, you might want to customize how it is handled by specifying a custom `AccessDeniedHandler`.
|
||||
The custom `AccessDeniedHandler` can process the `InvalidCsrfTokenException` any way you like.
|
||||
For an example of how to customize the `AccessDeniedHandler` refer to the provided links for both <<nsa-access-denied-handler,xml>> and https://github.com/spring-projects/spring-security/blob/3.2.0.RC1/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpAccessDeniedHandlerTests.java#L64[Java configuration].
|
||||
For an example of how to customize the `AccessDeniedHandler` refer to the provided links for both <<nsa-access-denied-handler,xml>> and {gh-url}/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpServerAccessDeniedHandlerTests.java#L64[Java configuration].
|
||||
// FIXME: We should add a custom AccessDeniedHandler section in the reference and update the links above
|
||||
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ fun httpFirewall(): StrictHttpFirewall {
|
||||
----
|
||||
====
|
||||
|
||||
The `StrictHttpFirewall` provides an allowed list of valid HTTP methods that are allowed to protect against https://www.owasp.org/index.php/Cross_Site_Tracing[Cross Site Tracing (XST)] and https://www.owasp.org/index.php/Test_HTTP_Methods_(OTG-CONFIG-006)[HTTP Verb Tampering].
|
||||
The `StrictHttpFirewall` provides an allowed list of valid HTTP methods that are allowed to protect against https://owasp.org/www-community/attacks/Cross_Site_Tracing[Cross Site Tracing (XST)] and https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/02-Configuration_and_Deployment_Management_Testing/06-Test_HTTP_Methods[HTTP Verb Tampering].
|
||||
The default valid methods are "DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", and "PUT".
|
||||
If your application needs to modify the valid methods, you can configure a custom `StrictHttpFirewall` bean.
|
||||
For example, the following will only allow HTTP "GET" and "POST" methods:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user