1
0
mirror of synced 2026-08-04 17:27:13 +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
workflow_dispatch: # Manual trigger
permissions:
contents: read
jobs:
update:
name: Update Algolia Index
runs-on: ubuntu-latest
if: ${{ github.repository == 'spring-projects/spring-security' }}
steps:
- name: Checkout Source
uses: actions/checkout@v2
+7 -6
View File
@@ -10,20 +10,21 @@ on:
env:
GH_ACTIONS_REPO_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
if: ${{ github.repository == 'spring-projects/spring-security' }}
steps:
- name: Checkout Source
uses: actions/checkout@v2
- name: Set up JDK
uses: actions/setup-java@v1
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
with:
java-version: '11'
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
env:
GRADLE_USER_HOME: ~/.gradle
distribution: 'adopt'
- name: Generate antora.yml
run: ./gradlew :spring-security-docs:generateAntora
- name: Extract Branch Name
+7
View File
@@ -8,9 +8,16 @@ on:
push:
branches:
- '*.x'
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
if: ${{ github.repository == 'spring-projects/spring-security' }}
permissions:
contents: read
issues: write
pull-requests: write
steps:
- uses: actions/checkout@v3
- uses: actions/setup-java@v3
@@ -3,9 +3,15 @@ on:
schedule:
- cron: '0 10 * * *' # Once per day at 10am UTC
permissions:
contents: read
jobs:
main:
runs-on: ubuntu-latest
if: ${{ github.repository == 'spring-projects/spring-security' }}
permissions:
contents: none
steps:
- name: Delete artifacts in cron job
env:
@@ -2,6 +2,8 @@ name: CI
on:
push:
branches:
- '**'
schedule:
- cron: '0 10 * * *' # Once per day at 10am UTC
workflow_dispatch: # Manual trigger
@@ -16,12 +18,15 @@ env:
STRUCTURE101_LICENSEID: ${{ secrets.STRUCTURE101_LICENSEID }}
ARTIFACTORY_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
ARTIFACTORY_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
RUN_JOBS: ${{ github.repository == 'spring-projects/spring-security' }}
permissions:
contents: read
jobs:
prerequisites:
name: Pre-requisites for building
runs-on: ubuntu-latest
if: ${{ github.repository == 'spring-projects/spring-security' }}
outputs:
runjobs: ${{ steps.continue.outputs.runjobs }}
project_version: ${{ steps.continue.outputs.project_version }}
@@ -29,8 +34,8 @@ jobs:
- uses: actions/checkout@v2
- id: continue
name: Determine if should continue
if: env.RUN_JOBS == 'true'
run: |
# Run jobs if in upstream repository
echo "::set-output name=runjobs::true"
# Extract version from gradle.properties
version=$(cat gradle.properties | grep "version=" | awk -F'=' '{print $2}')
@@ -46,17 +51,14 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Set up JDK 11
uses: actions/setup-java@v1
uses: actions/setup-java@v2
with:
java-version: '11'
- name: Setup gradle user name
run: |
mkdir -p ~/.gradle
echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties
- name: Setup Gradle
distribution: 'adopt'
- name: Set up Gradle
uses: gradle/gradle-build-action@v2
env:
GRADLE_USER_HOME: ~/.gradle
- name: Set up gradle user name
run: echo 'systemProp.user.name=spring-builds+github' >> gradle.properties
- name: Build with Gradle
env:
GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }}
@@ -70,18 +72,11 @@ jobs:
if: needs.prerequisites.outputs.runjobs
steps:
- uses: actions/checkout@v2
- name: Set up JDK
uses: actions/setup-java@v1
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
with:
java-version: '11'
- name: Setup gradle user name
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
distribution: 'adopt'
- name: Snapshot Tests
run: |
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
@@ -95,18 +90,11 @@ jobs:
if: needs.prerequisites.outputs.runjobs
steps:
- uses: actions/checkout@v2
- name: Set up JDK
uses: actions/setup-java@v1
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
with:
java-version: '11'
- name: Setup gradle user name
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
distribution: 'adopt'
- name: Check samples project
env:
LOCAL_REPOSITORY_PATH: ${{ github.workspace }}/build/publications/repos
@@ -124,18 +112,11 @@ jobs:
if: needs.prerequisites.outputs.runjobs
steps:
- uses: actions/checkout@v2
- name: Set up JDK
uses: actions/setup-java@v1
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
with:
java-version: '11'
- name: Setup gradle user name
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
distribution: 'adopt'
- name: Check for package tangles
run: |
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
@@ -148,18 +129,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK
uses: actions/setup-java@v1
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
with:
java-version: '11'
- name: Setup gradle user name
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
distribution: 'adopt'
- name: Deploy artifacts
run: |
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
@@ -179,18 +153,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK
uses: actions/setup-java@v1
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
with:
java-version: '11'
- name: Setup gradle user name
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
distribution: 'adopt'
- name: Deploy Docs
run: |
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
@@ -207,18 +174,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK
uses: actions/setup-java@v1
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
with:
java-version: '11'
- name: Setup gradle user name
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
distribution: 'adopt'
- name: Deploy Schema
run: |
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
@@ -233,6 +193,8 @@ jobs:
name: Perform release
needs: [prerequisites, deploy_artifacts, deploy_docs, deploy_schema]
runs-on: ubuntu-latest
permissions:
contents: write
timeout-minutes: 90
if: ${{ !endsWith(needs.prerequisites.outputs.project_version, '-SNAPSHOT') }}
env:
@@ -244,18 +206,11 @@ jobs:
- uses: actions/checkout@v2
with:
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
- name: Set up JDK
uses: actions/setup-java@v1
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
with:
java-version: '11'
- name: Setup gradle user name
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
distribution: 'adopt'
- name: Wait for Artifactory Artifacts
if: ${{ contains(needs.prerequisites.outputs.project_version, '-RC') || contains(needs.prerequisites.outputs.project_version, '-M') }}
run: |
@@ -321,6 +276,9 @@ jobs:
name: Perform post-release
needs: [prerequisites, deploy_artifacts, deploy_docs, deploy_schema]
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
timeout-minutes: 90
if: ${{ endsWith(needs.prerequisites.outputs.project_version, '-SNAPSHOT') }}
env:
@@ -328,7 +286,8 @@ jobs:
VERSION: ${{ needs.prerequisites.outputs.project_version }}
steps:
- 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:
java-version: '11'
distribution: 'adopt'
@@ -336,9 +295,11 @@ jobs:
run: ./gradlew scheduleNextRelease -PnextVersion=$VERSION -PgitHubAccessToken=$TOKEN
notify_result:
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()
runs-on: ubuntu-latest
permissions:
actions: read
steps:
- name: Send Slack message
# 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
workflow_dispatch: # Manual trigger
permissions:
contents: read
jobs:
deploy:
name: deploy
runs-on: ubuntu-latest
if: ${{ github.repository == 'spring-projects/spring-security' }}
steps:
- uses: actions/checkout@v2
- name: Set up JDK 11
uses: actions/setup-java@v2
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
with:
java-version: '11'
distribution: 'adopt'
- name: Validate Gradle wrapper
uses: gradle/wrapper-validation-action@e6e38bacfdf1a337459f332974bb2327a31aaf4b
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
env:
GRADLE_USER_HOME: ~/.gradle
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: Cleanup Gradle Cache
# 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.
run: |
rm -f /home/runner/.gradle/caches/modules-2/modules-2.lock
rm -f /home/runner/.gradle/caches/modules-2/gc.properties
- name: Build with Gradle
run: ./gradlew :spring-security-docs:antora --stacktrace
- name: Deploy
@@ -5,10 +5,15 @@ on:
env:
DUE_ON: ${{ github.event.milestone.due_on }}
TITLE: ${{ github.event.milestone.title }}
permissions:
contents: read
jobs:
spring-releasetrain-checks:
name: Check DueOn is on a Release Date
runs-on: ubuntu-latest
if: ${{ github.repository == 'spring-projects/spring-security' }}
permissions:
contents: none
steps:
- name: Print Milestone Being Checked
run: echo "Validating DueOn '$DUE_ON' for milestone '$TITLE'"
@@ -23,6 +28,8 @@ jobs:
needs: [spring-releasetrain-checks]
if: failure()
runs-on: ubuntu-latest
permissions:
actions: read
steps:
- name: Send Slack message
uses: Gamesight/slack-workflow-status@v1.0.1
+7 -15
View File
@@ -2,28 +2,20 @@ name: PR Build
on: pull_request
env:
RUN_JOBS: ${{ github.repository == 'spring-projects/spring-security' }}
permissions:
contents: read
jobs:
build:
name: Build
runs-on: ubuntu-latest
if: ${{ github.repository == 'spring-projects/spring-security' }}
steps:
- if: env.RUN_JOBS == 'true'
uses: actions/checkout@v2
- name: Set up JDK
if: env.RUN_JOBS == 'true'
uses: actions/setup-java@v1
- uses: actions/checkout@v2
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
with:
java-version: '11'
- name: Setup Gradle
if: env.RUN_JOBS == 'true'
uses: gradle/gradle-build-action@v2
with:
cache-read-only: true
env:
GRADLE_USER_HOME: ~/.gradle
distribution: 'adopt'
- name: Build with Gradle
if: env.RUN_JOBS == 'true'
run: ./gradlew clean build --continue --scan
@@ -9,30 +9,28 @@ env:
GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }}
GRADLE_ENTERPRISE_SECRET_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
permissions:
contents: read
jobs:
update_scheduled_release_version:
name: Initiate Release If Scheduled
if: ${{ github.repository == 'spring-projects/spring-security' }}
runs-on: ubuntu-latest
permissions:
contents: read
actions: read
steps:
- id: checkout-source
name: Checkout Source Code
uses: actions/checkout@v2
with:
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
- id: setup-jdk
name: Set up JDK 11
uses: actions/setup-java@v1
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
with:
java-version: '11'
- name: Setup gradle user name
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
distribution: 'adopt'
- id: check-release-due
name: Check Release Due
run: |
+1 -1
View File
@@ -96,7 +96,7 @@ dependencies {
implementation 'io.spring.nohttp:nohttp-gradle:0.0.10'
implementation 'net.sourceforge.htmlunit:htmlunit:2.37.0'
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'
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.Project
import org.gradle.api.publish.maven.plugins.MavenPublishPlugin
class ArtifactoryPlugin implements Plugin<Project> {
@@ -36,8 +37,14 @@ class ArtifactoryPlugin implements Plugin<Project> {
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(SpringNexusPublishPlugin)
pluginManager.apply(CheckProhibitedDependenciesLifecyclePlugin)
pluginManager.apply(ArtifactoryPlugin)
pluginManager.apply("org.sonarqube")
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");
* 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
* public class MyGlobalAuthenticationConfiguration {
*
* &#064;Autowired
* public void configureGlobal(AuthenticationManagerBuilder auth) {
* auth.inMemoryAuthentication().withUser(&quot;user&quot;).password(&quot;password&quot;).roles(&quot;USER&quot;)
* .and().withUser(&quot;admin&quot;).password(&quot;password&quot;).roles(&quot;USER&quot;, &quot;ADMIN&quot;);
* &#064;Bean
* public UserDetailsService userDetailsService() {
* UserDetails user = User.withDefaultPasswordEncoder()
* .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>
@@ -54,15 +63,24 @@ import org.springframework.security.config.annotation.web.servlet.configuration.
* <pre class="code">
* &#064;Configuration
* &#064;EnableWebSecurity
* public class MyWebSecurityConfiguration extends WebSecurityConfigurerAdapter {
* public class MyWebSecurityConfiguration {
*
* &#064;Autowired
* public void configureGlobal(AuthenticationManagerBuilder auth) {
* auth.inMemoryAuthentication().withUser(&quot;user&quot;).password(&quot;password&quot;).roles(&quot;USER&quot;)
* .and().withUser(&quot;admin&quot;).password(&quot;password&quot;).roles(&quot;USER&quot;, &quot;ADMIN&quot;);
* &#064;Bean
* public UserDetailsService userDetailsService() {
* UserDetails user = User.withDefaultPasswordEncoder()
* .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>
*
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,19 +23,16 @@ import org.springframework.security.config.annotation.SecurityBuilder;
import org.springframework.security.config.annotation.SecurityConfigurer;
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.WebSecurityConfigurerAdapter;
import org.springframework.security.web.SecurityFilterChain;
/**
* Allows customization to the {@link WebSecurity}. In most instances users will use
* {@link EnableWebSecurity} and either create a {@link Configuration} that extends
* {@link WebSecurityConfigurerAdapter} or expose a {@link SecurityFilterChain} bean. Both
* will automatically be applied to the {@link WebSecurity} by the
* {@link EnableWebSecurity} annotation.
* {@link EnableWebSecurity} and create a {@link Configuration} that exposes a
* {@link SecurityFilterChain} bean. This will automatically be applied to the
* {@link WebSecurity} by the {@link EnableWebSecurity} annotation.
*
* @author Rob Winch
* @since 3.2
* @see WebSecurityConfigurerAdapter
* @see SecurityFilterChain
*/
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.configuration.EnableWebSecurity;
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.core.context.SecurityContext;
import org.springframework.security.web.DefaultSecurityFilterChain;
@@ -77,8 +76,7 @@ import org.springframework.web.filter.DelegatingFilterProxy;
*
* <p>
* Customizations to the {@link WebSecurity} can be made by creating a
* {@link WebSecurityConfigurer}, overriding {@link WebSecurityConfigurerAdapter} or
* exposing a {@link WebSecurityCustomizer} bean.
* {@link WebSecurityConfigurer} or exposing a {@link WebSecurityCustomizer} bean.
* </p>
*
* @author Rob Winch
@@ -200,7 +198,7 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
*
* <p>
* Typically this method is invoked automatically within the framework from
* {@link WebSecurityConfigurerAdapter#init(WebSecurity)}
* {@link WebSecurityConfiguration#springSecurityFilterChain()}
* </p>
* @param securityFilterChainBuilder the builder to use to create the
* {@link SecurityFilterChain} instances
@@ -258,12 +256,13 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
/**
* Sets the {@link FilterSecurityInterceptor}. This is typically invoked by
* {@link WebSecurityConfigurerAdapter}.
* {@link WebSecurityConfiguration#springSecurityFilterChain()}.
* @param securityInterceptor the {@link FilterSecurityInterceptor} to use
* @return the {@link WebSecurity} for further customizations
* @deprecated Use {@link #privilegeEvaluator(WebInvocationPrivilegeEvaluator)}
* instead
*/
@Deprecated
public WebSecurity securityInterceptor(FilterSecurityInterceptor securityInterceptor) {
this.filterSecurityInterceptor = securityInterceptor;
return this;
@@ -296,8 +295,7 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
protected Filter performBuild() throws Exception {
Assert.state(!this.securityFilterChainBuilders.isEmpty(),
() -> "At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. "
+ "Typically this is done by exposing a SecurityFilterChain bean "
+ "or by adding a @Configuration that extends WebSecurityConfigurerAdapter. "
+ "Typically this is done by exposing a SecurityFilterChain bean. "
+ "More advanced users can invoke " + WebSecurity.class.getSimpleName()
+ ".addSecurityFilterChainBuilder directly");
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");
* 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.security.config.annotation.authentication.configuration.EnableGlobalAuthentication;
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
* configuration defined in any {@link WebSecurityConfigurer} or more likely by extending
* the {@link WebSecurityConfigurerAdapter} base class and overriding individual methods:
* configuration defined in any {@link WebSecurityConfigurer} or more likely by exposing a
* {@link SecurityFilterChain} bean:
*
* <pre class="code">
* &#064;Configuration
* &#064;EnableWebSecurity
* public class MyWebSecurityConfiguration extends WebSecurityConfigurerAdapter {
* public class MyWebSecurityConfiguration {
*
* &#064;Override
* public void configure(WebSecurity web) throws Exception {
* web.ignoring()
* &#064;Bean
* public WebSecurityCustomizer webSecurityCustomizer() {
* return (web) -> web.ignoring()
* // Spring Security should completely ignore URLs starting with /resources/
* .antMatchers(&quot;/resources/**&quot;);
* }
*
* &#064;Override
* protected void configure(HttpSecurity http) throws Exception {
* &#064;Bean
* public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
* http.authorizeRequests().antMatchers(&quot;/public/**&quot;).permitAll().anyRequest()
* .hasRole(&quot;USER&quot;).and()
* // Possibly more configuration ...
* .formLogin() // enable form based log in
* // set permitAll for all URLs associated with Form Login
* .permitAll();
* return http.build();
* }
*
* &#064;Override
* protected void configure(AuthenticationManagerBuilder auth) throws Exception {
* auth
* // enable in memory based authentication with a user named &quot;user&quot; and &quot;admin&quot;
* .inMemoryAuthentication().withUser(&quot;user&quot;).password(&quot;password&quot;).roles(&quot;USER&quot;)
* .and().withUser(&quot;admin&quot;).password(&quot;password&quot;).roles(&quot;USER&quot;, &quot;ADMIN&quot;);
* &#064;Bean
* public UserDetailsService userDetailsService() {
* UserDetails user = User.withDefaultPasswordEncoder()
* .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 more overridden methods ...
* // Possibly more bean methods ...
* }
* </pre>
*
* @see WebSecurityConfigurer
* @see WebSecurityConfigurerAdapter
*
* @author Rob Winch
* @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");
* 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
* based security for Spring Security. It then exports the necessary beans. Customizations
* can be made to {@link WebSecurity} by extending {@link WebSecurityConfigurerAdapter}
* and exposing it as a {@link Configuration} or implementing
* {@link WebSecurityConfigurer} and exposing it as a {@link Configuration}. This
* configuration is imported when using {@link EnableWebSecurity}.
* can be made to {@link WebSecurity} by implementing {@link WebSecurityConfigurer} and
* exposing it as a {@link Configuration} or exposing a {@link WebSecurityCustomizer}
* bean. This configuration is imported when using {@link EnableWebSecurity}.
*
* @author Rob Winch
* @author Keesun Baik
@@ -25,7 +25,7 @@ import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationManager;
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.web.AuthenticationEntryPoint;
import org.springframework.security.web.PortMapper;
@@ -307,14 +307,14 @@ public abstract class AbstractAuthenticationFilterConfigurer<B extends HttpSecur
/**
* <p>
* Specifies the URL to send users to if login is required. If used with
* {@link WebSecurityConfigurerAdapter} a default login page will be generated when
* this attribute is not specified.
* {@link EnableWebSecurity} a default login page will be generated when this
* attribute is not specified.
* </p>
*
* <p>
* If a URL is specified or this is not being used in conjunction with
* {@link WebSecurityConfigurerAdapter}, users are required to process the specified
* URL to generate a login page.
* {@link EnableWebSecurity}, users are required to process the specified URL to
* generate a login page.
* </p>
*/
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");
* 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 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.authentication.ui.DefaultLoginPageGeneratingFilter;
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
* using {@link WebSecurityConfigurerAdapter}.
* using {@link EnableWebSecurity}.
*
* <p>
* By default an
@@ -64,7 +64,7 @@ import org.springframework.security.web.csrf.CsrfToken;
*
* @author Rob Winch
* @since 3.2
* @see WebSecurityConfigurerAdapter
* @see EnableWebSecurity
*/
public final class DefaultLoginPageConfigurer<H extends HttpSecurityBuilder<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");
* 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.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.authentication.ForwardAuthenticationFailureHandler;
import org.springframework.security.web.authentication.ForwardAuthenticationSuccessHandler;
@@ -84,15 +84,15 @@ public final class FormLoginConfigurer<H extends HttpSecurityBuilder<H>> extends
/**
* <p>
* Specifies the URL to send users to if login is required. If used with
* {@link WebSecurityConfigurerAdapter} a default login page will be generated when
* this attribute is not specified.
* {@link EnableWebSecurity} a default login page will be generated when this
* attribute is not specified.
* </p>
*
* <p>
* If a URL is specified or this is not being used in conjunction with
* {@link WebSecurityConfigurerAdapter}, users are required to process the specified
* URL to generate a login page. In general, the login page should create a form that
* submits a request with the following requirements to work with
* {@link EnableWebSecurity}, users are required to process the specified URL to
* generate a login page. In general, the login page should create a form that submits
* a request with the following requirements to work with
* {@link UsernamePasswordAuthenticationFilter}:
* </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");
* 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.annotation.web.HttpSecurityBuilder;
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.HeaderWriterFilter;
import org.springframework.security.web.header.writers.CacheControlHeadersWriter;
@@ -50,7 +50,7 @@ import org.springframework.util.Assert;
/**
* <p>
* 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>
@@ -22,10 +22,8 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.security.authentication.AuthenticationManager;
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.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
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}
* when a remember me token is valid. The default is to use the
* {@link UserDetailsService} found by invoking
* {@link HttpSecurity#getSharedObject(Class)} which is set when using
* {@link WebSecurityConfigurerAdapter#configure(AuthenticationManagerBuilder)}. When
* 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)}.
* when a remember me token is valid. When 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
* @return the {@link RememberMeConfigurer} for further customization
* @see AbstractRememberMeServices
@@ -24,13 +24,11 @@ import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
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.userdetails.AuthenticationUserDetailsService;
import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper;
import org.springframework.security.core.userdetails.UserDetailsService;
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.preauth.PreAuthenticatedAuthenticationProvider;
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,
* the shared {@link UserDetailsService} will be used to create a
* {@link UserDetailsByNameServiceWrapper}. If a {@link SecurityFilterChain} bean is
* used instead of the {@link WebSecurityConfigurerAdapter}, then the
* {@link UserDetailsService} bean will be used by default.
* then the {@link UserDetailsService} bean will be used by default.
* @param authenticationUserDetailsService the
* {@link AuthenticationUserDetailsService} to use
* @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.OAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.util.Assert;
@@ -171,6 +172,8 @@ public final class OAuth2ClientConfigurer<B extends HttpSecurityBuilder<B>>
private AuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository;
private RedirectStrategy authorizationRedirectStrategy;
private OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient;
private AuthorizationCodeGrantConfigurer() {
@@ -202,6 +205,17 @@ public final class OAuth2ClientConfigurer<B extends HttpSecurityBuilder<B>>
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
* Endpoint.
@@ -247,6 +261,9 @@ public final class OAuth2ClientConfigurer<B extends HttpSecurityBuilder<B>>
authorizationRequestRedirectFilter
.setAuthorizationRequestRepository(this.authorizationRequestRepository);
}
if (this.authorizationRedirectStrategy != null) {
authorizationRequestRedirectFilter.setAuthorizationRedirectStrategy(this.authorizationRedirectStrategy);
}
RequestCache requestCache = builder.getSharedObject(RequestCache.class);
if (requestCache != null) {
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.jwt.JwtDecoderFactory;
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.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
@@ -368,6 +369,10 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
authorizationRequestFilter
.setAuthorizationRequestRepository(this.authorizationEndpointConfig.authorizationRequestRepository);
}
if (this.authorizationEndpointConfig.authorizationRedirectStrategy != null) {
authorizationRequestFilter
.setAuthorizationRedirectStrategy(this.authorizationEndpointConfig.authorizationRedirectStrategy);
}
RequestCache requestCache = http.getSharedObject(RequestCache.class);
if (requestCache != null) {
authorizationRequestFilter.setRequestCache(requestCache);
@@ -540,6 +545,8 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
private AuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository;
private RedirectStrategy authorizationRedirectStrategy;
private AuthorizationEndpointConfig() {
}
@@ -582,6 +589,17 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
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.
* @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");
* 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.annotation.web.HttpSecurityBuilder;
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.FormLoginConfigurer;
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>
*
* <pre>
*
* &#064;Configuration
* &#064;EnableWebSecurity
* public class OpenIDLoginConfig extends WebSecurityConfigurerAdapter {
* public class OpenIDLoginConfig {
*
* &#064;Override
* protected void configure(HttpSecurity http) {
* &#064;Bean
* public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
* http
* .authorizeRequests()
* .antMatchers(&quot;/**&quot;).hasRole(&quot;USER&quot;)
* .and()
* .openidLogin()
* .permitAll();
* return http.build();
* }
*
* &#064;Override
* protected void configure(AuthenticationManagerBuilder auth)(
* AuthenticationManagerBuilder auth) throws Exception {
* auth
* .inMemoryAuthentication()
* .withUser(&quot;https://www.google.com/accounts/o8/id?id=lmkCn9xzPdsxVwG7pjYMuDgNNdASFmobNkcRPaWU&quot;)
* .password(&quot;password&quot;)
* .roles(&quot;USER&quot;);
* &#064;Bean
* public UserDetailsService userDetailsService() {
* UserDetails user = User.withDefaultPasswordEncoder()
* .username(&quot;https://www.google.com/accounts/o8/id?id=lmkCn9xzPdsxVwG7pjYMuDgNNdASFmobNkcRPaWU&quot;)
* .password(&quot;password&quot;)
* .roles(&quot;USER&quot;)
* .build();
* return new InMemoryUserDetailsManager(user);
* }
* }
* </pre>
@@ -229,14 +229,14 @@ public final class OpenIDLoginConfigurer<H extends HttpSecurityBuilder<H>>
/**
* <p>
* Specifies the URL to send users to if login is required. If used with
* {@link WebSecurityConfigurerAdapter} a default login page will be generated when
* this attribute is not specified.
* {@link EnableWebSecurity} a default login page will be generated when this
* attribute is not specified.
* </p>
*
* <p>
* If a URL is specified or this is not being used in conjunction with
* {@link WebSecurityConfigurerAdapter}, users are required to process the specified
* URL to generate a login page.
* {@link EnableWebSecurity}, users are required to process the specified URL to
* generate a login page.
* </p>
*
* <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.Saml2AuthenticationTokenConverter;
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.DelegatingAuthenticationEntryPoint;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
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.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.util.Assert;
import org.springframework.util.ClassUtils;
@@ -252,8 +258,7 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
this.updateAuthenticationDefaults();
this.updateAccessDefaults(http);
String loginUrl = providerUrlMap.entrySet().iterator().next().getKey();
final LoginUrlAuthenticationEntryPoint entryPoint = new LoginUrlAuthenticationEntryPoint(loginUrl);
registerAuthenticationEntryPoint(http, entryPoint);
registerAuthenticationEntryPoint(http, getLoginEntryPoint(http, loginUrl));
}
else {
super.init(http);
@@ -295,6 +300,22 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
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,
Saml2WebSsoAuthenticationFilter saml2WebSsoAuthenticationFilter) {
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_REDIRECT_STRATEGY_REF = "authorization-redirect-strategy-ref";
private static final String ATT_ACCESS_TOKEN_RESPONSE_CLIENT_REF = "access-token-response-client-ref";
private final BeanReference requestCache;
@@ -87,6 +89,7 @@ final class OAuth2ClientBeanDefinitionParser implements BeanDefinitionParser {
}
BeanMetadataElement authorizationRequestRepository = getAuthorizationRequestRepository(
authorizationCodeGrantElt);
BeanMetadataElement authorizationRedirectStrategy = getAuthorizationRedirectStrategy(authorizationCodeGrantElt);
BeanDefinitionBuilder authorizationRequestRedirectFilterBuilder = BeanDefinitionBuilder
.rootBeanDefinition(OAuth2AuthorizationRequestRedirectFilter.class);
String authorizationRequestResolverRef = (authorizationCodeGrantElt != null)
@@ -99,6 +102,7 @@ final class OAuth2ClientBeanDefinitionParser implements BeanDefinitionParser {
}
this.authorizationRequestRedirectFilter = authorizationRequestRedirectFilterBuilder
.addPropertyValue("authorizationRequestRepository", authorizationRequestRepository)
.addPropertyValue("authorizationRedirectStrategy", authorizationRedirectStrategy)
.addPropertyValue("requestCache", this.requestCache).getBeanDefinition();
BeanDefinitionBuilder authorizationCodeGrantFilterBldr = BeanDefinitionBuilder
.rootBeanDefinition(OAuth2AuthorizationCodeGrantFilter.class)
@@ -130,6 +134,16 @@ final class OAuth2ClientBeanDefinitionParser implements BeanDefinitionParser {
.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) {
String accessTokenResponseClientRef = (element != 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_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_USER_AUTHORITIES_MAPPER_REF = "user-authorities-mapper-ref";
@@ -203,6 +205,7 @@ final class OAuth2LoginBeanDefinitionParser implements BeanDefinitionParser {
}
oauth2AuthorizationRequestRedirectFilterBuilder
.addPropertyValue("authorizationRequestRepository", authorizationRequestRepository)
.addPropertyValue("authorizationRedirectStrategy", getAuthorizationRedirectStrategy(element))
.addPropertyValue("requestCache", this.requestCache);
this.oauth2AuthorizationRequestRedirectFilter = oauth2AuthorizationRequestRedirectFilterBuilder
.getBeanDefinition();
@@ -267,6 +270,15 @@ final class OAuth2LoginBeanDefinitionParser implements BeanDefinitionParser {
.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,
String userAuthoritiesMapperRef) {
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.authentication.preauth.x509.SubjectDnX509PrincipalExtractor;
import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor;
import org.springframework.security.web.server.DefaultServerRedirectStrategy;
import org.springframework.security.web.server.DelegatingServerAuthenticationEntryPoint;
import org.springframework.security.web.server.DelegatingServerAuthenticationEntryPoint.DelegateEntry;
import org.springframework.security.web.server.ExchangeMatcherRedirectWebFilter;
import org.springframework.security.web.server.MatcherSecurityWebFilterChain;
import org.springframework.security.web.server.SecurityWebFilterChain;
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.AuthenticationConverterServerWebExchangeMatcher;
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
@@ -3375,6 +3377,8 @@ public class ServerHttpSecurity {
private ServerOAuth2AuthorizationRequestResolver authorizationRequestResolver;
private ServerRedirectStrategy authorizationRedirectStrategy;
private ServerWebExchangeMatcher authenticationMatcher;
private ServerAuthenticationSuccessHandler authenticationSuccessHandler;
@@ -3547,6 +3551,16 @@ public class ServerHttpSecurity {
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
* request is an authentication request.
@@ -3581,7 +3595,9 @@ public class ServerHttpSecurity {
OAuth2AuthorizationRequestRedirectWebFilter oauthRedirectFilter = getRedirectWebFilter();
ServerAuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository = getAuthorizationRequestRepository();
oauthRedirectFilter.setAuthorizationRequestRepository(authorizationRequestRepository);
oauthRedirectFilter.setAuthorizationRedirectStrategy(getAuthorizationRedirectStrategy());
oauthRedirectFilter.setRequestCache(http.requestCache.requestCache);
ReactiveAuthenticationManager manager = getAuthenticationManager();
AuthenticationWebFilter authenticationFilter = new OAuth2LoginAuthenticationWebFilter(manager,
authorizedClientRepository);
@@ -3591,6 +3607,7 @@ public class ServerHttpSecurity {
authenticationFilter.setAuthenticationSuccessHandler(getAuthenticationSuccessHandler(http));
authenticationFilter.setAuthenticationFailureHandler(getAuthenticationFailureHandler());
authenticationFilter.setSecurityContextRepository(this.securityContextRepository);
setDefaultEntryPoints(http);
http.addFilterAt(oauthRedirectFilter, SecurityWebFiltersOrder.HTTP_BASIC);
http.addFilterAt(authenticationFilter, SecurityWebFiltersOrder.AUTHENTICATION);
@@ -3737,6 +3754,13 @@ public class ServerHttpSecurity {
return this.authorizationRequestRepository;
}
private ServerRedirectStrategy getAuthorizationRedirectStrategy() {
if (this.authorizationRedirectStrategy == null) {
this.authorizationRedirectStrategy = new DefaultServerRedirectStrategy();
}
return this.authorizationRedirectStrategy;
}
private ReactiveOAuth2AuthorizedClientService getAuthorizedClientService() {
ReactiveOAuth2AuthorizedClientService bean = getBeanOrNull(ReactiveOAuth2AuthorizedClientService.class);
if (bean != null) {
@@ -3759,6 +3783,8 @@ public class ServerHttpSecurity {
private ServerAuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository;
private ServerRedirectStrategy authorizationRedirectStrategy;
private OAuth2ClientSpec() {
}
@@ -3851,6 +3877,23 @@ public class ServerHttpSecurity {
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}
* @return the {@link ServerHttpSecurity} to continue configuring
@@ -3870,12 +3913,15 @@ public class ServerHttpSecurity {
if (http.requestCache != null) {
codeGrantWebFilter.setRequestCache(http.requestCache.requestCache);
}
OAuth2AuthorizationRequestRedirectWebFilter oauthRedirectFilter = new OAuth2AuthorizationRequestRedirectWebFilter(
clientRegistrationRepository);
oauthRedirectFilter.setAuthorizationRequestRepository(getAuthorizationRequestRepository());
oauthRedirectFilter.setAuthorizationRedirectStrategy(getAuthorizationRedirectStrategy());
if (http.requestCache != null) {
oauthRedirectFilter.setRequestCache(http.requestCache.requestCache);
}
http.addFilterAt(codeGrantWebFilter, SecurityWebFiltersOrder.OAUTH2_AUTHORIZATION_CODE);
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.ServerOAuth2AuthorizedClientRepository
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.web.server.ServerWebExchange
@@ -37,6 +38,7 @@ import org.springframework.web.server.ServerWebExchange
* @property clientRegistrationRepository the repository of client registrations.
* @property authorizedClientRepository the repository for authorized client(s).
* @property authorizationRequestRepository the repository to use for storing [OAuth2AuthorizationRequest]s.
* @property authorizationRedirectStrategy the redirect strategy for Authorization Endpoint redirect URI.
*/
@ServerSecurityMarker
class ServerOAuth2ClientDsl {
@@ -45,6 +47,7 @@ class ServerOAuth2ClientDsl {
var clientRegistrationRepository: ReactiveClientRegistrationRepository? = null
var authorizedClientRepository: ServerOAuth2AuthorizedClientRepository? = null
var authorizationRequestRepository: ServerAuthorizationRequestRepository<OAuth2AuthorizationRequest>? = null
var authorizationRedirectStrategy: ServerRedirectStrategy? = null
internal fun get(): (ServerHttpSecurity.OAuth2ClientSpec) -> Unit {
return { oauth2Client ->
@@ -53,6 +56,7 @@ class ServerOAuth2ClientDsl {
clientRegistrationRepository?.also { oauth2Client.clientRegistrationRepository(clientRegistrationRepository) }
authorizedClientRepository?.also { oauth2Client.authorizedClientRepository(authorizedClientRepository) }
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.ServerOAuth2AuthorizedClientRepository
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.ServerAuthenticationFailureHandler
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 authorizationRequestRepository the repository to use for storing [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
* authentication request.
*/
@@ -64,6 +66,7 @@ class ServerOAuth2LoginDsl {
var authorizedClientRepository: ServerOAuth2AuthorizedClientRepository? = null
var authorizationRequestRepository: ServerAuthorizationRequestRepository<OAuth2AuthorizationRequest>? = null
var authorizationRequestResolver: ServerOAuth2AuthorizationRequestResolver? = null
var authorizationRedirectStrategy: ServerRedirectStrategy? = null
var authenticationMatcher: ServerWebExchangeMatcher? = null
internal fun get(): (ServerHttpSecurity.OAuth2LoginSpec) -> Unit {
@@ -78,6 +81,7 @@ class ServerOAuth2LoginDsl {
authorizedClientRepository?.also { oauth2Login.authorizedClientRepository(authorizedClientRepository) }
authorizationRequestRepository?.also { oauth2Login.authorizationRequestRepository(authorizationRequestRepository) }
authorizationRequestResolver?.also { oauth2Login.authorizationRequestResolver(authorizationRequestResolver) }
authorizationRedirectStrategy?.also { oauth2Login.authorizationRedirectStrategy(authorizationRedirectStrategy) }
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.OAuth2AuthorizationRequestResolver
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.
@@ -31,6 +32,7 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequ
* @since 5.3
* @property authorizationRequestResolver the resolver used for resolving [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
* from the Token Endpoint.
*/
@@ -38,12 +40,14 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequ
class AuthorizationCodeGrantDsl {
var authorizationRequestResolver: OAuth2AuthorizationRequestResolver? = null
var authorizationRequestRepository: AuthorizationRequestRepository<OAuth2AuthorizationRequest>? = null
var authorizationRedirectStrategy: RedirectStrategy? = null
var accessTokenResponseClient: OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest>? = null
internal fun get(): (OAuth2ClientConfigurer<HttpSecurity>.AuthorizationCodeGrantConfigurer) -> Unit {
return { authorizationCodeGrant ->
authorizationRequestResolver?.also { authorizationCodeGrant.authorizationRequestResolver(authorizationRequestResolver) }
authorizationRequestRepository?.also { authorizationCodeGrant.authorizationRequestRepository(authorizationRequestRepository) }
authorizationRedirectStrategy?.also { authorizationCodeGrant.authorizationRedirectStrategy(authorizationRedirectStrategy) }
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.OAuth2AuthorizationRequestResolver
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
@@ -31,18 +32,21 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequ
* @property baseUri the base URI used for authorization requests.
* @property authorizationRequestResolver the resolver used for resolving [OAuth2AuthorizationRequest]'s.
* @property authorizationRequestRepository the repository used for storing [OAuth2AuthorizationRequest]'s.
* @property authorizationRedirectStrategy the redirect strategy for Authorization Endpoint redirect URI.
*/
@OAuth2LoginSecurityMarker
class AuthorizationEndpointDsl {
var baseUri: String? = null
var authorizationRequestResolver: OAuth2AuthorizationRequestResolver? = null
var authorizationRequestRepository: AuthorizationRequestRepository<OAuth2AuthorizationRequest>? = null
var authorizationRedirectStrategy: RedirectStrategy? = null
internal fun get(): (OAuth2LoginConfigurer<HttpSecurity>.AuthorizationEndpointConfig) -> Unit {
return { authorizationEndpoint ->
baseUri?.also { authorizationEndpoint.baseUri(baseUri) }
authorizationRequestResolver?.also { authorizationEndpoint.authorizationRequestResolver(authorizationRequestResolver) }
authorizationRequestRepository?.also { authorizationEndpoint.authorizationRequestRepository(authorizationRequestRepository) }
authorizationRedirectStrategy?.also { authorizationEndpoint.authorizationRedirectStrategy(authorizationRedirectStrategy) }
}
}
}
@@ -500,6 +500,9 @@ oauth2-login.attlist &=
oauth2-login.attlist &=
## Reference to the OAuth2AuthorizationRequestResolver
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 &=
## Reference to the OAuth2AccessTokenResponseClient
attribute access-token-response-client-ref {xsd:token}?
@@ -547,6 +550,9 @@ authorization-code-grant =
authorization-code-grant.attlist &=
## Reference to the AuthorizationRequestRepository
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 &=
## Reference to the OAuth2AuthorizationRequestResolver
attribute authorization-request-resolver-ref {xsd:token}?
@@ -1651,6 +1651,12 @@
</xs:documentation>
</xs:annotation>
</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:annotation>
<xs:documentation>Reference to the OAuth2AccessTokenResponseClient
@@ -1754,6 +1760,12 @@
</xs:documentation>
</xs:annotation>
</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:annotation>
<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.OAuth2AuthorizationRequest;
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.test.web.servlet.MockMvc;
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.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -96,6 +99,8 @@ public class OAuth2ClientConfigurerTests {
private static OAuth2AuthorizationRequestResolver authorizationRequestResolver;
private static RedirectStrategy authorizationRedirectStrategy;
private static OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient;
private static RequestCache requestCache;
@@ -131,6 +136,7 @@ public class OAuth2ClientConfigurerTests {
authorizedClientService);
authorizationRequestResolver = new DefaultOAuth2AuthorizationRequestResolver(clientRegistrationRepository,
"/oauth2/authorization");
authorizationRedirectStrategy = new DefaultRedirectStrategy();
OAuth2AccessTokenResponse accessTokenResponse = OAuth2AccessTokenResponse.withToken("access-token-1234")
.tokenType(OAuth2AccessToken.TokenType.BEARER).expiresIn(300).build();
accessTokenResponseClient = mock(OAuth2AccessTokenResponseClient.class);
@@ -262,6 +268,19 @@ public class OAuth2ClientConfigurerTests {
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
@EnableWebMvc
static class OAuth2ClientConfig extends WebSecurityConfigurerAdapter {
@@ -279,6 +298,7 @@ public class OAuth2ClientConfigurerTests {
.oauth2Client()
.authorizationCodeGrant()
.authorizationRequestResolver(authorizationRequestResolver)
.authorizationRedirectStrategy(authorizationRedirectStrategy)
.accessTokenResponseClient(accessTokenResponseClient);
// @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.TestJwts;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
import org.springframework.security.web.context.HttpRequestResponseHolder;
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.assertThatExceptionOfType;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
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");
}
@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
@Test
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
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.Import;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
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.TestOpenSamlObjects;
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.RelyingPartyRegistrationRepository;
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.assertThatExceptionOfType;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
@@ -411,6 +413,16 @@ public class Saml2LoginConfigurerTests {
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() {
// get the OpenSamlAuthenticationProvider
Saml2WebSsoAuthenticationFilter filter = getSaml2SsoFilter(this.springSecurityFilterChain);
@@ -429,6 +441,8 @@ public class Saml2LoginConfigurerTests {
private void performSaml2Login(String expected) throws IOException, ServletException {
// setup authentication parameters
this.request.setRequestURI("/login/saml2/sso/registration-id");
this.request.setServletPath("/login/saml2/sso/registration-id");
this.request.setParameter("SAMLResponse",
Base64.getEncoder().encodeToString("saml2-xml-response-object".getBytes()));
// perform test
@@ -821,9 +835,7 @@ public class Saml2LoginConfigurerTests {
.assertingPartyDetails((party) -> party.verificationX509Credentials(
(c) -> c.add(TestSaml2X509Credentials.relyingPartyVerifyingCredential())))
.build();
RelyingPartyRegistrationRepository repository = mock(RelyingPartyRegistrationRepository.class);
given(repository.findByRegistrationId(anyString())).willReturn(registration);
return repository;
return spy(new InMemoryRelyingPartyRegistrationRepository(registration));
}
}
@@ -44,6 +44,7 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses;
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
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.web.servlet.MockMvc;
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.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.verify;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
@@ -90,6 +92,9 @@ public class OAuth2ClientBeanDefinitionParserTests {
@Autowired(required = false)
private OAuth2AuthorizationRequestResolver authorizationRequestResolver;
@Autowired(required = false)
private RedirectStrategy authorizationRedirectStrategy;
@Autowired(required = false)
private OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient;
@@ -148,6 +153,16 @@ public class OAuth2ClientBeanDefinitionParserTests {
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
public void requestWhenAuthorizationResponseMatchThenProcess() throws Exception {
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.test.context.annotation.SecurityTestExecutionListeners;
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.AuthenticationSuccessHandler;
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.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.times;
@@ -118,6 +120,9 @@ public class OAuth2LoginBeanDefinitionParserTests {
@Autowired(required = false)
private OAuth2AuthorizationRequestResolver authorizationRequestResolver;
@Autowired(required = false)
private RedirectStrategy authorizationRedirectStrategy;
@Autowired(required = false)
private OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient;
@@ -378,6 +383,17 @@ public class OAuth2LoginBeanDefinitionParserTests {
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
@Test
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.context.SecurityContext;
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.authentication.OAuth2LoginAuthenticationWebFilter;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationRequests;
import org.springframework.security.test.web.reactive.server.WebTestClientBuilder;
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.ServerAuthenticationEntryPoint;
import org.springframework.security.web.server.ServerRedirectStrategy;
import org.springframework.security.web.server.WebFilterChainProxy;
import org.springframework.security.web.server.authentication.AnonymousAuthenticationWebFilterTests;
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.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
@@ -531,6 +536,90 @@ public class ServerHttpSecurityTests {
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) {
try {
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.core.endpoint.OAuth2AuthorizationRequest
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.ServerRedirectStrategy
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter
import org.springframework.test.web.reactive.server.WebTestClient
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
fun `OAuth2 client when authentication converter configured then custom converter used`() {
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.WebSessionOAuth2ServerAuthorizationRequestRepository
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.ServerRedirectStrategy
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher
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
fun `OAuth2 login when authentication matcher configured then custom matcher used`() {
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.OAuth2AuthorizationRequest
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.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
fun `oauth2Client when custom access token response client then client used`() {
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.OAuth2AuthorizationRequestResolver
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.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
fun `oauth2Login when custom authorization uri repository then uri used`() {
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'
'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.
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.
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:
[source,java]
====
.Java
[source,java,role="primary"]
----
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = HelloWebfluxMethodApplication.class)
@@ -19,9 +21,35 @@ public class HelloWebfluxMethodApplicationTests {
// add Spring Security test Support
.apply(springSecurity())
.configureClient()
.filter(basicAuthentication())
.filter(basicAuthentication("user", "password"))
.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`.
[[nsa-oauth2-login-authorization-redirect-strategy-ref]]
* **authorization-redirect-strategy-ref**
Reference to the authorization `RedirectStrategy`.
[[nsa-oauth2-login-access-token-response-client-ref]]
* **access-token-response-client-ref**
Reference to the `OAuth2AccessTokenResponseClient`.
@@ -1084,6 +1089,11 @@ Configures xref:servlet/oauth2/client/authorization-grants.adoc#oauth2Client-aut
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]]
* **authorization-request-resolver-ref**
Reference to the `OAuth2AuthorizationRequestResolver`.
+1 -1
View File
@@ -3,7 +3,7 @@ springJavaformatVersion=0.0.34
springBootVersion=2.4.2
springFrameworkVersion=5.3.22
openSamlVersion=3.4.6
version=5.8.0-M1
version=5.8.0-M2
kotlinVersion=1.7.10
samplesBranch=5.8.x
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 RedirectStrategy authorizationRedirectStrategy = new DefaultRedirectStrategy();
private RedirectStrategy authorizationRedirectStrategy = new DefaultRedirectStrategy();
private OAuth2AuthorizationRequestResolver authorizationRequestResolver;
@@ -139,6 +139,15 @@ public class OAuth2AuthorizationRequestRedirectFilter extends OncePerRequestFilt
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.
* @param authorizationRequestRepository the repository used for storing
@@ -75,7 +75,7 @@ import org.springframework.web.util.UriComponentsBuilder;
*/
public class OAuth2AuthorizationRequestRedirectWebFilter implements WebFilter {
private final ServerRedirectStrategy authorizationRedirectStrategy = new DefaultServerRedirectStrategy();
private ServerRedirectStrategy authorizationRedirectStrategy = new DefaultServerRedirectStrategy();
private final ServerOAuth2AuthorizationRequestResolver authorizationRequestResolver;
@@ -105,6 +105,15 @@ public class OAuth2AuthorizationRequestRedirectWebFilter implements WebFilter {
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.
* @param authorizationRequestRepository the repository used for storing
@@ -17,6 +17,7 @@
package org.springframework.security.oauth2.client.web;
import java.lang.reflect.Constructor;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@@ -30,7 +31,9 @@ import javax.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
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.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.util.ClassUtils;
import org.springframework.web.util.UriComponentsBuilder;
@@ -116,6 +120,11 @@ public class OAuth2AuthorizationRequestRedirectFilterTests {
assertThatIllegalArgumentException().isThrownBy(() -> this.filter.setAuthorizationRequestRepository(null));
}
@Test
public void setAuthorizationRedirectStrategyWhenAuthorizationRedirectStrategyIsNullThenThrowIllegalArgumentException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.filter.setAuthorizationRedirectStrategy(null));
}
@Test
public void setRequestCacheWhenRequestCacheIsNullThenThrowIllegalArgumentException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.filter.setRequestCache(null));
@@ -333,4 +342,31 @@ public class OAuth2AuthorizationRequestRedirectFilterTests {
+ "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;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
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.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import reactor.core.publisher.Flux;
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.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
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.test.web.reactive.server.FluxExchangeResult;
import org.springframework.test.web.reactive.server.WebTestClient;
@@ -81,6 +89,11 @@ public class OAuth2AuthorizationRequestRedirectWebFilterTests {
.isThrownBy(() -> new OAuth2AuthorizationRequestRedirectWebFilter(this.clientRepository));
}
@Test
public void setterWhenAuthorizationRedirectStrategyNullThenIllegalArgumentException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.filter.setAuthorizationRedirectStrategy(null));
}
@Test
public void filterWhenDoesNotMatchThenClientRegistrationRepositoryNotSubscribed() {
// @formatter:off
@@ -195,4 +208,46 @@ public class OAuth2AuthorizationRequestRedirectWebFilterTests {
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();
html.append("<!DOCTYPE html>\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(" </head>\n");
html.append(" <body onload=\"document.forms[0].submit()\">\n");
@@ -215,6 +215,8 @@ public final class Saml2LogoutRequestFilter extends OncePerRequestFilter {
StringBuilder html = new StringBuilder();
html.append("<!DOCTYPE html>\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(" </head>\n");
html.append(" <body onload=\"document.forms[0].submit()\">\n");
@@ -119,6 +119,8 @@ public final class Saml2RelyingPartyInitiatedLogoutSuccessHandler implements Log
StringBuilder html = new StringBuilder();
html.append("<!DOCTYPE html>\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(" </head>\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("website", Collections.singletonList("https://johndoe.com/"));
expected.put("registered", Collections.singletonList(true));
expected.put("role", Arrays.asList("RoleTwo"));
Instant registeredDate = Instant.ofEpochMilli(DateTime.parse("1970-01-01T00:00:00Z").getMillis());
expected.put("registeredDate", Collections.singletonList(registeredDate));
assertThat((String) principal.getFirstAttribute("name")).isEqualTo("John Doe");
@@ -23,7 +23,6 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
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.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
/**
@@ -645,7 +646,7 @@ public final class OpenSaml4AuthenticationProvider implements AuthenticationProv
}
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 (Attribute attribute : attributeStatement.getAttributes()) {
List<Object> attributeValues = new ArrayList<>();
@@ -655,7 +656,7 @@ public final class OpenSaml4AuthenticationProvider implements AuthenticationProv
attributeValues.add(attributeValue);
}
}
attributeMap.put(attribute.getName(), attributeValues);
attributeMap.addAll(attribute.getName(), attributeValues);
}
}
return attributeMap;
@@ -343,6 +343,7 @@ public class OpenSaml4AuthenticationProviderTests {
expected.put("registered", Collections.singletonList(true));
Instant registeredDate = Instant.parse("1970-01-01T00:00:00Z");
expected.put("registeredDate", Collections.singletonList(registeredDate));
expected.put("role", Arrays.asList("RoleOne", "RoleTwo")); // gh-11042
assertThat((String) principal.getFirstAttribute("name")).isEqualTo("John Doe");
assertThat(principal.getAttributes()).isEqualTo(expected);
assertThat(principal.getSessionIndexes()).contains("session-index");
@@ -327,6 +327,18 @@ public final class TestOpenSamlObjects {
name.setValue("John Doe");
nameAttr.getAttributeValues().add(name);
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();
ageAttr.setName("age");
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);
this.filter.doFilterInternal(this.request, this.response, this.filterChain);
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("<input type=\"hidden\" name=\"SAMLRequest\"")
.contains("value=\"" + relayStateEncoded + "\"");
@@ -117,6 +117,9 @@ public class Saml2LogoutRequestFilterTests {
String content = response.getContentAsString();
assertThat(content).contains(Saml2ParameterNames.SAML_RESPONSE);
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();
}
@@ -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");
* you may not use this file except in compliance with the License.
@@ -97,6 +97,9 @@ public class Saml2RelyingPartyInitiatedLogoutSuccessHandlerTests {
String content = response.getContentAsString();
assertThat(content).contains(Saml2ParameterNames.SAML_REQUEST);
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) {
@@ -41,6 +41,7 @@ import org.springframework.security.web.firewall.HttpFirewall;
import org.springframework.security.web.firewall.RequestRejectedException;
import org.springframework.security.web.firewall.RequestRejectedHandler;
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.matcher.RequestMatcher;
import org.springframework.util.Assert;
@@ -158,6 +159,8 @@ public class FilterChainProxy extends GenericFilterBean {
private RequestRejectedHandler requestRejectedHandler = new DefaultRequestRejectedHandler();
private ThrowableAnalyzer throwableAnalyzer = new ThrowableAnalyzer();
public FilterChainProxy() {
}
@@ -186,8 +189,15 @@ public class FilterChainProxy extends GenericFilterBean {
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
doFilterInternal(request, response, chain);
}
catch (RequestRejectedException ex) {
this.requestRejectedHandler.handle((HttpServletRequest) request, (HttpServletResponse) response, ex);
catch (Exception 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 {
this.securityContextHolderStrategy.clearContext();
@@ -66,18 +66,26 @@ public final class RequestAttributeSecurityContextRepository implements Security
@Override
public boolean containsContext(HttpServletRequest request) {
return loadContext(request).get() != null;
return getContext(request) != null;
}
@Override
public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
SecurityContext context = loadContext(requestResponseHolder.getRequest()).get();
return (context != null) ? context : SecurityContextHolder.createEmptyContext();
return getContextOrEmpty(requestResponseHolder.getRequest());
}
@Override
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
@@ -50,6 +50,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.willAnswer;
import static org.mockito.BDDMockito.willThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
@@ -262,4 +263,18 @@ public class FilterChainProxyTests {
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;
import java.util.function.Supplier;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpServletRequest;
@@ -67,4 +69,17 @@ class RequestAttributeSecurityContextRepositoryTests {
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());
}
}