1
0
mirror of synced 2026-08-06 02:08:01 +00:00

Compare commits

..

24 Commits

Author SHA1 Message Date
github-actions[bot] 063e56ce8b Release 5.8.0-M2 2022-08-15 15:24:27 +00:00
Marcus Da Coregio 1c4d6ed098 Consistently handle RequestRejectedException if it is wrapped
Closes gh-11645
2022-08-09 08:30:15 -03:00
Igor Bolic efaee4e56b Allow customization of redirect strategy
The default redirect strategy will provide authorization redirect
URI within HTTP 302 response Location header.
Allowing the configuration of custom redirect strategy will provide
an option for the clients to obtain the authorization URI from e.g.
HTTP response body as JSON payload, without a need to handle
automatic redirection initiated by the HTTP Location header.

Closes gh-11373
2022-08-08 15:35:49 -05:00
Rob Winch c9f8d2b111 RequestAttributeSecurityContextRepository never null SecurityContext
Previously loadContext(HttpServletRequest) could return a Supplier that
returned a null SecurityContext

This commit ensures that null is never returned by the Supplier by
returning SecurityContextHolder.createEmptyContext() instead.

Closes gh-11606
2022-08-08 13:52:12 -05:00
Josh Cummings c2d79fcbd6 Add Conditions to Generating AuthnRequest
Closes gh-11657
2022-08-03 17:34:31 -06:00
Josh Cummings aa225943d2 Polish Tests
Issue gh-11657
2022-08-03 17:34:26 -06:00
Steve Riesenberg 07ea139ebf Polish HttpSecurity 2022-07-29 17:42:39 -05:00
Steve Riesenberg 67544f36f9 Remove references to WebSecurityConfigurerAdapter
* AbstractAuthenticationFilterConfigurer
* DefaultLoginPageConfigurer
* EnableGlobalAuthentication
* FormLoginConfigurer
* HeadersConfigurer
* HttpSecurity
* OpenIDLoginConfigurer
* RememberMeConfigurer
* WebSecurity
* WebSecurityConfiguration
* WebSecurityConfigurer
* X509Configurer

Closes gh-11288
2022-07-29 17:42:39 -05:00
Steve Riesenberg 05725af4d8 Remove references to WebSecurityConfigurerAdapter in EnableWebSecurity
Closes gh-11277
2022-07-29 17:42:39 -05:00
Steve Riesenberg 4fbbfd2c8b Skip workflows on forks of spring-security 2022-07-28 15:07:02 -05:00
Steve Riesenberg 66da4301fc Use cache and user.name system property on Windows 2022-07-28 15:07:02 -05:00
Steve Riesenberg 8929bd5abc Only run prerequisites job if on upstream repo 2022-07-28 15:07:02 -05:00
Steve Riesenberg e3d1405f67 Simplify dependency graph 2022-07-28 15:07:02 -05:00
Steve Riesenberg e756a1df19 Use Spring Gradle Build Action
Closes gh-11630
2022-07-28 15:07:02 -05:00
Steve Riesenberg 81fae2db2c Polish gh-11367 2022-07-28 15:07:01 -05:00
naveen 054a3f0bc0 Set permissions for GitHub actions
Restrict the GitHub token permissions only to the required ones; this
way, even if the attackers will succeed in compromising your workflow,
they won’t be able to do much.

- Included permissions for the action.

https://github.com/ossf/scorecard/blob/main/docs/checks.md#token-permissions

https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions

https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs

Keeping your GitHub Actions and workflows secure Part 1: Preventing pwn requests
https://securitylab.github.com/research/github-actions-preventing-pwn-requests/

Signed-off-by: naveen <172697+naveensrinivasan@users.noreply.github.com>

Closes gh-11367
2022-07-28 15:07:00 -05:00
Ulrich Grave 409998a3fe Add hash-based Content-Security-Policy for SAML pages
Closes gh-11631
2022-07-27 17:59:42 -06:00
Marcus Da Coregio e5ae35ab71 Add Deprecated annotation to WebSecurity#securityInterceptor
Closes gh-11634
2022-07-27 14:39:33 -03:00
Rob Winch 0d74da4f97 Fix Snapshot Sources/Javadoc
This commit merges a workaround to an issue in JFrog's Gradle plugin
which causes SNAPSHOT javadoc and sources to become out of sync and thus
prevents users from being able to download either.

Closes gh-10602
2022-07-26 16:24:54 -05:00
Desmond Silveira 2a336d4f49 "Well-Know" should be "Well-Known" 2022-07-26 15:41:05 -05:00
Yuriy Savchenko 5322352427 Add Kotlin example for WebTestClient setup docs
Closes gh-9998
2022-07-22 13:49:21 -03:00
Josh Cummings 561f65b34d Merge Same-named Attribute Elements
Closes gh-11042
2022-07-20 18:40:20 -06:00
Steve Riesenberg 631076e4dd Build only on branches
Issue gh-11480
2022-07-18 11:45:39 -05:00
github-actions[bot] 8d147100ee Next development version 2022-07-18 16:00:47 +00:00
72 changed files with 1867 additions and 661 deletions
+4
View File
@@ -5,10 +5,14 @@ on:
- cron: '0 10 * * *' # Once per day at 10am UTC - cron: '0 10 * * *' # Once per day at 10am UTC
workflow_dispatch: # Manual trigger workflow_dispatch: # Manual trigger
permissions:
contents: read
jobs: jobs:
update: update:
name: Update Algolia Index name: Update Algolia Index
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.repository == 'spring-projects/spring-security' }}
steps: steps:
- name: Checkout Source - name: Checkout Source
uses: actions/checkout@v2 uses: actions/checkout@v2
+7 -6
View File
@@ -10,20 +10,21 @@ on:
env: env:
GH_ACTIONS_REPO_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} GH_ACTIONS_REPO_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
permissions:
contents: read
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.repository == 'spring-projects/spring-security' }}
steps: steps:
- name: Checkout Source - name: Checkout Source
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Set up JDK - name: Set up gradle
uses: actions/setup-java@v1 uses: spring-io/spring-gradle-build-action@v1
with: with:
java-version: '11' java-version: '11'
- name: Setup Gradle distribution: 'adopt'
uses: gradle/gradle-build-action@v2
env:
GRADLE_USER_HOME: ~/.gradle
- name: Generate antora.yml - name: Generate antora.yml
run: ./gradlew :spring-security-docs:generateAntora run: ./gradlew :spring-security-docs:generateAntora
- name: Extract Branch Name - name: Extract Branch Name
+7
View File
@@ -8,9 +8,16 @@ on:
push: push:
branches: branches:
- '*.x' - '*.x'
permissions:
contents: read
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.repository == 'spring-projects/spring-security' }}
permissions:
contents: read
issues: write
pull-requests: write
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: actions/setup-java@v3 - uses: actions/setup-java@v3
@@ -3,9 +3,15 @@ on:
schedule: schedule:
- cron: '0 10 * * *' # Once per day at 10am UTC - cron: '0 10 * * *' # Once per day at 10am UTC
permissions:
contents: read
jobs: jobs:
main: main:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.repository == 'spring-projects/spring-security' }}
permissions:
contents: none
steps: steps:
- name: Delete artifacts in cron job - name: Delete artifacts in cron job
env: env:
@@ -2,6 +2,8 @@ name: CI
on: on:
push: push:
branches:
- '**'
schedule: schedule:
- cron: '0 10 * * *' # Once per day at 10am UTC - cron: '0 10 * * *' # Once per day at 10am UTC
workflow_dispatch: # Manual trigger workflow_dispatch: # Manual trigger
@@ -16,12 +18,15 @@ env:
STRUCTURE101_LICENSEID: ${{ secrets.STRUCTURE101_LICENSEID }} STRUCTURE101_LICENSEID: ${{ secrets.STRUCTURE101_LICENSEID }}
ARTIFACTORY_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }} ARTIFACTORY_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
ARTIFACTORY_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }} ARTIFACTORY_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
RUN_JOBS: ${{ github.repository == 'spring-projects/spring-security' }}
permissions:
contents: read
jobs: jobs:
prerequisites: prerequisites:
name: Pre-requisites for building name: Pre-requisites for building
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.repository == 'spring-projects/spring-security' }}
outputs: outputs:
runjobs: ${{ steps.continue.outputs.runjobs }} runjobs: ${{ steps.continue.outputs.runjobs }}
project_version: ${{ steps.continue.outputs.project_version }} project_version: ${{ steps.continue.outputs.project_version }}
@@ -29,8 +34,8 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- id: continue - id: continue
name: Determine if should continue name: Determine if should continue
if: env.RUN_JOBS == 'true'
run: | run: |
# Run jobs if in upstream repository
echo "::set-output name=runjobs::true" echo "::set-output name=runjobs::true"
# Extract version from gradle.properties # Extract version from gradle.properties
version=$(cat gradle.properties | grep "version=" | awk -F'=' '{print $2}') version=$(cat gradle.properties | grep "version=" | awk -F'=' '{print $2}')
@@ -46,17 +51,14 @@ jobs:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up JDK 11 - name: Set up JDK 11
uses: actions/setup-java@v1 uses: actions/setup-java@v2
with: with:
java-version: '11' java-version: '11'
- name: Setup gradle user name distribution: 'adopt'
run: | - name: Set up Gradle
mkdir -p ~/.gradle
echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties
- name: Setup Gradle
uses: gradle/gradle-build-action@v2 uses: gradle/gradle-build-action@v2
env: - name: Set up gradle user name
GRADLE_USER_HOME: ~/.gradle run: echo 'systemProp.user.name=spring-builds+github' >> gradle.properties
- name: Build with Gradle - name: Build with Gradle
env: env:
GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }} GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }}
@@ -70,18 +72,11 @@ jobs:
if: needs.prerequisites.outputs.runjobs if: needs.prerequisites.outputs.runjobs
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up JDK - name: Set up gradle
uses: actions/setup-java@v1 uses: spring-io/spring-gradle-build-action@v1
with: with:
java-version: '11' java-version: '11'
- name: Setup gradle user name distribution: 'adopt'
run: |
mkdir -p ~/.gradle
echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
env:
GRADLE_USER_HOME: ~/.gradle
- name: Snapshot Tests - name: Snapshot Tests
run: | run: |
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER" export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
@@ -95,18 +90,11 @@ jobs:
if: needs.prerequisites.outputs.runjobs if: needs.prerequisites.outputs.runjobs
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up JDK - name: Set up gradle
uses: actions/setup-java@v1 uses: spring-io/spring-gradle-build-action@v1
with: with:
java-version: '11' java-version: '11'
- name: Setup gradle user name distribution: 'adopt'
run: |
mkdir -p ~/.gradle
echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
env:
GRADLE_USER_HOME: ~/.gradle
- name: Check samples project - name: Check samples project
env: env:
LOCAL_REPOSITORY_PATH: ${{ github.workspace }}/build/publications/repos LOCAL_REPOSITORY_PATH: ${{ github.workspace }}/build/publications/repos
@@ -124,18 +112,11 @@ jobs:
if: needs.prerequisites.outputs.runjobs if: needs.prerequisites.outputs.runjobs
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up JDK - name: Set up gradle
uses: actions/setup-java@v1 uses: spring-io/spring-gradle-build-action@v1
with: with:
java-version: '11' java-version: '11'
- name: Setup gradle user name distribution: 'adopt'
run: |
mkdir -p ~/.gradle
echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
env:
GRADLE_USER_HOME: ~/.gradle
- name: Check for package tangles - name: Check for package tangles
run: | run: |
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER" export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
@@ -148,18 +129,11 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up JDK - name: Set up gradle
uses: actions/setup-java@v1 uses: spring-io/spring-gradle-build-action@v1
with: with:
java-version: '11' java-version: '11'
- name: Setup gradle user name distribution: 'adopt'
run: |
mkdir -p ~/.gradle
echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
env:
GRADLE_USER_HOME: ~/.gradle
- name: Deploy artifacts - name: Deploy artifacts
run: | run: |
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER" export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
@@ -179,18 +153,11 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up JDK - name: Set up gradle
uses: actions/setup-java@v1 uses: spring-io/spring-gradle-build-action@v1
with: with:
java-version: '11' java-version: '11'
- name: Setup gradle user name distribution: 'adopt'
run: |
mkdir -p ~/.gradle
echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
env:
GRADLE_USER_HOME: ~/.gradle
- name: Deploy Docs - name: Deploy Docs
run: | run: |
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER" export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
@@ -207,18 +174,11 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up JDK - name: Set up gradle
uses: actions/setup-java@v1 uses: spring-io/spring-gradle-build-action@v1
with: with:
java-version: '11' java-version: '11'
- name: Setup gradle user name distribution: 'adopt'
run: |
mkdir -p ~/.gradle
echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
env:
GRADLE_USER_HOME: ~/.gradle
- name: Deploy Schema - name: Deploy Schema
run: | run: |
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER" export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
@@ -233,6 +193,8 @@ jobs:
name: Perform release name: Perform release
needs: [prerequisites, deploy_artifacts, deploy_docs, deploy_schema] needs: [prerequisites, deploy_artifacts, deploy_docs, deploy_schema]
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: write
timeout-minutes: 90 timeout-minutes: 90
if: ${{ !endsWith(needs.prerequisites.outputs.project_version, '-SNAPSHOT') }} if: ${{ !endsWith(needs.prerequisites.outputs.project_version, '-SNAPSHOT') }}
env: env:
@@ -244,18 +206,11 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
- name: Set up JDK - name: Set up gradle
uses: actions/setup-java@v1 uses: spring-io/spring-gradle-build-action@v1
with: with:
java-version: '11' java-version: '11'
- name: Setup gradle user name distribution: 'adopt'
run: |
mkdir -p ~/.gradle
echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
env:
GRADLE_USER_HOME: ~/.gradle
- name: Wait for Artifactory Artifacts - name: Wait for Artifactory Artifacts
if: ${{ contains(needs.prerequisites.outputs.project_version, '-RC') || contains(needs.prerequisites.outputs.project_version, '-M') }} if: ${{ contains(needs.prerequisites.outputs.project_version, '-RC') || contains(needs.prerequisites.outputs.project_version, '-M') }}
run: | run: |
@@ -321,6 +276,9 @@ jobs:
name: Perform post-release name: Perform post-release
needs: [prerequisites, deploy_artifacts, deploy_docs, deploy_schema] needs: [prerequisites, deploy_artifacts, deploy_docs, deploy_schema]
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: read
issues: write
timeout-minutes: 90 timeout-minutes: 90
if: ${{ endsWith(needs.prerequisites.outputs.project_version, '-SNAPSHOT') }} if: ${{ endsWith(needs.prerequisites.outputs.project_version, '-SNAPSHOT') }}
env: env:
@@ -328,7 +286,8 @@ jobs:
VERSION: ${{ needs.prerequisites.outputs.project_version }} VERSION: ${{ needs.prerequisites.outputs.project_version }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: spring-io/spring-gradle-build-action@v1 - name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
with: with:
java-version: '11' java-version: '11'
distribution: 'adopt' distribution: 'adopt'
@@ -336,9 +295,11 @@ jobs:
run: ./gradlew scheduleNextRelease -PnextVersion=$VERSION -PgitHubAccessToken=$TOKEN run: ./gradlew scheduleNextRelease -PnextVersion=$VERSION -PgitHubAccessToken=$TOKEN
notify_result: notify_result:
name: Check for failures name: Check for failures
needs: [build_jdk_11, snapshot_tests, check_samples, check_tangles, deploy_artifacts, deploy_docs, deploy_schema, perform_release, perform_post_release] needs: [perform_release, perform_post_release]
if: failure() if: failure()
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
actions: read
steps: steps:
- name: Send Slack message - name: Send Slack message
# Workaround while waiting for Gamesight/slack-workflow-status#38 to be fixed # Workaround while waiting for Gamesight/slack-workflow-status#38 to be fixed
+12 -14
View File
@@ -7,29 +7,27 @@ on:
- cron: '0 10 * * *' # Once per day at 10am UTC - cron: '0 10 * * *' # Once per day at 10am UTC
workflow_dispatch: # Manual trigger workflow_dispatch: # Manual trigger
permissions:
contents: read
jobs: jobs:
deploy: deploy:
name: deploy name: deploy
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.repository == 'spring-projects/spring-security' }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up JDK 11 - name: Set up gradle
uses: actions/setup-java@v2 uses: spring-io/spring-gradle-build-action@v1
with: with:
java-version: '11' java-version: '11'
distribution: 'adopt' distribution: 'adopt'
- name: Validate Gradle wrapper - name: Cleanup Gradle Cache
uses: gradle/wrapper-validation-action@e6e38bacfdf1a337459f332974bb2327a31aaf4b # Remove some files from the Gradle cache, so they aren't cached by GitHub Actions.
- name: Setup Gradle # Restoring these files from a GitHub Actions cache might cause problems for future builds.
uses: gradle/gradle-build-action@v2 run: |
env: rm -f /home/runner/.gradle/caches/modules-2/modules-2.lock
GRADLE_USER_HOME: ~/.gradle rm -f /home/runner/.gradle/caches/modules-2/gc.properties
with:
# Remove some files from the Gradle cache, so they aren't cached by GitHub Actions.
# Restoring these files from a GitHub Actions cache might cause problems for future builds.
gradle-home-cache-excludes: |
caches/modules-2/modules-2.lock
caches/modules-2/gc.properties
- name: Build with Gradle - name: Build with Gradle
run: ./gradlew :spring-security-docs:antora --stacktrace run: ./gradlew :spring-security-docs:antora --stacktrace
- name: Deploy - name: Deploy
@@ -5,10 +5,15 @@ on:
env: env:
DUE_ON: ${{ github.event.milestone.due_on }} DUE_ON: ${{ github.event.milestone.due_on }}
TITLE: ${{ github.event.milestone.title }} TITLE: ${{ github.event.milestone.title }}
permissions:
contents: read
jobs: jobs:
spring-releasetrain-checks: spring-releasetrain-checks:
name: Check DueOn is on a Release Date name: Check DueOn is on a Release Date
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.repository == 'spring-projects/spring-security' }}
permissions:
contents: none
steps: steps:
- name: Print Milestone Being Checked - name: Print Milestone Being Checked
run: echo "Validating DueOn '$DUE_ON' for milestone '$TITLE'" run: echo "Validating DueOn '$DUE_ON' for milestone '$TITLE'"
@@ -23,6 +28,8 @@ jobs:
needs: [spring-releasetrain-checks] needs: [spring-releasetrain-checks]
if: failure() if: failure()
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
actions: read
steps: steps:
- name: Send Slack message - name: Send Slack message
uses: Gamesight/slack-workflow-status@v1.0.1 uses: Gamesight/slack-workflow-status@v1.0.1
+7 -15
View File
@@ -2,28 +2,20 @@ name: PR Build
on: pull_request on: pull_request
env: permissions:
RUN_JOBS: ${{ github.repository == 'spring-projects/spring-security' }} contents: read
jobs: jobs:
build: build:
name: Build name: Build
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.repository == 'spring-projects/spring-security' }}
steps: steps:
- if: env.RUN_JOBS == 'true' - uses: actions/checkout@v2
uses: actions/checkout@v2 - name: Set up gradle
- name: Set up JDK uses: spring-io/spring-gradle-build-action@v1
if: env.RUN_JOBS == 'true'
uses: actions/setup-java@v1
with: with:
java-version: '11' java-version: '11'
- name: Setup Gradle distribution: 'adopt'
if: env.RUN_JOBS == 'true'
uses: gradle/gradle-build-action@v2
with:
cache-read-only: true
env:
GRADLE_USER_HOME: ~/.gradle
- name: Build with Gradle - name: Build with Gradle
if: env.RUN_JOBS == 'true'
run: ./gradlew clean build --continue --scan run: ./gradlew clean build --continue --scan
@@ -9,30 +9,28 @@ env:
GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }} GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }}
GRADLE_ENTERPRISE_SECRET_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }} GRADLE_ENTERPRISE_SECRET_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
permissions:
contents: read
jobs: jobs:
update_scheduled_release_version: update_scheduled_release_version:
name: Initiate Release If Scheduled name: Initiate Release If Scheduled
if: ${{ github.repository == 'spring-projects/spring-security' }} if: ${{ github.repository == 'spring-projects/spring-security' }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: read
actions: read
steps: steps:
- id: checkout-source - id: checkout-source
name: Checkout Source Code name: Checkout Source Code
uses: actions/checkout@v2 uses: actions/checkout@v2
with: with:
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
- id: setup-jdk - name: Set up gradle
name: Set up JDK 11 uses: spring-io/spring-gradle-build-action@v1
uses: actions/setup-java@v1
with: with:
java-version: '11' java-version: '11'
- name: Setup gradle user name distribution: 'adopt'
run: |
mkdir -p ~/.gradle
echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
env:
GRADLE_USER_HOME: ~/.gradle
- id: check-release-due - id: check-release-due
name: Check Release Due name: Check Release Due
run: | run: |
+1 -1
View File
@@ -96,7 +96,7 @@ dependencies {
implementation 'io.spring.nohttp:nohttp-gradle:0.0.10' implementation 'io.spring.nohttp:nohttp-gradle:0.0.10'
implementation 'net.sourceforge.htmlunit:htmlunit:2.37.0' implementation 'net.sourceforge.htmlunit:htmlunit:2.37.0'
implementation 'org.hidetake:gradle-ssh-plugin:2.10.1' implementation 'org.hidetake:gradle-ssh-plugin:2.10.1'
implementation 'org.jfrog.buildinfo:build-info-extractor-gradle:4.24.20' implementation 'org.jfrog.buildinfo:build-info-extractor-gradle:4.29.0'
implementation 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.7.1' implementation 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.7.1'
testImplementation platform('org.junit:junit-bom:5.9.0-RC1') testImplementation platform('org.junit:junit-bom:5.9.0-RC1')
@@ -17,6 +17,7 @@ package io.spring.gradle.convention
import org.gradle.api.Plugin import org.gradle.api.Plugin
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.publish.maven.plugins.MavenPublishPlugin
class ArtifactoryPlugin implements Plugin<Project> { class ArtifactoryPlugin implements Plugin<Project> {
@@ -36,8 +37,14 @@ class ArtifactoryPlugin implements Plugin<Project> {
password = artifactoryPassword password = artifactoryPassword
} }
} }
defaults { }
publications('mavenJava') }
project.plugins.withType(MavenPublishPlugin) {
project.artifactory {
publish {
defaults {
publications('mavenJava')
}
} }
} }
} }
@@ -34,6 +34,7 @@ class RootProjectPlugin implements Plugin<Project> {
pluginManager.apply(NoHttpPlugin) pluginManager.apply(NoHttpPlugin)
pluginManager.apply(SpringNexusPublishPlugin) pluginManager.apply(SpringNexusPublishPlugin)
pluginManager.apply(CheckProhibitedDependenciesLifecyclePlugin) pluginManager.apply(CheckProhibitedDependenciesLifecyclePlugin)
pluginManager.apply(ArtifactoryPlugin)
pluginManager.apply("org.sonarqube") pluginManager.apply("org.sonarqube")
project.repositories.mavenCentral() project.repositories.mavenCentral()
@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2002-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -39,10 +39,19 @@ import org.springframework.security.config.annotation.web.servlet.configuration.
* &#064;EnableGlobalAuthentication * &#064;EnableGlobalAuthentication
* public class MyGlobalAuthenticationConfiguration { * public class MyGlobalAuthenticationConfiguration {
* *
* &#064;Autowired * &#064;Bean
* public void configureGlobal(AuthenticationManagerBuilder auth) { * public UserDetailsService userDetailsService() {
* auth.inMemoryAuthentication().withUser(&quot;user&quot;).password(&quot;password&quot;).roles(&quot;USER&quot;) * UserDetails user = User.withDefaultPasswordEncoder()
* .and().withUser(&quot;admin&quot;).password(&quot;password&quot;).roles(&quot;USER&quot;, &quot;ADMIN&quot;); * .username(&quot;user&quot;)
* .password(&quot;password&quot;)
* .roles(&quot;USER&quot;)
* .build();
* UserDetails admin = User.withDefaultPasswordEncoder()
* .username(&quot;admin&quot;)
* .password(&quot;password&quot;)
* .roles(&quot;ADMIN&quot;, &quot;USER&quot;)
* .build();
* return new InMemoryUserDetailsManager(user, admin);
* } * }
* } * }
* </pre> * </pre>
@@ -54,15 +63,24 @@ import org.springframework.security.config.annotation.web.servlet.configuration.
* <pre class="code"> * <pre class="code">
* &#064;Configuration * &#064;Configuration
* &#064;EnableWebSecurity * &#064;EnableWebSecurity
* public class MyWebSecurityConfiguration extends WebSecurityConfigurerAdapter { * public class MyWebSecurityConfiguration {
* *
* &#064;Autowired * &#064;Bean
* public void configureGlobal(AuthenticationManagerBuilder auth) { * public UserDetailsService userDetailsService() {
* auth.inMemoryAuthentication().withUser(&quot;user&quot;).password(&quot;password&quot;).roles(&quot;USER&quot;) * UserDetails user = User.withDefaultPasswordEncoder()
* .and().withUser(&quot;admin&quot;).password(&quot;password&quot;).roles(&quot;USER&quot;, &quot;ADMIN&quot;); * .username(&quot;user&quot;)
* .password(&quot;password&quot;)
* .roles(&quot;USER&quot;)
* .build();
* UserDetails admin = User.withDefaultPasswordEncoder()
* .username(&quot;admin&quot;)
* .password(&quot;password&quot;)
* .roles(&quot;ADMIN&quot;, &quot;USER&quot;)
* .build();
* return new InMemoryUserDetailsManager(user, admin);
* } * }
* *
* // Possibly overridden methods ... * // Possibly more bean methods ...
* } * }
* </pre> * </pre>
* *
@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -23,19 +23,16 @@ import org.springframework.security.config.annotation.SecurityBuilder;
import org.springframework.security.config.annotation.SecurityConfigurer; import org.springframework.security.config.annotation.SecurityConfigurer;
import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
/** /**
* Allows customization to the {@link WebSecurity}. In most instances users will use * Allows customization to the {@link WebSecurity}. In most instances users will use
* {@link EnableWebSecurity} and either create a {@link Configuration} that extends * {@link EnableWebSecurity} and create a {@link Configuration} that exposes a
* {@link WebSecurityConfigurerAdapter} or expose a {@link SecurityFilterChain} bean. Both * {@link SecurityFilterChain} bean. This will automatically be applied to the
* will automatically be applied to the {@link WebSecurity} by the * {@link WebSecurity} by the {@link EnableWebSecurity} annotation.
* {@link EnableWebSecurity} annotation.
* *
* @author Rob Winch * @author Rob Winch
* @since 3.2 * @since 3.2
* @see WebSecurityConfigurerAdapter
* @see SecurityFilterChain * @see SecurityFilterChain
*/ */
public interface WebSecurityConfigurer<T extends SecurityBuilder<Filter>> extends SecurityConfigurer<Filter, T> { public interface WebSecurityConfigurer<T extends SecurityBuilder<Filter>> extends SecurityConfigurer<Filter, T> {
@@ -42,7 +42,6 @@ import org.springframework.security.config.annotation.web.AbstractRequestMatcher
import org.springframework.security.config.annotation.web.WebSecurityConfigurer; import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.web.DefaultSecurityFilterChain; import org.springframework.security.web.DefaultSecurityFilterChain;
@@ -77,8 +76,7 @@ import org.springframework.web.filter.DelegatingFilterProxy;
* *
* <p> * <p>
* Customizations to the {@link WebSecurity} can be made by creating a * Customizations to the {@link WebSecurity} can be made by creating a
* {@link WebSecurityConfigurer}, overriding {@link WebSecurityConfigurerAdapter} or * {@link WebSecurityConfigurer} or exposing a {@link WebSecurityCustomizer} bean.
* exposing a {@link WebSecurityCustomizer} bean.
* </p> * </p>
* *
* @author Rob Winch * @author Rob Winch
@@ -200,7 +198,7 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
* *
* <p> * <p>
* Typically this method is invoked automatically within the framework from * Typically this method is invoked automatically within the framework from
* {@link WebSecurityConfigurerAdapter#init(WebSecurity)} * {@link WebSecurityConfiguration#springSecurityFilterChain()}
* </p> * </p>
* @param securityFilterChainBuilder the builder to use to create the * @param securityFilterChainBuilder the builder to use to create the
* {@link SecurityFilterChain} instances * {@link SecurityFilterChain} instances
@@ -258,12 +256,13 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
/** /**
* Sets the {@link FilterSecurityInterceptor}. This is typically invoked by * Sets the {@link FilterSecurityInterceptor}. This is typically invoked by
* {@link WebSecurityConfigurerAdapter}. * {@link WebSecurityConfiguration#springSecurityFilterChain()}.
* @param securityInterceptor the {@link FilterSecurityInterceptor} to use * @param securityInterceptor the {@link FilterSecurityInterceptor} to use
* @return the {@link WebSecurity} for further customizations * @return the {@link WebSecurity} for further customizations
* @deprecated Use {@link #privilegeEvaluator(WebInvocationPrivilegeEvaluator)} * @deprecated Use {@link #privilegeEvaluator(WebInvocationPrivilegeEvaluator)}
* instead * instead
*/ */
@Deprecated
public WebSecurity securityInterceptor(FilterSecurityInterceptor securityInterceptor) { public WebSecurity securityInterceptor(FilterSecurityInterceptor securityInterceptor) {
this.filterSecurityInterceptor = securityInterceptor; this.filterSecurityInterceptor = securityInterceptor;
return this; return this;
@@ -296,8 +295,7 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
protected Filter performBuild() throws Exception { protected Filter performBuild() throws Exception {
Assert.state(!this.securityFilterChainBuilders.isEmpty(), Assert.state(!this.securityFilterChainBuilders.isEmpty(),
() -> "At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. " () -> "At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. "
+ "Typically this is done by exposing a SecurityFilterChain bean " + "Typically this is done by exposing a SecurityFilterChain bean. "
+ "or by adding a @Configuration that extends WebSecurityConfigurerAdapter. "
+ "More advanced users can invoke " + WebSecurity.class.getSimpleName() + "More advanced users can invoke " + WebSecurity.class.getSimpleName()
+ ".addSecurityFilterChainBuilder directly"); + ".addSecurityFilterChainBuilder directly");
int chainSize = this.ignoredRequests.size() + this.securityFilterChainBuilders.size(); int chainSize = this.ignoredRequests.size() + this.securityFilterChainBuilders.size();
@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -26,48 +26,56 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.security.config.annotation.authentication.configuration.EnableGlobalAuthentication; import org.springframework.security.config.annotation.authentication.configuration.EnableGlobalAuthentication;
import org.springframework.security.config.annotation.web.WebSecurityConfigurer; import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
import org.springframework.security.web.SecurityFilterChain;
/** /**
* Add this annotation to an {@code @Configuration} class to have the Spring Security * Add this annotation to an {@code @Configuration} class to have the Spring Security
* configuration defined in any {@link WebSecurityConfigurer} or more likely by extending * configuration defined in any {@link WebSecurityConfigurer} or more likely by exposing a
* the {@link WebSecurityConfigurerAdapter} base class and overriding individual methods: * {@link SecurityFilterChain} bean:
* *
* <pre class="code"> * <pre class="code">
* &#064;Configuration * &#064;Configuration
* &#064;EnableWebSecurity * &#064;EnableWebSecurity
* public class MyWebSecurityConfiguration extends WebSecurityConfigurerAdapter { * public class MyWebSecurityConfiguration {
* *
* &#064;Override * &#064;Bean
* public void configure(WebSecurity web) throws Exception { * public WebSecurityCustomizer webSecurityCustomizer() {
* web.ignoring() * return (web) -> web.ignoring()
* // Spring Security should completely ignore URLs starting with /resources/ * // Spring Security should completely ignore URLs starting with /resources/
* .antMatchers(&quot;/resources/**&quot;); * .antMatchers(&quot;/resources/**&quot;);
* } * }
* *
* &#064;Override * &#064;Bean
* protected void configure(HttpSecurity http) throws Exception { * public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
* http.authorizeRequests().antMatchers(&quot;/public/**&quot;).permitAll().anyRequest() * http.authorizeRequests().antMatchers(&quot;/public/**&quot;).permitAll().anyRequest()
* .hasRole(&quot;USER&quot;).and() * .hasRole(&quot;USER&quot;).and()
* // Possibly more configuration ... * // Possibly more configuration ...
* .formLogin() // enable form based log in * .formLogin() // enable form based log in
* // set permitAll for all URLs associated with Form Login * // set permitAll for all URLs associated with Form Login
* .permitAll(); * .permitAll();
* return http.build();
* } * }
* *
* &#064;Override * &#064;Bean
* protected void configure(AuthenticationManagerBuilder auth) throws Exception { * public UserDetailsService userDetailsService() {
* auth * UserDetails user = User.withDefaultPasswordEncoder()
* // enable in memory based authentication with a user named &quot;user&quot; and &quot;admin&quot; * .username(&quot;user&quot;)
* .inMemoryAuthentication().withUser(&quot;user&quot;).password(&quot;password&quot;).roles(&quot;USER&quot;) * .password(&quot;password&quot;)
* .and().withUser(&quot;admin&quot;).password(&quot;password&quot;).roles(&quot;USER&quot;, &quot;ADMIN&quot;); * .roles(&quot;USER&quot;)
* .build();
* UserDetails admin = User.withDefaultPasswordEncoder()
* .username(&quot;admin&quot;)
* .password(&quot;password&quot;)
* .roles(&quot;ADMIN&quot;, &quot;USER&quot;)
* .build();
* return new InMemoryUserDetailsManager(user, admin);
* } * }
* *
* // Possibly more overridden methods ... * // Possibly more bean methods ...
* } * }
* </pre> * </pre>
* *
* @see WebSecurityConfigurer * @see WebSecurityConfigurer
* @see WebSecurityConfigurerAdapter
* *
* @author Rob Winch * @author Rob Winch
* @since 3.2 * @since 3.2
@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2021 the original author or authors. * Copyright 2002-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -54,10 +54,9 @@ import org.springframework.util.Assert;
/** /**
* Uses a {@link WebSecurity} to create the {@link FilterChainProxy} that performs the web * Uses a {@link WebSecurity} to create the {@link FilterChainProxy} that performs the web
* based security for Spring Security. It then exports the necessary beans. Customizations * based security for Spring Security. It then exports the necessary beans. Customizations
* can be made to {@link WebSecurity} by extending {@link WebSecurityConfigurerAdapter} * can be made to {@link WebSecurity} by implementing {@link WebSecurityConfigurer} and
* and exposing it as a {@link Configuration} or implementing * exposing it as a {@link Configuration} or exposing a {@link WebSecurityCustomizer}
* {@link WebSecurityConfigurer} and exposing it as a {@link Configuration}. This * bean. This configuration is imported when using {@link EnableWebSecurity}.
* configuration is imported when using {@link EnableWebSecurity}.
* *
* @author Rob Winch * @author Rob Winch
* @author Keesun Baik * @author Keesun Baik
@@ -25,7 +25,7 @@ import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationDetailsSource; import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder; import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.openid.OpenIDLoginConfigurer; import org.springframework.security.config.annotation.web.configurers.openid.OpenIDLoginConfigurer;
import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.PortMapper; import org.springframework.security.web.PortMapper;
@@ -307,14 +307,14 @@ public abstract class AbstractAuthenticationFilterConfigurer<B extends HttpSecur
/** /**
* <p> * <p>
* Specifies the URL to send users to if login is required. If used with * Specifies the URL to send users to if login is required. If used with
* {@link WebSecurityConfigurerAdapter} a default login page will be generated when * {@link EnableWebSecurity} a default login page will be generated when this
* this attribute is not specified. * attribute is not specified.
* </p> * </p>
* *
* <p> * <p>
* If a URL is specified or this is not being used in conjunction with * If a URL is specified or this is not being used in conjunction with
* {@link WebSecurityConfigurerAdapter}, users are required to process the specified * {@link EnableWebSecurity}, users are required to process the specified URL to
* URL to generate a login page. * generate a login page.
* </p> * </p>
*/ */
protected T loginPage(String loginPage) { protected T loginPage(String loginPage) {
@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2021 the original author or authors. * Copyright 2002-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -22,7 +22,7 @@ import java.util.Map;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder; import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter; import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
import org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter; import org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter;
@@ -30,7 +30,7 @@ import org.springframework.security.web.csrf.CsrfToken;
/** /**
* Adds a Filter that will generate a login page if one is not specified otherwise when * Adds a Filter that will generate a login page if one is not specified otherwise when
* using {@link WebSecurityConfigurerAdapter}. * using {@link EnableWebSecurity}.
* *
* <p> * <p>
* By default an * By default an
@@ -64,7 +64,7 @@ import org.springframework.security.web.csrf.CsrfToken;
* *
* @author Rob Winch * @author Rob Winch
* @since 3.2 * @since 3.2
* @see WebSecurityConfigurerAdapter * @see EnableWebSecurity
*/ */
public final class DefaultLoginPageConfigurer<H extends HttpSecurityBuilder<H>> public final class DefaultLoginPageConfigurer<H extends HttpSecurityBuilder<H>>
extends AbstractHttpConfigurer<DefaultLoginPageConfigurer<H>, H> { extends AbstractHttpConfigurer<DefaultLoginPageConfigurer<H>, H> {
@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2002-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -18,7 +18,7 @@ package org.springframework.security.config.annotation.web.configurers;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder; import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.ForwardAuthenticationFailureHandler; import org.springframework.security.web.authentication.ForwardAuthenticationFailureHandler;
import org.springframework.security.web.authentication.ForwardAuthenticationSuccessHandler; import org.springframework.security.web.authentication.ForwardAuthenticationSuccessHandler;
@@ -84,15 +84,15 @@ public final class FormLoginConfigurer<H extends HttpSecurityBuilder<H>> extends
/** /**
* <p> * <p>
* Specifies the URL to send users to if login is required. If used with * Specifies the URL to send users to if login is required. If used with
* {@link WebSecurityConfigurerAdapter} a default login page will be generated when * {@link EnableWebSecurity} a default login page will be generated when this
* this attribute is not specified. * attribute is not specified.
* </p> * </p>
* *
* <p> * <p>
* If a URL is specified or this is not being used in conjunction with * If a URL is specified or this is not being used in conjunction with
* {@link WebSecurityConfigurerAdapter}, users are required to process the specified * {@link EnableWebSecurity}, users are required to process the specified URL to
* URL to generate a login page. In general, the login page should create a form that * generate a login page. In general, the login page should create a form that submits
* submits a request with the following requirements to work with * a request with the following requirements to work with
* {@link UsernamePasswordAuthenticationFilter}: * {@link UsernamePasswordAuthenticationFilter}:
* </p> * </p>
* *
@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2021 the original author or authors. * Copyright 2002-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -26,7 +26,7 @@ import javax.servlet.http.HttpServletRequest;
import org.springframework.security.config.Customizer; import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder; import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.header.HeaderWriter; import org.springframework.security.web.header.HeaderWriter;
import org.springframework.security.web.header.HeaderWriterFilter; import org.springframework.security.web.header.HeaderWriterFilter;
import org.springframework.security.web.header.writers.CacheControlHeadersWriter; import org.springframework.security.web.header.writers.CacheControlHeadersWriter;
@@ -50,7 +50,7 @@ import org.springframework.util.Assert;
/** /**
* <p> * <p>
* Adds the Security HTTP headers to the response. Security HTTP headers is activated by * Adds the Security HTTP headers to the response. Security HTTP headers is activated by
* default when using {@link WebSecurityConfigurerAdapter}'s default constructor. * default when using {@link EnableWebSecurity}'s default constructor.
* </p> * </p>
* *
* <p> * <p>
@@ -22,10 +22,8 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.RememberMeAuthenticationProvider; import org.springframework.security.authentication.RememberMeAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder; import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
@@ -150,13 +148,10 @@ public final class RememberMeConfigurer<H extends HttpSecurityBuilder<H>>
/** /**
* Specifies the {@link UserDetailsService} used to look up the {@link UserDetails} * Specifies the {@link UserDetailsService} used to look up the {@link UserDetails}
* when a remember me token is valid. The default is to use the * when a remember me token is valid. When using a
* {@link UserDetailsService} found by invoking * {@link org.springframework.security.web.SecurityFilterChain} bean, the default is
* {@link HttpSecurity#getSharedObject(Class)} which is set when using * to look for a {@link UserDetailsService} bean. Alternatively, one can populate
* {@link WebSecurityConfigurerAdapter#configure(AuthenticationManagerBuilder)}. When * {@link #rememberMeServices(RememberMeServices)}.
* using a {@link org.springframework.security.web.SecurityFilterChain} bean, the
* default is to look for a {@link UserDetailsService} bean. Alternatively, one can
* populate {@link #rememberMeServices(RememberMeServices)}.
* @param userDetailsService the {@link UserDetailsService} to configure * @param userDetailsService the {@link UserDetailsService} to configure
* @return the {@link RememberMeConfigurer} for further customization * @return the {@link RememberMeConfigurer} for further customization
* @see AbstractRememberMeServices * @see AbstractRememberMeServices
@@ -24,13 +24,11 @@ import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder; import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService; import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper; import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint; import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider; import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
@@ -144,10 +142,7 @@ public final class X509Configurer<H extends HttpSecurityBuilder<H>>
/** /**
* Specifies the {@link AuthenticationUserDetailsService} to use. If not specified, * Specifies the {@link AuthenticationUserDetailsService} to use. If not specified,
* the shared {@link UserDetailsService} will be used to create a * then the {@link UserDetailsService} bean will be used by default.
* {@link UserDetailsByNameServiceWrapper}. If a {@link SecurityFilterChain} bean is
* used instead of the {@link WebSecurityConfigurerAdapter}, then the
* {@link UserDetailsService} bean will be used by default.
* @param authenticationUserDetailsService the * @param authenticationUserDetailsService the
* {@link AuthenticationUserDetailsService} to use * {@link AuthenticationUserDetailsService} to use
* @return the {@link X509Configurer} for further customizations * @return the {@link X509Configurer} for further customizations
@@ -34,6 +34,7 @@ import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequest
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver; import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.savedrequest.RequestCache; import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@@ -171,6 +172,8 @@ public final class OAuth2ClientConfigurer<B extends HttpSecurityBuilder<B>>
private AuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository; private AuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository;
private RedirectStrategy authorizationRedirectStrategy;
private OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient; private OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient;
private AuthorizationCodeGrantConfigurer() { private AuthorizationCodeGrantConfigurer() {
@@ -202,6 +205,17 @@ public final class OAuth2ClientConfigurer<B extends HttpSecurityBuilder<B>>
return this; return this;
} }
/**
* Sets the redirect strategy for Authorization Endpoint redirect URI.
* @param authorizationRedirectStrategy the redirect strategy
* @return the {@link AuthorizationCodeGrantConfigurer} for further configuration
*/
public AuthorizationCodeGrantConfigurer authorizationRedirectStrategy(
RedirectStrategy authorizationRedirectStrategy) {
this.authorizationRedirectStrategy = authorizationRedirectStrategy;
return this;
}
/** /**
* Sets the client used for requesting the access token credential from the Token * Sets the client used for requesting the access token credential from the Token
* Endpoint. * Endpoint.
@@ -247,6 +261,9 @@ public final class OAuth2ClientConfigurer<B extends HttpSecurityBuilder<B>>
authorizationRequestRedirectFilter authorizationRequestRedirectFilter
.setAuthorizationRequestRepository(this.authorizationRequestRepository); .setAuthorizationRequestRepository(this.authorizationRequestRepository);
} }
if (this.authorizationRedirectStrategy != null) {
authorizationRequestRedirectFilter.setAuthorizationRedirectStrategy(this.authorizationRedirectStrategy);
}
RequestCache requestCache = builder.getSharedObject(RequestCache.class); RequestCache requestCache = builder.getSharedObject(RequestCache.class);
if (requestCache != null) { if (requestCache != null) {
authorizationRequestRedirectFilter.setRequestCache(requestCache); authorizationRequestRedirectFilter.setRequestCache(requestCache);
@@ -68,6 +68,7 @@ import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.oauth2.jwt.JwtDecoderFactory; import org.springframework.security.oauth2.jwt.JwtDecoderFactory;
import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.authentication.DelegatingAuthenticationEntryPoint; import org.springframework.security.web.authentication.DelegatingAuthenticationEntryPoint;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter; import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
@@ -368,6 +369,10 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
authorizationRequestFilter authorizationRequestFilter
.setAuthorizationRequestRepository(this.authorizationEndpointConfig.authorizationRequestRepository); .setAuthorizationRequestRepository(this.authorizationEndpointConfig.authorizationRequestRepository);
} }
if (this.authorizationEndpointConfig.authorizationRedirectStrategy != null) {
authorizationRequestFilter
.setAuthorizationRedirectStrategy(this.authorizationEndpointConfig.authorizationRedirectStrategy);
}
RequestCache requestCache = http.getSharedObject(RequestCache.class); RequestCache requestCache = http.getSharedObject(RequestCache.class);
if (requestCache != null) { if (requestCache != null) {
authorizationRequestFilter.setRequestCache(requestCache); authorizationRequestFilter.setRequestCache(requestCache);
@@ -540,6 +545,8 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
private AuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository; private AuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository;
private RedirectStrategy authorizationRedirectStrategy;
private AuthorizationEndpointConfig() { private AuthorizationEndpointConfig() {
} }
@@ -582,6 +589,17 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
return this; return this;
} }
/**
* Sets the redirect strategy for Authorization Endpoint redirect URI.
* @param authorizationRedirectStrategy the redirect strategy
* @return the {@link AuthorizationEndpointConfig} for further configuration
*/
public AuthorizationEndpointConfig authorizationRedirectStrategy(
RedirectStrategy authorizationRedirectStrategy) {
this.authorizationRedirectStrategy = authorizationRedirectStrategy;
return this;
}
/** /**
* Returns the {@link OAuth2LoginConfigurer} for further configuration. * Returns the {@link OAuth2LoginConfigurer} for further configuration.
* @return the {@link OAuth2LoginConfigurer} * @return the {@link OAuth2LoginConfigurer}
@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -31,7 +31,7 @@ import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.Customizer; import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder; import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer; import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer;
import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer; import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer;
import org.springframework.security.config.annotation.web.configurers.RememberMeConfigurer; import org.springframework.security.config.annotation.web.configurers.RememberMeConfigurer;
@@ -61,29 +61,29 @@ import org.springframework.security.web.util.matcher.RequestMatcher;
* <h2>Example Configuration</h2> * <h2>Example Configuration</h2>
* *
* <pre> * <pre>
*
* &#064;Configuration * &#064;Configuration
* &#064;EnableWebSecurity * &#064;EnableWebSecurity
* public class OpenIDLoginConfig extends WebSecurityConfigurerAdapter { * public class OpenIDLoginConfig {
* *
* &#064;Override * &#064;Bean
* protected void configure(HttpSecurity http) { * public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
* http * http
* .authorizeRequests() * .authorizeRequests()
* .antMatchers(&quot;/**&quot;).hasRole(&quot;USER&quot;) * .antMatchers(&quot;/**&quot;).hasRole(&quot;USER&quot;)
* .and() * .and()
* .openidLogin() * .openidLogin()
* .permitAll(); * .permitAll();
* return http.build();
* } * }
* *
* &#064;Override * &#064;Bean
* protected void configure(AuthenticationManagerBuilder auth)( * public UserDetailsService userDetailsService() {
* AuthenticationManagerBuilder auth) throws Exception { * UserDetails user = User.withDefaultPasswordEncoder()
* auth * .username(&quot;https://www.google.com/accounts/o8/id?id=lmkCn9xzPdsxVwG7pjYMuDgNNdASFmobNkcRPaWU&quot;)
* .inMemoryAuthentication() * .password(&quot;password&quot;)
* .withUser(&quot;https://www.google.com/accounts/o8/id?id=lmkCn9xzPdsxVwG7pjYMuDgNNdASFmobNkcRPaWU&quot;) * .roles(&quot;USER&quot;)
* .password(&quot;password&quot;) * .build();
* .roles(&quot;USER&quot;); * return new InMemoryUserDetailsManager(user);
* } * }
* } * }
* </pre> * </pre>
@@ -229,14 +229,14 @@ public final class OpenIDLoginConfigurer<H extends HttpSecurityBuilder<H>>
/** /**
* <p> * <p>
* Specifies the URL to send users to if login is required. If used with * Specifies the URL to send users to if login is required. If used with
* {@link WebSecurityConfigurerAdapter} a default login page will be generated when * {@link EnableWebSecurity} a default login page will be generated when this
* this attribute is not specified. * attribute is not specified.
* </p> * </p>
* *
* <p> * <p>
* If a URL is specified or this is not being used in conjunction with * If a URL is specified or this is not being used in conjunction with
* {@link WebSecurityConfigurerAdapter}, users are required to process the specified * {@link EnableWebSecurity}, users are required to process the specified URL to
* URL to generate a login page. * generate a login page.
* </p> * </p>
* *
* <ul> * <ul>
@@ -47,10 +47,16 @@ import org.springframework.security.saml2.provider.service.web.Saml2Authenticati
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestRepository; import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestRepository;
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationTokenConverter; import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationTokenConverter;
import org.springframework.security.saml2.provider.service.web.authentication.Saml2AuthenticationRequestResolver; import org.springframework.security.saml2.provider.service.web.authentication.Saml2AuthenticationRequestResolver;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.AuthenticationConverter; import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.DelegatingAuthenticationEntryPoint;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter; import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
import org.springframework.security.web.util.matcher.AndRequestMatcher;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
@@ -252,8 +258,7 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
this.updateAuthenticationDefaults(); this.updateAuthenticationDefaults();
this.updateAccessDefaults(http); this.updateAccessDefaults(http);
String loginUrl = providerUrlMap.entrySet().iterator().next().getKey(); String loginUrl = providerUrlMap.entrySet().iterator().next().getKey();
final LoginUrlAuthenticationEntryPoint entryPoint = new LoginUrlAuthenticationEntryPoint(loginUrl); registerAuthenticationEntryPoint(http, getLoginEntryPoint(http, loginUrl));
registerAuthenticationEntryPoint(http, entryPoint);
} }
else { else {
super.init(http); super.init(http);
@@ -295,6 +300,22 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
return this.relyingPartyRegistrationRepository; return this.relyingPartyRegistrationRepository;
} }
private AuthenticationEntryPoint getLoginEntryPoint(B http, String providerLoginPage) {
RequestMatcher loginPageMatcher = new AntPathRequestMatcher(this.getLoginPage());
RequestMatcher faviconMatcher = new AntPathRequestMatcher("/favicon.ico");
RequestMatcher defaultEntryPointMatcher = this.getAuthenticationEntryPointMatcher(http);
RequestMatcher defaultLoginPageMatcher = new AndRequestMatcher(
new OrRequestMatcher(loginPageMatcher, faviconMatcher), defaultEntryPointMatcher);
RequestMatcher notXRequestedWith = new NegatedRequestMatcher(
new RequestHeaderRequestMatcher("X-Requested-With", "XMLHttpRequest"));
LinkedHashMap<RequestMatcher, AuthenticationEntryPoint> entryPoints = new LinkedHashMap<>();
entryPoints.put(new AndRequestMatcher(notXRequestedWith, new NegatedRequestMatcher(defaultLoginPageMatcher)),
new LoginUrlAuthenticationEntryPoint(providerLoginPage));
DelegatingAuthenticationEntryPoint loginEntryPoint = new DelegatingAuthenticationEntryPoint(entryPoints);
loginEntryPoint.setDefaultEntryPoint(this.getAuthenticationEntryPoint());
return loginEntryPoint;
}
private void setAuthenticationRequestRepository(B http, private void setAuthenticationRequestRepository(B http,
Saml2WebSsoAuthenticationFilter saml2WebSsoAuthenticationFilter) { Saml2WebSsoAuthenticationFilter saml2WebSsoAuthenticationFilter) {
saml2WebSsoAuthenticationFilter.setAuthenticationRequestRepository(getAuthenticationRequestRepository(http)); saml2WebSsoAuthenticationFilter.setAuthenticationRequestRepository(getAuthenticationRequestRepository(http));
@@ -44,6 +44,8 @@ final class OAuth2ClientBeanDefinitionParser implements BeanDefinitionParser {
private static final String ATT_AUTHORIZATION_REQUEST_RESOLVER_REF = "authorization-request-resolver-ref"; private static final String ATT_AUTHORIZATION_REQUEST_RESOLVER_REF = "authorization-request-resolver-ref";
private static final String ATT_AUTHORIZATION_REDIRECT_STRATEGY_REF = "authorization-redirect-strategy-ref";
private static final String ATT_ACCESS_TOKEN_RESPONSE_CLIENT_REF = "access-token-response-client-ref"; private static final String ATT_ACCESS_TOKEN_RESPONSE_CLIENT_REF = "access-token-response-client-ref";
private final BeanReference requestCache; private final BeanReference requestCache;
@@ -87,6 +89,7 @@ final class OAuth2ClientBeanDefinitionParser implements BeanDefinitionParser {
} }
BeanMetadataElement authorizationRequestRepository = getAuthorizationRequestRepository( BeanMetadataElement authorizationRequestRepository = getAuthorizationRequestRepository(
authorizationCodeGrantElt); authorizationCodeGrantElt);
BeanMetadataElement authorizationRedirectStrategy = getAuthorizationRedirectStrategy(authorizationCodeGrantElt);
BeanDefinitionBuilder authorizationRequestRedirectFilterBuilder = BeanDefinitionBuilder BeanDefinitionBuilder authorizationRequestRedirectFilterBuilder = BeanDefinitionBuilder
.rootBeanDefinition(OAuth2AuthorizationRequestRedirectFilter.class); .rootBeanDefinition(OAuth2AuthorizationRequestRedirectFilter.class);
String authorizationRequestResolverRef = (authorizationCodeGrantElt != null) String authorizationRequestResolverRef = (authorizationCodeGrantElt != null)
@@ -99,6 +102,7 @@ final class OAuth2ClientBeanDefinitionParser implements BeanDefinitionParser {
} }
this.authorizationRequestRedirectFilter = authorizationRequestRedirectFilterBuilder this.authorizationRequestRedirectFilter = authorizationRequestRedirectFilterBuilder
.addPropertyValue("authorizationRequestRepository", authorizationRequestRepository) .addPropertyValue("authorizationRequestRepository", authorizationRequestRepository)
.addPropertyValue("authorizationRedirectStrategy", authorizationRedirectStrategy)
.addPropertyValue("requestCache", this.requestCache).getBeanDefinition(); .addPropertyValue("requestCache", this.requestCache).getBeanDefinition();
BeanDefinitionBuilder authorizationCodeGrantFilterBldr = BeanDefinitionBuilder BeanDefinitionBuilder authorizationCodeGrantFilterBldr = BeanDefinitionBuilder
.rootBeanDefinition(OAuth2AuthorizationCodeGrantFilter.class) .rootBeanDefinition(OAuth2AuthorizationCodeGrantFilter.class)
@@ -130,6 +134,16 @@ final class OAuth2ClientBeanDefinitionParser implements BeanDefinitionParser {
.getBeanDefinition(); .getBeanDefinition();
} }
private BeanMetadataElement getAuthorizationRedirectStrategy(Element element) {
String authorizationRedirectStrategyRef = (element != null)
? element.getAttribute(ATT_AUTHORIZATION_REDIRECT_STRATEGY_REF) : null;
if (StringUtils.hasText(authorizationRedirectStrategyRef)) {
return new RuntimeBeanReference(authorizationRedirectStrategyRef);
}
return BeanDefinitionBuilder.rootBeanDefinition("org.springframework.security.web.DefaultRedirectStrategy")
.getBeanDefinition();
}
private BeanMetadataElement getAccessTokenResponseClient(Element element) { private BeanMetadataElement getAccessTokenResponseClient(Element element) {
String accessTokenResponseClientRef = (element != null) String accessTokenResponseClientRef = (element != null)
? element.getAttribute(ATT_ACCESS_TOKEN_RESPONSE_CLIENT_REF) : null; ? element.getAttribute(ATT_ACCESS_TOKEN_RESPONSE_CLIENT_REF) : null;
@@ -87,6 +87,8 @@ final class OAuth2LoginBeanDefinitionParser implements BeanDefinitionParser {
private static final String ATT_AUTHORIZATION_REQUEST_RESOLVER_REF = "authorization-request-resolver-ref"; private static final String ATT_AUTHORIZATION_REQUEST_RESOLVER_REF = "authorization-request-resolver-ref";
private static final String ATT_AUTHORIZATION_REDIRECT_STRATEGY_REF = "authorization-redirect-strategy-ref";
private static final String ATT_ACCESS_TOKEN_RESPONSE_CLIENT_REF = "access-token-response-client-ref"; private static final String ATT_ACCESS_TOKEN_RESPONSE_CLIENT_REF = "access-token-response-client-ref";
private static final String ATT_USER_AUTHORITIES_MAPPER_REF = "user-authorities-mapper-ref"; private static final String ATT_USER_AUTHORITIES_MAPPER_REF = "user-authorities-mapper-ref";
@@ -203,6 +205,7 @@ final class OAuth2LoginBeanDefinitionParser implements BeanDefinitionParser {
} }
oauth2AuthorizationRequestRedirectFilterBuilder oauth2AuthorizationRequestRedirectFilterBuilder
.addPropertyValue("authorizationRequestRepository", authorizationRequestRepository) .addPropertyValue("authorizationRequestRepository", authorizationRequestRepository)
.addPropertyValue("authorizationRedirectStrategy", getAuthorizationRedirectStrategy(element))
.addPropertyValue("requestCache", this.requestCache); .addPropertyValue("requestCache", this.requestCache);
this.oauth2AuthorizationRequestRedirectFilter = oauth2AuthorizationRequestRedirectFilterBuilder this.oauth2AuthorizationRequestRedirectFilter = oauth2AuthorizationRequestRedirectFilterBuilder
.getBeanDefinition(); .getBeanDefinition();
@@ -267,6 +270,15 @@ final class OAuth2LoginBeanDefinitionParser implements BeanDefinitionParser {
.getBeanDefinition(); .getBeanDefinition();
} }
private BeanMetadataElement getAuthorizationRedirectStrategy(Element element) {
String authorizationRedirectStrategyRef = element.getAttribute(ATT_AUTHORIZATION_REDIRECT_STRATEGY_REF);
if (StringUtils.hasText(authorizationRedirectStrategyRef)) {
return new RuntimeBeanReference(authorizationRedirectStrategyRef);
}
return BeanDefinitionBuilder.rootBeanDefinition("org.springframework.security.web.DefaultRedirectStrategy")
.getBeanDefinition();
}
private BeanDefinition getOidcAuthProvider(Element element, BeanMetadataElement accessTokenResponseClient, private BeanDefinition getOidcAuthProvider(Element element, BeanMetadataElement accessTokenResponseClient,
String userAuthoritiesMapperRef) { String userAuthoritiesMapperRef) {
boolean oidcAuthenticationProviderEnabled = ClassUtils boolean oidcAuthenticationProviderEnabled = ClassUtils
@@ -102,12 +102,14 @@ import org.springframework.security.oauth2.server.resource.web.server.ServerBear
import org.springframework.security.web.PortMapper; import org.springframework.security.web.PortMapper;
import org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor; import org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor;
import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor; import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor;
import org.springframework.security.web.server.DefaultServerRedirectStrategy;
import org.springframework.security.web.server.DelegatingServerAuthenticationEntryPoint; import org.springframework.security.web.server.DelegatingServerAuthenticationEntryPoint;
import org.springframework.security.web.server.DelegatingServerAuthenticationEntryPoint.DelegateEntry; import org.springframework.security.web.server.DelegatingServerAuthenticationEntryPoint.DelegateEntry;
import org.springframework.security.web.server.ExchangeMatcherRedirectWebFilter; import org.springframework.security.web.server.ExchangeMatcherRedirectWebFilter;
import org.springframework.security.web.server.MatcherSecurityWebFilterChain; import org.springframework.security.web.server.MatcherSecurityWebFilterChain;
import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.ServerAuthenticationEntryPoint; import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
import org.springframework.security.web.server.ServerRedirectStrategy;
import org.springframework.security.web.server.authentication.AnonymousAuthenticationWebFilter; import org.springframework.security.web.server.authentication.AnonymousAuthenticationWebFilter;
import org.springframework.security.web.server.authentication.AuthenticationConverterServerWebExchangeMatcher; import org.springframework.security.web.server.authentication.AuthenticationConverterServerWebExchangeMatcher;
import org.springframework.security.web.server.authentication.AuthenticationWebFilter; import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
@@ -3375,6 +3377,8 @@ public class ServerHttpSecurity {
private ServerOAuth2AuthorizationRequestResolver authorizationRequestResolver; private ServerOAuth2AuthorizationRequestResolver authorizationRequestResolver;
private ServerRedirectStrategy authorizationRedirectStrategy;
private ServerWebExchangeMatcher authenticationMatcher; private ServerWebExchangeMatcher authenticationMatcher;
private ServerAuthenticationSuccessHandler authenticationSuccessHandler; private ServerAuthenticationSuccessHandler authenticationSuccessHandler;
@@ -3547,6 +3551,16 @@ public class ServerHttpSecurity {
return this; return this;
} }
/**
* Sets the redirect strategy for Authorization Endpoint redirect URI.
* @param authorizationRedirectStrategy the redirect strategy
* @return the {@link OAuth2LoginSpec} for further configuration
*/
public OAuth2LoginSpec authorizationRedirectStrategy(ServerRedirectStrategy authorizationRedirectStrategy) {
this.authorizationRedirectStrategy = authorizationRedirectStrategy;
return this;
}
/** /**
* Sets the {@link ServerWebExchangeMatcher matcher} used for determining if the * Sets the {@link ServerWebExchangeMatcher matcher} used for determining if the
* request is an authentication request. * request is an authentication request.
@@ -3581,7 +3595,9 @@ public class ServerHttpSecurity {
OAuth2AuthorizationRequestRedirectWebFilter oauthRedirectFilter = getRedirectWebFilter(); OAuth2AuthorizationRequestRedirectWebFilter oauthRedirectFilter = getRedirectWebFilter();
ServerAuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository = getAuthorizationRequestRepository(); ServerAuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository = getAuthorizationRequestRepository();
oauthRedirectFilter.setAuthorizationRequestRepository(authorizationRequestRepository); oauthRedirectFilter.setAuthorizationRequestRepository(authorizationRequestRepository);
oauthRedirectFilter.setAuthorizationRedirectStrategy(getAuthorizationRedirectStrategy());
oauthRedirectFilter.setRequestCache(http.requestCache.requestCache); oauthRedirectFilter.setRequestCache(http.requestCache.requestCache);
ReactiveAuthenticationManager manager = getAuthenticationManager(); ReactiveAuthenticationManager manager = getAuthenticationManager();
AuthenticationWebFilter authenticationFilter = new OAuth2LoginAuthenticationWebFilter(manager, AuthenticationWebFilter authenticationFilter = new OAuth2LoginAuthenticationWebFilter(manager,
authorizedClientRepository); authorizedClientRepository);
@@ -3591,6 +3607,7 @@ public class ServerHttpSecurity {
authenticationFilter.setAuthenticationSuccessHandler(getAuthenticationSuccessHandler(http)); authenticationFilter.setAuthenticationSuccessHandler(getAuthenticationSuccessHandler(http));
authenticationFilter.setAuthenticationFailureHandler(getAuthenticationFailureHandler()); authenticationFilter.setAuthenticationFailureHandler(getAuthenticationFailureHandler());
authenticationFilter.setSecurityContextRepository(this.securityContextRepository); authenticationFilter.setSecurityContextRepository(this.securityContextRepository);
setDefaultEntryPoints(http); setDefaultEntryPoints(http);
http.addFilterAt(oauthRedirectFilter, SecurityWebFiltersOrder.HTTP_BASIC); http.addFilterAt(oauthRedirectFilter, SecurityWebFiltersOrder.HTTP_BASIC);
http.addFilterAt(authenticationFilter, SecurityWebFiltersOrder.AUTHENTICATION); http.addFilterAt(authenticationFilter, SecurityWebFiltersOrder.AUTHENTICATION);
@@ -3737,6 +3754,13 @@ public class ServerHttpSecurity {
return this.authorizationRequestRepository; return this.authorizationRequestRepository;
} }
private ServerRedirectStrategy getAuthorizationRedirectStrategy() {
if (this.authorizationRedirectStrategy == null) {
this.authorizationRedirectStrategy = new DefaultServerRedirectStrategy();
}
return this.authorizationRedirectStrategy;
}
private ReactiveOAuth2AuthorizedClientService getAuthorizedClientService() { private ReactiveOAuth2AuthorizedClientService getAuthorizedClientService() {
ReactiveOAuth2AuthorizedClientService bean = getBeanOrNull(ReactiveOAuth2AuthorizedClientService.class); ReactiveOAuth2AuthorizedClientService bean = getBeanOrNull(ReactiveOAuth2AuthorizedClientService.class);
if (bean != null) { if (bean != null) {
@@ -3759,6 +3783,8 @@ public class ServerHttpSecurity {
private ServerAuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository; private ServerAuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository;
private ServerRedirectStrategy authorizationRedirectStrategy;
private OAuth2ClientSpec() { private OAuth2ClientSpec() {
} }
@@ -3851,6 +3877,23 @@ public class ServerHttpSecurity {
return this.authorizationRequestRepository; return this.authorizationRequestRepository;
} }
/**
* Sets the redirect strategy for Authorization Endpoint redirect URI.
* @param authorizationRedirectStrategy the redirect strategy
* @return the {@link OAuth2ClientSpec} for further configuration
*/
public OAuth2ClientSpec authorizationRedirectStrategy(ServerRedirectStrategy authorizationRedirectStrategy) {
this.authorizationRedirectStrategy = authorizationRedirectStrategy;
return this;
}
private ServerRedirectStrategy getAuthorizationRedirectStrategy() {
if (this.authorizationRedirectStrategy == null) {
this.authorizationRedirectStrategy = new DefaultServerRedirectStrategy();
}
return this.authorizationRedirectStrategy;
}
/** /**
* Allows method chaining to continue configuring the {@link ServerHttpSecurity} * Allows method chaining to continue configuring the {@link ServerHttpSecurity}
* @return the {@link ServerHttpSecurity} to continue configuring * @return the {@link ServerHttpSecurity} to continue configuring
@@ -3870,12 +3913,15 @@ public class ServerHttpSecurity {
if (http.requestCache != null) { if (http.requestCache != null) {
codeGrantWebFilter.setRequestCache(http.requestCache.requestCache); codeGrantWebFilter.setRequestCache(http.requestCache.requestCache);
} }
OAuth2AuthorizationRequestRedirectWebFilter oauthRedirectFilter = new OAuth2AuthorizationRequestRedirectWebFilter( OAuth2AuthorizationRequestRedirectWebFilter oauthRedirectFilter = new OAuth2AuthorizationRequestRedirectWebFilter(
clientRegistrationRepository); clientRegistrationRepository);
oauthRedirectFilter.setAuthorizationRequestRepository(getAuthorizationRequestRepository()); oauthRedirectFilter.setAuthorizationRequestRepository(getAuthorizationRequestRepository());
oauthRedirectFilter.setAuthorizationRedirectStrategy(getAuthorizationRedirectStrategy());
if (http.requestCache != null) { if (http.requestCache != null) {
oauthRedirectFilter.setRequestCache(http.requestCache.requestCache); oauthRedirectFilter.setRequestCache(http.requestCache.requestCache);
} }
http.addFilterAt(codeGrantWebFilter, SecurityWebFiltersOrder.OAUTH2_AUTHORIZATION_CODE); http.addFilterAt(codeGrantWebFilter, SecurityWebFiltersOrder.OAUTH2_AUTHORIZATION_CODE);
http.addFilterAt(oauthRedirectFilter, SecurityWebFiltersOrder.HTTP_BASIC); http.addFilterAt(oauthRedirectFilter, SecurityWebFiltersOrder.HTTP_BASIC);
} }
@@ -22,6 +22,7 @@ import org.springframework.security.oauth2.client.registration.ReactiveClientReg
import org.springframework.security.oauth2.client.web.server.ServerAuthorizationRequestRepository import org.springframework.security.oauth2.client.web.server.ServerAuthorizationRequestRepository
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest
import org.springframework.security.web.server.ServerRedirectStrategy
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter import org.springframework.security.web.server.authentication.ServerAuthenticationConverter
import org.springframework.web.server.ServerWebExchange import org.springframework.web.server.ServerWebExchange
@@ -37,6 +38,7 @@ import org.springframework.web.server.ServerWebExchange
* @property clientRegistrationRepository the repository of client registrations. * @property clientRegistrationRepository the repository of client registrations.
* @property authorizedClientRepository the repository for authorized client(s). * @property authorizedClientRepository the repository for authorized client(s).
* @property authorizationRequestRepository the repository to use for storing [OAuth2AuthorizationRequest]s. * @property authorizationRequestRepository the repository to use for storing [OAuth2AuthorizationRequest]s.
* @property authorizationRedirectStrategy the redirect strategy for Authorization Endpoint redirect URI.
*/ */
@ServerSecurityMarker @ServerSecurityMarker
class ServerOAuth2ClientDsl { class ServerOAuth2ClientDsl {
@@ -45,6 +47,7 @@ class ServerOAuth2ClientDsl {
var clientRegistrationRepository: ReactiveClientRegistrationRepository? = null var clientRegistrationRepository: ReactiveClientRegistrationRepository? = null
var authorizedClientRepository: ServerOAuth2AuthorizedClientRepository? = null var authorizedClientRepository: ServerOAuth2AuthorizedClientRepository? = null
var authorizationRequestRepository: ServerAuthorizationRequestRepository<OAuth2AuthorizationRequest>? = null var authorizationRequestRepository: ServerAuthorizationRequestRepository<OAuth2AuthorizationRequest>? = null
var authorizationRedirectStrategy: ServerRedirectStrategy? = null
internal fun get(): (ServerHttpSecurity.OAuth2ClientSpec) -> Unit { internal fun get(): (ServerHttpSecurity.OAuth2ClientSpec) -> Unit {
return { oauth2Client -> return { oauth2Client ->
@@ -53,6 +56,7 @@ class ServerOAuth2ClientDsl {
clientRegistrationRepository?.also { oauth2Client.clientRegistrationRepository(clientRegistrationRepository) } clientRegistrationRepository?.also { oauth2Client.clientRegistrationRepository(clientRegistrationRepository) }
authorizedClientRepository?.also { oauth2Client.authorizedClientRepository(authorizedClientRepository) } authorizedClientRepository?.also { oauth2Client.authorizedClientRepository(authorizedClientRepository) }
authorizationRequestRepository?.also { oauth2Client.authorizationRequestRepository(authorizationRequestRepository) } authorizationRequestRepository?.also { oauth2Client.authorizationRequestRepository(authorizationRequestRepository) }
authorizationRedirectStrategy?.also { oauth2Client.authorizationRedirectStrategy(authorizationRedirectStrategy) }
} }
} }
} }
@@ -24,6 +24,7 @@ import org.springframework.security.oauth2.client.web.server.ServerAuthorization
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizationRequestResolver import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizationRequestResolver
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest
import org.springframework.security.web.server.ServerRedirectStrategy
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter import org.springframework.security.web.server.authentication.ServerAuthenticationConverter
import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler
import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler
@@ -49,6 +50,7 @@ import org.springframework.web.server.ServerWebExchange
* @property authorizedClientRepository the repository for authorized client(s). * @property authorizedClientRepository the repository for authorized client(s).
* @property authorizationRequestRepository the repository to use for storing [OAuth2AuthorizationRequest]s. * @property authorizationRequestRepository the repository to use for storing [OAuth2AuthorizationRequest]s.
* @property authorizationRequestResolver the resolver used for resolving [OAuth2AuthorizationRequest]s. * @property authorizationRequestResolver the resolver used for resolving [OAuth2AuthorizationRequest]s.
* @property authorizationRedirectStrategy the redirect strategy for Authorization Endpoint redirect URI.
* @property authenticationMatcher the [ServerWebExchangeMatcher] used for determining if the request is an * @property authenticationMatcher the [ServerWebExchangeMatcher] used for determining if the request is an
* authentication request. * authentication request.
*/ */
@@ -64,6 +66,7 @@ class ServerOAuth2LoginDsl {
var authorizedClientRepository: ServerOAuth2AuthorizedClientRepository? = null var authorizedClientRepository: ServerOAuth2AuthorizedClientRepository? = null
var authorizationRequestRepository: ServerAuthorizationRequestRepository<OAuth2AuthorizationRequest>? = null var authorizationRequestRepository: ServerAuthorizationRequestRepository<OAuth2AuthorizationRequest>? = null
var authorizationRequestResolver: ServerOAuth2AuthorizationRequestResolver? = null var authorizationRequestResolver: ServerOAuth2AuthorizationRequestResolver? = null
var authorizationRedirectStrategy: ServerRedirectStrategy? = null
var authenticationMatcher: ServerWebExchangeMatcher? = null var authenticationMatcher: ServerWebExchangeMatcher? = null
internal fun get(): (ServerHttpSecurity.OAuth2LoginSpec) -> Unit { internal fun get(): (ServerHttpSecurity.OAuth2LoginSpec) -> Unit {
@@ -78,6 +81,7 @@ class ServerOAuth2LoginDsl {
authorizedClientRepository?.also { oauth2Login.authorizedClientRepository(authorizedClientRepository) } authorizedClientRepository?.also { oauth2Login.authorizedClientRepository(authorizedClientRepository) }
authorizationRequestRepository?.also { oauth2Login.authorizationRequestRepository(authorizationRequestRepository) } authorizationRequestRepository?.also { oauth2Login.authorizationRequestRepository(authorizationRequestRepository) }
authorizationRequestResolver?.also { oauth2Login.authorizationRequestResolver(authorizationRequestResolver) } authorizationRequestResolver?.also { oauth2Login.authorizationRequestResolver(authorizationRequestResolver) }
authorizationRedirectStrategy?.also { oauth2Login.authorizationRedirectStrategy(authorizationRedirectStrategy) }
authenticationMatcher?.also { oauth2Login.authenticationMatcher(authenticationMatcher) } authenticationMatcher?.also { oauth2Login.authenticationMatcher(authenticationMatcher) }
} }
} }
@@ -23,6 +23,7 @@ import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCo
import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest
import org.springframework.security.web.RedirectStrategy
/** /**
* A Kotlin DSL to configure OAuth 2.0 Authorization Code Grant. * A Kotlin DSL to configure OAuth 2.0 Authorization Code Grant.
@@ -31,6 +32,7 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequ
* @since 5.3 * @since 5.3
* @property authorizationRequestResolver the resolver used for resolving [OAuth2AuthorizationRequest]'s. * @property authorizationRequestResolver the resolver used for resolving [OAuth2AuthorizationRequest]'s.
* @property authorizationRequestRepository the repository used for storing [OAuth2AuthorizationRequest]'s. * @property authorizationRequestRepository the repository used for storing [OAuth2AuthorizationRequest]'s.
* @property authorizationRedirectStrategy the redirect strategy for Authorization Endpoint redirect URI.
* @property accessTokenResponseClient the client used for requesting the access token credential * @property accessTokenResponseClient the client used for requesting the access token credential
* from the Token Endpoint. * from the Token Endpoint.
*/ */
@@ -38,12 +40,14 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequ
class AuthorizationCodeGrantDsl { class AuthorizationCodeGrantDsl {
var authorizationRequestResolver: OAuth2AuthorizationRequestResolver? = null var authorizationRequestResolver: OAuth2AuthorizationRequestResolver? = null
var authorizationRequestRepository: AuthorizationRequestRepository<OAuth2AuthorizationRequest>? = null var authorizationRequestRepository: AuthorizationRequestRepository<OAuth2AuthorizationRequest>? = null
var authorizationRedirectStrategy: RedirectStrategy? = null
var accessTokenResponseClient: OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest>? = null var accessTokenResponseClient: OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest>? = null
internal fun get(): (OAuth2ClientConfigurer<HttpSecurity>.AuthorizationCodeGrantConfigurer) -> Unit { internal fun get(): (OAuth2ClientConfigurer<HttpSecurity>.AuthorizationCodeGrantConfigurer) -> Unit {
return { authorizationCodeGrant -> return { authorizationCodeGrant ->
authorizationRequestResolver?.also { authorizationCodeGrant.authorizationRequestResolver(authorizationRequestResolver) } authorizationRequestResolver?.also { authorizationCodeGrant.authorizationRequestResolver(authorizationRequestResolver) }
authorizationRequestRepository?.also { authorizationCodeGrant.authorizationRequestRepository(authorizationRequestRepository) } authorizationRequestRepository?.also { authorizationCodeGrant.authorizationRequestRepository(authorizationRequestRepository) }
authorizationRedirectStrategy?.also { authorizationCodeGrant.authorizationRedirectStrategy(authorizationRedirectStrategy) }
accessTokenResponseClient?.also { authorizationCodeGrant.accessTokenResponseClient(accessTokenResponseClient) } accessTokenResponseClient?.also { authorizationCodeGrant.accessTokenResponseClient(accessTokenResponseClient) }
} }
} }
@@ -21,6 +21,7 @@ import org.springframework.security.config.annotation.web.configurers.oauth2.cli
import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest
import org.springframework.security.web.RedirectStrategy
/** /**
* A Kotlin DSL to configure the Authorization Server's Authorization Endpoint using * A Kotlin DSL to configure the Authorization Server's Authorization Endpoint using
@@ -31,18 +32,21 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequ
* @property baseUri the base URI used for authorization requests. * @property baseUri the base URI used for authorization requests.
* @property authorizationRequestResolver the resolver used for resolving [OAuth2AuthorizationRequest]'s. * @property authorizationRequestResolver the resolver used for resolving [OAuth2AuthorizationRequest]'s.
* @property authorizationRequestRepository the repository used for storing [OAuth2AuthorizationRequest]'s. * @property authorizationRequestRepository the repository used for storing [OAuth2AuthorizationRequest]'s.
* @property authorizationRedirectStrategy the redirect strategy for Authorization Endpoint redirect URI.
*/ */
@OAuth2LoginSecurityMarker @OAuth2LoginSecurityMarker
class AuthorizationEndpointDsl { class AuthorizationEndpointDsl {
var baseUri: String? = null var baseUri: String? = null
var authorizationRequestResolver: OAuth2AuthorizationRequestResolver? = null var authorizationRequestResolver: OAuth2AuthorizationRequestResolver? = null
var authorizationRequestRepository: AuthorizationRequestRepository<OAuth2AuthorizationRequest>? = null var authorizationRequestRepository: AuthorizationRequestRepository<OAuth2AuthorizationRequest>? = null
var authorizationRedirectStrategy: RedirectStrategy? = null
internal fun get(): (OAuth2LoginConfigurer<HttpSecurity>.AuthorizationEndpointConfig) -> Unit { internal fun get(): (OAuth2LoginConfigurer<HttpSecurity>.AuthorizationEndpointConfig) -> Unit {
return { authorizationEndpoint -> return { authorizationEndpoint ->
baseUri?.also { authorizationEndpoint.baseUri(baseUri) } baseUri?.also { authorizationEndpoint.baseUri(baseUri) }
authorizationRequestResolver?.also { authorizationEndpoint.authorizationRequestResolver(authorizationRequestResolver) } authorizationRequestResolver?.also { authorizationEndpoint.authorizationRequestResolver(authorizationRequestResolver) }
authorizationRequestRepository?.also { authorizationEndpoint.authorizationRequestRepository(authorizationRequestRepository) } authorizationRequestRepository?.also { authorizationEndpoint.authorizationRequestRepository(authorizationRequestRepository) }
authorizationRedirectStrategy?.also { authorizationEndpoint.authorizationRedirectStrategy(authorizationRedirectStrategy) }
} }
} }
} }
@@ -500,6 +500,9 @@ oauth2-login.attlist &=
oauth2-login.attlist &= oauth2-login.attlist &=
## Reference to the OAuth2AuthorizationRequestResolver ## Reference to the OAuth2AuthorizationRequestResolver
attribute authorization-request-resolver-ref {xsd:token}? attribute authorization-request-resolver-ref {xsd:token}?
oauth2-login.attlist &=
## Reference to the authorization RedirectStrategy
attribute authorization-redirect-strategy-ref {xsd:token}?
oauth2-login.attlist &= oauth2-login.attlist &=
## Reference to the OAuth2AccessTokenResponseClient ## Reference to the OAuth2AccessTokenResponseClient
attribute access-token-response-client-ref {xsd:token}? attribute access-token-response-client-ref {xsd:token}?
@@ -547,6 +550,9 @@ authorization-code-grant =
authorization-code-grant.attlist &= authorization-code-grant.attlist &=
## Reference to the AuthorizationRequestRepository ## Reference to the AuthorizationRequestRepository
attribute authorization-request-repository-ref {xsd:token}? attribute authorization-request-repository-ref {xsd:token}?
authorization-code-grant.attlist &=
## Reference to the authorization RedirectStrategy
attribute authorization-redirect-strategy-ref {xsd:token}?
authorization-code-grant.attlist &= authorization-code-grant.attlist &=
## Reference to the OAuth2AuthorizationRequestResolver ## Reference to the OAuth2AuthorizationRequestResolver
attribute authorization-request-resolver-ref {xsd:token}? attribute authorization-request-resolver-ref {xsd:token}?
@@ -1651,6 +1651,12 @@
</xs:documentation> </xs:documentation>
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
<xs:attribute name="authorization-redirect-strategy-ref" type="xs:token">
<xs:annotation>
<xs:documentation>Reference to the authorization RedirectStrategy
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="access-token-response-client-ref" type="xs:token"> <xs:attribute name="access-token-response-client-ref" type="xs:token">
<xs:annotation> <xs:annotation>
<xs:documentation>Reference to the OAuth2AccessTokenResponseClient <xs:documentation>Reference to the OAuth2AccessTokenResponseClient
@@ -1754,6 +1760,12 @@
</xs:documentation> </xs:documentation>
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
<xs:attribute name="authorization-redirect-strategy-ref" type="xs:token">
<xs:annotation>
<xs:documentation>Reference to the authorization RedirectStrategy
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="authorization-request-resolver-ref" type="xs:token"> <xs:attribute name="authorization-request-resolver-ref" type="xs:token">
<xs:annotation> <xs:annotation>
<xs:documentation>Reference to the OAuth2AuthorizationRequestResolver <xs:documentation>Reference to the OAuth2AuthorizationRequestResolver
@@ -59,6 +59,8 @@ import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.savedrequest.RequestCache; import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.MvcResult;
@@ -69,6 +71,7 @@ import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
@@ -96,6 +99,8 @@ public class OAuth2ClientConfigurerTests {
private static OAuth2AuthorizationRequestResolver authorizationRequestResolver; private static OAuth2AuthorizationRequestResolver authorizationRequestResolver;
private static RedirectStrategy authorizationRedirectStrategy;
private static OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient; private static OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient;
private static RequestCache requestCache; private static RequestCache requestCache;
@@ -131,6 +136,7 @@ public class OAuth2ClientConfigurerTests {
authorizedClientService); authorizedClientService);
authorizationRequestResolver = new DefaultOAuth2AuthorizationRequestResolver(clientRegistrationRepository, authorizationRequestResolver = new DefaultOAuth2AuthorizationRequestResolver(clientRegistrationRepository,
"/oauth2/authorization"); "/oauth2/authorization");
authorizationRedirectStrategy = new DefaultRedirectStrategy();
OAuth2AccessTokenResponse accessTokenResponse = OAuth2AccessTokenResponse.withToken("access-token-1234") OAuth2AccessTokenResponse accessTokenResponse = OAuth2AccessTokenResponse.withToken("access-token-1234")
.tokenType(OAuth2AccessToken.TokenType.BEARER).expiresIn(300).build(); .tokenType(OAuth2AccessToken.TokenType.BEARER).expiresIn(300).build();
accessTokenResponseClient = mock(OAuth2AccessTokenResponseClient.class); accessTokenResponseClient = mock(OAuth2AccessTokenResponseClient.class);
@@ -262,6 +268,19 @@ public class OAuth2ClientConfigurerTests {
verify(authorizationRequestResolver).resolve(any()); verify(authorizationRequestResolver).resolve(any());
} }
@Test
public void configureWhenCustomAuthorizationRedirectStrategySetThenAuthorizationRedirectStrategyUsed()
throws Exception {
authorizationRedirectStrategy = mock(RedirectStrategy.class);
this.spring.register(OAuth2ClientConfig.class).autowire();
// @formatter:off
this.mockMvc.perform(get("/oauth2/authorization/registration-1"))
.andExpect(status().isOk())
.andReturn();
// @formatter:on
verify(authorizationRedirectStrategy).sendRedirect(any(), any(), anyString());
}
@EnableWebSecurity @EnableWebSecurity
@EnableWebMvc @EnableWebMvc
static class OAuth2ClientConfig extends WebSecurityConfigurerAdapter { static class OAuth2ClientConfig extends WebSecurityConfigurerAdapter {
@@ -279,6 +298,7 @@ public class OAuth2ClientConfigurerTests {
.oauth2Client() .oauth2Client()
.authorizationCodeGrant() .authorizationCodeGrant()
.authorizationRequestResolver(authorizationRequestResolver) .authorizationRequestResolver(authorizationRequestResolver)
.authorizationRedirectStrategy(authorizationRedirectStrategy)
.accessTokenResponseClient(accessTokenResponseClient); .accessTokenResponseClient(accessTokenResponseClient);
// @formatter:on // @formatter:on
} }
@@ -90,6 +90,7 @@ import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtDecoderFactory; import org.springframework.security.oauth2.jwt.JwtDecoderFactory;
import org.springframework.security.oauth2.jwt.TestJwts; import org.springframework.security.oauth2.jwt.TestJwts;
import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.authentication.HttpStatusEntryPoint; import org.springframework.security.web.authentication.HttpStatusEntryPoint;
import org.springframework.security.web.context.HttpRequestResponseHolder; import org.springframework.security.web.context.HttpRequestResponseHolder;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository; import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
@@ -101,7 +102,9 @@ import org.springframework.web.context.support.AnnotationConfigWebApplicationCon
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
@@ -382,6 +385,32 @@ public class OAuth2LoginConfigurerTests {
"https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=clientId&scope=openid+profile+email&state=state&redirect_uri=http%3A%2F%2Flocalhost%2Flogin%2Foauth2%2Fcode%2Fgoogle&custom-param1=custom-value1"); "https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=clientId&scope=openid+profile+email&state=state&redirect_uri=http%3A%2F%2Flocalhost%2Flogin%2Foauth2%2Fcode%2Fgoogle&custom-param1=custom-value1");
} }
@Test
public void oauth2LoginWithAuthorizationRedirectStrategyThenCustomAuthorizationRedirectStrategyUsed()
throws Exception {
loadConfig(OAuth2LoginConfigCustomAuthorizationRedirectStrategy.class);
RedirectStrategy redirectStrategy = this.context
.getBean(OAuth2LoginConfigCustomAuthorizationRedirectStrategy.class).redirectStrategy;
String requestUri = "/oauth2/authorization/google";
this.request = new MockHttpServletRequest("GET", requestUri);
this.request.setServletPath(requestUri);
this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain);
then(redirectStrategy).should().sendRedirect(any(), any(), anyString());
}
@Test
public void requestWhenOauth2LoginWithCustomAuthorizationRedirectStrategyThenCustomAuthorizationRedirectStrategyUsed()
throws Exception {
loadConfig(OAuth2LoginConfigCustomAuthorizationRedirectStrategyInLambda.class);
RedirectStrategy redirectStrategy = this.context
.getBean(OAuth2LoginConfigCustomAuthorizationRedirectStrategyInLambda.class).redirectStrategy;
String requestUri = "/oauth2/authorization/google";
this.request = new MockHttpServletRequest("GET", requestUri);
this.request.setServletPath(requestUri);
this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain);
then(redirectStrategy).should().sendRedirect(any(), any(), anyString());
}
// gh-5347 // gh-5347
@Test @Test
public void oauth2LoginWithOneClientConfiguredThenRedirectForAuthorization() throws Exception { public void oauth2LoginWithOneClientConfiguredThenRedirectForAuthorization() throws Exception {
@@ -883,6 +912,59 @@ public class OAuth2LoginConfigurerTests {
} }
@EnableWebSecurity
static class OAuth2LoginConfigCustomAuthorizationRedirectStrategy extends CommonWebSecurityConfigurerAdapter {
private final ClientRegistrationRepository clientRegistrationRepository = new InMemoryClientRegistrationRepository(
GOOGLE_CLIENT_REGISTRATION);
RedirectStrategy redirectStrategy = mock(RedirectStrategy.class);
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.oauth2Login((oauth2Login) ->
oauth2Login
.clientRegistrationRepository(this.clientRegistrationRepository)
.authorizationEndpoint((authorizationEndpoint) ->
authorizationEndpoint
.authorizationRedirectStrategy(this.redirectStrategy)
)
);
// @formatter:on
super.configure(http);
}
}
@EnableWebSecurity
static class OAuth2LoginConfigCustomAuthorizationRedirectStrategyInLambda
extends CommonLambdaWebSecurityConfigurerAdapter {
private final ClientRegistrationRepository clientRegistrationRepository = new InMemoryClientRegistrationRepository(
GOOGLE_CLIENT_REGISTRATION);
RedirectStrategy redirectStrategy = mock(RedirectStrategy.class);
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.oauth2Login((oauth2Login) ->
oauth2Login
.clientRegistrationRepository(this.clientRegistrationRepository)
.authorizationEndpoint((authorizationEndpoint) ->
authorizationEndpoint
.authorizationRedirectStrategy(this.redirectStrategy)
)
);
// @formatter:on
super.configure(http);
}
}
@EnableWebSecurity @EnableWebSecurity
static class OAuth2LoginConfigMultipleClients extends CommonWebSecurityConfigurerAdapter { static class OAuth2LoginConfigMultipleClients extends CommonWebSecurityConfigurerAdapter {
@@ -43,6 +43,7 @@ import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.Converter;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockFilterChain; import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpServletResponse;
@@ -82,6 +83,7 @@ import org.springframework.security.saml2.provider.service.authentication.Saml2A
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationToken; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationToken;
import org.springframework.security.saml2.provider.service.authentication.TestOpenSamlObjects; import org.springframework.security.saml2.provider.service.authentication.TestOpenSamlObjects;
import org.springframework.security.saml2.provider.service.authentication.TestSaml2AuthenticationRequestContexts; import org.springframework.security.saml2.provider.service.authentication.TestSaml2AuthenticationRequestContexts;
import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations; import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations;
@@ -113,10 +115,10 @@ import org.springframework.web.util.UriComponentsBuilder;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyNoInteractions;
@@ -411,6 +413,16 @@ public class Saml2LoginConfigurerTests {
verify(authenticationConverter).convert(any(HttpServletRequest.class)); verify(authenticationConverter).convert(any(HttpServletRequest.class));
} }
// gh-11657
@Test
public void getFaviconWhenDefaultConfigurationThenDoesNotSaveAuthnRequest() throws Exception {
this.spring.register(Saml2LoginConfig.class).autowire();
this.mvc.perform(get("/favicon.ico").accept(MediaType.TEXT_HTML)).andExpect(status().isFound())
.andExpect(redirectedUrl("http://localhost/login"));
this.mvc.perform(get("/").accept(MediaType.TEXT_HTML)).andExpect(status().isFound())
.andExpect(redirectedUrl("http://localhost/saml2/authenticate/registration-id"));
}
private void validateSaml2WebSsoAuthenticationFilterConfiguration() { private void validateSaml2WebSsoAuthenticationFilterConfiguration() {
// get the OpenSamlAuthenticationProvider // get the OpenSamlAuthenticationProvider
Saml2WebSsoAuthenticationFilter filter = getSaml2SsoFilter(this.springSecurityFilterChain); Saml2WebSsoAuthenticationFilter filter = getSaml2SsoFilter(this.springSecurityFilterChain);
@@ -429,6 +441,8 @@ public class Saml2LoginConfigurerTests {
private void performSaml2Login(String expected) throws IOException, ServletException { private void performSaml2Login(String expected) throws IOException, ServletException {
// setup authentication parameters // setup authentication parameters
this.request.setRequestURI("/login/saml2/sso/registration-id");
this.request.setServletPath("/login/saml2/sso/registration-id");
this.request.setParameter("SAMLResponse", this.request.setParameter("SAMLResponse",
Base64.getEncoder().encodeToString("saml2-xml-response-object".getBytes())); Base64.getEncoder().encodeToString("saml2-xml-response-object".getBytes()));
// perform test // perform test
@@ -821,9 +835,7 @@ public class Saml2LoginConfigurerTests {
.assertingPartyDetails((party) -> party.verificationX509Credentials( .assertingPartyDetails((party) -> party.verificationX509Credentials(
(c) -> c.add(TestSaml2X509Credentials.relyingPartyVerifyingCredential()))) (c) -> c.add(TestSaml2X509Credentials.relyingPartyVerifyingCredential())))
.build(); .build();
RelyingPartyRegistrationRepository repository = mock(RelyingPartyRegistrationRepository.class); return spy(new InMemoryRelyingPartyRegistrationRepository(registration));
given(repository.findByRegistrationId(anyString())).willReturn(registration);
return repository;
} }
} }
@@ -44,6 +44,7 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses; import org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses;
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
import org.springframework.security.test.context.support.WithMockUser; import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.MvcResult;
@@ -55,6 +56,7 @@ import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
@@ -90,6 +92,9 @@ public class OAuth2ClientBeanDefinitionParserTests {
@Autowired(required = false) @Autowired(required = false)
private OAuth2AuthorizationRequestResolver authorizationRequestResolver; private OAuth2AuthorizationRequestResolver authorizationRequestResolver;
@Autowired(required = false)
private RedirectStrategy authorizationRedirectStrategy;
@Autowired(required = false) @Autowired(required = false)
private OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient; private OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient;
@@ -148,6 +153,16 @@ public class OAuth2ClientBeanDefinitionParserTests {
verify(this.authorizationRequestResolver).resolve(any()); verify(this.authorizationRequestResolver).resolve(any());
} }
@Test
public void requestWhenCustomAuthorizationRedirectStrategyThenCalled() throws Exception {
this.spring.configLocations(xml("CustomAuthorizationRedirectStrategy")).autowire();
// @formatter:off
this.mvc.perform(get("/oauth2/authorization/google"))
.andExpect(status().isOk());
// @formatter:on
verify(this.authorizationRedirectStrategy).sendRedirect(any(), any(), anyString());
}
@Test @Test
public void requestWhenAuthorizationResponseMatchThenProcess() throws Exception { public void requestWhenAuthorizationResponseMatchThenProcess() throws Exception {
this.spring.configLocations(xml("CustomConfiguration")).autowire(); this.spring.configLocations(xml("CustomConfiguration")).autowire();
@@ -64,6 +64,7 @@ import org.springframework.security.oauth2.jwt.JwtDecoderFactory;
import org.springframework.security.oauth2.jwt.TestJwts; import org.springframework.security.oauth2.jwt.TestJwts;
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
import org.springframework.security.test.context.support.WithMockUser; import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.savedrequest.RequestCache; import org.springframework.security.web.savedrequest.RequestCache;
@@ -78,6 +79,7 @@ import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
@@ -118,6 +120,9 @@ public class OAuth2LoginBeanDefinitionParserTests {
@Autowired(required = false) @Autowired(required = false)
private OAuth2AuthorizationRequestResolver authorizationRequestResolver; private OAuth2AuthorizationRequestResolver authorizationRequestResolver;
@Autowired(required = false)
private RedirectStrategy authorizationRedirectStrategy;
@Autowired(required = false) @Autowired(required = false)
private OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient; private OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient;
@@ -378,6 +383,17 @@ public class OAuth2LoginBeanDefinitionParserTests {
verify(this.authorizationRequestResolver).resolve(any()); verify(this.authorizationRequestResolver).resolve(any());
} }
@Test
public void requestWhenCustomAuthorizationRedirectStrategyThenCalled() throws Exception {
this.spring.configLocations(this.xml("SingleClientRegistration-WithCustomAuthorizationRedirectStrategy"))
.autowire();
// @formatter:off
this.mvc.perform(get("/oauth2/authorization/google-login"))
.andExpect(status().isOk());
// @formatter:on
verify(this.authorizationRedirectStrategy).sendRedirect(any(), any(), anyString());
}
// gh-5347 // gh-5347
@Test @Test
public void requestWhenMultiClientRegistrationThenRedirectDefaultLoginPage() throws Exception { public void requestWhenMultiClientRegistrationThenRedirectDefaultLoginPage() throws Exception {
@@ -39,14 +39,18 @@ import org.springframework.security.config.annotation.web.reactive.ServerHttpSec
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
import org.springframework.security.oauth2.client.web.server.OAuth2AuthorizationRequestRedirectWebFilter;
import org.springframework.security.oauth2.client.web.server.ServerAuthorizationRequestRepository; import org.springframework.security.oauth2.client.web.server.ServerAuthorizationRequestRepository;
import org.springframework.security.oauth2.client.web.server.authentication.OAuth2LoginAuthenticationWebFilter; import org.springframework.security.oauth2.client.web.server.authentication.OAuth2LoginAuthenticationWebFilter;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationRequests; import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationRequests;
import org.springframework.security.test.web.reactive.server.WebTestClientBuilder; import org.springframework.security.test.web.reactive.server.WebTestClientBuilder;
import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor; import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor;
import org.springframework.security.web.server.DefaultServerRedirectStrategy;
import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.ServerAuthenticationEntryPoint; import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
import org.springframework.security.web.server.ServerRedirectStrategy;
import org.springframework.security.web.server.WebFilterChainProxy; import org.springframework.security.web.server.WebFilterChainProxy;
import org.springframework.security.web.server.authentication.AnonymousAuthenticationWebFilterTests; import org.springframework.security.web.server.authentication.AnonymousAuthenticationWebFilterTests;
import org.springframework.security.web.server.authentication.HttpBasicServerAuthenticationEntryPoint; import org.springframework.security.web.server.authentication.HttpBasicServerAuthenticationEntryPoint;
@@ -76,6 +80,7 @@ import org.springframework.web.server.WebFilterChain;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
@@ -531,6 +536,90 @@ public class ServerHttpSecurityTests {
verify(authorizationRequestRepository).removeAuthorizationRequest(any()); verify(authorizationRequestRepository).removeAuthorizationRequest(any());
} }
@Test
public void shouldUseDefaultAuthorizationRedirectStrategyForOAuth2Login() {
ReactiveClientRegistrationRepository clientRegistrationRepository = mock(
ReactiveClientRegistrationRepository.class);
given(clientRegistrationRepository.findByRegistrationId(anyString()))
.willReturn(Mono.just(TestClientRegistrations.clientRegistration().build()));
SecurityWebFilterChain securityFilterChain = this.http.oauth2Login()
.clientRegistrationRepository(clientRegistrationRepository).and().build();
WebTestClient client = WebTestClientBuilder.bindToWebFilters(securityFilterChain).build();
client.get().uri("/oauth2/authorization/registration-id").exchange().expectStatus().is3xxRedirection();
OAuth2AuthorizationRequestRedirectWebFilter filter = getWebFilter(securityFilterChain,
OAuth2AuthorizationRequestRedirectWebFilter.class).get();
assertThat(ReflectionTestUtils.getField(filter, "authorizationRedirectStrategy"))
.isInstanceOf(DefaultServerRedirectStrategy.class);
}
@Test
public void shouldConfigureAuthorizationRedirectStrategyForOAuth2Login() {
ServerRedirectStrategy authorizationRedirectStrategy = mock(ServerRedirectStrategy.class);
ReactiveClientRegistrationRepository clientRegistrationRepository = mock(
ReactiveClientRegistrationRepository.class);
given(clientRegistrationRepository.findByRegistrationId(anyString()))
.willReturn(Mono.just(TestClientRegistrations.clientRegistration().build()));
given(authorizationRedirectStrategy.sendRedirect(any(), any())).willReturn(Mono.empty());
SecurityWebFilterChain securityFilterChain = this.http.oauth2Login()
.clientRegistrationRepository(clientRegistrationRepository)
.authorizationRedirectStrategy(authorizationRedirectStrategy).and().build();
WebTestClient client = WebTestClientBuilder.bindToWebFilters(securityFilterChain).build();
client.get().uri("/oauth2/authorization/registration-id").exchange();
verify(authorizationRedirectStrategy).sendRedirect(any(), any());
OAuth2AuthorizationRequestRedirectWebFilter filter = getWebFilter(securityFilterChain,
OAuth2AuthorizationRequestRedirectWebFilter.class).get();
assertThat(ReflectionTestUtils.getField(filter, "authorizationRedirectStrategy"))
.isSameAs(authorizationRedirectStrategy);
}
@Test
public void shouldUseDefaultAuthorizationRedirectStrategyForOAuth2Client() {
ReactiveClientRegistrationRepository clientRegistrationRepository = mock(
ReactiveClientRegistrationRepository.class);
given(clientRegistrationRepository.findByRegistrationId(anyString()))
.willReturn(Mono.just(TestClientRegistrations.clientRegistration().build()));
SecurityWebFilterChain securityFilterChain = this.http.oauth2Client()
.clientRegistrationRepository(clientRegistrationRepository).and().build();
WebTestClient client = WebTestClientBuilder.bindToWebFilters(securityFilterChain).build();
client.get().uri("/oauth2/authorization/registration-id").exchange().expectStatus().is3xxRedirection();
OAuth2AuthorizationRequestRedirectWebFilter filter = getWebFilter(securityFilterChain,
OAuth2AuthorizationRequestRedirectWebFilter.class).get();
assertThat(ReflectionTestUtils.getField(filter, "authorizationRedirectStrategy"))
.isInstanceOf(DefaultServerRedirectStrategy.class);
}
@Test
public void shouldConfigureAuthorizationRedirectStrategyForOAuth2Client() {
ServerRedirectStrategy authorizationRedirectStrategy = mock(ServerRedirectStrategy.class);
ReactiveClientRegistrationRepository clientRegistrationRepository = mock(
ReactiveClientRegistrationRepository.class);
given(clientRegistrationRepository.findByRegistrationId(anyString()))
.willReturn(Mono.just(TestClientRegistrations.clientRegistration().build()));
given(authorizationRedirectStrategy.sendRedirect(any(), any())).willReturn(Mono.empty());
SecurityWebFilterChain securityFilterChain = this.http.oauth2Client()
.clientRegistrationRepository(clientRegistrationRepository)
.authorizationRedirectStrategy(authorizationRedirectStrategy).and().build();
WebTestClient client = WebTestClientBuilder.bindToWebFilters(securityFilterChain).build();
client.get().uri("/oauth2/authorization/registration-id").exchange();
verify(authorizationRedirectStrategy).sendRedirect(any(), any());
OAuth2AuthorizationRequestRedirectWebFilter filter = getWebFilter(securityFilterChain,
OAuth2AuthorizationRequestRedirectWebFilter.class).get();
assertThat(ReflectionTestUtils.getField(filter, "authorizationRedirectStrategy"))
.isSameAs(authorizationRedirectStrategy);
}
private boolean isX509Filter(WebFilter filter) { private boolean isX509Filter(WebFilter filter) {
try { try {
Object converter = ReflectionTestUtils.getField(filter, "authenticationConverter"); Object converter = ReflectionTestUtils.getField(filter, "authenticationConverter");
@@ -37,7 +37,9 @@ import org.springframework.security.oauth2.client.web.server.ServerAuthorization
import org.springframework.security.oauth2.client.web.server.WebSessionOAuth2ServerAuthorizationRequestRepository import org.springframework.security.oauth2.client.web.server.WebSessionOAuth2ServerAuthorizationRequestRepository
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames
import org.springframework.security.web.server.DefaultServerRedirectStrategy
import org.springframework.security.web.server.SecurityWebFilterChain import org.springframework.security.web.server.SecurityWebFilterChain
import org.springframework.security.web.server.ServerRedirectStrategy
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter import org.springframework.security.web.server.authentication.ServerAuthenticationConverter
import org.springframework.test.web.reactive.server.WebTestClient import org.springframework.test.web.reactive.server.WebTestClient
import org.springframework.web.reactive.config.EnableWebFlux import org.springframework.web.reactive.config.EnableWebFlux
@@ -128,6 +130,41 @@ class ServerOAuth2ClientDslTests {
} }
} }
@Test
fun `OAuth2 client when authorization redirect strategy configured then custom redirect strategy used`() {
this.spring.register(AuthorizationRedirectStrategyConfig::class.java, ClientConfig::class.java).autowire()
mockkObject(AuthorizationRedirectStrategyConfig.AUTHORIZATION_REDIRECT_STRATEGY)
every {
AuthorizationRedirectStrategyConfig.AUTHORIZATION_REDIRECT_STRATEGY.sendRedirect(any(), any())
} returns Mono.empty()
this.client.get()
.uri("/oauth2/authorization/google")
.exchange()
verify(exactly = 1) {
AuthorizationRedirectStrategyConfig.AUTHORIZATION_REDIRECT_STRATEGY.sendRedirect(any(), any())
}
}
@EnableWebFluxSecurity
@EnableWebFlux
open class AuthorizationRedirectStrategyConfig {
companion object {
val AUTHORIZATION_REDIRECT_STRATEGY : ServerRedirectStrategy = DefaultServerRedirectStrategy()
}
@Bean
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
oauth2Client {
authorizationRedirectStrategy = AUTHORIZATION_REDIRECT_STRATEGY
}
}
}
}
@Test @Test
fun `OAuth2 client when authentication converter configured then custom converter used`() { fun `OAuth2 client when authentication converter configured then custom converter used`() {
this.spring.register(AuthenticationConverterConfig::class.java, ClientConfig::class.java).autowire() this.spring.register(AuthenticationConverterConfig::class.java, ClientConfig::class.java).autowire()
@@ -34,7 +34,9 @@ import org.springframework.security.oauth2.client.registration.ReactiveClientReg
import org.springframework.security.oauth2.client.web.server.ServerAuthorizationRequestRepository import org.springframework.security.oauth2.client.web.server.ServerAuthorizationRequestRepository
import org.springframework.security.oauth2.client.web.server.WebSessionOAuth2ServerAuthorizationRequestRepository import org.springframework.security.oauth2.client.web.server.WebSessionOAuth2ServerAuthorizationRequestRepository
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest
import org.springframework.security.web.server.DefaultServerRedirectStrategy
import org.springframework.security.web.server.SecurityWebFilterChain import org.springframework.security.web.server.SecurityWebFilterChain
import org.springframework.security.web.server.ServerRedirectStrategy
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter import org.springframework.security.web.server.authentication.ServerAuthenticationConverter
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher
import org.springframework.test.web.reactive.server.WebTestClient import org.springframework.test.web.reactive.server.WebTestClient
@@ -139,6 +141,38 @@ class ServerOAuth2LoginDslTests {
} }
} }
@Test
fun `OAuth2 login when authorization redirect strategy configured then custom redirect strategy used`() {
this.spring.register(AuthorizationRedirectStrategyConfig::class.java, ClientConfig::class.java).autowire()
mockkObject(AuthorizationRedirectStrategyConfig.AUTHORIZATION_REDIRECT_STRATEGY)
every {
AuthorizationRedirectStrategyConfig.AUTHORIZATION_REDIRECT_STRATEGY.sendRedirect(any(), any())
} returns Mono.empty()
this.client.get()
.uri("/oauth2/authorization/google")
.exchange()
verify(exactly = 1) { AuthorizationRedirectStrategyConfig.AUTHORIZATION_REDIRECT_STRATEGY.sendRedirect(any(), any()) }
}
@EnableWebFluxSecurity
@EnableWebFlux
open class AuthorizationRedirectStrategyConfig {
companion object {
val AUTHORIZATION_REDIRECT_STRATEGY : ServerRedirectStrategy = DefaultServerRedirectStrategy()
}
@Bean
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
oauth2Login {
authorizationRedirectStrategy = AUTHORIZATION_REDIRECT_STRATEGY
}
}
}
}
@Test @Test
fun `OAuth2 login when authentication matcher configured then custom matcher used`() { fun `OAuth2 login when authentication matcher configured then custom matcher used`() {
this.spring.register(AuthenticationMatcherConfig::class.java, ClientConfig::class.java).autowire() this.spring.register(AuthenticationMatcherConfig::class.java, ClientConfig::class.java).autowire()
@@ -43,6 +43,9 @@ import org.springframework.security.oauth2.core.OAuth2AccessToken
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames
import org.springframework.security.web.DefaultRedirectStrategy
import org.springframework.security.web.RedirectStrategy
import org.springframework.security.web.SecurityFilterChain
import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get import org.springframework.test.web.servlet.get
@@ -101,6 +104,40 @@ class AuthorizationCodeGrantDslTests {
} }
} }
@Test
fun `oauth2Client when custom authorization redirect strategy then redirect strategy used`() {
this.spring.register(RedirectStrategyConfig::class.java, ClientConfig::class.java).autowire()
mockkObject(RedirectStrategyConfig.REDIRECT_STRATEGY)
every { RedirectStrategyConfig.REDIRECT_STRATEGY.sendRedirect(any(), any(), any()) }
this.mockMvc.get("/oauth2/authorization/registrationId")
verify(exactly = 1) { RedirectStrategyConfig.REDIRECT_STRATEGY.sendRedirect(any(), any(), any()) }
}
@EnableWebSecurity
open class RedirectStrategyConfig {
companion object {
val REDIRECT_STRATEGY: RedirectStrategy = DefaultRedirectStrategy()
}
@Bean
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
oauth2Client {
authorizationCodeGrant {
authorizationRedirectStrategy = REDIRECT_STRATEGY
}
}
authorizeRequests {
authorize(anyRequest, authenticated)
}
}
return http.build()
}
}
@Test @Test
fun `oauth2Client when custom access token response client then client used`() { fun `oauth2Client when custom access token response client then client used`() {
this.spring.register(AuthorizedClientConfig::class.java, ClientConfig::class.java).autowire() this.spring.register(AuthorizedClientConfig::class.java, ClientConfig::class.java).autowire()
@@ -38,6 +38,9 @@ import org.springframework.security.oauth2.client.web.AuthorizationRequestReposi
import org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizationRequestRepository import org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizationRequestRepository
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest
import org.springframework.security.web.DefaultRedirectStrategy
import org.springframework.security.web.RedirectStrategy
import org.springframework.security.web.SecurityFilterChain
import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get import org.springframework.test.web.servlet.get
@@ -121,6 +124,37 @@ class AuthorizationEndpointDslTests {
} }
} }
@Test
fun `oauth2Login when custom authorization redirect strategy then redirect strategy used`() {
this.spring.register(RedirectStrategyConfig::class.java, ClientConfig::class.java).autowire()
mockkObject(RedirectStrategyConfig.REDIRECT_STRATEGY)
every { RedirectStrategyConfig.REDIRECT_STRATEGY.sendRedirect(any(), any(), any()) }
this.mockMvc.get("/oauth2/authorization/google")
verify(exactly = 1) { RedirectStrategyConfig.REDIRECT_STRATEGY.sendRedirect(any(), any(), any()) }
}
@EnableWebSecurity
open class RedirectStrategyConfig {
companion object {
val REDIRECT_STRATEGY: RedirectStrategy = DefaultRedirectStrategy()
}
@Bean
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
oauth2Login {
authorizationEndpoint {
authorizationRedirectStrategy = REDIRECT_STRATEGY
}
}
}
return http.build()
}
}
@Test @Test
fun `oauth2Login when custom authorization uri repository then uri used`() { fun `oauth2Login when custom authorization uri repository then uri used`() {
this.spring.register(AuthorizationUriConfig::class.java, ClientConfig::class.java).autowire() this.spring.register(AuthorizationUriConfig::class.java, ClientConfig::class.java).autowire()
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/security"
xsi:schemaLocation="
http://www.springframework.org/schema/security
https://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<http auto-config="true">
<oauth2-client>
<authorization-code-grant
authorization-redirect-strategy-ref="authorizationRedirectStrategy"/>
</oauth2-client>
</http>
<b:bean id="authorizationRedirectStrategy" class="org.mockito.Mockito" factory-method="mock">
<b:constructor-arg value="org.springframework.security.web.RedirectStrategy"/>
</b:bean>
<client-registrations>
<client-registration registration-id="google"
client-id="google-client-id"
client-secret="google-client-secret"
redirect-uri="http://localhost/callback/google"
scope="scope1,scope2"
provider-id="google"/>
</client-registrations>
<b:import resource="userservice.xml"/>
</b:beans>
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/security"
xsi:schemaLocation="
http://www.springframework.org/schema/security
https://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<http auto-config="true">
<intercept-url pattern="/**" access="authenticated"/>
<oauth2-login authorization-redirect-strategy-ref="authorizationRedirectStrategy"/>
</http>
<b:bean id="authorizationRedirectStrategy" class="org.mockito.Mockito" factory-method="mock">
<b:constructor-arg value="org.springframework.security.web.RedirectStrategy"/>
</b:bean>
<b:import resource="../oauth2/client/google-registration.xml"/>
<b:import resource="userservice.xml"/>
</b:beans>
+2 -2
View File
@@ -1,4 +1,4 @@
'display_version': '5.8.0-M1' 'display_version': '5.8.0-M2'
'name': 'ROOT' 'name': 'ROOT'
'prerelease': 'true' 'prerelease': 'true'
'version': '5.8.0-M1' 'version': '5.8.0-M2'
@@ -495,7 +495,7 @@ XML Configuration requires the `NoOpPasswordEncoder` bean name to be `passwordEn
Most applications that allow a user to specify a password also require a feature for updating that password. Most applications that allow a user to specify a password also require a feature for updating that password.
https://w3c.github.io/webappsec-change-password-url/[A Well-Know URL for Changing Passwords] indicates a mechanism by which password managers can discover the password update endpoint for a given application. https://w3c.github.io/webappsec-change-password-url/[A Well-Known URL for Changing Passwords] indicates a mechanism by which password managers can discover the password update endpoint for a given application.
You can configure Spring Security to provide this discovery endpoint. You can configure Spring Security to provide this discovery endpoint.
For example, if the change password endpoint in your application is `/change-password`, then you can configure Spring Security like so: For example, if the change password endpoint in your application is `/change-password`, then you can configure Spring Security like so:
@@ -2,7 +2,9 @@
The basic setup looks like this: The basic setup looks like this:
[source,java] ====
.Java
[source,java,role="primary"]
---- ----
@ExtendWith(SpringExtension.class) @ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = HelloWebfluxMethodApplication.class) @ContextConfiguration(classes = HelloWebfluxMethodApplication.class)
@@ -19,9 +21,35 @@ public class HelloWebfluxMethodApplicationTests {
// add Spring Security test Support // add Spring Security test Support
.apply(springSecurity()) .apply(springSecurity())
.configureClient() .configureClient()
.filter(basicAuthentication()) .filter(basicAuthentication("user", "password"))
.build(); .build();
} }
// ... // ...
} }
---- ----
.Kotlin
[source,kotlin,role="secondary"]
----
@ExtendWith(SpringExtension::class)
@ContextConfiguration(classes = [HelloWebfluxMethodApplication::class])
class HelloWebfluxMethodApplicationTests {
@Autowired
lateinit var context: ApplicationContext
lateinit var rest: WebTestClient
@BeforeEach
fun setup() {
this.rest = WebTestClient
.bindToApplicationContext(this.context)
// add Spring Security test Support
.apply(springSecurity())
.configureClient()
.filter(basicAuthentication("user", "password"))
.build()
}
// ...
}
----
====
@@ -984,6 +984,11 @@ Reference to the `AuthorizationRequestRepository`.
Reference to the `OAuth2AuthorizationRequestResolver`. Reference to the `OAuth2AuthorizationRequestResolver`.
[[nsa-oauth2-login-authorization-redirect-strategy-ref]]
* **authorization-redirect-strategy-ref**
Reference to the authorization `RedirectStrategy`.
[[nsa-oauth2-login-access-token-response-client-ref]] [[nsa-oauth2-login-access-token-response-client-ref]]
* **access-token-response-client-ref** * **access-token-response-client-ref**
Reference to the `OAuth2AccessTokenResponseClient`. Reference to the `OAuth2AccessTokenResponseClient`.
@@ -1084,6 +1089,11 @@ Configures xref:servlet/oauth2/client/authorization-grants.adoc#oauth2Client-aut
Reference to the `AuthorizationRequestRepository`. Reference to the `AuthorizationRequestRepository`.
[[nsa-authorization-code-grant-authorization-redirect-strategy-ref]]
* **authorization-redirect-strategy-ref**
Reference to the authorization `RedirectStrategy`.
[[nsa-authorization-code-grant-authorization-request-resolver-ref]] [[nsa-authorization-code-grant-authorization-request-resolver-ref]]
* **authorization-request-resolver-ref** * **authorization-request-resolver-ref**
Reference to the `OAuth2AuthorizationRequestResolver`. Reference to the `OAuth2AuthorizationRequestResolver`.
+1 -1
View File
@@ -3,7 +3,7 @@ springJavaformatVersion=0.0.34
springBootVersion=2.4.2 springBootVersion=2.4.2
springFrameworkVersion=5.3.22 springFrameworkVersion=5.3.22
openSamlVersion=3.4.6 openSamlVersion=3.4.6
version=5.8.0-M1 version=5.8.0-M2
kotlinVersion=1.7.10 kotlinVersion=1.7.10
samplesBranch=5.8.x samplesBranch=5.8.x
org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError
@@ -95,7 +95,7 @@ public class OAuth2AuthorizationRequestRedirectFilter extends OncePerRequestFilt
private final ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer(); private final ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer();
private final RedirectStrategy authorizationRedirectStrategy = new DefaultRedirectStrategy(); private RedirectStrategy authorizationRedirectStrategy = new DefaultRedirectStrategy();
private OAuth2AuthorizationRequestResolver authorizationRequestResolver; private OAuth2AuthorizationRequestResolver authorizationRequestResolver;
@@ -139,6 +139,15 @@ public class OAuth2AuthorizationRequestRedirectFilter extends OncePerRequestFilt
this.authorizationRequestResolver = authorizationRequestResolver; this.authorizationRequestResolver = authorizationRequestResolver;
} }
/**
* Sets the redirect strategy for Authorization Endpoint redirect URI.
* @param authorizationRedirectStrategy the redirect strategy
*/
public void setAuthorizationRedirectStrategy(RedirectStrategy authorizationRedirectStrategy) {
Assert.notNull(authorizationRedirectStrategy, "authorizationRedirectStrategy cannot be null");
this.authorizationRedirectStrategy = authorizationRedirectStrategy;
}
/** /**
* Sets the repository used for storing {@link OAuth2AuthorizationRequest}'s. * Sets the repository used for storing {@link OAuth2AuthorizationRequest}'s.
* @param authorizationRequestRepository the repository used for storing * @param authorizationRequestRepository the repository used for storing
@@ -75,7 +75,7 @@ import org.springframework.web.util.UriComponentsBuilder;
*/ */
public class OAuth2AuthorizationRequestRedirectWebFilter implements WebFilter { public class OAuth2AuthorizationRequestRedirectWebFilter implements WebFilter {
private final ServerRedirectStrategy authorizationRedirectStrategy = new DefaultServerRedirectStrategy(); private ServerRedirectStrategy authorizationRedirectStrategy = new DefaultServerRedirectStrategy();
private final ServerOAuth2AuthorizationRequestResolver authorizationRequestResolver; private final ServerOAuth2AuthorizationRequestResolver authorizationRequestResolver;
@@ -105,6 +105,15 @@ public class OAuth2AuthorizationRequestRedirectWebFilter implements WebFilter {
this.authorizationRequestResolver = authorizationRequestResolver; this.authorizationRequestResolver = authorizationRequestResolver;
} }
/**
* Sets the redirect strategy for Authorization Endpoint redirect URI.
* @param authorizationRedirectStrategy the redirect strategy
*/
public void setAuthorizationRedirectStrategy(ServerRedirectStrategy authorizationRedirectStrategy) {
Assert.notNull(authorizationRedirectStrategy, "authorizationRedirectStrategy cannot be null");
this.authorizationRedirectStrategy = authorizationRedirectStrategy;
}
/** /**
* Sets the repository used for storing {@link OAuth2AuthorizationRequest}'s. * Sets the repository used for storing {@link OAuth2AuthorizationRequest}'s.
* @param authorizationRequestRepository the repository used for storing * @param authorizationRequestRepository the repository used for storing
@@ -17,6 +17,7 @@
package org.springframework.security.oauth2.client.web; package org.springframework.security.oauth2.client.web;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.nio.charset.StandardCharsets;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@@ -30,7 +31,9 @@ import javax.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.oauth2.client.ClientAuthorizationRequiredException; import org.springframework.security.oauth2.client.ClientAuthorizationRequiredException;
@@ -40,6 +43,7 @@ import org.springframework.security.oauth2.client.registration.InMemoryClientReg
import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.savedrequest.RequestCache; import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriComponentsBuilder;
@@ -116,6 +120,11 @@ public class OAuth2AuthorizationRequestRedirectFilterTests {
assertThatIllegalArgumentException().isThrownBy(() -> this.filter.setAuthorizationRequestRepository(null)); assertThatIllegalArgumentException().isThrownBy(() -> this.filter.setAuthorizationRequestRepository(null));
} }
@Test
public void setAuthorizationRedirectStrategyWhenAuthorizationRedirectStrategyIsNullThenThrowIllegalArgumentException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.filter.setAuthorizationRedirectStrategy(null));
}
@Test @Test
public void setRequestCacheWhenRequestCacheIsNullThenThrowIllegalArgumentException() { public void setRequestCacheWhenRequestCacheIsNullThenThrowIllegalArgumentException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.filter.setRequestCache(null)); assertThatIllegalArgumentException().isThrownBy(() -> this.filter.setRequestCache(null));
@@ -333,4 +342,31 @@ public class OAuth2AuthorizationRequestRedirectFilterTests {
+ "login_hint=user@provider\\.com"); + "login_hint=user@provider\\.com");
} }
@Test
public void doFilterWhenCustomAuthorizationRedirectStrategySetThenCustomAuthorizationRedirectStrategyUsed()
throws Exception {
String requestUri = OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI + "/"
+ this.registration1.getRegistrationId();
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
request.setServletPath(requestUri);
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain filterChain = mock(FilterChain.class);
RedirectStrategy customRedirectStrategy = (httpRequest, httpResponse, url) -> {
String redirectUrl = httpResponse.encodeRedirectURL(url);
httpResponse.setStatus(HttpStatus.OK.value());
httpResponse.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE);
httpResponse.getWriter().write(redirectUrl);
httpResponse.getWriter().flush();
};
this.filter.setAuthorizationRedirectStrategy(customRedirectStrategy);
this.filter.doFilter(request, response, filterChain);
verifyZeroInteractions(filterChain);
assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value());
assertThat(response.getContentType()).isEqualTo(MediaType.TEXT_PLAIN_VALUE);
assertThat(response.getContentAsString(StandardCharsets.UTF_8))
.matches("https://example.com/login/oauth/authorize\\?" + "response_type=code&client_id=client-id&"
+ "scope=read:user&state=.{15,}&"
+ "redirect_uri=http://localhost/login/oauth2/code/registration-id");
}
} }
@@ -17,6 +17,7 @@
package org.springframework.security.oauth2.client.web.server; package org.springframework.security.oauth2.client.web.server;
import java.net.URI; import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.Arrays; import java.util.Arrays;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
@@ -24,13 +25,20 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.oauth2.client.ClientAuthorizationRequiredException; import org.springframework.security.oauth2.client.ClientAuthorizationRequiredException;
import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.web.server.ServerRedirectStrategy;
import org.springframework.security.web.server.savedrequest.ServerRequestCache; import org.springframework.security.web.server.savedrequest.ServerRequestCache;
import org.springframework.test.web.reactive.server.FluxExchangeResult; import org.springframework.test.web.reactive.server.FluxExchangeResult;
import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.test.web.reactive.server.WebTestClient;
@@ -81,6 +89,11 @@ public class OAuth2AuthorizationRequestRedirectWebFilterTests {
.isThrownBy(() -> new OAuth2AuthorizationRequestRedirectWebFilter(this.clientRepository)); .isThrownBy(() -> new OAuth2AuthorizationRequestRedirectWebFilter(this.clientRepository));
} }
@Test
public void setterWhenAuthorizationRedirectStrategyNullThenIllegalArgumentException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.filter.setAuthorizationRedirectStrategy(null));
}
@Test @Test
public void filterWhenDoesNotMatchThenClientRegistrationRepositoryNotSubscribed() { public void filterWhenDoesNotMatchThenClientRegistrationRepositoryNotSubscribed() {
// @formatter:off // @formatter:off
@@ -195,4 +208,46 @@ public class OAuth2AuthorizationRequestRedirectWebFilterTests {
verifyNoInteractions(this.requestCache); verifyNoInteractions(this.requestCache);
} }
@Test
public void filterWhenCustomRedirectStrategySetThenRedirectUriInResponseBody() {
given(this.clientRepository.findByRegistrationId(this.registration.getRegistrationId()))
.willReturn(Mono.just(this.registration));
given(this.authzRequestRepository.saveAuthorizationRequest(any(), any())).willReturn(Mono.empty());
ServerRedirectStrategy customRedirectStrategy = (exchange, location) -> {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.OK);
response.getHeaders().setContentType(MediaType.TEXT_PLAIN);
DataBuffer buffer = exchange.getResponse().bufferFactory()
.wrap(location.toASCIIString().getBytes(StandardCharsets.UTF_8));
return exchange.getResponse().writeWith(Flux.just(buffer));
};
this.filter.setAuthorizationRedirectStrategy(customRedirectStrategy);
this.filter.setRequestCache(this.requestCache);
FluxExchangeResult<String> result = this.client.get()
.uri("https://example.com/oauth2/authorization/registration-id").exchange().expectHeader()
.contentType(MediaType.TEXT_PLAIN).expectStatus().isOk().returnResult(String.class);
// @formatter:off
StepVerifier.create(result.getResponseBody())
.assertNext((uri) -> {
URI location = URI.create(uri);
assertThat(location)
.hasScheme("https")
.hasHost("example.com")
.hasPath("/login/oauth/authorize")
.hasParameter("response_type", "code")
.hasParameter("client_id", "client-id")
.hasParameter("scope", "read:user")
.hasParameter("state")
.hasParameter("redirect_uri", "https://example.com/login/oauth2/code/registration-id");
})
.verifyComplete();
// @formatter:on
verifyNoInteractions(this.requestCache);
}
} }
@@ -236,6 +236,8 @@ public class Saml2WebSsoAuthenticationRequestFilter extends OncePerRequestFilter
StringBuilder html = new StringBuilder(); StringBuilder html = new StringBuilder();
html.append("<!DOCTYPE html>\n"); html.append("<!DOCTYPE html>\n");
html.append("<html>\n").append(" <head>\n"); html.append("<html>\n").append(" <head>\n");
html.append(" <meta http-equiv=\"Content-Security-Policy\" ")
.append("content=\"script-src 'sha256-ePniVEkSivX/c7XWBGafqh8tSpiRrKiqYeqbG7N1TOE='\">\n");
html.append(" <meta charset=\"utf-8\" />\n"); html.append(" <meta charset=\"utf-8\" />\n");
html.append(" </head>\n"); html.append(" </head>\n");
html.append(" <body onload=\"document.forms[0].submit()\">\n"); html.append(" <body onload=\"document.forms[0].submit()\">\n");
@@ -215,6 +215,8 @@ public final class Saml2LogoutRequestFilter extends OncePerRequestFilter {
StringBuilder html = new StringBuilder(); StringBuilder html = new StringBuilder();
html.append("<!DOCTYPE html>\n"); html.append("<!DOCTYPE html>\n");
html.append("<html>\n").append(" <head>\n"); html.append("<html>\n").append(" <head>\n");
html.append(" <meta http-equiv=\"Content-Security-Policy\" ")
.append("content=\"script-src 'sha256-ePniVEkSivX/c7XWBGafqh8tSpiRrKiqYeqbG7N1TOE='\">\n");
html.append(" <meta charset=\"utf-8\" />\n"); html.append(" <meta charset=\"utf-8\" />\n");
html.append(" </head>\n"); html.append(" </head>\n");
html.append(" <body onload=\"document.forms[0].submit()\">\n"); html.append(" <body onload=\"document.forms[0].submit()\">\n");
@@ -119,6 +119,8 @@ public final class Saml2RelyingPartyInitiatedLogoutSuccessHandler implements Log
StringBuilder html = new StringBuilder(); StringBuilder html = new StringBuilder();
html.append("<!DOCTYPE html>\n"); html.append("<!DOCTYPE html>\n");
html.append("<html>\n").append(" <head>\n"); html.append("<html>\n").append(" <head>\n");
html.append(" <meta http-equiv=\"Content-Security-Policy\" ")
.append("content=\"script-src 'sha256-ePniVEkSivX/c7XWBGafqh8tSpiRrKiqYeqbG7N1TOE='\">\n");
html.append(" <meta charset=\"utf-8\" />\n"); html.append(" <meta charset=\"utf-8\" />\n");
html.append(" </head>\n"); html.append(" </head>\n");
html.append(" <body onload=\"document.forms[0].submit()\">\n"); html.append(" <body onload=\"document.forms[0].submit()\">\n");
@@ -244,6 +244,7 @@ public class OpenSamlAuthenticationProviderTests {
expected.put("age", Collections.singletonList(21)); expected.put("age", Collections.singletonList(21));
expected.put("website", Collections.singletonList("https://johndoe.com/")); expected.put("website", Collections.singletonList("https://johndoe.com/"));
expected.put("registered", Collections.singletonList(true)); expected.put("registered", Collections.singletonList(true));
expected.put("role", Arrays.asList("RoleTwo"));
Instant registeredDate = Instant.ofEpochMilli(DateTime.parse("1970-01-01T00:00:00Z").getMillis()); Instant registeredDate = Instant.ofEpochMilli(DateTime.parse("1970-01-01T00:00:00Z").getMillis());
expected.put("registeredDate", Collections.singletonList(registeredDate)); expected.put("registeredDate", Collections.singletonList(registeredDate));
assertThat((String) principal.getFirstAttribute("name")).isEqualTo("John Doe"); assertThat((String) principal.getFirstAttribute("name")).isEqualTo("John Doe");
@@ -23,7 +23,6 @@ import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Consumer; import java.util.function.Consumer;
@@ -92,6 +91,8 @@ import org.springframework.security.saml2.provider.service.registration.RelyingP
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
@@ -645,7 +646,7 @@ public final class OpenSaml4AuthenticationProvider implements AuthenticationProv
} }
private static Map<String, List<Object>> getAssertionAttributes(Assertion assertion) { private static Map<String, List<Object>> getAssertionAttributes(Assertion assertion) {
Map<String, List<Object>> attributeMap = new LinkedHashMap<>(); MultiValueMap<String, Object> attributeMap = new LinkedMultiValueMap<>();
for (AttributeStatement attributeStatement : assertion.getAttributeStatements()) { for (AttributeStatement attributeStatement : assertion.getAttributeStatements()) {
for (Attribute attribute : attributeStatement.getAttributes()) { for (Attribute attribute : attributeStatement.getAttributes()) {
List<Object> attributeValues = new ArrayList<>(); List<Object> attributeValues = new ArrayList<>();
@@ -655,7 +656,7 @@ public final class OpenSaml4AuthenticationProvider implements AuthenticationProv
attributeValues.add(attributeValue); attributeValues.add(attributeValue);
} }
} }
attributeMap.put(attribute.getName(), attributeValues); attributeMap.addAll(attribute.getName(), attributeValues);
} }
} }
return attributeMap; return attributeMap;
@@ -343,6 +343,7 @@ public class OpenSaml4AuthenticationProviderTests {
expected.put("registered", Collections.singletonList(true)); expected.put("registered", Collections.singletonList(true));
Instant registeredDate = Instant.parse("1970-01-01T00:00:00Z"); Instant registeredDate = Instant.parse("1970-01-01T00:00:00Z");
expected.put("registeredDate", Collections.singletonList(registeredDate)); expected.put("registeredDate", Collections.singletonList(registeredDate));
expected.put("role", Arrays.asList("RoleOne", "RoleTwo")); // gh-11042
assertThat((String) principal.getFirstAttribute("name")).isEqualTo("John Doe"); assertThat((String) principal.getFirstAttribute("name")).isEqualTo("John Doe");
assertThat(principal.getAttributes()).isEqualTo(expected); assertThat(principal.getAttributes()).isEqualTo(expected);
assertThat(principal.getSessionIndexes()).contains("session-index"); assertThat(principal.getSessionIndexes()).contains("session-index");
@@ -327,6 +327,18 @@ public final class TestOpenSamlObjects {
name.setValue("John Doe"); name.setValue("John Doe");
nameAttr.getAttributeValues().add(name); nameAttr.getAttributeValues().add(name);
attrStmt1.getAttributes().add(nameAttr); attrStmt1.getAttributes().add(nameAttr);
Attribute roleOneAttr = attributeBuilder.buildObject(); // gh-11042
roleOneAttr.setName("role");
XSString roleOne = new XSStringBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSString.TYPE_NAME);
roleOne.setValue("RoleOne");
roleOneAttr.getAttributeValues().add(roleOne);
attrStmt1.getAttributes().add(roleOneAttr);
Attribute roleTwoAttr = attributeBuilder.buildObject(); // gh-11042
roleTwoAttr.setName("role");
XSString roleTwo = new XSStringBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSString.TYPE_NAME);
roleTwo.setValue("RoleTwo");
roleTwoAttr.getAttributeValues().add(roleTwo);
attrStmt1.getAttributes().add(roleTwoAttr);
Attribute ageAttr = attributeBuilder.buildObject(); Attribute ageAttr = attributeBuilder.buildObject();
ageAttr.setName("age"); ageAttr.setName("age");
XSInteger age = new XSIntegerBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSInteger.TYPE_NAME); XSInteger age = new XSIntegerBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSInteger.TYPE_NAME);
@@ -198,7 +198,9 @@ public class Saml2WebSsoAuthenticationRequestFilterTests {
given(this.factory.createPostAuthenticationRequest(any())).willReturn(request); given(this.factory.createPostAuthenticationRequest(any())).willReturn(request);
this.filter.doFilterInternal(this.request, this.response, this.filterChain); this.filter.doFilterInternal(this.request, this.response, this.filterChain);
assertThat(this.response.getHeader("Location")).isNull(); assertThat(this.response.getHeader("Location")).isNull();
assertThat(this.response.getContentAsString()) assertThat(this.response.getContentAsString()).contains(
"<meta http-equiv=\"Content-Security-Policy\" content=\"script-src 'sha256-ePniVEkSivX/c7XWBGafqh8tSpiRrKiqYeqbG7N1TOE='\">")
.contains("<body onload=\"document.forms[0].submit()\">")
.contains("<form action=\"https://sso-url.example.com/IDP/SSO\" method=\"post\">") .contains("<form action=\"https://sso-url.example.com/IDP/SSO\" method=\"post\">")
.contains("<input type=\"hidden\" name=\"SAMLRequest\"") .contains("<input type=\"hidden\" name=\"SAMLRequest\"")
.contains("value=\"" + relayStateEncoded + "\""); .contains("value=\"" + relayStateEncoded + "\"");
@@ -117,6 +117,9 @@ public class Saml2LogoutRequestFilterTests {
String content = response.getContentAsString(); String content = response.getContentAsString();
assertThat(content).contains(Saml2ParameterNames.SAML_RESPONSE); assertThat(content).contains(Saml2ParameterNames.SAML_RESPONSE);
assertThat(content).contains(registration.getAssertingPartyDetails().getSingleLogoutServiceResponseLocation()); assertThat(content).contains(registration.getAssertingPartyDetails().getSingleLogoutServiceResponseLocation());
assertThat(content).contains(
"<meta http-equiv=\"Content-Security-Policy\" content=\"script-src 'sha256-ePniVEkSivX/c7XWBGafqh8tSpiRrKiqYeqbG7N1TOE='\">");
assertThat(content).contains("<body onload=\"document.forms[0].submit()\">");
verify(this.securityContextHolderStrategy).getContext(); verify(this.securityContextHolderStrategy).getContext();
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2021 the original author or authors. * Copyright 2002-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -97,6 +97,9 @@ public class Saml2RelyingPartyInitiatedLogoutSuccessHandlerTests {
String content = response.getContentAsString(); String content = response.getContentAsString();
assertThat(content).contains(Saml2ParameterNames.SAML_REQUEST); assertThat(content).contains(Saml2ParameterNames.SAML_REQUEST);
assertThat(content).contains(registration.getAssertingPartyDetails().getSingleLogoutServiceLocation()); assertThat(content).contains(registration.getAssertingPartyDetails().getSingleLogoutServiceLocation());
assertThat(content).contains(
"<meta http-equiv=\"Content-Security-Policy\" content=\"script-src 'sha256-ePniVEkSivX/c7XWBGafqh8tSpiRrKiqYeqbG7N1TOE='\">");
assertThat(content).contains("<body onload=\"document.forms[0].submit()\">");
} }
private Saml2Authentication authentication(RelyingPartyRegistration registration) { private Saml2Authentication authentication(RelyingPartyRegistration registration) {
@@ -41,6 +41,7 @@ import org.springframework.security.web.firewall.HttpFirewall;
import org.springframework.security.web.firewall.RequestRejectedException; import org.springframework.security.web.firewall.RequestRejectedException;
import org.springframework.security.web.firewall.RequestRejectedHandler; import org.springframework.security.web.firewall.RequestRejectedHandler;
import org.springframework.security.web.firewall.StrictHttpFirewall; import org.springframework.security.web.firewall.StrictHttpFirewall;
import org.springframework.security.web.util.ThrowableAnalyzer;
import org.springframework.security.web.util.UrlUtils; import org.springframework.security.web.util.UrlUtils;
import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@@ -158,6 +159,8 @@ public class FilterChainProxy extends GenericFilterBean {
private RequestRejectedHandler requestRejectedHandler = new DefaultRequestRejectedHandler(); private RequestRejectedHandler requestRejectedHandler = new DefaultRequestRejectedHandler();
private ThrowableAnalyzer throwableAnalyzer = new ThrowableAnalyzer();
public FilterChainProxy() { public FilterChainProxy() {
} }
@@ -186,8 +189,15 @@ public class FilterChainProxy extends GenericFilterBean {
request.setAttribute(FILTER_APPLIED, Boolean.TRUE); request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
doFilterInternal(request, response, chain); doFilterInternal(request, response, chain);
} }
catch (RequestRejectedException ex) { catch (Exception ex) {
this.requestRejectedHandler.handle((HttpServletRequest) request, (HttpServletResponse) response, ex); Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex);
Throwable requestRejectedException = this.throwableAnalyzer
.getFirstThrowableOfType(RequestRejectedException.class, causeChain);
if (!(requestRejectedException instanceof RequestRejectedException)) {
throw ex;
}
this.requestRejectedHandler.handle((HttpServletRequest) request, (HttpServletResponse) response,
(RequestRejectedException) requestRejectedException);
} }
finally { finally {
this.securityContextHolderStrategy.clearContext(); this.securityContextHolderStrategy.clearContext();
@@ -66,18 +66,26 @@ public final class RequestAttributeSecurityContextRepository implements Security
@Override @Override
public boolean containsContext(HttpServletRequest request) { public boolean containsContext(HttpServletRequest request) {
return loadContext(request).get() != null; return getContext(request) != null;
} }
@Override @Override
public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) { public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
SecurityContext context = loadContext(requestResponseHolder.getRequest()).get(); return getContextOrEmpty(requestResponseHolder.getRequest());
return (context != null) ? context : SecurityContextHolder.createEmptyContext();
} }
@Override @Override
public Supplier<SecurityContext> loadContext(HttpServletRequest request) { public Supplier<SecurityContext> loadContext(HttpServletRequest request) {
return () -> (SecurityContext) request.getAttribute(this.requestAttributeName); return () -> getContextOrEmpty(request);
}
private SecurityContext getContextOrEmpty(HttpServletRequest request) {
SecurityContext context = getContext(request);
return (context != null) ? context : SecurityContextHolder.createEmptyContext();
}
private SecurityContext getContext(HttpServletRequest request) {
return (SecurityContext) request.getAttribute(this.requestAttributeName);
} }
@Override @Override
@@ -50,6 +50,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.willAnswer; import static org.mockito.BDDMockito.willAnswer;
import static org.mockito.BDDMockito.willThrow;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.verifyZeroInteractions;
@@ -262,4 +263,18 @@ public class FilterChainProxyTests {
verify(rjh).handle(eq(this.request), eq(this.response), eq((requestRejectedException))); verify(rjh).handle(eq(this.request), eq(this.response), eq((requestRejectedException)));
} }
@Test
public void requestRejectedHandlerIsCalledIfFirewallThrowsWrappedRequestRejectedException() throws Exception {
HttpFirewall fw = mock(HttpFirewall.class);
RequestRejectedHandler rjh = mock(RequestRejectedHandler.class);
this.fcp.setFirewall(fw);
this.fcp.setRequestRejectedHandler(rjh);
RequestRejectedException requestRejectedException = new RequestRejectedException("Contains illegal chars");
ServletException servletException = new ServletException(requestRejectedException);
given(fw.getFirewalledRequest(this.request)).willReturn(mock(FirewalledRequest.class));
willThrow(servletException).given(this.chain).doFilter(any(), any());
this.fcp.doFilter(this.request, this.response, this.chain);
verify(rjh).handle(eq(this.request), eq(this.response), eq((requestRejectedException)));
}
} }
@@ -16,6 +16,8 @@
package org.springframework.security.web.context; package org.springframework.security.web.context;
import java.util.function.Supplier;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletRequest;
@@ -67,4 +69,17 @@ class RequestAttributeSecurityContextRepositoryTests {
assertThat(this.repository.containsContext(this.request)).isTrue(); assertThat(this.repository.containsContext(this.request)).isTrue();
} }
@Test
void loadDeferredContextWhenNotPresentThenEmptyContext() {
Supplier<SecurityContext> deferredContext = this.repository.loadContext(this.request);
assertThat(deferredContext.get()).isEqualTo(SecurityContextHolder.createEmptyContext());
}
@Test
void loadContextWhenNotPresentThenEmptyContext() {
SecurityContext context = this.repository
.loadContext(new HttpRequestResponseHolder(this.request, this.response));
assertThat(context).isEqualTo(SecurityContextHolder.createEmptyContext());
}
} }