1
0
mirror of synced 2026-07-20 10:05:13 +00:00

Compare commits

..

1 Commits

Author SHA1 Message Date
Josh Cummings e3e7d76b70 Add Workflow to Finalize a Release 2025-11-04 09:32:41 -07:00
356 changed files with 1949 additions and 8060 deletions
+8
View File
@@ -111,3 +111,11 @@ updates:
labels: labels:
- 'type: task' - 'type: task'
- 'in: build' - 'in: build'
- package-ecosystem: npm
target-branch: 6.3.x
directory: /docs
schedule:
interval: weekly
labels:
- 'type: task'
- 'in: build'
+5 -3
View File
@@ -14,15 +14,17 @@ permissions:
jobs: jobs:
snapshot-test: snapshot-test:
name: Test Against Snapshots name: Test Against Snapshots
uses: spring-io/spring-security-release-tools/.github/workflows/test.yml@ed473b4dafba053c63a453d2d88a89df3b3e18b3 # v1 uses: spring-io/spring-security-release-tools/.github/workflows/test.yml@v1
strategy: strategy:
matrix: matrix:
include: include:
- java-version: 21-ea
toolchain: 21
- java-version: 17 - java-version: 17
toolchain: 17 toolchain: 17
with: with:
java-version: ${{ matrix.java-version }} java-version: ${{ matrix.java-version }}
test-args: --refresh-dependencies -PforceMavenRepositories=snapshot,https://oss.sonatype.org/content/repositories/snapshots -PisOverrideVersionCatalog -PtestToolchain=${{ matrix.toolchain }} -PspringFrameworkVersion=7.0.+ -PreactorVersion=2025.0.+ -PspringDataVersion=2025.1.+ -PmicrometerVersion=1.16.+ --stacktrace test-args: --refresh-dependencies -PforceMavenRepositories=snapshot,https://oss.sonatype.org/content/repositories/snapshots -PisOverrideVersionCatalog -PtestToolchain=${{ matrix.toolchain }} -PspringFrameworkVersion=7.+ -PreactorVersion=2025.+ -PspringDataVersion=2025.+ --stacktrace
secrets: inherit secrets: inherit
send-notification: send-notification:
name: Send Notification name: Send Notification
@@ -31,6 +33,6 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Send Notification - name: Send Notification
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@ed473b4dafba053c63a453d2d88a89df3b3e18b3 # v1 uses: spring-io/spring-security-release-tools/.github/actions/send-notification@v1
with: with:
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }} webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
+1 -1
View File
@@ -14,4 +14,4 @@ jobs:
actions: read actions: read
contents: read contents: read
security-events: write security-events: write
uses: spring-io/github-actions/.github/workflows/codeql-analysis.yml@e415dadd0910c901e7a7fabd67bbb355b2324500 # 1 uses: spring-io/github-actions/.github/workflows/codeql-analysis.yml@1
@@ -17,7 +17,7 @@ permissions:
jobs: jobs:
build: build:
name: Build name: Build
uses: spring-io/spring-security-release-tools/.github/workflows/build.yml@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15 uses: spring-io/spring-security-release-tools/.github/workflows/build.yml@v1
strategy: strategy:
matrix: matrix:
os: [ ubuntu-latest, windows-latest ] os: [ ubuntu-latest, windows-latest ]
@@ -29,23 +29,30 @@ jobs:
secrets: inherit secrets: inherit
deploy-artifacts: deploy-artifacts:
name: Deploy Artifacts name: Deploy Artifacts
needs: [ build ] needs: [ build]
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-artifacts.yml@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15 uses: spring-io/spring-security-release-tools/.github/workflows/deploy-artifacts.yml@v1
with: with:
should-deploy-artifacts: ${{ needs.build.outputs.should-deploy-artifacts }} should-deploy-artifacts: ${{ needs.build.outputs.should-deploy-artifacts }}
default-publish-milestones-central: true default-publish-milestones-central: true
secrets: inherit secrets: inherit
deploy-docs:
name: Deploy Docs
needs: [ build ]
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-docs.yml@v1
with:
should-deploy-docs: ${{ needs.build.outputs.should-deploy-artifacts }}
secrets: inherit
deploy-schema: deploy-schema:
name: Deploy Schema name: Deploy Schema
needs: [ build ] needs: [ build ]
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-schema.yml@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15 uses: spring-io/spring-security-release-tools/.github/workflows/deploy-schema.yml@v1
with: with:
should-deploy-schema: ${{ needs.build.outputs.should-deploy-artifacts }} should-deploy-schema: ${{ needs.build.outputs.should-deploy-artifacts }}
secrets: inherit secrets: inherit
perform-release: perform-release:
name: Perform Release name: Perform Release
needs: [ deploy-artifacts, deploy-schema ] needs: [ deploy-artifacts, deploy-docs, deploy-schema ]
uses: spring-io/spring-security-release-tools/.github/workflows/perform-release.yml@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15 uses: spring-io/spring-security-release-tools/.github/workflows/perform-release.yml@v1
with: with:
should-perform-release: ${{ needs.deploy-artifacts.outputs.artifacts-deployed }} should-perform-release: ${{ needs.deploy-artifacts.outputs.artifacts-deployed }}
project-version: ${{ needs.deploy-artifacts.outputs.project-version }} project-version: ${{ needs.deploy-artifacts.outputs.project-version }}
@@ -61,6 +68,6 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Send Notification - name: Send Notification
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15 uses: spring-io/spring-security-release-tools/.github/actions/send-notification@v1
with: with:
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }} webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
-76
View File
@@ -1,76 +0,0 @@
name: Defer Issues
on:
workflow_dispatch:
permissions:
contents: read
jobs:
defer-issues:
name: Defer Issues
runs-on: ubuntu-latest
if: github.repository_owner == 'spring-projects'
permissions:
issues: write
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Compute Version
id: compute-version
uses: spring-io/spring-release-actions/compute-version@a1f321783a0769dd2aea4fad6c2ae2f95a52b885 # 0.0.5
- name: Get Today's Release Version
id: todays-release
uses: spring-io/spring-release-actions/get-todays-release-version@a1f321783a0769dd2aea4fad6c2ae2f95a52b885 # 0.0.5
with:
snapshot-version: ${{ steps.compute-version.outputs.version }}
milestone-repository: ${{ github.repository }}
milestone-token: ${{ secrets.GITHUB_TOKEN }}
- name: Compute Next Version
id: next-version
uses: spring-io/spring-release-actions/compute-next-version@a1f321783a0769dd2aea4fad6c2ae2f95a52b885 # 0.0.5
with:
version: ${{ steps.todays-release.outputs.release-version }}
- name: Schedule Next Milestone
uses: spring-io/spring-release-actions/schedule-milestone@a1f321783a0769dd2aea4fad6c2ae2f95a52b885 # 0.0.5
with:
version: ${{ steps.next-version.outputs.version }}
version-date: ${{ steps.next-version.outputs.version-date }}
repository: ${{ github.repository }}
token: ${{ secrets.GITHUB_TOKEN }}
- name: Move Open Issues to Next Milestone
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CURRENT_MILESTONE: ${{ steps.todays-release.outputs.release-version }}
NEXT_MILESTONE: ${{ steps.next-version.outputs.version }}
run: |
current_milestone_number=$(gh api repos/${{ github.repository }}/milestones \
--jq ".[] | select(.title == \"$CURRENT_MILESTONE\") | .number")
if [ -z "$current_milestone_number" ]; then
echo "No milestone found for $CURRENT_MILESTONE"
exit 0
fi
next_milestone_number=$(gh api repos/${{ github.repository }}/milestones \
--jq ".[] | select(.title == \"$NEXT_MILESTONE\") | .number")
if [ -z "$next_milestone_number" ]; then
echo "No milestone found for $NEXT_MILESTONE"
exit 1
fi
echo "Moving open issues from milestone '$CURRENT_MILESTONE' (#$current_milestone_number) to '$NEXT_MILESTONE' (#$next_milestone_number)"
page=1
while true; do
issues=$(gh api "repos/${{ github.repository }}/issues?milestone=$current_milestone_number&state=open&per_page=100&page=$page" \
--jq '.[].number')
if [ -z "$issues" ]; then
break
fi
for issue in $issues; do
echo "Moving issue/PR #$issue to milestone $NEXT_MILESTONE"
gh api repos/${{ github.repository }}/issues/$issue \
--method PATCH \
--field milestone=$next_milestone_number \
--silent
done
page=$((page + 1))
done
echo "Done."
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
if: github.repository_owner == 'spring-projects' if: github.repository_owner == 'spring-projects'
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 uses: actions/checkout@v4
with: with:
ref: docs-build ref: docs-build
fetch-depth: 1 fetch-depth: 1
+20 -6
View File
@@ -2,10 +2,6 @@ name: Finalize Release
on: on:
workflow_dispatch: # Manual trigger workflow_dispatch: # Manual trigger
inputs:
version:
description: The Spring Security release to finalize (e.g. 7.0.0-RC2)
required: true
env: env:
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
@@ -14,14 +10,32 @@ permissions:
contents: read contents: read
jobs: jobs:
project-version:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.project-version.outputs.version }}
steps:
- id: project-version
run: echo "version=$(grep '^version=' gradle.properties | cut -d'=' -f2)" >> $GITHUB_OUTPUT
perform-release: perform-release:
name: Perform Release name: Perform Release
uses: spring-io/spring-security-release-tools/.github/workflows/perform-release.yml@ed473b4dafba053c63a453d2d88a89df3b3e18b3 # v1 needs: [ project-version ]
uses: spring-io/spring-security-release-tools/.github/workflows/perform-release.yml@v1
with: with:
should-perform-release: true should-perform-release: true
project-version: ${{ inputs.version }} project-version: ${{ needs.project-version.outputs.version }}
milestone-repo-url: https://repo1.maven.org/maven2 milestone-repo-url: https://repo1.maven.org/maven2
release-repo-url: https://repo1.maven.org/maven2 release-repo-url: https://repo1.maven.org/maven2
artifact-path: org/springframework/security/spring-security-core artifact-path: org/springframework/security/spring-security-core
slack-announcing-id: spring-security-announcing slack-announcing-id: spring-security-announcing
secrets: inherit secrets: inherit
send-notification:
name: Send Notification
needs: [ perform-release ]
if: ${{ !success() }}
runs-on: ubuntu-latest
steps:
- name: Send Notification
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@v1
with:
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
@@ -19,14 +19,14 @@ jobs:
git config --global user.name 'github-actions[bot]' git config --global user.name 'github-actions[bot]'
git config --global user.email 'github-actions[bot]@users.noreply.github.com' git config --global user.email 'github-actions[bot]@users.noreply.github.com'
- name: Checkout - name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 uses: actions/checkout@v4
- name: Set up JDK 17 - name: Set up JDK 17
uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0 uses: actions/setup-java@v4
with: with:
java-version: '17' java-version: '17'
distribution: 'temurin' distribution: 'temurin'
- name: Set up Gradle - name: Set up Gradle
uses: gradle/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1 uses: gradle/gradle-build-action@v2
- name: Upgrade Wrappers - name: Upgrade Wrappers
run: ./gradlew clean upgradeGradleWrapperAll --continue -Porg.gradle.java.installations.auto-download=false run: ./gradlew clean upgradeGradleWrapperAll --continue -Porg.gradle.java.installations.auto-download=false
env: env:
@@ -30,6 +30,6 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Send Notification - name: Send Notification
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15 uses: spring-io/spring-security-release-tools/.github/actions/send-notification@v1
with: with:
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }} webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
+6 -6
View File
@@ -11,9 +11,9 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.repository == 'spring-projects/spring-security' }} if: ${{ github.repository == 'spring-projects/spring-security' }}
steps: steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: actions/checkout@v4
- name: Set up gradle - name: Set up gradle
uses: spring-io/spring-gradle-build-action@c8668747d7c264864c8c7f7026d0d277d14a78dc # v2.0.6 uses: spring-io/spring-gradle-build-action@v2
with: with:
java-version: '17' java-version: '17'
distribution: 'temurin' distribution: 'temurin'
@@ -24,9 +24,9 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.repository == 'spring-projects/spring-security' }} if: ${{ github.repository == 'spring-projects/spring-security' }}
steps: steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: actions/checkout@v4
- name: Set up gradle - name: Set up gradle
uses: spring-io/spring-gradle-build-action@c8668747d7c264864c8c7f7026d0d277d14a78dc # v2.0.6 uses: spring-io/spring-gradle-build-action@v2
with: with:
java-version: '17' java-version: '17'
distribution: 'temurin' distribution: 'temurin'
@@ -34,7 +34,7 @@ jobs:
run: ./gradlew -PbuildSrc.skipTests=true :spring-security-docs:antora run: ./gradlew -PbuildSrc.skipTests=true :spring-security-docs:antora
- name: Upload Docs - name: Upload Docs
id: upload id: upload
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 uses: actions/upload-artifact@v4
with: with:
name: docs name: docs
path: docs/build/site path: docs/build/site
@@ -46,6 +46,6 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Send Notification - name: Send Notification
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15 uses: spring-io/spring-security-release-tools/.github/actions/send-notification@v1
with: with:
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }} webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
+1 -1
View File
@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 uses: actions/checkout@v4
with: with:
fetch-depth: 1 fetch-depth: 1
- name: Dispatch - name: Dispatch
@@ -16,9 +16,9 @@ jobs:
name: Update on Supported Branches name: Update on Supported Branches
strategy: strategy:
matrix: matrix:
branch: [ '6.4.x', '6.5.x', 'main' ] branch: [ '5.8.x', '6.2.x', '6.3.x', 'main' ]
steps: steps:
- uses: spring-io/spring-doc-actions/update-antora-spring-ui@415e2b11a766ba64799fffb5c97a4f7e17f677cf # v0.0.22 - uses: spring-io/spring-doc-actions/update-antora-spring-ui@e28269199d1d27975cf7f65e16d6095c555b3cd0
name: Update name: Update
with: with:
docs-branch: ${{ matrix.branch }} docs-branch: ${{ matrix.branch }}
@@ -28,7 +28,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: Update on docs-build name: Update on docs-build
steps: steps:
- uses: spring-io/spring-doc-actions/update-antora-spring-ui@415e2b11a766ba64799fffb5c97a4f7e17f677cf # v0.0.22 - uses: spring-io/spring-doc-actions/update-antora-spring-ui@e28269199d1d27975cf7f65e16d6095c555b3cd0
name: Update name: Update
with: with:
docs-branch: 'docs-build' docs-branch: 'docs-build'
@@ -9,7 +9,7 @@ permissions:
jobs: jobs:
update-scheduled-release-version: update-scheduled-release-version:
name: Update Scheduled Release Version name: Update Scheduled Release Version
uses: spring-io/spring-security-release-tools/.github/workflows/update-scheduled-release-version.yml@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15 uses: spring-io/spring-security-release-tools/.github/workflows/update-scheduled-release-version.yml@v1
secrets: inherit secrets: inherit
send-notification: send-notification:
name: Send Notification name: Send Notification
@@ -18,6 +18,6 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Send Notification - name: Send Notification
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15 uses: spring-io/spring-security-release-tools/.github/actions/send-notification@v1
with: with:
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }} webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
-21
View File
@@ -68,27 +68,6 @@ The https://github.com/spring-projects/spring-security/tree/docs-build[playbook
Discover more commands with `./gradlew tasks`. Discover more commands with `./gradlew tasks`.
=== IDE setup (IntelliJ)
No special steps are needed to open Spring Security in IntelliJ.
=== IDE setup (Eclipse and VS Code)
To work in Eclipse or VS Code, first generate Eclipse metadata so you can import the project into Eclipse or VS Code:
[indent=0]
----
./gradlew cleanEclipse eclipse
----
If you have not built the project yet, run `./gradlew publishToMavenLocal` first so dependencies are resolved.
*VS Code:* Open the repository root as a folder. The repository includes `.vscode/settings.json` which disables automatic Gradle import so that the generated Eclipse metadata (`.classpath`, `.project`) is used. Do not use the Gradle for Java extension to import the project.
*Eclipse:* File → Import → General → Existing Projects into Workspace, then select the repository root.
The build uses a custom Eclipse plugin to work around Gradle dependency cycles that confuse IDE metadata generation. You may see Eclipse warnings about `xml-apis` from some test dependencies; those are excluded in the build and can be ignored.
== Getting Support == Getting Support
Check out the https://stackoverflow.com/questions/tagged/spring-security[Spring Security tags on Stack Overflow]. Check out the https://stackoverflow.com/questions/tagged/spring-security[Spring Security tags on Stack Overflow].
https://spring.io/support[Commercial support] is available too. https://spring.io/support[Commercial support] is available too.
@@ -39,7 +39,7 @@ interface EvaluationContextPostProcessor<I> {
* that was passed in. * that was passed in.
* @param context the original {@link EvaluationContext} * @param context the original {@link EvaluationContext}
* @param invocation the security invocation object (i.e. Message) * @param invocation the security invocation object (i.e. Message)
* @return the updated context. * @return the upated context.
*/ */
EvaluationContext postProcess(EvaluationContext context, I invocation); EvaluationContext postProcess(EvaluationContext context, I invocation);
@@ -35,7 +35,6 @@ import org.springframework.security.web.FilterInvocation;
*/ */
@Deprecated @Deprecated
@NullUnmarked @NullUnmarked
@SuppressWarnings("serial")
class WebExpressionConfigAttribute implements ConfigAttribute, EvaluationContextPostProcessor<FilterInvocation> { class WebExpressionConfigAttribute implements ConfigAttribute, EvaluationContextPostProcessor<FilterInvocation> {
private final Expression authorizeExpression; private final Expression authorizeExpression;
@@ -20,7 +20,7 @@ import org.springframework.security.acls.model.Acl;
/** /**
* Strategy used by {@link AclImpl} to determine whether a principal is permitted to call * Strategy used by {@link AclImpl} to determine whether a principal is permitted to call
* administrative methods on the <code>AclImpl</code>. * adminstrative methods on the <code>AclImpl</code>.
* *
* @author Ben Alex * @author Ben Alex
*/ */
@@ -42,7 +42,7 @@ public class GrantedAuthoritySid implements Sid {
public GrantedAuthoritySid(GrantedAuthority grantedAuthority) { public GrantedAuthoritySid(GrantedAuthority grantedAuthority) {
Assert.notNull(grantedAuthority, "GrantedAuthority required"); Assert.notNull(grantedAuthority, "GrantedAuthority required");
Assert.notNull(grantedAuthority.getAuthority(), Assert.notNull(grantedAuthority.getAuthority(),
"This Sid is only compatible with GrantedAuthority that provide a non-null getAuthority()"); "This Sid is only compatible with GrantedAuthoritys that provide a non-null getAuthority()");
this.grantedAuthority = grantedAuthority.getAuthority(); this.grantedAuthority = grantedAuthority.getAuthority();
} }
@@ -160,7 +160,7 @@ public class JdbcAclService implements AclService {
this.findChildrenSql = DEFAULT_SELECT_ACL_WITH_PARENT_SQL_WITH_CLASS_ID_TYPE; this.findChildrenSql = DEFAULT_SELECT_ACL_WITH_PARENT_SQL_WITH_CLASS_ID_TYPE;
} }
else { else {
log.debug("Find children statement has already been overridden, so not overriding the default"); log.debug("Find children statement has already been overridden, so not overridding the default");
} }
} }
} }
@@ -50,7 +50,7 @@ import org.springframework.util.Assert;
* The default settings are for HSQLDB. If you are using a different database you will * The default settings are for HSQLDB. If you are using a different database you will
* probably need to set the {@link #setSidIdentityQuery(String) sidIdentityQuery} and * probably need to set the {@link #setSidIdentityQuery(String) sidIdentityQuery} and
* {@link #setClassIdentityQuery(String) classIdentityQuery} properties appropriately. The * {@link #setClassIdentityQuery(String) classIdentityQuery} properties appropriately. The
* other queries, SQL inserts and updates can also be customized to accommodate schema * other queries, SQL inserts and updates can also be customized to accomodate schema
* variations, but must produce results consistent with those expected by the defaults. * variations, but must produce results consistent with those expected by the defaults.
* <p> * <p>
* See the appendix of the Spring Security reference manual for more information on the * See the appendix of the Spring Security reference manual for more information on the
@@ -471,7 +471,7 @@ public class JdbcMutableAclService extends JdbcAclService implements MutableAclS
this.insertClass = DEFAULT_INSERT_INTO_ACL_CLASS_WITH_ID; this.insertClass = DEFAULT_INSERT_INTO_ACL_CLASS_WITH_ID;
} }
else { else {
log.debug("Insert class statement has already been overridden, so not overriding the default"); log.debug("Insert class statement has already been overridden, so not overridding the default");
} }
} }
} }
+4 -4
View File
@@ -2,12 +2,12 @@ apply plugin: 'io.spring.convention.spring-module'
apply plugin: 'io.freefair.aspectj' apply plugin: 'io.freefair.aspectj'
compileAspectj { compileAspectj {
sourceCompatibility = "17" sourceCompatibility "17"
targetCompatibility = "17" targetCompatibility "17"
} }
compileTestAspectj { compileTestAspectj {
sourceCompatibility = "17" sourceCompatibility "17"
targetCompatibility = "17" targetCompatibility "17"
} }
dependencies { dependencies {
+37 -10
View File
@@ -10,7 +10,7 @@ buildscript {
classpath libs.com.netflix.nebula.nebula.project.plugin classpath libs.com.netflix.nebula.nebula.project.plugin
} }
repositories { repositories {
maven { url='https://plugins.gradle.org/m2/' } maven { url 'https://plugins.gradle.org/m2/' }
} }
} }
@@ -35,30 +35,57 @@ ext.milestoneBuild = !(snapshotBuild || releaseBuild)
repositories { repositories {
mavenCentral() mavenCentral()
maven { url = "https://repo.spring.io/milestone" } maven { url "https://repo.spring.io/milestone" }
} }
springRelease { springRelease {
weekOfMonth = 3 weekOfMonth = 3
dayOfWeek = 1 dayOfWeek = 1
referenceDocUrl = "https://docs.spring.io/spring-security/reference/{version}/index.html" referenceDocUrl = "https://docs.spring.io/spring-security/reference/{version}/index.html"
apiDocUrl = "https://docs.spring.io/spring-security/reference/{version}/api/java/index.html" apiDocUrl = "https://docs.spring.io/spring-security/site/docs/{version}/api/"
replaceSnapshotVersionInReferenceDocUrl = true replaceSnapshotVersionInReferenceDocUrl = true
} }
def toolchainVersion() {
if (project.hasProperty('testToolchain')) {
return project.property('testToolchain').toString().toInteger()
}
return 17
}
subprojects {
java {
toolchain {
languageVersion = JavaLanguageVersion.of(toolchainVersion())
}
}
kotlin {
jvmToolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
tasks.withType(JavaCompile).configureEach {
options.encoding = "UTF-8"
options.compilerArgs.add("-parameters")
options.release.set(17)
}
}
allprojects { allprojects {
if (!['spring-security-bom', 'spring-security-docs'].contains(project.name)) { if (!['spring-security-bom', 'spring-security-docs'].contains(project.name)) {
apply plugin: 'io.spring.javaformat' apply plugin: 'io.spring.javaformat'
apply plugin: 'checkstyle' apply plugin: 'checkstyle'
pluginManager.withPlugin("io.spring.convention.checkstyle") { pluginManager.withPlugin("io.spring.convention.checkstyle", { plugin ->
dependencies { configure(plugin) {
checkstyle libs.io.spring.javaformat.spring.javaformat.checkstyle dependencies {
checkstyle libs.io.spring.javaformat.spring.javaformat.checkstyle
}
checkstyle {
toolVersion = '8.34'
}
} }
checkstyle { })
toolVersion = '8.34'
}
}
if (project.name.contains('sample')) { if (project.name.contains('sample')) {
tasks.whenTaskAdded { task -> tasks.whenTaskAdded { task ->
+1 -1
View File
@@ -12,7 +12,7 @@ java {
repositories { repositories {
gradlePluginPortal() gradlePluginPortal()
mavenCentral() mavenCentral()
maven { url = 'https://repo.spring.io/snapshot' } maven { url 'https://repo.spring.io/snapshot' }
} }
sourceSets { sourceSets {
Binary file not shown.
+5
View File
@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored Executable
+240
View File
@@ -0,0 +1,240 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
+91
View File
@@ -0,0 +1,91 @@
@rem
@rem Copyright 2004-present the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
@@ -81,8 +81,8 @@ class ArtifactoryPlugin implements Plugin<Project> {
repository { repository {
repoKey = isSnapshot ? snapshotRepository : isMilestone ? milestoneRepository : releaseRepository repoKey = isSnapshot ? snapshotRepository : isMilestone ? milestoneRepository : releaseRepository
if(project.hasProperty('artifactoryUsername')) { if(project.hasProperty('artifactoryUsername')) {
username = project.artifactoryUsername username = artifactoryUsername
password = project.artifactoryPassword password = artifactoryPassword
} }
} }
} }
@@ -0,0 +1,82 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package io.spring.gradle.convention
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.tasks.bundling.Zip
import org.gradle.api.Plugin
import org.gradle.api.Project
public class DeployDocsPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
project.getPluginManager().apply('org.hidetake.ssh')
project.ssh.settings {
knownHosts = allowAnyHosts
}
project.remotes {
docs {
role 'docs'
if (project.hasProperty('deployDocsHost')) {
host = project.findProperty('deployDocsHost')
} else {
host = 'docs.af.pivotal.io'
}
retryCount = 5 // retry 5 times (default is 0)
retryWaitSec = 10 // wait 10 seconds between retries (default is 0)
user = project.findProperty('deployDocsSshUsername')
if (project.hasProperty('deployDocsSshKeyPath')) {
identity = project.file(project.findProperty('deployDocsSshKeyPath'))
} else if (project.hasProperty('deployDocsSshKey')) {
identity = project.findProperty('deployDocsSshKey')
}
if(project.hasProperty('deployDocsSshPassphrase')) {
passphrase = project.findProperty('deployDocsSshPassphrase')
}
}
}
project.task('deployDocs') {
dependsOn 'docsZip'
doFirst {
project.ssh.run {
session(project.remotes.docs) {
def now = System.currentTimeMillis()
def name = project.rootProject.name
def version = project.rootProject.version
def tempPath = "/tmp/${name}-${now}-docs/".replaceAll(' ', '_')
execute "mkdir -p $tempPath"
project.tasks.docsZip.outputs.each { o ->
put from: o.files, into: tempPath
}
execute "unzip $tempPath*.zip -d $tempPath"
def extractPath = "/var/www/domains/spring.io/docs/htdocs/autorepo/docs/${name}/${version}/"
execute "rm -rf $extractPath"
execute "mkdir -p $extractPath"
execute "mv $tempPath/docs/* $extractPath"
execute "chmod -R g+w $extractPath"
}
}
}
}
}
}
@@ -17,6 +17,7 @@ public class DocsPlugin implements Plugin<Project> {
PluginManager pluginManager = project.getPluginManager(); PluginManager pluginManager = project.getPluginManager();
pluginManager.apply(BasePlugin); pluginManager.apply(BasePlugin);
pluginManager.apply(DeployDocsPlugin);
pluginManager.apply(JavadocApiPlugin); pluginManager.apply(JavadocApiPlugin);
Task docsZip = project.tasks.create('docsZip', Zip) { Task docsZip = project.tasks.create('docsZip', Zip) {
@@ -31,12 +32,12 @@ public class DocsPlugin implements Plugin<Project> {
into 'api' into 'api'
} }
into 'docs' into 'docs'
duplicatesStrategy = 'exclude' duplicatesStrategy 'exclude'
} }
Task docs = project.tasks.create("docs") { Task docs = project.tasks.create("docs") {
group = 'Documentation' group = 'Documentation'
description = 'An aggregator task to generate all the documentation' description 'An aggregator task to generate all the documentation'
dependsOn docsZip dependsOn docsZip
} }
project.tasks.assemble.dependsOn docs project.tasks.assemble.dependsOn docs
@@ -90,7 +90,7 @@ public class IntegrationTestPlugin implements Plugin<Project> {
project.plugins.withType(IdeaPlugin) { project.plugins.withType(IdeaPlugin) {
project.idea { project.idea {
module { module {
testSources.from(project.file('src/integration-test/java')) testSourceDirs += project.file('src/integration-test/java')
scopes.TEST.plus += [ project.configurations.integrationTestCompileClasspath ] scopes.TEST.plus += [ project.configurations.integrationTestCompileClasspath ]
} }
} }
@@ -105,7 +105,7 @@ public class IntegrationTestPlugin implements Plugin<Project> {
project.plugins.withType(IdeaPlugin) { project.plugins.withType(IdeaPlugin) {
project.idea { project.idea {
module { module {
testSources.from(project.file('src/integration-test/groovy')) testSourceDirs += project.file('src/integration-test/groovy')
} }
} }
} }
@@ -26,7 +26,7 @@ import org.gradle.api.Action;
import org.gradle.api.JavaVersion import org.gradle.api.JavaVersion
import org.gradle.api.Plugin; import org.gradle.api.Plugin;
import org.gradle.api.Project; import org.gradle.api.Project;
import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.plugins.JavaPluginConvention;
import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.javadoc.Javadoc; import org.gradle.api.tasks.javadoc.Javadoc;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -71,7 +71,7 @@ public class JavadocApiPlugin implements Plugin<Project> {
} }
api.setMaxMemory("1024m"); api.setMaxMemory("1024m");
api.setDestinationDir(project.layout.getBuildDirectory().dir("api").get().getAsFile()); api.setDestinationDir(new File(project.getBuildDir(), "api"));
project.getPluginManager().apply("io.spring.convention.javadoc-options"); project.getPluginManager().apply("io.spring.convention.javadoc-options");
} }
@@ -99,7 +99,7 @@ public class JavadocApiPlugin implements Plugin<Project> {
public void execute(SpringModulePlugin plugin) { public void execute(SpringModulePlugin plugin) {
logger.info("Added sources for {}", project); logger.info("Added sources for {}", project);
JavaPluginExtension java = project.getExtensions().getByType(JavaPluginExtension.class); JavaPluginConvention java = project.getConvention().getPlugin(JavaPluginConvention.class);
SourceSet mainSourceSet = java.getSourceSets().getByName("main"); SourceSet mainSourceSet = java.getSourceSets().getByName("main");
api.setSource(api.getSource().plus(mainSourceSet.getAllJava())); api.setSource(api.getSource().plus(mainSourceSet.getAllJava()));
@@ -40,7 +40,7 @@ public class SchemaZipPlugin implements Plugin<Project> {
throw new IllegalStateException("Could not find schema file for resource name " + schemaResourceName + " in src/main/resources") throw new IllegalStateException("Could not find schema file for resource name " + schemaResourceName + " in src/main/resources")
} }
schemaZip.into (shortName) { schemaZip.into (shortName) {
duplicatesStrategy = 'exclude' duplicatesStrategy 'exclude'
from xsdFile.path from xsdFile.path
} }
versionlessXsd.getInputFiles().from(xsdFile.path) versionlessXsd.getInputFiles().from(xsdFile.path)
@@ -35,7 +35,6 @@ class SpringModulePlugin extends AbstractSpringJavaPlugin {
pluginManager.apply(SpringMavenPlugin.class); pluginManager.apply(SpringMavenPlugin.class);
pluginManager.apply(CheckClasspathForProhibitedDependenciesPlugin.class); pluginManager.apply(CheckClasspathForProhibitedDependenciesPlugin.class);
pluginManager.apply("io.spring.convention.jacoco"); pluginManager.apply("io.spring.convention.jacoco");
pluginManager.apply("java-toolchain");
def deployArtifacts = project.task("deployArtifacts") def deployArtifacts = project.task("deployArtifacts")
deployArtifacts.group = 'Deploy tasks' deployArtifacts.group = 'Deploy tasks'
@@ -1,36 +0,0 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
def toolchainVersion() {
if (project.hasProperty('testToolchain')) {
return project.property('testToolchain').toString().toInteger()
}
return 17
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(toolchainVersion())
}
}
tasks.withType(JavaCompile).configureEach {
options.encoding = "UTF-8"
options.compilerArgs.add("-parameters")
options.release = 17
}
pluginManager.withPlugin("org.jetbrains.kotlin.jvm") {
kotlin {
jvmToolchain {
languageVersion = JavaLanguageVersion.of(toolchainVersion())
}
}
tasks.withType(KotlinCompile).configureEach {
compilerOptions {
javaParameters = true
jvmTarget.set(JvmTarget.JVM_17)
}
}
}
+11 -1
View File
@@ -30,6 +30,16 @@ ossrh: {
} }
} }
}, },
docs: {
stage('Deploy Docs') {
node {
checkout scm
withCredentials([file(credentialsId: 'docs.spring.io-jenkins_private_ssh_key', variable: 'DEPLOY_SSH_KEY')]) {
sh "./gradlew deployDocs -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME --refresh-dependencies --no-daemon --stacktrace"
}
}
}
},
schema: { schema: {
stage('Deploy Schema') { stage('Deploy Schema') {
node { node {
@@ -39,4 +49,4 @@ schema: {
} }
} }
} }
} }
@@ -48,7 +48,6 @@ import org.springframework.security.jackson.SecurityJacksonModules;
* @since 7.0 * @since 7.0
* @see SecurityJacksonModules * @see SecurityJacksonModules
*/ */
@SuppressWarnings("serial")
public class CasJacksonModule extends SecurityJacksonModule { public class CasJacksonModule extends SecurityJacksonModule {
public CasJacksonModule() { public CasJacksonModule() {
@@ -326,7 +326,7 @@ public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFil
/** /**
* Use this {@code RequestMatcher} to match proxy receptor requests. Without setting * Use this {@code RequestMatcher} to match proxy receptor requests. Without setting
* this matcher, {@link CasAuthenticationFilter} will not capture any proxy receptor * this matcher, {@link CasAuthenticationFilter} will not capture any proxy receptor
* requests. * requets.
* @param proxyReceptorMatcher the {@link RequestMatcher} to use * @param proxyReceptorMatcher the {@link RequestMatcher} to use
* @since 6.5 * @since 6.5
*/ */
@@ -383,8 +383,8 @@ public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFil
} }
/** /**
* Indicates if the request is eligible to process a service ticket. This method * Indicates if the request is elgible to process a service ticket. This method exists
* exists for readability. * for readability.
* @param request * @param request
* @param response * @param response
* @return * @return
@@ -396,7 +396,7 @@ public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFil
} }
/** /**
* Indicates if the request is eligible to process a proxy ticket. * Indicates if the request is elgible to process a proxy ticket.
* @param request * @param request
* @return * @return
*/ */
@@ -419,7 +419,7 @@ public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFil
} }
/** /**
* Indicates if the request is eligible to be processed as the proxy receptor. * Indicates if the request is elgible to be processed as the proxy receptor.
* @param request * @param request
* @return * @return
*/ */
+2 -2
View File
@@ -144,14 +144,14 @@ tasks.named('processResources', ProcessResources).configure {
into 'org/springframework/security/config/' into 'org/springframework/security/config/'
} }
from(rncToXsd) { from(rncToXsd) {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE duplicatesStrategy DuplicatesStrategy.EXCLUDE
into 'org/springframework/security/config/' into 'org/springframework/security/config/'
} }
} }
tasks.named('sourcesJar', Jar).configure { tasks.named('sourcesJar', Jar).configure {
from(rncToXsd) { from(rncToXsd) {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE duplicatesStrategy DuplicatesStrategy.EXCLUDE
into 'org/springframework/security/config/' into 'org/springframework/security/config/'
} }
} }
@@ -31,7 +31,6 @@ import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.openqa.selenium.By; import org.openqa.selenium.By;
import org.openqa.selenium.WebDriverException; import org.openqa.selenium.WebDriverException;
@@ -56,7 +55,6 @@ import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.util.StringUtils;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.filter.DelegatingFilterProxy; import org.springframework.web.filter.DelegatingFilterProxy;
import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.EnableWebMvc;
@@ -69,7 +67,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* *
* @author Daniel Garnier-Moiroux * @author Daniel Garnier-Moiroux
*/ */
@Disabled @org.junit.jupiter.api.Disabled
class WebAuthnWebDriverTests { class WebAuthnWebDriverTests {
private String baseUrl; private String baseUrl;
@@ -84,8 +82,6 @@ class WebAuthnWebDriverTests {
private static final String PASSWORD = "password"; private static final String PASSWORD = "password";
private String authenticatorId = null;
@BeforeAll @BeforeAll
static void startChromeDriverService() throws Exception { static void startChromeDriverService() throws Exception {
driverService = new ChromeDriverService.Builder().usingAnyFreePort().build(); driverService = new ChromeDriverService.Builder().usingAnyFreePort().build();
@@ -148,7 +144,7 @@ class WebAuthnWebDriverTests {
@Test @Test
void loginWhenNoValidAuthenticatorCredentialsThenRejects() { void loginWhenNoValidAuthenticatorCredentialsThenRejects() {
createVirtualAuthenticator(true); createVirtualAuthenticator(true);
this.getAndWait("/", "/login"); this.driver.get(this.baseUrl);
this.driver.findElement(signinWithPasskeyButton()).click(); this.driver.findElement(signinWithPasskeyButton()).click();
await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/login?error")); await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/login?error"));
} }
@@ -157,7 +153,7 @@ class WebAuthnWebDriverTests {
void registerWhenNoLabelThenRejects() { void registerWhenNoLabelThenRejects() {
login(); login();
this.getAndWait("/webauthn/register"); this.driver.get(this.baseUrl + "/webauthn/register");
this.driver.findElement(registerPasskeyButton()).click(); this.driver.findElement(registerPasskeyButton()).click();
assertHasAlertStartingWith("error", "Error: Passkey Label is required"); assertHasAlertStartingWith("error", "Error: Passkey Label is required");
@@ -167,7 +163,7 @@ class WebAuthnWebDriverTests {
void registerWhenAuthenticatorNoUserVerificationThenRejects() { void registerWhenAuthenticatorNoUserVerificationThenRejects() {
createVirtualAuthenticator(false); createVirtualAuthenticator(false);
login(); login();
this.getAndWait("/webauthn/register"); this.driver.get(this.baseUrl + "/webauthn/register");
this.driver.findElement(passkeyLabel()).sendKeys("Virtual authenticator"); this.driver.findElement(passkeyLabel()).sendKeys("Virtual authenticator");
this.driver.findElement(registerPasskeyButton()).click(); this.driver.findElement(registerPasskeyButton()).click();
@@ -182,8 +178,7 @@ class WebAuthnWebDriverTests {
* <li>Step 1: Log in with username / password</li> * <li>Step 1: Log in with username / password</li>
* <li>Step 2: Register a credential from the virtual authenticator</li> * <li>Step 2: Register a credential from the virtual authenticator</li>
* <li>Step 3: Log out</li> * <li>Step 3: Log out</li>
* <li>Step 4: Log in with the authenticator (no allowCredentials)</li> * <li>Step 4: Log in with the authenticator</li>
* <li>Step 5: Log in again with the same authenticator (with allowCredentials)</li>
* </ul> * </ul>
*/ */
@Test @Test
@@ -195,7 +190,7 @@ class WebAuthnWebDriverTests {
login(); login();
// Step 2: register a credential from the virtual authenticator // Step 2: register a credential from the virtual authenticator
this.getAndWait("/webauthn/register"); this.driver.get(this.baseUrl + "/webauthn/register");
this.driver.findElement(passkeyLabel()).sendKeys("Virtual authenticator"); this.driver.findElement(passkeyLabel()).sendKeys("Virtual authenticator");
this.driver.findElement(registerPasskeyButton()).click(); this.driver.findElement(registerPasskeyButton()).click();
@@ -217,58 +212,9 @@ class WebAuthnWebDriverTests {
logout(); logout();
// Step 4: log in with the virtual authenticator // Step 4: log in with the virtual authenticator
this.getAndWait("/webauthn/register", "/login"); this.driver.get(this.baseUrl + "/webauthn/register");
this.driver.findElement(signinWithPasskeyButton()).click(); this.driver.findElement(signinWithPasskeyButton()).click();
await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/webauthn/register?continue")); await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/webauthn/register?continue"));
// Step 5: authenticate while being already logged in
// This simulates some use-cases with MFA. Since the user is already logged in,
// the "allowCredentials" property is populated
this.getAndWait("/login");
this.driver.findElement(signinWithPasskeyButton()).click();
await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/"));
}
@Test
void registerWhenAuthenticatorAlreadyRegisteredThenRejects() {
createVirtualAuthenticator(true);
login();
registerAuthenticator("Virtual authenticator");
// Cannot re-register the same authenticator because excludeCredentials
// is not empty and contains the given authenticator
this.driver.findElement(passkeyLabel()).sendKeys("Same authenticator");
this.driver.findElement(registerPasskeyButton()).click();
await(() -> assertHasAlertStartingWith("error", "Registration failed"));
}
@Test
void registerSecondAuthenticatorThenSucceeds() {
createVirtualAuthenticator(true);
login();
registerAuthenticator("Virtual authenticator");
this.getAndWait("/webauthn/register");
List<WebElement> passkeyRows = this.driver.findElements(passkeyTableRows());
assertThat(passkeyRows).hasSize(1)
.first()
.extracting((row) -> row.findElement(firstCell()))
.extracting(WebElement::getText)
.isEqualTo("Virtual authenticator");
// Create second authenticator and register
removeAuthenticator();
createVirtualAuthenticator(true);
registerAuthenticator("Second virtual authenticator");
this.getAndWait("/webauthn/register");
passkeyRows = this.driver.findElements(passkeyTableRows());
assertThat(passkeyRows).hasSize(2)
.extracting((row) -> row.findElement(firstCell()))
.extracting(WebElement::getText)
.contains("Second virtual authenticator");
} }
/** /**
@@ -285,14 +231,11 @@ class WebAuthnWebDriverTests {
* "https://chromedevtools.github.io/devtools-protocol/tot/WebAuthn/">https://chromedevtools.github.io/devtools-protocol/tot/WebAuthn/</a> * "https://chromedevtools.github.io/devtools-protocol/tot/WebAuthn/">https://chromedevtools.github.io/devtools-protocol/tot/WebAuthn/</a>
*/ */
private void createVirtualAuthenticator(boolean userIsVerified) { private void createVirtualAuthenticator(boolean userIsVerified) {
if (StringUtils.hasText(this.authenticatorId)) {
throw new IllegalStateException("Authenticator already exists, please remove it before re-creating one");
}
HasCdp cdpDriver = (HasCdp) this.driver; HasCdp cdpDriver = (HasCdp) this.driver;
cdpDriver.executeCdpCommand("WebAuthn.enable", Map.of("enableUI", false)); cdpDriver.executeCdpCommand("WebAuthn.enable", Map.of("enableUI", false));
// this.driver.addVirtualAuthenticator(createVirtualAuthenticatorOptions()); // this.driver.addVirtualAuthenticator(createVirtualAuthenticatorOptions());
//@formatter:off //@formatter:off
Map<String, Object> cmdResponse = cdpDriver.executeCdpCommand("WebAuthn.addVirtualAuthenticator", cdpDriver.executeCdpCommand("WebAuthn.addVirtualAuthenticator",
Map.of( Map.of(
"options", "options",
Map.of( Map.of(
@@ -305,38 +248,21 @@ class WebAuthnWebDriverTests {
) )
)); ));
//@formatter:on //@formatter:on
this.authenticatorId = cmdResponse.get("authenticatorId").toString();
}
private void removeAuthenticator() {
HasCdp cdpDriver = (HasCdp) this.driver;
cdpDriver.executeCdpCommand("WebAuthn.removeVirtualAuthenticator",
Map.of("authenticatorId", this.authenticatorId));
this.authenticatorId = null;
} }
private void login() { private void login() {
this.getAndWait("/", "/login"); this.driver.get(this.baseUrl);
this.driver.findElement(usernameField()).sendKeys(USERNAME); this.driver.findElement(usernameField()).sendKeys(USERNAME);
this.driver.findElement(passwordField()).sendKeys(PASSWORD); this.driver.findElement(passwordField()).sendKeys(PASSWORD);
this.driver.findElement(signinWithUsernamePasswordButton()).click(); this.driver.findElement(signinWithUsernamePasswordButton()).click();
// Ensure login has completed
await(() -> assertThat(this.driver.getCurrentUrl()).doesNotContain("/login"));
} }
private void logout() { private void logout() {
this.getAndWait("/logout"); this.driver.get(this.baseUrl + "/logout");
this.driver.findElement(logoutButton()).click(); this.driver.findElement(logoutButton()).click();
await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/login?logout")); await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/login?logout"));
} }
private void registerAuthenticator(String passkeyName) {
this.getAndWait("/webauthn/register");
this.driver.findElement(passkeyLabel()).sendKeys(passkeyName);
this.driver.findElement(registerPasskeyButton()).click();
await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/webauthn/register?success"));
}
private AbstractStringAssert<?> assertHasAlertStartingWith(String alertType, String alertMessage) { private AbstractStringAssert<?> assertHasAlertStartingWith(String alertType, String alertMessage) {
WebElement alert = this.driver.findElement(new By.ById(alertType)); WebElement alert = this.driver.findElement(new By.ById(alertType));
assertThat(alert.isDisplayed()) assertThat(alert.isDisplayed())
@@ -363,15 +289,6 @@ class WebAuthnWebDriverTests {
}); });
} }
private void getAndWait(String endpoint) {
this.getAndWait(endpoint, endpoint);
}
private void getAndWait(String endpoint, String redirectUrl) {
this.driver.get(this.baseUrl + endpoint);
this.await(() -> assertThat(this.driver.getCurrentUrl()).endsWith(redirectUrl));
}
private static By.ById passkeyLabel() { private static By.ById passkeyLabel() {
return new By.ById("label"); return new By.ById("label");
} }
@@ -408,10 +325,6 @@ class WebAuthnWebDriverTests {
return new By.ByCssSelector("button"); return new By.ByCssSelector("button");
} }
private static By.ByCssSelector deletePasskeyButton() {
return new By.ByCssSelector("table > tbody > tr > button");
}
/** /**
* The configuration for WebAuthN tests. It accesses the Server's current port, so we * The configuration for WebAuthN tests. It accesses the Server's current port, so we
* can configurer WebAuthnConfigurer#allowedOrigin * can configurer WebAuthnConfigurer#allowedOrigin
@@ -177,7 +177,7 @@ public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBui
} }
/** /**
* Gets a shared Object. Note that object hierarchies are not considered. * Gets a shared Object. Note that object heirarchies are not considered.
* @param sharedType the type of the shared Object * @param sharedType the type of the shared Object
* @return the shared Object or null if it is not found * @return the shared Object or null if it is not found
*/ */
@@ -360,7 +360,7 @@ public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBui
/** /**
* Subclasses must implement this method to build the object that is being returned. * Subclasses must implement this method to build the object that is being returned.
* @return the Object to be built or null if the implementation allows it * @return the Object to be buit or null if the implementation allows it
*/ */
protected abstract O performBuild(); protected abstract O performBuild();
@@ -414,13 +414,13 @@ public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBui
private enum BuildState { private enum BuildState {
/** /**
* This is the state before the {@link SecurityBuilder#build()} is invoked * This is the state before the {@link Builder#build()} is invoked
*/ */
UNBUILT(0), UNBUILT(0),
/** /**
* The state from when {@link SecurityBuilder#build()} is first invoked until all * The state from when {@link Builder#build()} is first invoked until all the
* the {@link SecurityConfigurer#init(SecurityBuilder)} methods have been invoked. * {@link SecurityConfigurer#init(SecurityBuilder)} methods have been invoked.
*/ */
INITIALIZING(1), INITIALIZING(1),
@@ -42,8 +42,7 @@ final class MethodSecuritySelector implements ImportSelector {
.isPresent("org.springframework.security.data.aot.hint.AuthorizeReturnObjectDataHintsRegistrar", null); .isPresent("org.springframework.security.data.aot.hint.AuthorizeReturnObjectDataHintsRegistrar", null);
private static final boolean isWebPresent = ClassUtils private static final boolean isWebPresent = ClassUtils
.isPresent("org.springframework.web.servlet.DispatcherServlet", null) .isPresent("org.springframework.web.servlet.DispatcherServlet", null);
&& ClassUtils.isPresent("org.springframework.security.web.util.ThrowableAnalyzer", null);
private static final boolean isObservabilityPresent = ClassUtils private static final boolean isObservabilityPresent = ClassUtils
.isPresent("io.micrometer.observation.ObservationRegistry", null); .isPresent("io.micrometer.observation.ObservationRegistry", null);
@@ -82,7 +82,7 @@ public interface HttpSecurityBuilder<H extends HttpSecurityBuilder<H>>
<C> void setSharedObject(Class<C> sharedType, C object); <C> void setSharedObject(Class<C> sharedType, C object);
/** /**
* Gets a shared Object. Note that object hierarchies are not considered. * Gets a shared Object. Note that object heirarchies are not considered.
* @param sharedType the type of the shared Object * @param sharedType the type of the shared Object
* @return the shared Object or null if it is not found * @return the shared Object or null if it is not found
*/ */
@@ -133,7 +133,7 @@ final class FilterOrderRegistration {
/** /**
* Register a {@link Filter} with its specific position. If the {@link Filter} was * Register a {@link Filter} with its specific position. If the {@link Filter} was
* already registered before, the position previously defined is not going to be * already registered before, the position previously defined is not going to be
* overridden * overriden
* @param filter the {@link Filter} to register * @param filter the {@link Filter} to register
* @param position the position to associate with the {@link Filter} * @param position the position to associate with the {@link Filter}
*/ */
@@ -2035,9 +2035,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
*/ */
public HttpSecurity securityMatcher(String... patterns) { public HttpSecurity securityMatcher(String... patterns) {
List<RequestMatcher> matchers = new ArrayList<>(); List<RequestMatcher> matchers = new ArrayList<>();
ApplicationContext context = getSharedObject(ApplicationContext.class); PathPatternRequestMatcher.Builder builder = getSharedObject(PathPatternRequestMatcher.Builder.class);
PathPatternRequestMatcher.Builder builder = context.getBeanProvider(PathPatternRequestMatcher.Builder.class)
.getIfUnique(() -> getSharedObject(PathPatternRequestMatcher.Builder.class));
for (String pattern : patterns) { for (String pattern : patterns) {
matchers.add(builder.matcher(pattern)); matchers.add(builder.matcher(pattern));
} }
@@ -2054,6 +2052,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
* http * http
* // ... * // ...
* .webAuthn((webAuthn) -&gt; webAuthn * .webAuthn((webAuthn) -&gt; webAuthn
* .rpName("Spring Security Relying Party")
* .rpId("example.com") * .rpId("example.com")
* .allowedOrigins("https://example.com") * .allowedOrigins("https://example.com")
* ); * );
@@ -305,7 +305,7 @@ public final class LogoutConfigurer<H extends HttpSecurityBuilder<H>>
} }
/** /**
* Gets the logoutSuccessUrl or null if a * Gets the logoutSuccesUrl or null if a
* {@link #logoutSuccessHandler(LogoutSuccessHandler)} was configured. * {@link #logoutSuccessHandler(LogoutSuccessHandler)} was configured.
* @return the logoutSuccessUrl * @return the logoutSuccessUrl
*/ */
@@ -146,7 +146,7 @@ public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
/** /**
* This should not use RequestAttributeSecurityContextRepository since that is * This should not use RequestAttributeSecurityContextRepository since that is
* stateless and session management is about state management. * stateless and sesison management is about state management.
*/ */
private SecurityContextRepository sessionManagementSecurityContextRepository = new HttpSessionSecurityContextRepository(); private SecurityContextRepository sessionManagementSecurityContextRepository = new HttpSessionSecurityContextRepository();
@@ -39,7 +39,6 @@ import org.springframework.security.web.webauthn.api.PublicKeyCredentialRpEntity
import org.springframework.security.web.webauthn.authentication.PublicKeyCredentialRequestOptionsFilter; import org.springframework.security.web.webauthn.authentication.PublicKeyCredentialRequestOptionsFilter;
import org.springframework.security.web.webauthn.authentication.WebAuthnAuthenticationFilter; import org.springframework.security.web.webauthn.authentication.WebAuthnAuthenticationFilter;
import org.springframework.security.web.webauthn.authentication.WebAuthnAuthenticationProvider; import org.springframework.security.web.webauthn.authentication.WebAuthnAuthenticationProvider;
import org.springframework.security.web.webauthn.management.CredentialRecordOwnerAuthorizationManager;
import org.springframework.security.web.webauthn.management.MapPublicKeyCredentialUserEntityRepository; import org.springframework.security.web.webauthn.management.MapPublicKeyCredentialUserEntityRepository;
import org.springframework.security.web.webauthn.management.MapUserCredentialRepository; import org.springframework.security.web.webauthn.management.MapUserCredentialRepository;
import org.springframework.security.web.webauthn.management.PublicKeyCredentialUserEntityRepository; import org.springframework.security.web.webauthn.management.PublicKeyCredentialUserEntityRepository;
@@ -178,11 +177,8 @@ public class WebAuthnConfigurer<H extends HttpSecurityBuilder<H>>
WebAuthnAuthenticationFilter webAuthnAuthnFilter = new WebAuthnAuthenticationFilter(); WebAuthnAuthenticationFilter webAuthnAuthnFilter = new WebAuthnAuthenticationFilter();
webAuthnAuthnFilter.setAuthenticationManager( webAuthnAuthnFilter.setAuthenticationManager(
new ProviderManager(new WebAuthnAuthenticationProvider(rpOperations, userDetailsService))); new ProviderManager(new WebAuthnAuthenticationProvider(rpOperations, userDetailsService)));
webAuthnAuthnFilter = postProcess(webAuthnAuthnFilter);
WebAuthnRegistrationFilter webAuthnRegistrationFilter = new WebAuthnRegistrationFilter(userCredentials, WebAuthnRegistrationFilter webAuthnRegistrationFilter = new WebAuthnRegistrationFilter(userCredentials,
rpOperations); rpOperations);
webAuthnRegistrationFilter.setDeleteCredentialAuthorizationManager(
new CredentialRecordOwnerAuthorizationManager(userCredentials, userEntities));
PublicKeyCredentialCreationOptionsFilter creationOptionsFilter = new PublicKeyCredentialCreationOptionsFilter( PublicKeyCredentialCreationOptionsFilter creationOptionsFilter = new PublicKeyCredentialCreationOptionsFilter(
rpOperations); rpOperations);
if (creationOptionsRepository != null) { if (creationOptionsRepository != null) {
@@ -260,10 +256,9 @@ public class WebAuthnConfigurer<H extends HttpSecurityBuilder<H>>
PublicKeyCredentialUserEntityRepository userEntities, UserCredentialRepository userCredentials) { PublicKeyCredentialUserEntityRepository userEntities, UserCredentialRepository userCredentials) {
Optional<WebAuthnRelyingPartyOperations> webauthnOperationsBean = getBeanOrNull( Optional<WebAuthnRelyingPartyOperations> webauthnOperationsBean = getBeanOrNull(
WebAuthnRelyingPartyOperations.class); WebAuthnRelyingPartyOperations.class);
String rpName = (this.rpName != null) ? this.rpName : this.rpId; return webauthnOperationsBean.orElseGet(() -> new Webauthn4JRelyingPartyOperations(userEntities,
return webauthnOperationsBean userCredentials, PublicKeyCredentialRpEntity.builder().id(this.rpId).name(this.rpName).build(),
.orElseGet(() -> new Webauthn4JRelyingPartyOperations(userEntities, userCredentials, this.allowedOrigins));
PublicKeyCredentialRpEntity.builder().id(this.rpId).name(rpName).build(), this.allowedOrigins));
} }
} }
@@ -16,12 +16,10 @@
package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization; package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
import jakarta.servlet.Filter;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
@@ -38,12 +36,10 @@ import org.springframework.security.oauth2.server.authorization.authentication.O
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationValidator; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationValidator;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationProvider; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationToken; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter; import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeRequestAuthenticationConverter; import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeRequestAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationConsentAuthenticationConverter; import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationConsentAuthenticationConverter;
import org.springframework.security.web.access.intercept.AuthorizationFilter;
import org.springframework.security.web.authentication.AuthenticationConverter; import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
@@ -54,7 +50,6 @@ import org.springframework.security.web.servlet.util.matcher.PathPatternRequestM
import org.springframework.security.web.util.matcher.OrRequestMatcher; import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
@@ -88,8 +83,6 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C
private Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authorizationCodeRequestAuthenticationValidator; private Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authorizationCodeRequestAuthenticationValidator;
private Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authorizationCodeRequestAuthenticationValidatorComposite;
private SessionAuthenticationStrategy sessionAuthenticationStrategy; private SessionAuthenticationStrategy sessionAuthenticationStrategy;
/** /**
@@ -255,16 +248,8 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C
authenticationProviders.addAll(0, this.authenticationProviders); authenticationProviders.addAll(0, this.authenticationProviders);
} }
this.authenticationProvidersConsumer.accept(authenticationProviders); this.authenticationProvidersConsumer.accept(authenticationProviders);
authenticationProviders.forEach((authenticationProvider) -> { authenticationProviders.forEach(
httpSecurity.authenticationProvider(postProcess(authenticationProvider)); (authenticationProvider) -> httpSecurity.authenticationProvider(postProcess(authenticationProvider)));
if (authenticationProvider instanceof OAuth2AuthorizationCodeRequestAuthenticationProvider) {
Method method = ReflectionUtils.findMethod(OAuth2AuthorizationCodeRequestAuthenticationProvider.class,
"getAuthenticationValidatorComposite");
ReflectionUtils.makeAccessible(method);
this.authorizationCodeRequestAuthenticationValidatorComposite = (Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext>) ReflectionUtils
.invokeMethod(method, authenticationProvider);
}
});
} }
@Override @Override
@@ -297,18 +282,7 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C
if (this.sessionAuthenticationStrategy != null) { if (this.sessionAuthenticationStrategy != null) {
authorizationEndpointFilter.setSessionAuthenticationStrategy(this.sessionAuthenticationStrategy); authorizationEndpointFilter.setSessionAuthenticationStrategy(this.sessionAuthenticationStrategy);
} }
httpSecurity.addFilterAfter(postProcess(authorizationEndpointFilter), AuthorizationFilter.class); httpSecurity.addFilterBefore(postProcess(authorizationEndpointFilter),
// Create and add
// OAuth2AuthorizationEndpointFilter.OAuth2AuthorizationCodeRequestValidatingFilter
Method method = ReflectionUtils.findMethod(OAuth2AuthorizationEndpointFilter.class,
"createAuthorizationCodeRequestValidatingFilter", RegisteredClientRepository.class, Consumer.class);
ReflectionUtils.makeAccessible(method);
RegisteredClientRepository registeredClientRepository = OAuth2ConfigurerUtils
.getRegisteredClientRepository(httpSecurity);
Filter authorizationCodeRequestValidatingFilter = (Filter) ReflectionUtils.invokeMethod(method,
authorizationEndpointFilter, registeredClientRepository,
this.authorizationCodeRequestAuthenticationValidatorComposite);
httpSecurity.addFilterBefore(postProcess(authorizationCodeRequestValidatingFilter),
AbstractPreAuthenticatedProcessingFilter.class); AbstractPreAuthenticatedProcessingFilter.class);
} }
@@ -16,9 +16,14 @@
package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization; package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
import java.util.Map;
import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext; import com.nimbusds.jose.proc.SecurityContext;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.core.ResolvableType; import org.springframework.core.ResolvableType;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@@ -40,6 +45,7 @@ import org.springframework.security.oauth2.server.authorization.token.OAuth2Toke
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer; import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/** /**
* Utility methods for the OAuth 2.0 Configurers. * Utility methods for the OAuth 2.0 Configurers.
@@ -201,16 +207,41 @@ final class OAuth2ConfigurerUtils {
} }
static <T> T getBean(HttpSecurity httpSecurity, Class<T> type) { static <T> T getBean(HttpSecurity httpSecurity, Class<T> type) {
return httpSecurity.getSharedObject(ApplicationContext.class).getBeanProvider(type).getObject(); return httpSecurity.getSharedObject(ApplicationContext.class).getBean(type);
}
@SuppressWarnings("unchecked")
static <T> T getBean(HttpSecurity httpSecurity, ResolvableType type) {
ApplicationContext context = httpSecurity.getSharedObject(ApplicationContext.class);
String[] names = context.getBeanNamesForType(type);
if (names.length == 1) {
return (T) context.getBean(names[0]);
}
if (names.length > 1) {
throw new NoUniqueBeanDefinitionException(type, names);
}
throw new NoSuchBeanDefinitionException(type);
} }
static <T> T getOptionalBean(HttpSecurity httpSecurity, Class<T> type) { static <T> T getOptionalBean(HttpSecurity httpSecurity, Class<T> type) {
return httpSecurity.getSharedObject(ApplicationContext.class).getBeanProvider(type).getIfUnique(); Map<String, T> beansMap = BeanFactoryUtils
.beansOfTypeIncludingAncestors(httpSecurity.getSharedObject(ApplicationContext.class), type);
if (beansMap.size() > 1) {
throw new NoUniqueBeanDefinitionException(type, beansMap.size(),
"Expected single matching bean of type '" + type.getName() + "' but found " + beansMap.size() + ": "
+ StringUtils.collectionToCommaDelimitedString(beansMap.keySet()));
}
return (!beansMap.isEmpty() ? beansMap.values().iterator().next() : null);
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
static <T> T getOptionalBean(HttpSecurity httpSecurity, ResolvableType type) { static <T> T getOptionalBean(HttpSecurity httpSecurity, ResolvableType type) {
return (T) httpSecurity.getSharedObject(ApplicationContext.class).getBeanProvider(type).getIfUnique(); ApplicationContext context = httpSecurity.getSharedObject(ApplicationContext.class);
String[] names = context.getBeanNamesForType(type);
if (names.length > 1) {
throw new NoUniqueBeanDefinitionException(type, names);
}
return (names.length == 1) ? (T) context.getBean(names[0]) : null;
} }
} }
@@ -40,11 +40,11 @@ import org.springframework.security.oauth2.server.authorization.settings.Authori
import org.springframework.security.oauth2.server.authorization.web.OAuth2DeviceVerificationEndpointFilter; import org.springframework.security.oauth2.server.authorization.web.OAuth2DeviceVerificationEndpointFilter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2DeviceAuthorizationConsentAuthenticationConverter; import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2DeviceAuthorizationConsentAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2DeviceVerificationAuthenticationConverter; import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2DeviceVerificationAuthenticationConverter;
import org.springframework.security.web.access.intercept.AuthorizationFilter;
import org.springframework.security.web.authentication.AuthenticationConverter; import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.DelegatingAuthenticationConverter; import org.springframework.security.web.authentication.DelegatingAuthenticationConverter;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher; import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher;
@@ -279,7 +279,8 @@ public final class OAuth2DeviceVerificationEndpointConfigurer extends AbstractOA
if (StringUtils.hasText(this.consentPage)) { if (StringUtils.hasText(this.consentPage)) {
deviceVerificationEndpointFilter.setConsentPage(this.consentPage); deviceVerificationEndpointFilter.setConsentPage(this.consentPage);
} }
builder.addFilterAfter(postProcess(deviceVerificationEndpointFilter), AuthorizationFilter.class); builder.addFilterBefore(postProcess(deviceVerificationEndpointFilter),
AbstractPreAuthenticatedProcessingFilter.class);
} }
@Override @Override
@@ -95,7 +95,7 @@ public class Saml2MetadataConfigurer<H extends HttpSecurityBuilder<H>>
* If there is no {@code registrationId} and your * If there is no {@code registrationId} and your
* {@link RelyingPartyRegistrationRepository} is {code Iterable}, the metadata * {@link RelyingPartyRegistrationRepository} is {code Iterable}, the metadata
* endpoint will try and show all relying parties' metadata in a single * endpoint will try and show all relying parties' metadata in a single
* {@code <md:EntitiesDescriptor} element. * {@code <md:EntitiesDecriptor} element.
* *
* <p> * <p>
* If you need a more sophisticated lookup strategy than these, use * If you need a more sophisticated lookup strategy than these, use
@@ -167,7 +167,7 @@ class ServerHttpSecurityConfiguration {
} }
/** /**
* Applies all {@code Customizer<ServerHttpSecurity>} Beans to * Applies all {@code Custmizer<ServerHttpSecurity>} Beans to
* {@link ServerHttpSecurity}. * {@link ServerHttpSecurity}.
* @param context the {@link ApplicationContext} * @param context the {@link ApplicationContext}
* @param http the {@link ServerHttpSecurity} * @param http the {@link ServerHttpSecurity}
@@ -255,9 +255,7 @@ class ServerHttpSecurityConfiguration {
if (this.passwordEncoder != null) { if (this.passwordEncoder != null) {
manager.setPasswordEncoder(this.passwordEncoder); manager.setPasswordEncoder(this.passwordEncoder);
} }
if (this.userDetailsPasswordService != null) { manager.setUserDetailsPasswordService(this.userDetailsPasswordService);
manager.setUserDetailsPasswordService(this.userDetailsPasswordService);
}
manager.setCompromisedPasswordChecker(this.compromisedPasswordChecker); manager.setCompromisedPasswordChecker(this.compromisedPasswordChecker);
return this.postProcessor.postProcess(manager); return this.postProcessor.postProcess(manager);
} }
@@ -538,7 +538,7 @@ final class AuthenticationConfigBuilder {
} }
injectAuthenticationDetailsSource(x509Elt, filterBuilder); injectAuthenticationDetailsSource(x509Elt, filterBuilder);
filter = (RootBeanDefinition) filterBuilder.getBeanDefinition(); filter = (RootBeanDefinition) filterBuilder.getBeanDefinition();
createPreauthEntryPoint(x509Elt); createPrauthEntryPoint(x509Elt);
createX509Provider(); createX509Provider();
} }
this.x509Filter = filter; this.x509Filter = filter;
@@ -562,7 +562,7 @@ final class AuthenticationConfigBuilder {
this.x509ProviderRef = new RuntimeBeanReference(this.pc.getReaderContext().registerWithGeneratedName(provider)); this.x509ProviderRef = new RuntimeBeanReference(this.pc.getReaderContext().registerWithGeneratedName(provider));
} }
private void createPreauthEntryPoint(Element source) { private void createPrauthEntryPoint(Element source) {
if (this.preAuthEntryPoint == null) { if (this.preAuthEntryPoint == null) {
this.preAuthEntryPoint = new RootBeanDefinition(Http403ForbiddenEntryPoint.class); this.preAuthEntryPoint = new RootBeanDefinition(Http403ForbiddenEntryPoint.class);
this.preAuthEntryPoint.setSource(this.pc.extractSource(source)); this.preAuthEntryPoint.setSource(this.pc.extractSource(source));
@@ -595,7 +595,7 @@ final class AuthenticationConfigBuilder {
adsBldr.addPropertyValue("mappableRolesRetriever", mappableRolesRetriever); adsBldr.addPropertyValue("mappableRolesRetriever", mappableRolesRetriever);
filterBuilder.addPropertyValue("authenticationDetailsSource", adsBldr.getBeanDefinition()); filterBuilder.addPropertyValue("authenticationDetailsSource", adsBldr.getBeanDefinition());
filter = (RootBeanDefinition) filterBuilder.getBeanDefinition(); filter = (RootBeanDefinition) filterBuilder.getBeanDefinition();
createPreauthEntryPoint(jeeElt); createPrauthEntryPoint(jeeElt);
createJeeProvider(); createJeeProvider();
} }
this.jeeFilter = filter; this.jeeFilter = filter;
@@ -70,7 +70,7 @@ public final class PathPatternRequestMatcherFactoryBean
@Override @Override
public void afterPropertiesSet() throws Exception { public void afterPropertiesSet() throws Exception {
if (this.basePath != null) { if (this.basePath != null) {
this.builder = this.builder.basePath(this.basePath); this.builder.basePath(this.basePath);
} }
} }
@@ -165,20 +165,20 @@ public class LdapServerBeanDefinitionParser implements BeanDefinitionParser {
} }
private RootBeanDefinition getRootBeanDefinition(String mode) { private RootBeanDefinition getRootBeanDefinition(String mode) {
if (isUnboundIdEnabled(mode)) { if (isUnboundidEnabled(mode)) {
return new RootBeanDefinition(UNBOUNDID_CONTAINER_CLASSNAME, null, null); return new RootBeanDefinition(UNBOUNDID_CONTAINER_CLASSNAME, null, null);
} }
throw new IllegalStateException("Embedded LDAP server is not provided"); throw new IllegalStateException("Embedded LDAP server is not provided");
} }
private String resolveBeanId(String mode) { private String resolveBeanId(String mode) {
if (isUnboundIdEnabled(mode)) { if (isUnboundidEnabled(mode)) {
return BeanIds.EMBEDDED_UNBOUNDID; return BeanIds.EMBEDDED_UNBOUNDID;
} }
return null; return null;
} }
private boolean isUnboundIdEnabled(String mode) { private boolean isUnboundidEnabled(String mode) {
return "unboundid".equals(mode) || unboundIdPresent; return "unboundid".equals(mode) || unboundIdPresent;
} }
@@ -12,8 +12,8 @@ base64 =
## Whether a string should be base64 encoded ## Whether a string should be base64 encoded
attribute base64 {xsd:boolean} attribute base64 {xsd:boolean}
request-matcher = request-matcher =
## Defines the strategy use for matching incoming requests. Currently the options are 'path' (for PathPatternRequestMatcher), 'regex' for regular expressions and 'ciRegex' for case-insensitive regular expressions. ## Defines the strategy use for matching incoming requests. Currently the options are 'mvc' (for Spring MVC matcher), 'ant' (for ant path patterns), 'regex' for regular expressions and 'ciRegex' for case-insensitive regular expressions.
attribute request-matcher {"path" | "regex" | "ciRegex"} attribute request-matcher {"mvc" | "ant" | "regex" | "ciRegex"}
port = port =
## Specifies an IP port number. Used to configure an embedded LDAP server, for example. ## Specifies an IP port number. Used to configure an embedded LDAP server, for example.
attribute port { xsd:nonNegativeInteger } attribute port { xsd:nonNegativeInteger }
@@ -27,14 +27,15 @@
<xs:attributeGroup name="request-matcher"> <xs:attributeGroup name="request-matcher">
<xs:attribute name="request-matcher" use="required"> <xs:attribute name="request-matcher" use="required">
<xs:annotation> <xs:annotation>
<xs:documentation>Defines the strategy use for matching incoming requests. Currently the options are 'path' <xs:documentation>Defines the strategy use for matching incoming requests. Currently the options are 'mvc'
(for PathPatternRequestMatcher), 'regex' for regular expressions and 'ciRegex' for (for Spring MVC matcher), 'ant' (for ant path patterns), 'regex' for regular expressions
case-insensitive regular expressions. and 'ciRegex' for case-insensitive regular expressions.
</xs:documentation> </xs:documentation>
</xs:annotation> </xs:annotation>
<xs:simpleType> <xs:simpleType>
<xs:restriction base="xs:token"> <xs:restriction base="xs:token">
<xs:enumeration value="path"/> <xs:enumeration value="mvc"/>
<xs:enumeration value="ant"/>
<xs:enumeration value="regex"/> <xs:enumeration value="regex"/>
<xs:enumeration value="ciRegex"/> <xs:enumeration value="ciRegex"/>
</xs:restriction> </xs:restriction>
@@ -1305,14 +1306,15 @@
</xs:attribute> </xs:attribute>
<xs:attribute name="request-matcher"> <xs:attribute name="request-matcher">
<xs:annotation> <xs:annotation>
<xs:documentation>Defines the strategy use for matching incoming requests. Currently the options are 'path' <xs:documentation>Defines the strategy use for matching incoming requests. Currently the options are 'mvc'
(for PathPatternRequestMatcher), 'regex' for regular expressions and 'ciRegex' for (for Spring MVC matcher), 'ant' (for ant path patterns), 'regex' for regular expressions
case-insensitive regular expressions. and 'ciRegex' for case-insensitive regular expressions.
</xs:documentation> </xs:documentation>
</xs:annotation> </xs:annotation>
<xs:simpleType> <xs:simpleType>
<xs:restriction base="xs:token"> <xs:restriction base="xs:token">
<xs:enumeration value="path"/> <xs:enumeration value="mvc"/>
<xs:enumeration value="ant"/>
<xs:enumeration value="regex"/> <xs:enumeration value="regex"/>
<xs:enumeration value="ciRegex"/> <xs:enumeration value="ciRegex"/>
</xs:restriction> </xs:restriction>
@@ -2472,14 +2474,15 @@
<xs:attributeGroup name="filter-chain-map.attlist"> <xs:attributeGroup name="filter-chain-map.attlist">
<xs:attribute name="request-matcher"> <xs:attribute name="request-matcher">
<xs:annotation> <xs:annotation>
<xs:documentation>Defines the strategy use for matching incoming requests. Currently the options are 'path' <xs:documentation>Defines the strategy use for matching incoming requests. Currently the options are 'mvc'
(for PathPatternRequestMatcher), 'regex' for regular expressions and 'ciRegex' for (for Spring MVC matcher), 'ant' (for ant path patterns), 'regex' for regular expressions
case-insensitive regular expressions. and 'ciRegex' for case-insensitive regular expressions.
</xs:documentation> </xs:documentation>
</xs:annotation> </xs:annotation>
<xs:simpleType> <xs:simpleType>
<xs:restriction base="xs:token"> <xs:restriction base="xs:token">
<xs:enumeration value="path"/> <xs:enumeration value="mvc"/>
<xs:enumeration value="ant"/>
<xs:enumeration value="regex"/> <xs:enumeration value="regex"/>
<xs:enumeration value="ciRegex"/> <xs:enumeration value="ciRegex"/>
</xs:restriction> </xs:restriction>
@@ -2577,14 +2580,15 @@
</xs:attribute> </xs:attribute>
<xs:attribute name="request-matcher"> <xs:attribute name="request-matcher">
<xs:annotation> <xs:annotation>
<xs:documentation>Defines the strategy use for matching incoming requests. Currently the options are 'path' <xs:documentation>Defines the strategy use for matching incoming requests. Currently the options are 'mvc'
(for PathPatternRequestMatcher), 'regex' for regular expressions and 'ciRegex' for (for Spring MVC matcher), 'ant' (for ant path patterns), 'regex' for regular expressions
case-insensitive regular expressions. and 'ciRegex' for case-insensitive regular expressions.
</xs:documentation> </xs:documentation>
</xs:annotation> </xs:annotation>
<xs:simpleType> <xs:simpleType>
<xs:restriction base="xs:token"> <xs:restriction base="xs:token">
<xs:enumeration value="path"/> <xs:enumeration value="mvc"/>
<xs:enumeration value="ant"/>
<xs:enumeration value="regex"/> <xs:enumeration value="regex"/>
<xs:enumeration value="ciRegex"/> <xs:enumeration value="ciRegex"/>
</xs:restriction> </xs:restriction>
@@ -20,7 +20,6 @@ import java.io.IOException;
import java.io.Serializable; import java.io.Serializable;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.security.Principal; import java.security.Principal;
import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.util.Collection; import java.util.Collection;
import java.util.Date; import java.util.Date;
@@ -86,9 +85,6 @@ import org.springframework.security.authentication.password.CompromisedPasswordE
import org.springframework.security.authorization.AuthorityAuthorizationDecision; import org.springframework.security.authorization.AuthorityAuthorizationDecision;
import org.springframework.security.authorization.AuthorizationDecision; import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationDeniedException; import org.springframework.security.authorization.AuthorizationDeniedException;
import org.springframework.security.authorization.FactorAuthorizationDecision;
import org.springframework.security.authorization.RequiredFactor;
import org.springframework.security.authorization.RequiredFactorError;
import org.springframework.security.authorization.event.AuthorizationEvent; import org.springframework.security.authorization.event.AuthorizationEvent;
import org.springframework.security.authorization.event.AuthorizationGrantedEvent; import org.springframework.security.authorization.event.AuthorizationGrantedEvent;
import org.springframework.security.cas.authentication.CasAssertionAuthenticationToken; import org.springframework.security.cas.authentication.CasAssertionAuthenticationToken;
@@ -165,7 +161,6 @@ import org.springframework.security.oauth2.jwt.JwtException;
import org.springframework.security.oauth2.jwt.JwtValidationException; import org.springframework.security.oauth2.jwt.JwtValidationException;
import org.springframework.security.oauth2.jwt.TestJwts; import org.springframework.security.oauth2.jwt.TestJwts;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationServerMetadata; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationServerMetadata;
import org.springframework.security.oauth2.server.authorization.OAuth2ClientRegistration; import org.springframework.security.oauth2.server.authorization.OAuth2ClientRegistration;
@@ -173,22 +168,15 @@ import org.springframework.security.oauth2.server.authorization.OAuth2TokenIntro
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationException;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationToken; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationToken; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientRegistrationAuthenticationToken; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientRegistrationAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceAuthorizationConsentAuthenticationToken; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceAuthorizationConsentAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceAuthorizationRequestAuthenticationToken; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceAuthorizationRequestAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceCodeAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceVerificationAuthenticationToken; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceVerificationAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2PushedAuthorizationRequestAuthenticationToken; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2PushedAuthorizationRequestAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2RefreshTokenAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeActor;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeCompositeAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenIntrospectionAuthenticationToken; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenIntrospectionAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenRevocationAuthenticationToken; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenRevocationAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
@@ -202,7 +190,6 @@ import org.springframework.security.oauth2.server.authorization.settings.Authori
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat; import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings; import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimNames;
import org.springframework.security.oauth2.server.resource.BearerTokenError; import org.springframework.security.oauth2.server.resource.BearerTokenError;
import org.springframework.security.oauth2.server.resource.BearerTokenErrors; import org.springframework.security.oauth2.server.resource.BearerTokenErrors;
import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException; import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;
@@ -262,7 +249,6 @@ import org.springframework.security.web.webauthn.api.AuthenticationExtensionsCli
import org.springframework.security.web.webauthn.api.AuthenticationExtensionsClientOutputs; import org.springframework.security.web.webauthn.api.AuthenticationExtensionsClientOutputs;
import org.springframework.security.web.webauthn.api.AuthenticatorAssertionResponse; import org.springframework.security.web.webauthn.api.AuthenticatorAssertionResponse;
import org.springframework.security.web.webauthn.api.AuthenticatorAttachment; import org.springframework.security.web.webauthn.api.AuthenticatorAttachment;
import org.springframework.security.web.webauthn.api.AuthenticatorAttestationResponse;
import org.springframework.security.web.webauthn.api.AuthenticatorTransport; import org.springframework.security.web.webauthn.api.AuthenticatorTransport;
import org.springframework.security.web.webauthn.api.Bytes; import org.springframework.security.web.webauthn.api.Bytes;
import org.springframework.security.web.webauthn.api.CredProtectAuthenticationExtensionsClientInput; import org.springframework.security.web.webauthn.api.CredProtectAuthenticationExtensionsClientInput;
@@ -277,7 +263,6 @@ import org.springframework.security.web.webauthn.api.PublicKeyCredentialRequestO
import org.springframework.security.web.webauthn.api.PublicKeyCredentialType; import org.springframework.security.web.webauthn.api.PublicKeyCredentialType;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity; import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity;
import org.springframework.security.web.webauthn.api.TestAuthenticationAssertionResponses; import org.springframework.security.web.webauthn.api.TestAuthenticationAssertionResponses;
import org.springframework.security.web.webauthn.api.TestAuthenticatorAttestationResponses;
import org.springframework.security.web.webauthn.api.TestBytes; import org.springframework.security.web.webauthn.api.TestBytes;
import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialRequestOptions; import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialRequestOptions;
import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialUserEntities; import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialUserEntities;
@@ -442,8 +427,6 @@ final class SerializationSamples {
generatorByClassName.put(RegisteredClient.class, (r) -> registeredClient); generatorByClassName.put(RegisteredClient.class, (r) -> registeredClient);
generatorByClassName.put(OAuth2Authorization.class, (r) -> authorization); generatorByClassName.put(OAuth2Authorization.class, (r) -> authorization);
generatorByClassName.put(OAuth2Authorization.Token.class, (r) -> authorization.getAccessToken()); generatorByClassName.put(OAuth2Authorization.Token.class, (r) -> authorization.getAccessToken());
generatorByClassName.put(OAuth2AuthorizationCode.class,
(r) -> new OAuth2AuthorizationCode("code", Instant.now(), Instant.now().plusSeconds(300)));
generatorByClassName.put(OAuth2AuthorizationConsent.class, generatorByClassName.put(OAuth2AuthorizationConsent.class,
(r) -> OAuth2AuthorizationConsent.withId("registeredClientId", "principalName") (r) -> OAuth2AuthorizationConsent.withId("registeredClientId", "principalName")
.scope("scope1") .scope("scope1")
@@ -469,58 +452,6 @@ final class SerializationSamples {
authenticationToken.setDetails(details); authenticationToken.setDetails(details);
return authenticationToken; return authenticationToken;
}); });
generatorByClassName.put(
org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationToken.class,
(r) -> {
org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationToken token = new org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationToken(
"code", principal, "https://localhost/callback", Map.of("custom_param", "custom_value"));
token.setDetails(details);
return token;
});
generatorByClassName.put(OAuth2AuthorizationCodeRequestAuthenticationException.class, (r) -> {
OAuth2AuthorizationCodeRequestAuthenticationToken authToken = new OAuth2AuthorizationCodeRequestAuthenticationToken(
"https://localhost/authorize", "clientId", principal, "https://localhost/callback", "state",
authorizationRequest.getScopes(), authorizationRequest.getAdditionalParameters());
return new OAuth2AuthorizationCodeRequestAuthenticationException(
new OAuth2Error("invalid_request", "Missing required parameter", "https://example.com/error"),
authToken);
});
generatorByClassName.put(OAuth2ClientCredentialsAuthenticationToken.class, (r) -> {
OAuth2ClientCredentialsAuthenticationToken token = new OAuth2ClientCredentialsAuthenticationToken(principal,
Set.of("scope1", "scope2"), Map.of("custom_param", "custom_value"));
token.setDetails(details);
return token;
});
generatorByClassName.put(OAuth2DeviceCodeAuthenticationToken.class, (r) -> {
OAuth2DeviceCodeAuthenticationToken token = new OAuth2DeviceCodeAuthenticationToken("device-code",
principal, Map.of("custom_param", "custom_value"));
token.setDetails(details);
return token;
});
generatorByClassName.put(OAuth2RefreshTokenAuthenticationToken.class, (r) -> {
OAuth2RefreshTokenAuthenticationToken token = new OAuth2RefreshTokenAuthenticationToken("refresh-token",
principal, Set.of("scope1", "scope2"), Map.of("custom_param", "custom_value"));
token.setDetails(details);
return token;
});
generatorByClassName.put(OAuth2TokenExchangeAuthenticationToken.class, (r) -> {
OAuth2TokenExchangeAuthenticationToken token = new OAuth2TokenExchangeAuthenticationToken(
"urn:ietf:params:oauth:token-type:access_token", "subject-token",
"urn:ietf:params:oauth:token-type:jwt", principal, "actor-token",
"urn:ietf:params:oauth:token-type:jwt", Set.of("https://resource.example.com"), Set.of("audience"),
Set.of("scope1"), Map.of("custom_param", "custom_value"));
token.setDetails(details);
return token;
});
OAuth2TokenExchangeActor actor = new OAuth2TokenExchangeActor(Map.of(OAuth2TokenClaimNames.ISS,
"https://issuer.example.com", OAuth2TokenClaimNames.SUB, "actor-subject"));
generatorByClassName.put(OAuth2TokenExchangeActor.class, (r) -> actor);
generatorByClassName.put(OAuth2TokenExchangeCompositeAuthenticationToken.class, (r) -> {
AbstractAuthenticationToken token = new OAuth2TokenExchangeCompositeAuthenticationToken(authentication,
List.of(actor));
token.setDetails(details);
return token;
});
generatorByClassName.put(OAuth2AuthorizationConsentAuthenticationToken.class, (r) -> { generatorByClassName.put(OAuth2AuthorizationConsentAuthenticationToken.class, (r) -> {
OAuth2AuthorizationConsentAuthenticationToken authenticationToken = new OAuth2AuthorizationConsentAuthenticationToken( OAuth2AuthorizationConsentAuthenticationToken authenticationToken = new OAuth2AuthorizationConsentAuthenticationToken(
"authorizationUri", "clientId", principal, "state", authorizationRequest.getScopes(), "authorizationUri", "clientId", principal, "state", authorizationRequest.getScopes(),
@@ -737,12 +668,6 @@ final class SerializationSamples {
generatorByClassName.put(AuthorizationDecision.class, (r) -> new AuthorizationDecision(true)); generatorByClassName.put(AuthorizationDecision.class, (r) -> new AuthorizationDecision(true));
generatorByClassName.put(AuthorityAuthorizationDecision.class, generatorByClassName.put(AuthorityAuthorizationDecision.class,
(r) -> new AuthorityAuthorizationDecision(true, AuthorityUtils.createAuthorityList("ROLE_USER"))); (r) -> new AuthorityAuthorizationDecision(true, AuthorityUtils.createAuthorityList("ROLE_USER")));
RequiredFactor factor = RequiredFactor.withAuthority("authority").validDuration(Duration.ofSeconds(5)).build();
generatorByClassName.put(RequiredFactor.class, (r) -> factor);
RequiredFactorError error = RequiredFactorError.createMissing(factor);
generatorByClassName.put(RequiredFactorError.class, (r) -> error);
generatorByClassName.put(FactorAuthorizationDecision.class,
(r) -> new FactorAuthorizationDecision(List.of(error)));
generatorByClassName.put(CycleInRoleHierarchyException.class, (r) -> new CycleInRoleHierarchyException()); generatorByClassName.put(CycleInRoleHierarchyException.class, (r) -> new CycleInRoleHierarchyException());
generatorByClassName.put(AuthorizationEvent.class, generatorByClassName.put(AuthorizationEvent.class,
(r) -> new AuthorizationEvent(new SerializableSupplier<>(authentication), "source", (r) -> new AuthorizationEvent(new SerializableSupplier<>(authentication), "source",
@@ -933,8 +858,6 @@ final class SerializationSamples {
generatorByClassName.put(CredentialPropertiesOutput.class, (o) -> credentialOutput); generatorByClassName.put(CredentialPropertiesOutput.class, (o) -> credentialOutput);
generatorByClassName.put(ImmutableAuthenticationExtensionsClientOutputs.class, (o) -> outputs); generatorByClassName.put(ImmutableAuthenticationExtensionsClientOutputs.class, (o) -> outputs);
generatorByClassName.put(AuthenticatorAssertionResponse.class, (r) -> response); generatorByClassName.put(AuthenticatorAssertionResponse.class, (r) -> response);
generatorByClassName.put(AuthenticatorAttestationResponse.class,
(r) -> TestAuthenticatorAttestationResponses.createAuthenticatorAttestationResponse().build());
generatorByClassName.put(RelyingPartyAuthenticationRequest.class, (r) -> authRequest); generatorByClassName.put(RelyingPartyAuthenticationRequest.class, (r) -> authRequest);
generatorByClassName.put(PublicKeyCredential.class, (r) -> credential); generatorByClassName.put(PublicKeyCredential.class, (r) -> credential);
generatorByClassName.put(WebAuthnAuthenticationRequestToken.class, (r) -> requestToken); generatorByClassName.put(WebAuthnAuthenticationRequestToken.class, (r) -> requestToken);
@@ -33,10 +33,10 @@ import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.ObjectUtils;
@@ -207,7 +207,10 @@ class SpringSecurityCoreVersionSerializableTests {
boolean hasSerialVersion = Stream.of(clazz.getDeclaredFields()) boolean hasSerialVersion = Stream.of(clazz.getDeclaredFields())
.map(Field::getName) .map(Field::getName)
.anyMatch((n) -> n.equals("serialVersionUID")); .anyMatch((n) -> n.equals("serialVersionUID"));
if (!hasSerialVersion && !hasSuppressSerialInSource(clazz)) { SuppressWarnings suppressWarnings = clazz.getAnnotation(SuppressWarnings.class);
boolean hasSerialIgnore = suppressWarnings == null
|| Arrays.asList(suppressWarnings.value()).contains("Serial");
if (!hasSerialVersion && !hasSerialIgnore) {
classes.add(clazz); classes.add(clazz);
continue; continue;
} }
@@ -246,62 +249,11 @@ class SpringSecurityCoreVersionSerializableTests {
return classes.stream(); return classes.stream();
} }
private static boolean hasSuppressSerialInSource(Class<?> clazz) {
try {
Class<?> fileClass = clazz;
while (fileClass.getEnclosingClass() != null) {
fileClass = fileClass.getEnclosingClass();
}
var codeSource = fileClass.getProtectionDomain().getCodeSource();
if (codeSource == null) {
return false;
}
Path sourceFile = findSourceFile(Path.of(codeSource.getLocation().toURI()), fileClass);
if (sourceFile == null) {
return false;
}
return hasSuppressSerialAnnotation(Files.readAllLines(sourceFile), clazz.getSimpleName());
}
catch (Exception ex) {
return false;
}
}
private static Path findSourceFile(Path start, Class<?> clazz) {
String relativePath = clazz.getName().replace('.', '/') + ".java";
Path dir = start;
for (int i = 0; i < 10 && dir != null; i++) {
for (String sourceRoot : List.of("src/main/java", "src/test/java")) {
Path candidate = dir.resolve(sourceRoot).resolve(relativePath);
if (Files.exists(candidate)) {
return candidate;
}
}
dir = dir.getParent();
}
return null;
}
private static boolean hasSuppressSerialAnnotation(List<String> lines, String simpleClassName) {
Pattern classDeclaration = Pattern
.compile("\\b(?:class|interface|enum|record)\\s+" + Pattern.quote(simpleClassName) + "\\b");
for (int i = 0; i < lines.size(); i++) {
if (classDeclaration.matcher(lines.get(i)).find()) {
for (int j = Math.max(0, i - 5); j < i; j++) {
String line = lines.get(j);
if (line.contains("@SuppressWarnings") && line.contains("\"serial\"")) {
return true;
}
}
}
}
return false;
}
private static String getCurrentVersion() { private static String getCurrentVersion() {
String version = System.getProperty("springSecurityVersion"); String version = System.getProperty("springSecurityVersion");
String[] parts = version.split("\\."); String[] parts = version.split("\\.");
return parts[0] + "." + parts[1] + ".x"; parts[2] = "x";
return String.join(".", parts);
} }
private static String getPreviousVersion() { private static String getPreviousVersion() {
@@ -314,7 +266,8 @@ class SpringSecurityCoreVersionSerializableTests {
parts[0] = String.valueOf(Integer.parseInt(parts[0]) - 1); parts[0] = String.valueOf(Integer.parseInt(parts[0]) - 1);
parts[1] = "5"; // FIXME: this should not be hard coded parts[1] = "5"; // FIXME: this should not be hard coded
} }
return parts[0] + "." + parts[1] + ".x"; parts[2] = "x";
return String.join(".", parts);
} }
} }
@@ -37,7 +37,6 @@ import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockServletContext; import org.springframework.mock.web.MockServletContext;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.web.PathPatternRequestMatcherBuilderFactoryBean;
import org.springframework.security.core.userdetails.PasswordEncodedUser; import org.springframework.security.core.userdetails.PasswordEncodedUser;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.provisioning.InMemoryUserDetailsManager;
@@ -111,21 +110,6 @@ public class WebSecurityTests {
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_BAD_REQUEST); assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_BAD_REQUEST);
} }
// gh-19128
@Test
public void ignoringWhenBuilderBeanWithBasePathThenHonorsBasePath() throws Exception {
loadConfig(IgnoringBuilderBeanConfig.class);
this.request.setServletPath("/spring");
this.request.setRequestURI("/spring/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
setup();
this.request.setServletPath("");
this.request.setRequestURI("/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
}
public void loadConfig(Class<?>... configs) { public void loadConfig(Class<?>... configs) {
this.context = new AnnotationConfigWebApplicationContext(); this.context = new AnnotationConfigWebApplicationContext();
this.context.register(configs); this.context.register(configs);
@@ -217,52 +201,6 @@ public class WebSecurityTests {
} }
// gh-19128
@EnableWebSecurity
@Configuration
@EnableWebMvc
static class IgnoringBuilderBeanConfig {
@Bean
PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() {
PathPatternRequestMatcherBuilderFactoryBean bean = new PathPatternRequestMatcherBuilderFactoryBean();
bean.setBasePath("/spring");
return bean;
}
@Bean
WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().requestMatchers("/path");
}
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.httpBasic(withDefaults())
.authorizeHttpRequests((requests) -> requests
.anyRequest().denyAll());
// @formatter:on
return http.build();
}
@Bean
UserDetailsService userDetailsService() {
return new InMemoryUserDetailsManager(PasswordEncodedUser.user());
}
@RestController
static class PathController {
@RequestMapping("/path")
String path() {
return "path";
}
}
}
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
static class RequestRejectedHandlerConfig { static class RequestRejectedHandlerConfig {
@@ -452,18 +452,6 @@ public class AuthorizeHttpRequestsConfigurerTests {
this.mvc.perform(requestWithAdmin).andExpect(status().isOk()); this.mvc.perform(requestWithAdmin).andExpect(status().isOk());
} }
@Test
public void requestMatchersWhenBuilderBeanWithBasePathAndRawStringThenHonorsBasePath() throws Exception {
this.spring.register(RequestMatchersRawStringServletPathConfig.class, BasicController.class).autowire();
// @formatter:off
MockHttpServletRequestBuilder matchedByBasePath = get("/spring/path")
.servletPath("/spring")
.with(user("user").roles("USER"));
// @formatter:on
this.mvc.perform(matchedByBasePath).andExpect(status().isForbidden());
this.mvc.perform(get("/path").with(user("user").roles("USER"))).andExpect(status().isOk());
}
@Test @Test
public void getWhenAnyRequestAuthenticatedConfiguredAndNoUserThenRespondsWithUnauthorized() throws Exception { public void getWhenAnyRequestAuthenticatedConfiguredAndNoUserThenRespondsWithUnauthorized() throws Exception {
this.spring.register(AuthenticatedConfig.class, BasicController.class).autowire(); this.spring.register(AuthenticatedConfig.class, BasicController.class).autowire();
@@ -1352,7 +1340,7 @@ public class AuthorizeHttpRequestsConfigurerTests {
static class ServletPathConfig { static class ServletPathConfig {
@Bean @Bean
PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() { PathPatternRequestMatcherBuilderFactoryBean requesMatcherBuilder() {
PathPatternRequestMatcherBuilderFactoryBean bean = new PathPatternRequestMatcherBuilderFactoryBean(); PathPatternRequestMatcherBuilderFactoryBean bean = new PathPatternRequestMatcherBuilderFactoryBean();
bean.setBasePath("/spring"); bean.setBasePath("/spring");
return bean; return bean;
@@ -1371,32 +1359,6 @@ public class AuthorizeHttpRequestsConfigurerTests {
} }
@Configuration
@EnableWebMvc
@EnableWebSecurity
static class RequestMatchersRawStringServletPathConfig {
@Bean
PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() {
PathPatternRequestMatcherBuilderFactoryBean bean = new PathPatternRequestMatcherBuilderFactoryBean();
bean.setBasePath("/spring");
return bean;
}
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
return http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/path").hasRole("ADMIN")
.anyRequest().permitAll()
)
.build();
// @formatter:on
}
}
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
static class AuthenticatedConfig { static class AuthenticatedConfig {
@@ -40,7 +40,6 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.config.users.AuthenticationTestConfiguration; import org.springframework.security.config.users.AuthenticationTestConfiguration;
import org.springframework.security.config.web.PathPatternRequestMatcherBuilderFactoryBean;
import org.springframework.security.core.authority.FactorGrantedAuthority; import org.springframework.security.core.authority.FactorGrantedAuthority;
import org.springframework.security.core.context.SecurityContextChangedListener; import org.springframework.security.core.context.SecurityContextChangedListener;
import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.core.context.SecurityContextHolderStrategy;
@@ -154,17 +153,6 @@ public class FormLoginConfigurerTests {
// @formatter:on // @formatter:on
} }
// gh-19128
@Test
public void loginWhenBuilderBeanWithBasePathThenLoginProcessingUrlIgnoresBasePath() throws Exception {
this.spring.register(FormLoginBuilderBeanConfig.class).autowire();
// @formatter:off
this.mockMvc.perform(formLogin().user("invalid"))
.andExpect(status().isFound())
.andExpect(redirectedUrl("/login?error"));
// @formatter:on
}
@Test @Test
public void loginWhenFormLoginConfiguredThenHasDefaultSuccessUrl() throws Exception { public void loginWhenFormLoginConfiguredThenHasDefaultSuccessUrl() throws Exception {
this.spring.register(FormLoginConfig.class).autowire(); this.spring.register(FormLoginConfig.class).autowire();
@@ -531,37 +519,6 @@ public class FormLoginConfigurerTests {
} }
// gh-19128
@Configuration
@EnableWebSecurity
@EnableWebMvc
static class FormLoginBuilderBeanConfig {
@Bean
PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() {
PathPatternRequestMatcherBuilderFactoryBean bean = new PathPatternRequestMatcherBuilderFactoryBean();
bean.setBasePath("/spring");
return bean;
}
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((requests) -> requests
.anyRequest().authenticated())
.formLogin(withDefaults());
// @formatter:on
return http.build();
}
@Bean
UserDetailsService userDetailsService() {
return new InMemoryUserDetailsManager(PasswordEncodedUser.user());
}
}
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
static class FormLoginInLambdaConfig { static class FormLoginInLambdaConfig {
@@ -125,34 +125,6 @@ public class HttpSecuritySecurityMatchersTests {
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
} }
@Test
public void securityMatcherWhenBuilderBeanWithBasePathThenHonorsBasePath() throws Exception {
loadConfig(SecurityMatcherBuilderBeanConfig.class);
this.request.setServletPath("/spring");
this.request.setRequestURI("/spring/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setServletPath("");
this.request.setRequestURI("/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
}
@Test
public void securityMatchersWhenBuilderBeanWithBasePathAndRawStringsThenHonorsBasePath() throws Exception {
loadConfig(SecurityMatchersBuilderBeanConfig.class);
this.request.setServletPath("/spring");
this.request.setRequestURI("/spring/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setServletPath("");
this.request.setRequestURI("/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
}
@Test @Test
public void securityMatchersWhenMultiMvcMatcherInLambdaThenAllPathsAreDenied() throws Exception { public void securityMatchersWhenMultiMvcMatcherInLambdaThenAllPathsAreDenied() throws Exception {
loadConfig(MultiMvcMatcherInLambdaConfig.class); loadConfig(MultiMvcMatcherInLambdaConfig.class);
@@ -458,83 +430,6 @@ public class HttpSecuritySecurityMatchersTests {
} }
@EnableWebSecurity
@Configuration
@EnableWebMvc
@Import(UsersConfig.class)
static class SecurityMatcherBuilderBeanConfig {
@Bean
PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() {
PathPatternRequestMatcherBuilderFactoryBean bean = new PathPatternRequestMatcherBuilderFactoryBean();
bean.setBasePath("/spring");
return bean;
}
@Bean
SecurityFilterChain appSecurity(HttpSecurity http) throws Exception {
// @formatter:off
http
.securityMatcher("/path")
.httpBasic(withDefaults())
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().denyAll());
// @formatter:on
return http.build();
}
@RestController
static class PathController {
@RequestMapping("/path")
String path() {
return "path";
}
}
}
@EnableWebSecurity
@Configuration
@EnableWebMvc
@Import(UsersConfig.class)
static class SecurityMatchersBuilderBeanConfig {
@Bean
PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() {
PathPatternRequestMatcherBuilderFactoryBean bean = new PathPatternRequestMatcherBuilderFactoryBean();
bean.setBasePath("/spring");
return bean;
}
@Bean
SecurityFilterChain appSecurity(HttpSecurity http) throws Exception {
// @formatter:off
http
.securityMatchers((matchers) -> matchers
.requestMatchers("/path")
)
.httpBasic(withDefaults())
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().denyAll()
);
// @formatter:on
return http.build();
}
@RestController
static class PathController {
@RequestMapping("/path")
String path() {
return "path";
}
}
}
@Configuration @Configuration
static class UsersConfig { static class UsersConfig {
@@ -35,7 +35,6 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.config.web.PathPatternRequestMatcherBuilderFactoryBean;
import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.core.userdetails.PasswordEncodedUser; import org.springframework.security.core.userdetails.PasswordEncodedUser;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
@@ -128,17 +127,6 @@ public class LogoutConfigurerTests {
// @formatter:on // @formatter:on
} }
// gh-19128
@Test
public void logoutWhenBuilderBeanWithBasePathThenLogoutUrlIgnoresBasePath() throws Exception {
this.spring.register(LogoutBuilderBeanConfig.class).autowire();
// @formatter:off
this.mvc.perform(post("/logout").with(csrf()))
.andExpect(status().isFound())
.andExpect(redirectedUrl("/login?logout"));
// @formatter:on
}
// SEC-2311 // SEC-2311
@Test @Test
public void logoutWhenGetRequestAndCsrfDisabledThenRedirectsToLogin() throws Exception { public void logoutWhenGetRequestAndCsrfDisabledThenRedirectsToLogin() throws Exception {
@@ -536,29 +524,6 @@ public class LogoutConfigurerTests {
} }
// gh-19128
@Configuration
@EnableWebSecurity
static class LogoutBuilderBeanConfig {
@Bean
PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() {
PathPatternRequestMatcherBuilderFactoryBean bean = new PathPatternRequestMatcherBuilderFactoryBean();
bean.setBasePath("/spring");
return bean;
}
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.logout(withDefaults());
// @formatter:on
return http.build();
}
}
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
static class CsrfDisabledConfig { static class CsrfDisabledConfig {
@@ -26,7 +26,6 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.config.web.PathPatternRequestMatcherBuilderFactoryBean;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
@@ -67,16 +66,6 @@ public class PasswordManagementConfigurerTests {
.andExpect(redirectedUrl("/custom-change-password-page")); .andExpect(redirectedUrl("/custom-change-password-page"));
} }
// gh-19128
@Test
public void changePasswordWhenBuilderBeanWithBasePathThenChangePasswordUrlIgnoresBasePath() throws Exception {
this.spring.register(PasswordManagementBuilderBeanConfig.class).autowire();
this.mvc.perform(get("/.well-known/change-password"))
.andExpect(status().isFound())
.andExpect(redirectedUrl("/change-password"));
}
@Test @Test
public void whenSettingNullChangePasswordPage() { public void whenSettingNullChangePasswordPage() {
PasswordManagementConfigurer configurer = new PasswordManagementConfigurer(); PasswordManagementConfigurer configurer = new PasswordManagementConfigurer();
@@ -113,29 +102,6 @@ public class PasswordManagementConfigurerTests {
} }
// gh-19128
@Configuration
@EnableWebSecurity
static class PasswordManagementBuilderBeanConfig {
@Bean
PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() {
PathPatternRequestMatcherBuilderFactoryBean bean = new PathPatternRequestMatcherBuilderFactoryBean();
bean.setBasePath("/spring");
return bean;
}
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
return http
.passwordManagement(withDefaults())
.build();
// @formatter:on
}
}
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
static class PasswordManagementWithCustomChangePasswordPageConfig { static class PasswordManagementWithCustomChangePasswordPageConfig {
@@ -34,7 +34,6 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.config.web.PathPatternRequestMatcherBuilderFactoryBean;
import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.User;
import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.test.web.servlet.RequestCacheResultMatcher; import org.springframework.security.test.web.servlet.RequestCacheResultMatcher;
@@ -186,21 +185,6 @@ public class RequestCacheConfigurerTests {
this.mvc.perform(formLogin(session)).andExpect(redirectedUrl("/")); this.mvc.perform(formLogin(session)).andExpect(redirectedUrl("/"));
} }
// gh-19128
@Test
public void getWhenBuilderBeanWithBasePathThenSavedRequestMatcherIgnoresBasePath() throws Exception {
this.spring.register(RequestCacheBuilderBeanConfig.class, DefaultSecurityConfig.class).autowire();
MockHttpServletRequestBuilder request = get("/messages").header(HttpHeaders.ACCEPT, MediaType.TEXT_HTML);
// @formatter:off
MockHttpSession session = (MockHttpSession) this.mvc.perform(request)
.andExpect(redirectedUrl("/login"))
.andReturn()
.getRequest()
.getSession();
// @formatter:on
this.mvc.perform(formLogin(session)).andExpect(RequestCacheResultMatcher.redirectToCachedRequest());
}
@Test @Test
public void getWhenBookmarkedRequestIsAllMediaTypeThenPostAuthenticationRemembers() throws Exception { public void getWhenBookmarkedRequestIsAllMediaTypeThenPostAuthenticationRemembers() throws Exception {
this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire(); this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire();
@@ -417,31 +401,6 @@ public class RequestCacheConfigurerTests {
} }
// gh-19128
@Configuration
@EnableWebSecurity
static class RequestCacheBuilderBeanConfig {
@Bean
PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() {
PathPatternRequestMatcherBuilderFactoryBean bean = new PathPatternRequestMatcherBuilderFactoryBean();
bean.setBasePath("/spring");
return bean;
}
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((requests) -> requests
.anyRequest().authenticated())
.formLogin(withDefaults());
// @formatter:on
return http.build();
}
}
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
static class RequestCacheDisabledConfig { static class RequestCacheDisabledConfig {
@@ -19,13 +19,10 @@ package org.springframework.security.config.annotation.web.configurers;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.List; import java.util.List;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpOutputMessage; import org.springframework.http.HttpOutputMessage;
@@ -43,16 +40,8 @@ import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.ui.DefaultResourcesFilter; import org.springframework.security.web.authentication.ui.DefaultResourcesFilter;
import org.springframework.security.web.webauthn.api.Bytes;
import org.springframework.security.web.webauthn.api.ImmutablePublicKeyCredentialUserEntity;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions; import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions;
import org.springframework.security.web.webauthn.api.TestCredentialRecords;
import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialCreationOptions; import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialCreationOptions;
import org.springframework.security.web.webauthn.authentication.WebAuthnAuthenticationFilter;
import org.springframework.security.web.webauthn.management.MapPublicKeyCredentialUserEntityRepository;
import org.springframework.security.web.webauthn.management.MapUserCredentialRepository;
import org.springframework.security.web.webauthn.management.PublicKeyCredentialUserEntityRepository;
import org.springframework.security.web.webauthn.management.UserCredentialRepository;
import org.springframework.security.web.webauthn.management.WebAuthnRelyingPartyOperations; import org.springframework.security.web.webauthn.management.WebAuthnRelyingPartyOperations;
import org.springframework.security.web.webauthn.registration.HttpSessionPublicKeyCredentialCreationOptionsRepository; import org.springframework.security.web.webauthn.registration.HttpSessionPublicKeyCredentialCreationOptionsRepository;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
@@ -63,9 +52,6 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.willAnswer; import static org.mockito.BDDMockito.willAnswer;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
@@ -102,14 +88,6 @@ public class WebAuthnConfigurerTests {
.andExpect(content().string(containsString("body {"))); .andExpect(content().string(containsString("body {")));
} }
// gh-18128
@Test
public void webAuthnAuthenticationFilterIsPostProcessed() throws Exception {
this.spring.register(DefaultWebauthnConfiguration.class, PostProcessorConfiguration.class).autowire();
PostProcessorConfiguration postProcess = this.spring.getContext().getBean(PostProcessorConfiguration.class);
assertThat(postProcess.webauthnFilter).isNotNull();
}
@Test @Test
public void webauthnWhenNoFormLoginAndDefaultRegistrationPageConfiguredThenServesJavascript() throws Exception { public void webauthnWhenNoFormLoginAndDefaultRegistrationPageConfiguredThenServesJavascript() throws Exception {
this.spring.register(NoFormLoginAndDefaultRegistrationPageConfiguration.class).autowire(); this.spring.register(NoFormLoginAndDefaultRegistrationPageConfiguration.class).autowire();
@@ -149,42 +127,6 @@ public class WebAuthnConfigurerTests {
.hasSize(1); .hasSize(1);
} }
@Test
void webauthnWhenConfiguredDefaultsRpNameToRpId() throws Exception {
ObjectMapper mapper = new ObjectMapper();
this.spring.register(DefaultWebauthnConfiguration.class).autowire();
String response = this.mvc
.perform(post("/webauthn/register/options").with(csrf())
.with(authentication(new TestingAuthenticationToken("test", "ignored", "ROLE_user"))))
.andExpect(status().is2xxSuccessful())
.andReturn()
.getResponse()
.getContentAsString();
JsonNode parsedResponse = mapper.readTree(response);
assertThat(parsedResponse.get("rp").get("id").asText()).isEqualTo("example.com");
assertThat(parsedResponse.get("rp").get("name").asText()).isEqualTo("example.com");
}
@Test
void webauthnWhenRpNameConfiguredUsesRpName() throws Exception {
ObjectMapper mapper = new ObjectMapper();
this.spring.register(CustomRpNameWebauthnConfiguration.class).autowire();
String response = this.mvc
.perform(post("/webauthn/register/options").with(csrf())
.with(authentication(new TestingAuthenticationToken("test", "ignored", "ROLE_user"))))
.andExpect(status().is2xxSuccessful())
.andReturn()
.getResponse()
.getContentAsString();
JsonNode parsedResponse = mapper.readTree(response);
assertThat(parsedResponse.get("rp").get("id").asText()).isEqualTo("example.com");
assertThat(parsedResponse.get("rp").get("name").asText()).isEqualTo("Test RP Name");
}
@Test @Test
public void webauthnWhenConfiguredAndFormLoginThenDoesServesJavascript() throws Exception { public void webauthnWhenConfiguredAndFormLoginThenDoesServesJavascript() throws Exception {
this.spring.register(FormLoginAndNoDefaultRegistrationPageConfiguration.class).autowire(); this.spring.register(FormLoginAndNoDefaultRegistrationPageConfiguration.class).autowire();
@@ -265,24 +207,6 @@ public class WebAuthnConfigurerTests {
.andExpect(content().string(expectedBody)); .andExpect(content().string(expectedBody));
} }
@Test
void webauthnWhenDeleteAndCredentialBelongsToUserThenNoContent() throws Exception {
this.spring.register(DeleteCredentialConfiguration.class).autowire();
this.mvc
.perform(delete("/webauthn/register/" + DeleteCredentialConfiguration.CREDENTIAL_ID_BASE64URL)
.with(authentication(new TestingAuthenticationToken("user", "password", "ROLE_USER"))))
.andExpect(status().isNoContent());
}
@Test
void webauthnWhenDeleteAndCredentialBelongsToDifferentUserThenForbidden() throws Exception {
this.spring.register(DeleteCredentialConfiguration.class).autowire();
this.mvc
.perform(delete("/webauthn/register/" + DeleteCredentialConfiguration.CREDENTIAL_ID_BASE64URL)
.with(authentication(new TestingAuthenticationToken("other-user", "password", "ROLE_USER"))))
.andExpect(status().isForbidden());
}
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
static class ConfigCredentialCreationOptionsRepository { static class ConfigCredentialCreationOptionsRepository {
@@ -365,26 +289,6 @@ public class WebAuthnConfigurerTests {
} }
@Configuration(proxyBeanMethods = false)
static class PostProcessorConfiguration {
WebAuthnAuthenticationFilter webauthnFilter;
@Bean
BeanPostProcessor beanPostProcessor() {
return new BeanPostProcessor() {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean instanceof WebAuthnAuthenticationFilter filter) {
PostProcessorConfiguration.this.webauthnFilter = filter;
}
return bean;
}
};
}
}
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
static class DefaultWebauthnConfiguration { static class DefaultWebauthnConfiguration {
@@ -400,7 +304,8 @@ public class WebAuthnConfigurerTests {
http http
.formLogin(Customizer.withDefaults()) .formLogin(Customizer.withDefaults())
.webAuthn((authn) -> authn .webAuthn((authn) -> authn
.rpId("example.com") .rpId("spring.io")
.rpName("spring")
); );
// @formatter:on // @formatter:on
return http.build(); return http.build();
@@ -408,24 +313,6 @@ public class WebAuthnConfigurerTests {
} }
@Configuration
@EnableWebSecurity
static class CustomRpNameWebauthnConfiguration {
@Bean
UserDetailsService userDetailsService() {
return new InMemoryUserDetailsManager();
}
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http.formLogin(Customizer.withDefaults())
.webAuthn((webauthn) -> webauthn.rpId("example.com").rpName("Test RP Name"))
.build();
}
}
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
static class NoFormLoginAndDefaultRegistrationPageConfiguration { static class NoFormLoginAndDefaultRegistrationPageConfiguration {
@@ -501,47 +388,4 @@ public class WebAuthnConfigurerTests {
} }
@Configuration
@EnableWebSecurity
static class DeleteCredentialConfiguration {
static final String CREDENTIAL_ID_BASE64URL = "NauGCN7bZ5jEBwThcde51g";
static final Bytes USER_ENTITY_ID = Bytes.fromBase64("vKBFhsWT3gQnn-gHdT4VXIvjDkVXVYg5w8CLGHPunMM");
@Bean
UserDetailsService userDetailsService() {
return new InMemoryUserDetailsManager();
}
@Bean
WebAuthnRelyingPartyOperations webAuthnRelyingPartyOperations() {
return mock(WebAuthnRelyingPartyOperations.class);
}
@Bean
UserCredentialRepository userCredentialRepository() {
MapUserCredentialRepository repository = new MapUserCredentialRepository();
repository.save(TestCredentialRecords.userCredential().build());
return repository;
}
@Bean
PublicKeyCredentialUserEntityRepository userEntityRepository() {
MapPublicKeyCredentialUserEntityRepository repository = new MapPublicKeyCredentialUserEntityRepository();
repository.save(ImmutablePublicKeyCredentialUserEntity.builder()
.name("user")
.id(USER_ENTITY_ID)
.displayName("User")
.build());
return repository;
}
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http.csrf(AbstractHttpConfigurer::disable).webAuthn(Customizer.withDefaults()).build();
}
}
} }
@@ -56,7 +56,6 @@ import org.springframework.security.config.annotation.web.configurers.oauth2.cli
import org.springframework.security.config.oauth2.client.CommonOAuth2Provider; import org.springframework.security.config.oauth2.client.CommonOAuth2Provider;
import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.config.web.PathPatternRequestMatcherBuilderFactoryBean;
import org.springframework.security.context.DelegatingApplicationListener; import org.springframework.security.context.DelegatingApplicationListener;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
@@ -441,18 +440,6 @@ public class OAuth2LoginConfigurerTests {
then(redirectStrategy).should().sendRedirect(any(), any(), anyString()); then(redirectStrategy).should().sendRedirect(any(), any(), anyString());
} }
// gh-19128
@Test
public void oauth2LoginWhenBuilderBeanWithBasePathThenLoginProcessingUrlIgnoresBasePath() throws Exception {
loadConfig(OAuth2LoginBuilderBeanConfig.class);
String requestUri = "/login/oauth2/code/google";
this.request = get(requestUri).build();
this.request.setParameter("code", "code123");
this.request.setParameter("state", "state123");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain);
assertThat(this.response.getRedirectedUrl()).endsWith("/login?error");
}
// gh-5347 // gh-5347
@Test @Test
public void oauth2LoginWithOneClientConfiguredThenRedirectForAuthorization() throws Exception { public void oauth2LoginWithOneClientConfiguredThenRedirectForAuthorization() throws Exception {
@@ -801,31 +788,6 @@ public class OAuth2LoginConfigurerTests {
} }
// gh-19128
@Configuration
@EnableWebSecurity
static class OAuth2LoginBuilderBeanConfig extends CommonSecurityFilterChainConfig {
@Bean
PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() {
PathPatternRequestMatcherBuilderFactoryBean bean = new PathPatternRequestMatcherBuilderFactoryBean();
bean.setBasePath("/spring");
return bean;
}
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.oauth2Login((login) -> login
.clientRegistrationRepository(
new InMemoryClientRegistrationRepository(GOOGLE_CLIENT_REGISTRATION)));
// @formatter:on
return super.configureFilterChain(http);
}
}
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
static class OAuth2LoginConfigFormLogin extends CommonSecurityFilterChainConfig { static class OAuth2LoginConfigFormLogin extends CommonSecurityFilterChainConfig {
@@ -307,8 +307,8 @@ public class OAuth2AuthorizationCodeGrantTests {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
this.mvc this.mvc
.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI) .perform(
.queryParams(getAuthorizationRequestParameters(registeredClient))) get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).params(getAuthorizationRequestParameters(registeredClient)))
.andExpect(status().isBadRequest()) .andExpect(status().isBadRequest())
.andReturn(); .andReturn();
} }
@@ -851,31 +851,21 @@ public class OAuth2AuthorizationCodeGrantTests {
this.spring.register(AuthorizationServerConfigurationCustomAuthorizationEndpoint.class).autowire(); this.spring.register(AuthorizationServerConfigurationCustomAuthorizationEndpoint.class).autowire();
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
this.registeredClientRepository.save(registeredClient);
TestingAuthenticationToken principal = new TestingAuthenticationToken("principalName", "password"); TestingAuthenticationToken principal = new TestingAuthenticationToken("principalName", "password");
Map<String, Object> additionalParameters = new HashMap<>();
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, S256_CODE_CHALLENGE);
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256");
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication = new OAuth2AuthorizationCodeRequestAuthenticationToken(
"https://provider.com/oauth2/authorize", registeredClient.getClientId(), principal,
registeredClient.getRedirectUris().iterator().next(), STATE_URL_UNENCODED, registeredClient.getScopes(),
additionalParameters);
OAuth2AuthorizationCode authorizationCode = new OAuth2AuthorizationCode("code", Instant.now(), OAuth2AuthorizationCode authorizationCode = new OAuth2AuthorizationCode("code", Instant.now(),
Instant.now().plus(5, ChronoUnit.MINUTES)); Instant.now().plus(5, ChronoUnit.MINUTES));
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult = new OAuth2AuthorizationCodeRequestAuthenticationToken( OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult = new OAuth2AuthorizationCodeRequestAuthenticationToken(
"https://provider.com/oauth2/authorize", registeredClient.getClientId(), principal, authorizationCode, "https://provider.com/oauth2/authorize", registeredClient.getClientId(), principal, authorizationCode,
registeredClient.getRedirectUris().iterator().next(), STATE_URL_UNENCODED, registeredClient.getRedirectUris().iterator().next(), STATE_URL_UNENCODED,
registeredClient.getScopes()); registeredClient.getScopes());
given(authorizationRequestConverter.convert(any())).willReturn(authorizationCodeRequestAuthentication); given(authorizationRequestConverter.convert(any())).willReturn(authorizationCodeRequestAuthenticationResult);
given(authorizationRequestAuthenticationProvider given(authorizationRequestAuthenticationProvider
.supports(eq(OAuth2AuthorizationCodeRequestAuthenticationToken.class))).willReturn(true); .supports(eq(OAuth2AuthorizationCodeRequestAuthenticationToken.class))).willReturn(true);
given(authorizationRequestAuthenticationProvider.authenticate(any())) given(authorizationRequestAuthenticationProvider.authenticate(any()))
.willReturn(authorizationCodeRequestAuthenticationResult); .willReturn(authorizationCodeRequestAuthenticationResult);
this.mvc this.mvc
.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI) .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).params(getAuthorizationRequestParameters(registeredClient))
.queryParams(getAuthorizationRequestParameters(registeredClient))
.with(user("user"))) .with(user("user")))
.andExpect(status().isOk()); .andExpect(status().isOk());
@@ -890,7 +880,8 @@ public class OAuth2AuthorizationCodeGrantTests {
|| converter instanceof OAuth2AuthorizationCodeRequestAuthenticationConverter || converter instanceof OAuth2AuthorizationCodeRequestAuthenticationConverter
|| converter instanceof OAuth2AuthorizationConsentAuthenticationConverter); || converter instanceof OAuth2AuthorizationConsentAuthenticationConverter);
verify(authorizationRequestAuthenticationProvider).authenticate(eq(authorizationCodeRequestAuthentication)); verify(authorizationRequestAuthenticationProvider)
.authenticate(eq(authorizationCodeRequestAuthenticationResult));
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
ArgumentCaptor<List<AuthenticationProvider>> authenticationProvidersCaptor = ArgumentCaptor ArgumentCaptor<List<AuthenticationProvider>> authenticationProvidersCaptor = ArgumentCaptor
@@ -45,7 +45,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Primary;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
@@ -159,8 +158,6 @@ public class OAuth2ClientCredentialsGrantTests {
private static AuthenticationFailureHandler authenticationFailureHandler; private static AuthenticationFailureHandler authenticationFailureHandler;
private static PasswordEncoder passwordEncoder;
public final SpringTestContext spring = new SpringTestContext(this); public final SpringTestContext spring = new SpringTestContext(this);
@Autowired @Autowired
@@ -186,9 +183,6 @@ public class OAuth2ClientCredentialsGrantTests {
authenticationProvidersConsumer = mock(Consumer.class); authenticationProvidersConsumer = mock(Consumer.class);
authenticationSuccessHandler = mock(AuthenticationSuccessHandler.class); authenticationSuccessHandler = mock(AuthenticationSuccessHandler.class);
authenticationFailureHandler = mock(AuthenticationFailureHandler.class); authenticationFailureHandler = mock(AuthenticationFailureHandler.class);
passwordEncoder = mock(PasswordEncoder.class);
given(passwordEncoder.matches(any(), any())).willReturn(true);
given(passwordEncoder.upgradeEncoding(any())).willReturn(false);
db = new EmbeddedDatabaseBuilder().generateUniqueName(true) db = new EmbeddedDatabaseBuilder().generateUniqueName(true)
.setType(EmbeddedDatabaseType.HSQL) .setType(EmbeddedDatabaseType.HSQL)
.setScriptEncoding("UTF-8") .setScriptEncoding("UTF-8")
@@ -502,26 +496,6 @@ public class OAuth2ClientCredentialsGrantTests {
.andExpect(jsonPath("$.token_type").value(OAuth2AccessToken.TokenType.DPOP.getValue())); .andExpect(jsonPath("$.token_type").value(OAuth2AccessToken.TokenType.DPOP.getValue()));
} }
@Test
public void requestWhenTokenRequestWithMultiplePasswordEncodersThenPrimaryPasswordEncoderUsed() throws Exception {
this.spring.register(AuthorizationServerConfigurationWithMultiplePasswordEncoders.class).autowire();
RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build();
this.registeredClientRepository.save(registeredClient);
this.mvc
.perform(post(DEFAULT_TOKEN_ENDPOINT_URI)
.param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
.param(OAuth2ParameterNames.SCOPE, "scope1 scope2")
.header(HttpHeaders.AUTHORIZATION,
"Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret())))
.andExpect(status().isOk())
.andExpect(jsonPath("$.access_token").isNotEmpty())
.andExpect(jsonPath("$.scope").value("scope1 scope2"));
verify(passwordEncoder).matches(any(), any());
}
private static String generateDPoPProof(String tokenEndpointUri) { private static String generateDPoPProof(String tokenEndpointUri) {
// @formatter:off // @formatter:off
Map<String, Object> publicJwk = TestJwks.DEFAULT_EC_JWK Map<String, Object> publicJwk = TestJwks.DEFAULT_EC_JWK
@@ -684,16 +658,4 @@ public class OAuth2ClientCredentialsGrantTests {
} }
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
static class AuthorizationServerConfigurationWithMultiplePasswordEncoders extends AuthorizationServerConfiguration {
@Primary
@Bean
PasswordEncoder primaryPasswordEncoder() {
return passwordEncoder;
}
}
} }
@@ -79,7 +79,6 @@ import org.springframework.security.oauth2.server.authorization.OAuth2Authorizat
import org.springframework.security.oauth2.server.authorization.OAuth2ClientRegistration; import org.springframework.security.oauth2.server.authorization.OAuth2ClientRegistration;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientRegistrationAuthenticationProvider; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientRegistrationAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientRegistrationAuthenticationToken; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientRegistrationAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientRegistrationAuthenticationValidator;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientParametersMapper; import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientParametersMapper;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
@@ -412,102 +411,6 @@ public class OAuth2ClientRegistrationTests {
.isCloseTo(expectedSecretExpiryDate, allowedDelta); .isCloseTo(expectedSecretExpiryDate, allowedDelta);
} }
@Test
public void requestWhenProtocolRelativeRedirectUriThenBadRequest() throws Exception {
this.spring.register(DefaultValidatorConfiguration.class).autowire();
assertThat(requestWhenInvalidClientMetadataThenBadRequest("""
{
"client_name": "client-name",
"redirect_uris": ["//client.example.com/path"],
"grant_types": ["authorization_code"]
}
""")).isEqualTo(HttpStatus.BAD_REQUEST.value());
}
@Test
public void requestWhenJavascriptSchemeRedirectUriThenBadRequest() throws Exception {
this.spring.register(DefaultValidatorConfiguration.class).autowire();
assertThat(requestWhenInvalidClientMetadataThenBadRequest("""
{
"client_name": "client-name",
"redirect_uris": ["javascript:alert(document.cookie)"],
"grant_types": ["authorization_code"]
}
""")).isEqualTo(HttpStatus.BAD_REQUEST.value());
}
@Test
public void requestWhenDataSchemeRedirectUriThenBadRequest() throws Exception {
this.spring.register(DefaultValidatorConfiguration.class).autowire();
assertThat(requestWhenInvalidClientMetadataThenBadRequest("""
{
"client_name": "client-name",
"redirect_uris": ["data:text/html,<h1>content</h1>"],
"grant_types": ["authorization_code"]
}
""")).isEqualTo(HttpStatus.BAD_REQUEST.value());
}
@Test
public void requestWhenHttpJwkSetUriThenBadRequest() throws Exception {
this.spring.register(DefaultValidatorConfiguration.class).autowire();
assertThat(requestWhenInvalidClientMetadataThenBadRequest("""
{
"client_name": "client-name",
"redirect_uris": ["https://client.example.com"],
"grant_types": ["authorization_code"],
"jwks_uri": "http://169.254.169.254/keys",
"token_endpoint_auth_method": "private_key_jwt"
}
""")).isEqualTo(HttpStatus.BAD_REQUEST.value());
}
@Test
public void requestWhenArbitraryScopeThenBadRequest() throws Exception {
this.spring.register(DefaultValidatorConfiguration.class).autowire();
assertThat(requestWhenInvalidClientMetadataThenBadRequest("""
{
"client_name": "client-name",
"redirect_uris": ["https://client.example.com"],
"grant_types": ["client_credentials"],
"scope": "read write"
}
""")).isEqualTo(HttpStatus.BAD_REQUEST.value());
}
private int requestWhenInvalidClientMetadataThenBadRequest(String json) throws Exception {
String clientRegistrationScope = "client.create";
// @formatter:off
RegisteredClient clientRegistrar = RegisteredClient.withId("client-registrar-" + System.nanoTime())
.clientId("client-registrar-" + System.nanoTime())
.clientSecret("{noop}secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.scope(clientRegistrationScope)
.build();
// @formatter:on
this.registeredClientRepository.save(clientRegistrar);
MvcResult tokenResult = this.mvc
.perform(post(ISSUER.concat(DEFAULT_TOKEN_ENDPOINT_URI))
.param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
.param(OAuth2ParameterNames.SCOPE, clientRegistrationScope)
.with(httpBasic(clientRegistrar.getClientId(), "secret")))
.andExpect(status().isOk())
.andReturn();
OAuth2AccessToken accessToken = readAccessTokenResponse(tokenResult.getResponse()).getAccessToken();
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setBearerAuth(accessToken.getTokenValue());
MvcResult registerResult = this.mvc
.perform(post(ISSUER.concat(DEFAULT_OAUTH2_CLIENT_REGISTRATION_ENDPOINT_URI)).headers(httpHeaders)
.contentType(MediaType.APPLICATION_JSON)
.content(json))
.andReturn();
return registerResult.getResponse().getStatus();
}
private OAuth2ClientRegistration registerClient(OAuth2ClientRegistration clientRegistration) throws Exception { private OAuth2ClientRegistration registerClient(OAuth2ClientRegistration clientRegistration) throws Exception {
// ***** (1) Obtain the "initial" access token used for registering the client // ***** (1) Obtain the "initial" access token used for registering the client
@@ -593,17 +496,6 @@ public class OAuth2ClientRegistrationTests {
return clientRegistrationHttpMessageConverter.read(OAuth2ClientRegistration.class, httpResponse); return clientRegistrationHttpMessageConverter.read(OAuth2ClientRegistration.class, httpResponse);
} }
private static Consumer<List<AuthenticationProvider>> scopePermissiveValidatorCustomizer() {
return (authenticationProviders) -> authenticationProviders.forEach((authenticationProvider) -> {
if (authenticationProvider instanceof OAuth2ClientRegistrationAuthenticationProvider provider) {
provider.setAuthenticationValidator(
OAuth2ClientRegistrationAuthenticationValidator.DEFAULT_REDIRECT_URI_VALIDATOR
.andThen(OAuth2ClientRegistrationAuthenticationValidator.DEFAULT_JWK_SET_URI_VALIDATOR)
.andThen(OAuth2ClientRegistrationAuthenticationValidator.SIMPLE_SCOPE_VALIDATOR));
}
});
}
@EnableWebSecurity @EnableWebSecurity
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
static class CustomClientRegistrationConfiguration extends AuthorizationServerConfiguration { static class CustomClientRegistrationConfiguration extends AuthorizationServerConfiguration {
@@ -620,7 +512,7 @@ public class OAuth2ClientRegistrationTests {
.clientRegistrationRequestConverter(authenticationConverter) .clientRegistrationRequestConverter(authenticationConverter)
.clientRegistrationRequestConverters(authenticationConvertersConsumer) .clientRegistrationRequestConverters(authenticationConvertersConsumer)
.authenticationProvider(authenticationProvider) .authenticationProvider(authenticationProvider)
.authenticationProviders(scopePermissiveValidatorCustomizer().andThen(authenticationProvidersConsumer)) .authenticationProviders(authenticationProvidersConsumer)
.clientRegistrationResponseHandler(authenticationSuccessHandler) .clientRegistrationResponseHandler(authenticationSuccessHandler)
.errorResponseHandler(authenticationFailureHandler) .errorResponseHandler(authenticationFailureHandler)
) )
@@ -647,7 +539,7 @@ public class OAuth2ClientRegistrationTests {
authorizationServer authorizationServer
.clientRegistrationEndpoint((clientRegistration) -> .clientRegistrationEndpoint((clientRegistration) ->
clientRegistration clientRegistration
.authenticationProviders(scopePermissiveValidatorCustomizer().andThen(configureClientRegistrationConverters())) .authenticationProviders(configureClientRegistrationConverters())
) )
) )
.authorizeHttpRequests((authorize) -> .authorizeHttpRequests((authorize) ->
@@ -685,7 +577,7 @@ public class OAuth2ClientRegistrationTests {
authorizationServer authorizationServer
.clientRegistrationEndpoint((clientRegistration) -> .clientRegistrationEndpoint((clientRegistration) ->
clientRegistration clientRegistration
.authenticationProviders(scopePermissiveValidatorCustomizer().andThen(configureClientRegistrationConverters())) .authenticationProviders(configureClientRegistrationConverters())
) )
) )
.authorizeHttpRequests((authorize) -> .authorizeHttpRequests((authorize) ->
@@ -722,7 +614,6 @@ public class OAuth2ClientRegistrationTests {
.clientRegistrationEndpoint((clientRegistration) -> .clientRegistrationEndpoint((clientRegistration) ->
clientRegistration clientRegistration
.openRegistrationAllowed(true) .openRegistrationAllowed(true)
.authenticationProviders(scopePermissiveValidatorCustomizer())
) )
) )
.authorizeHttpRequests((authorize) -> .authorizeHttpRequests((authorize) ->
@@ -736,30 +627,6 @@ public class OAuth2ClientRegistrationTests {
} }
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
static class DefaultValidatorConfiguration extends AuthorizationServerConfiguration {
// Override with Customizer.withDefaults() so the default (strict)
// OAuth2ClientRegistrationAuthenticationValidator is in effect.
// @formatter:off
@Bean
@Override
SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
http
.oauth2AuthorizationServer((authorizationServer) ->
authorizationServer
.clientRegistrationEndpoint(Customizer.withDefaults())
)
.authorizeHttpRequests((authorize) ->
authorize.anyRequest().authenticated()
);
return http.build();
}
// @formatter:on
}
@EnableWebSecurity @EnableWebSecurity
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
static class AuthorizationServerConfiguration { static class AuthorizationServerConfiguration {
@@ -770,10 +637,7 @@ public class OAuth2ClientRegistrationTests {
http http
.oauth2AuthorizationServer((authorizationServer) -> .oauth2AuthorizationServer((authorizationServer) ->
authorizationServer authorizationServer
.clientRegistrationEndpoint((clientRegistration) -> .clientRegistrationEndpoint(Customizer.withDefaults())
clientRegistration
.authenticationProviders(scopePermissiveValidatorCustomizer())
)
) )
.authorizeHttpRequests((authorize) -> .authorizeHttpRequests((authorize) ->
authorize.anyRequest().authenticated() authorize.anyRequest().authenticated()
@@ -359,7 +359,7 @@ public class OAuth2DeviceCodeGrantTests {
} }
@Test @Test
public void requestWhenDeviceAuthorizationConsentRequestUnauthenticatedThenUnauthorized() throws Exception { public void requestWhenDeviceAuthorizationConsentRequestUnauthenticatedThenBadRequest() throws Exception {
this.spring.register(AuthorizationServerConfiguration.class).autowire(); this.spring.register(AuthorizationServerConfiguration.class).autowire();
// @formatter:off // @formatter:off
@@ -392,7 +392,7 @@ public class OAuth2DeviceCodeGrantTests {
// @formatter:off // @formatter:off
this.mvc.perform(post(DEFAULT_DEVICE_VERIFICATION_ENDPOINT_URI) this.mvc.perform(post(DEFAULT_DEVICE_VERIFICATION_ENDPOINT_URI)
.params(parameters)) .params(parameters))
.andExpect(status().isUnauthorized()); .andExpect(status().isBadRequest());
// @formatter:on // @formatter:on
} }
@@ -97,7 +97,6 @@ import org.springframework.security.oauth2.server.authorization.oidc.OidcClientR
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientConfigurationAuthenticationProvider; import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientConfigurationAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationProvider; import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationToken; import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationValidator;
import org.springframework.security.oauth2.server.authorization.oidc.converter.OidcClientRegistrationRegisteredClientConverter; import org.springframework.security.oauth2.server.authorization.oidc.converter.OidcClientRegistrationRegisteredClientConverter;
import org.springframework.security.oauth2.server.authorization.oidc.converter.RegisteredClientOidcClientRegistrationConverter; import org.springframework.security.oauth2.server.authorization.oidc.converter.RegisteredClientOidcClientRegistrationConverter;
import org.springframework.security.oauth2.server.authorization.oidc.http.converter.OidcClientRegistrationHttpMessageConverter; import org.springframework.security.oauth2.server.authorization.oidc.http.converter.OidcClientRegistrationHttpMessageConverter;
@@ -546,129 +545,6 @@ public class OidcClientRegistrationTests {
.isCloseTo(expectedSecretExpiryDate, allowedDelta); .isCloseTo(expectedSecretExpiryDate, allowedDelta);
} }
@Test
public void requestWhenProtocolRelativeRedirectUriThenBadRequest() throws Exception {
this.spring.register(DefaultValidatorConfiguration.class).autowire();
assertThat(requestWhenInvalidClientMetadataThenBadRequest("""
{
"client_name": "client-name",
"redirect_uris": ["//client.example.com/path"],
"grant_types": ["authorization_code"]
}
""")).isEqualTo(HttpStatus.BAD_REQUEST.value());
}
@Test
public void requestWhenJavascriptSchemeRedirectUriThenBadRequest() throws Exception {
this.spring.register(DefaultValidatorConfiguration.class).autowire();
assertThat(requestWhenInvalidClientMetadataThenBadRequest("""
{
"client_name": "client-name",
"redirect_uris": ["javascript:alert(document.cookie)"],
"grant_types": ["authorization_code"]
}
""")).isEqualTo(HttpStatus.BAD_REQUEST.value());
}
@Test
public void requestWhenDataSchemeRedirectUriThenBadRequest() throws Exception {
this.spring.register(DefaultValidatorConfiguration.class).autowire();
assertThat(requestWhenInvalidClientMetadataThenBadRequest("""
{
"client_name": "client-name",
"redirect_uris": ["data:text/html,<h1>content</h1>"],
"grant_types": ["authorization_code"]
}
""")).isEqualTo(HttpStatus.BAD_REQUEST.value());
}
@Test
public void requestWhenJavascriptSchemePostLogoutRedirectUriThenBadRequest() throws Exception {
this.spring.register(DefaultValidatorConfiguration.class).autowire();
assertThat(requestWhenInvalidClientMetadataThenBadRequest("""
{
"client_name": "client-name",
"redirect_uris": ["https://client.example.com"],
"post_logout_redirect_uris": ["javascript:alert(document.cookie)"],
"grant_types": ["authorization_code"]
}
""")).isEqualTo(HttpStatus.BAD_REQUEST.value());
}
@Test
public void requestWhenDataSchemePostLogoutRedirectUriThenBadRequest() throws Exception {
this.spring.register(DefaultValidatorConfiguration.class).autowire();
assertThat(requestWhenInvalidClientMetadataThenBadRequest("""
{
"client_name": "client-name",
"redirect_uris": ["https://client.example.com"],
"post_logout_redirect_uris": ["data:text/html,<h1>content</h1>"],
"grant_types": ["authorization_code"]
}
""")).isEqualTo(HttpStatus.BAD_REQUEST.value());
}
@Test
public void requestWhenHttpJwkSetUriThenBadRequest() throws Exception {
this.spring.register(DefaultValidatorConfiguration.class).autowire();
assertThat(requestWhenInvalidClientMetadataThenBadRequest("""
{
"client_name": "client-name",
"redirect_uris": ["https://client.example.com"],
"grant_types": ["authorization_code"],
"jwks_uri": "http://169.254.169.254/keys",
"token_endpoint_auth_method": "private_key_jwt"
}
""")).isEqualTo(HttpStatus.BAD_REQUEST.value());
}
@Test
public void requestWhenArbitraryScopeThenBadRequest() throws Exception {
this.spring.register(DefaultValidatorConfiguration.class).autowire();
assertThat(requestWhenInvalidClientMetadataThenBadRequest("""
{
"client_name": "client-name",
"redirect_uris": ["https://client.example.com"],
"grant_types": ["authorization_code"],
"scope": "read write"
}
""")).isEqualTo(HttpStatus.BAD_REQUEST.value());
}
private int requestWhenInvalidClientMetadataThenBadRequest(String json) throws Exception {
String clientRegistrationScope = "client.create";
String clientId = "client-registrar-" + System.nanoTime();
// @formatter:off
RegisteredClient clientRegistrar = RegisteredClient.withId(clientId)
.clientId(clientId)
.clientSecret("{noop}secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.scope(clientRegistrationScope)
.build();
// @formatter:on
this.registeredClientRepository.save(clientRegistrar);
MvcResult tokenResult = this.mvc
.perform(post(ISSUER.concat(DEFAULT_TOKEN_ENDPOINT_URI))
.param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
.param(OAuth2ParameterNames.SCOPE, clientRegistrationScope)
.with(httpBasic(clientId, "secret")))
.andExpect(status().isOk())
.andReturn();
OAuth2AccessToken accessToken = readAccessTokenResponse(tokenResult.getResponse()).getAccessToken();
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setBearerAuth(accessToken.getTokenValue());
MvcResult registerResult = this.mvc
.perform(post(ISSUER.concat(DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI)).headers(httpHeaders)
.contentType(MediaType.APPLICATION_JSON)
.content(json))
.andReturn();
return registerResult.getResponse().getStatus();
}
private OidcClientRegistration registerClient(OidcClientRegistration clientRegistration) throws Exception { private OidcClientRegistration registerClient(OidcClientRegistration clientRegistration) throws Exception {
// ***** (1) Obtain the "initial" access token used for registering the client // ***** (1) Obtain the "initial" access token used for registering the client
@@ -766,18 +642,6 @@ public class OidcClientRegistrationTests {
return clientRegistrationHttpMessageConverter.read(OidcClientRegistration.class, httpResponse); return clientRegistrationHttpMessageConverter.read(OidcClientRegistration.class, httpResponse);
} }
private static Consumer<List<AuthenticationProvider>> scopePermissiveValidatorCustomizer() {
return (authenticationProviders) -> authenticationProviders.forEach((authenticationProvider) -> {
if (authenticationProvider instanceof OidcClientRegistrationAuthenticationProvider provider) {
provider.setAuthenticationValidator(
OidcClientRegistrationAuthenticationValidator.DEFAULT_REDIRECT_URI_VALIDATOR.andThen(
OidcClientRegistrationAuthenticationValidator.DEFAULT_POST_LOGOUT_REDIRECT_URI_VALIDATOR)
.andThen(OidcClientRegistrationAuthenticationValidator.DEFAULT_JWK_SET_URI_VALIDATOR)
.andThen(OidcClientRegistrationAuthenticationValidator.SIMPLE_SCOPE_VALIDATOR));
}
});
}
@EnableWebSecurity @EnableWebSecurity
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
static class CustomClientRegistrationConfiguration extends AuthorizationServerConfiguration { static class CustomClientRegistrationConfiguration extends AuthorizationServerConfiguration {
@@ -796,7 +660,7 @@ public class OidcClientRegistrationTests {
.clientRegistrationRequestConverter(authenticationConverter) .clientRegistrationRequestConverter(authenticationConverter)
.clientRegistrationRequestConverters(authenticationConvertersConsumer) .clientRegistrationRequestConverters(authenticationConvertersConsumer)
.authenticationProvider(authenticationProvider) .authenticationProvider(authenticationProvider)
.authenticationProviders(scopePermissiveValidatorCustomizer().andThen(authenticationProvidersConsumer)) .authenticationProviders(authenticationProvidersConsumer)
.clientRegistrationResponseHandler(authenticationSuccessHandler) .clientRegistrationResponseHandler(authenticationSuccessHandler)
.errorResponseHandler(authenticationFailureHandler) .errorResponseHandler(authenticationFailureHandler)
) )
@@ -826,7 +690,7 @@ public class OidcClientRegistrationTests {
oidc oidc
.clientRegistrationEndpoint((clientRegistration) -> .clientRegistrationEndpoint((clientRegistration) ->
clientRegistration clientRegistration
.authenticationProviders(scopePermissiveValidatorCustomizer().andThen(configureClientRegistrationConverters())) .authenticationProviders(configureClientRegistrationConverters())
) )
) )
) )
@@ -867,7 +731,7 @@ public class OidcClientRegistrationTests {
oidc oidc
.clientRegistrationEndpoint((clientRegistration) -> .clientRegistrationEndpoint((clientRegistration) ->
clientRegistration clientRegistration
.authenticationProviders(scopePermissiveValidatorCustomizer().andThen(configureClientRegistrationConverters())) .authenticationProviders(configureClientRegistrationConverters())
) )
) )
) )
@@ -891,33 +755,6 @@ public class OidcClientRegistrationTests {
} }
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
static class DefaultValidatorConfiguration extends AuthorizationServerConfiguration {
// Override with Customizer.withDefaults() so the default (strict)
// OidcClientRegistrationAuthenticationValidator is in effect.
// @formatter:off
@Bean
@Override
SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
http
.oauth2AuthorizationServer((authorizationServer) ->
authorizationServer
.oidc((oidc) ->
oidc
.clientRegistrationEndpoint(Customizer.withDefaults())
)
)
.authorizeHttpRequests((authorize) ->
authorize.anyRequest().authenticated()
);
return http.build();
}
// @formatter:on
}
@EnableWebSecurity @EnableWebSecurity
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
static class AuthorizationServerConfiguration { static class AuthorizationServerConfiguration {
@@ -930,10 +767,7 @@ public class OidcClientRegistrationTests {
authorizationServer authorizationServer
.oidc((oidc) -> .oidc((oidc) ->
oidc oidc
.clientRegistrationEndpoint((clientRegistration) -> .clientRegistrationEndpoint(Customizer.withDefaults())
clientRegistration
.authenticationProviders(scopePermissiveValidatorCustomizer())
)
) )
) )
.authorizeHttpRequests((authorize) -> .authorizeHttpRequests((authorize) ->
@@ -60,7 +60,6 @@ import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.FactorGrantedAuthority;
import org.springframework.security.core.session.SessionRegistry; import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl; import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.NoOpPasswordEncoder;
@@ -211,8 +210,7 @@ public class OidcTests {
registeredClient); registeredClient);
MvcResult mvcResult = this.mvc MvcResult mvcResult = this.mvc
.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters) .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters)
.with(user("user").roles("A", "B") .with(user("user").roles("A", "B")))
.authorities(FactorGrantedAuthority.fromAuthority(FactorGrantedAuthority.PASSWORD_AUTHORITY))))
.andExpect(status().is3xxRedirection()) .andExpect(status().is3xxRedirection())
.andReturn(); .andReturn();
String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); String redirectedUrl = mvcResult.getResponse().getRedirectedUrl();
@@ -272,8 +270,7 @@ public class OidcTests {
registeredClient); registeredClient);
MvcResult mvcResult = this.mvc MvcResult mvcResult = this.mvc
.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters) .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters)
.with(user("user").roles("A", "B") .with(user("user").roles("A", "B")))
.authorities(FactorGrantedAuthority.fromAuthority(FactorGrantedAuthority.PASSWORD_AUTHORITY))))
.andExpect(status().is3xxRedirection()) .andExpect(status().is3xxRedirection())
.andReturn(); .andReturn();
String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); String redirectedUrl = mvcResult.getResponse().getRedirectedUrl();
@@ -338,8 +335,7 @@ public class OidcTests {
registeredClient); registeredClient);
MvcResult mvcResult = this.mvc MvcResult mvcResult = this.mvc
.perform(get(issuer.concat(DEFAULT_AUTHORIZATION_ENDPOINT_URI)).queryParams(authorizationRequestParameters) .perform(get(issuer.concat(DEFAULT_AUTHORIZATION_ENDPOINT_URI)).queryParams(authorizationRequestParameters)
.with(user("user") .with(user("user")))
.authorities(FactorGrantedAuthority.fromAuthority(FactorGrantedAuthority.PASSWORD_AUTHORITY))))
.andExpect(status().is3xxRedirection()) .andExpect(status().is3xxRedirection())
.andReturn(); .andReturn();
@@ -392,8 +388,7 @@ public class OidcTests {
registeredClient1); registeredClient1);
MvcResult mvcResult = this.mvc MvcResult mvcResult = this.mvc
.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters) .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters)
.with(user("user1") .with(user("user1")))
.authorities(FactorGrantedAuthority.fromAuthority(FactorGrantedAuthority.PASSWORD_AUTHORITY))))
.andExpect(status().is3xxRedirection()) .andExpect(status().is3xxRedirection())
.andReturn(); .andReturn();
@@ -429,8 +424,7 @@ public class OidcTests {
authorizationRequestParameters = getAuthorizationRequestParameters(registeredClient2); authorizationRequestParameters = getAuthorizationRequestParameters(registeredClient2);
mvcResult = this.mvc mvcResult = this.mvc
.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters) .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters)
.with(user("user2") .with(user("user2")))
.authorities(FactorGrantedAuthority.fromAuthority(FactorGrantedAuthority.PASSWORD_AUTHORITY))))
.andExpect(status().is3xxRedirection()) .andExpect(status().is3xxRedirection())
.andReturn(); .andReturn();
@@ -503,8 +497,7 @@ public class OidcTests {
registeredClient); registeredClient);
MvcResult mvcResult = this.mvc MvcResult mvcResult = this.mvc
.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters) .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters)
.with(user("user") .with(user("user")))
.authorities(FactorGrantedAuthority.fromAuthority(FactorGrantedAuthority.PASSWORD_AUTHORITY))))
.andExpect(status().is3xxRedirection()) .andExpect(status().is3xxRedirection())
.andReturn(); .andReturn();
String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); String redirectedUrl = mvcResult.getResponse().getRedirectedUrl();
@@ -544,8 +537,7 @@ public class OidcTests {
registeredClient); registeredClient);
MvcResult mvcResult = this.mvc MvcResult mvcResult = this.mvc
.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters) .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters)
.with(user("user") .with(user("user")))
.authorities(FactorGrantedAuthority.fromAuthority(FactorGrantedAuthority.PASSWORD_AUTHORITY))))
.andExpect(status().is3xxRedirection()) .andExpect(status().is3xxRedirection())
.andReturn(); .andReturn();
String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); String redirectedUrl = mvcResult.getResponse().getRedirectedUrl();
@@ -40,7 +40,6 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.config.web.PathPatternRequestMatcherBuilderFactoryBean;
import org.springframework.security.core.userdetails.PasswordEncodedUser; import org.springframework.security.core.userdetails.PasswordEncodedUser;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.provisioning.InMemoryUserDetailsManager;
@@ -99,19 +98,6 @@ public class OneTimeTokenLoginConfigurerTests {
.andExpectAll(status().isFound(), redirectedUrl("/"), authenticated()); .andExpectAll(status().isFound(), redirectedUrl("/"), authenticated());
} }
// gh-19128
@Test
void oneTimeTokenWhenBuilderBeanWithBasePathThenGenerateAndLoginUrlsIgnoreBasePath() throws Exception {
this.spring.register(OneTimeTokenBuilderBeanConfig.class).autowire();
this.mvc.perform(post("/ott/generate").param("username", "user").with(csrf()))
.andExpectAll(status().isFound(), redirectedUrl("/login/ott"));
String token = getLastToken().getTokenValue();
this.mvc.perform(post("/login/ott").param("token", token).with(csrf()))
.andExpectAll(status().isFound(), redirectedUrl("/"), authenticated());
}
@Test @Test
void oneTimeTokenWhenDifferentAuthenticationUrlsThenCanAuthenticate() throws Exception { void oneTimeTokenWhenDifferentAuthenticationUrlsThenCanAuthenticate() throws Exception {
this.spring.register(OneTimeTokenDifferentUrlsConfig.class).autowire(); this.spring.register(OneTimeTokenDifferentUrlsConfig.class).autowire();
@@ -310,41 +296,6 @@ public class OneTimeTokenLoginConfigurerTests {
} }
// gh-19128
@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
@Import(UserDetailsServiceConfig.class)
static class OneTimeTokenBuilderBeanConfig {
@Bean
PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() {
PathPatternRequestMatcherBuilderFactoryBean bean = new PathPatternRequestMatcherBuilderFactoryBean();
bean.setBasePath("/spring");
return bean;
}
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http,
OneTimeTokenGenerationSuccessHandler ottSuccessHandler) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.oneTimeTokenLogin((ott) -> ott
.tokenGenerationSuccessHandler(ottSuccessHandler)
);
// @formatter:on
return http.build();
}
@Bean
TestOneTimeTokenGenerationSuccessHandler ottSuccessHandler() {
return new TestOneTimeTokenGenerationSuccessHandler();
}
}
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@EnableWebSecurity @EnableWebSecurity
@Import(UserDetailsServiceConfig.class) @Import(UserDetailsServiceConfig.class)
@@ -57,7 +57,6 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.config.web.PathPatternRequestMatcherBuilderFactoryBean;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.annotation.AuthenticationPrincipal;
@@ -245,14 +244,6 @@ public class Saml2LoginConfigurerTests {
performSaml2Login("ROLE_AUTH_MANAGER"); performSaml2Login("ROLE_AUTH_MANAGER");
} }
// gh-19128
@Test
public void saml2LoginWhenBuilderBeanWithBasePathThenAuthenticateUriIgnoresBasePath() throws Exception {
this.spring.register(Saml2LoginBuilderBeanConfig.class).autowire();
MvcResult result = this.mvc.perform(get("/saml2/authenticate/registration-id")).andReturn();
assertThat(result.getResponse().getRedirectedUrl()).contains("SAMLRequest");
}
@Test @Test
public void authenticationRequestWhenAuthenticationRequestResolverBeanThenUses() throws Exception { public void authenticationRequestWhenAuthenticationRequestResolverBeanThenUses() throws Exception {
this.spring.register(CustomAuthenticationRequestResolverBean.class).autowire(); this.spring.register(CustomAuthenticationRequestResolverBean.class).autowire();
@@ -531,33 +522,6 @@ public class Saml2LoginConfigurerTests {
} }
// gh-19128
@Configuration
@EnableWebSecurity
@Import(Saml2LoginConfigBeans.class)
static class Saml2LoginBuilderBeanConfig {
@Bean
PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() {
PathPatternRequestMatcherBuilderFactoryBean bean = new PathPatternRequestMatcherBuilderFactoryBean();
bean.setBasePath("/spring");
return bean;
}
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((authz) -> authz
.anyRequest().authenticated()
)
.saml2Login(Customizer.withDefaults());
// @formatter:on
return http.build();
}
}
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
@Import(Saml2LoginConfigBeans.class) @Import(Saml2LoginConfigBeans.class)
@@ -49,7 +49,6 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.config.web.PathPatternRequestMatcherBuilderFactoryBean;
import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.saml2.core.Saml2Utils; import org.springframework.security.saml2.core.Saml2Utils;
@@ -197,17 +196,6 @@ public class Saml2LogoutConfigurerTests {
verify(logoutHandler).logout(any(), any(), any()); verify(logoutHandler).logout(any(), any(), any());
} }
// gh-19128
@Test
public void saml2LogoutWhenBuilderBeanWithBasePathThenLogoutUrlIgnoresBasePath() throws Exception {
this.spring.register(Saml2LogoutBuilderBeanConfig.class).autowire();
MvcResult result = this.mvc.perform(post("/logout").with(authentication(this.user)).with(csrf()))
.andExpect(status().isFound())
.andReturn();
String location = result.getResponse().getHeader("Location");
assertThat(location).startsWith("https://ap.example.org/logout/saml2/request");
}
@Test @Test
public void saml2LogoutWhenUnauthenticatedThenEntryPoint() throws Exception { public void saml2LogoutWhenUnauthenticatedThenEntryPoint() throws Exception {
this.spring.register(Saml2LogoutDefaultsConfig.class).autowire(); this.spring.register(Saml2LogoutDefaultsConfig.class).autowire();
@@ -589,32 +577,6 @@ public class Saml2LogoutConfigurerTests {
} }
// gh-19128
@Configuration
@EnableWebSecurity
@Import(Saml2LoginConfigBeans.class)
static class Saml2LogoutBuilderBeanConfig {
@Bean
PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() {
PathPatternRequestMatcherBuilderFactoryBean bean = new PathPatternRequestMatcherBuilderFactoryBean();
bean.setBasePath("/spring");
return bean;
}
@Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated())
.saml2Login(withDefaults())
.saml2Logout(withDefaults());
// @formatter:on
return http.build();
}
}
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
@Import(Saml2LoginConfigBeans.class) @Import(Saml2LoginConfigBeans.class)
@@ -30,7 +30,6 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.config.web.PathPatternRequestMatcherBuilderFactoryBean;
import org.springframework.security.saml2.provider.service.metadata.OpenSaml5MetadataResolver; import org.springframework.security.saml2.provider.service.metadata.OpenSaml5MetadataResolver;
import org.springframework.security.saml2.provider.service.metadata.RequestMatcherMetadataResponseResolver; import org.springframework.security.saml2.provider.service.metadata.RequestMatcherMetadataResponseResolver;
import org.springframework.security.saml2.provider.service.metadata.Saml2MetadataResponse; import org.springframework.security.saml2.provider.service.metadata.Saml2MetadataResponse;
@@ -125,15 +124,6 @@ public class Saml2MetadataConfigurerTests {
this.mvc.perform(get("/saml2/metadata")).andExpect(status().isForbidden()); this.mvc.perform(get("/saml2/metadata")).andExpect(status().isForbidden());
} }
// gh-19128
@Test
void saml2MetadataWhenBuilderBeanWithBasePathThenMetadataUrlIgnoresBasePath() throws Exception {
this.spring.register(MetadataBuilderBeanConfig.class).autowire();
this.mvc.perform(get("/saml/metadata"))
.andExpect(status().isOk())
.andExpect(content().string(containsString("md:EntityDescriptor")));
}
@EnableWebSecurity @EnableWebSecurity
@Configuration @Configuration
@Import(RelyingPartyRegistrationConfig.class) @Import(RelyingPartyRegistrationConfig.class)
@@ -151,31 +141,6 @@ public class Saml2MetadataConfigurerTests {
} }
// gh-19128
@EnableWebSecurity
@Configuration
@Import(RelyingPartyRegistrationConfig.class)
static class MetadataBuilderBeanConfig {
@Bean
PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() {
PathPatternRequestMatcherBuilderFactoryBean bean = new PathPatternRequestMatcherBuilderFactoryBean();
bean.setBasePath("/spring");
return bean;
}
@Bean
SecurityFilterChain filters(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated())
.saml2Metadata((saml2) -> saml2.metadataUrl("/saml/metadata"));
return http.build();
// @formatter:on
}
}
@EnableWebSecurity @EnableWebSecurity
@Configuration @Configuration
@Import(RelyingPartyRegistrationConfig.class) @Import(RelyingPartyRegistrationConfig.class)
@@ -107,7 +107,7 @@ public class ServerHttpSecurityConfigurationTests {
} }
@Test @Test
public void loadConfigWhenReactiveUserAuthenticationServiceConfiguredThenServerHttpSecurityExists() { public void loadConfigWhenReactiveUserDetailsServiceConfiguredThenServerHttpSecurityExists() {
this.spring this.spring
.register(ServerHttpSecurityConfiguration.class, ReactiveAuthenticationTestConfiguration.class, .register(ServerHttpSecurityConfiguration.class, ReactiveAuthenticationTestConfiguration.class,
WebFluxSecurityConfiguration.class) WebFluxSecurityConfiguration.class)
@@ -116,16 +116,6 @@ public class ServerHttpSecurityConfigurationTests {
assertThat(serverHttpSecurity).isNotNull(); assertThat(serverHttpSecurity).isNotNull();
} }
@Test
public void loadConfigWhenOnlyReactiveUserDetailsServiceConfiguredThenServerHttpSecurityExists() {
this.spring
.register(ServerHttpSecurityConfiguration.class, ReactiveUserDetailsServiceOnlyTestConfiguration.class,
WebFluxSecurityConfiguration.class)
.autowire();
ServerHttpSecurity serverHttpSecurity = this.spring.getContext().getBean(ServerHttpSecurity.class);
assertThat(serverHttpSecurity).isNotNull();
}
@Test @Test
public void loadConfigWhenProxyingEnabledAndSubclassThenServerHttpSecurityExists() { public void loadConfigWhenProxyingEnabledAndSubclassThenServerHttpSecurityExists() {
this.spring this.spring
@@ -591,14 +581,4 @@ public class ServerHttpSecurityConfigurationTests {
} }
@Configuration(proxyBeanMethods = false)
static class ReactiveUserDetailsServiceOnlyTestConfiguration {
@Bean
static ReactiveUserDetailsService userDetailsService() {
return (username) -> Mono.just(PasswordEncodedUser.user());
}
}
} }
@@ -28,17 +28,14 @@ import jakarta.servlet.http.HttpSession;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException; import org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException;
import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.session.SessionLimit; import org.springframework.security.web.authentication.session.SessionLimit;
import org.springframework.security.web.header.HeaderWriterFilter;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultMatcher; import org.springframework.test.web.servlet.ResultMatcher;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
@@ -153,16 +150,6 @@ public class HttpHeadersConfigTests {
// @formatter:on // @formatter:on
} }
@Test
public void requestWhenHeadersEagerlyConfiguredThenHeadersAreWritten() throws Exception {
this.spring.configLocations(this.xml("HeadersEagerlyConfigured")).autowire();
// @formatter:off
this.mvc.perform(get("/").secure(true))
.andExpect(status().isOk())
.andExpect(includesDefaults());
// @formatter:on
}
@Test @Test
public void requestWhenFrameOptionsConfiguredThenIncludesHeader() throws Exception { public void requestWhenFrameOptionsConfiguredThenIncludesHeader() throws Exception {
Map<String, String> headers = new HashMap<>(defaultHeaders); Map<String, String> headers = new HashMap<>(defaultHeaders);
@@ -968,18 +955,6 @@ public class HttpHeadersConfigTests {
} }
public static class EagerHeadersBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof HeaderWriterFilter headerWriterFilter) {
headerWriterFilter.setShouldWriteHeadersEagerly(true);
}
return bean;
}
}
public static class CustomSessionLimit implements SessionLimit { public static class CustomSessionLimit implements SessionLimit {
@Override @Override
@@ -314,78 +314,6 @@ public class InterceptUrlConfigTests {
.autowire()); .autowire());
} }
@Test
public void requestWhenUsingDefaultMatcherAndServletPathThenAuthorizesRequestsAccordingly() throws Exception {
this.spring.configLocations(this.xml("DefaultMatcherServletPath")).autowire();
// @formatter:off
this.mvc.perform(get("/spring/path").with(userCredentials()))
.andExpect(status().isForbidden());
this.mvc.perform(get("/path").with(userCredentials()))
.andExpect(status().isOk());
// @formatter:on
}
@Test
public void requestWhenUsingDefaultMatcherAndServletPathAndAuthorizationManagerThenAuthorizesRequestsAccordingly()
throws Exception {
this.spring.configLocations(this.xml("DefaultMatcherServletPathAuthorizationManager")).autowire();
// @formatter:off
this.mvc.perform(get("/spring/path").with(userCredentials()))
.andExpect(status().isForbidden());
this.mvc.perform(get("/path").with(userCredentials()))
.andExpect(status().isOk());
// @formatter:on
assertThat(this.spring.getContext().getBean(AuthorizationManager.class)).isNotNull();
}
@Test
public void requestWhenUsingRegexMatcherThenAuthorizesRequestsAccordingly() throws Exception {
this.spring.configLocations(this.xml("RegexMatcher")).autowire();
// @formatter:off
this.mvc.perform(get("/path").with(userCredentials()))
.andExpect(status().isForbidden());
this.mvc.perform(get("/other").with(userCredentials()))
.andExpect(status().isNotFound());
// @formatter:on
}
@Test
public void requestWhenUsingRegexMatcherAndAuthorizationManagerThenAuthorizesRequestsAccordingly()
throws Exception {
this.spring.configLocations(this.xml("RegexMatcherAuthorizationManager")).autowire();
// @formatter:off
this.mvc.perform(get("/path").with(userCredentials()))
.andExpect(status().isForbidden());
this.mvc.perform(get("/other").with(userCredentials()))
.andExpect(status().isNotFound());
// @formatter:on
assertThat(this.spring.getContext().getBean(AuthorizationManager.class)).isNotNull();
}
@Test
public void requestWhenUsingCiRegexMatcherThenAuthorizesRequestsAccordingly() throws Exception {
this.spring.configLocations(this.xml("CiRegexMatcher")).autowire();
// @formatter:off
this.mvc.perform(get("/path").with(userCredentials()))
.andExpect(status().isForbidden());
this.mvc.perform(get("/PATH").with(userCredentials()))
.andExpect(status().isForbidden());
// @formatter:on
}
@Test
public void requestWhenUsingCiRegexMatcherAndAuthorizationManagerThenAuthorizesRequestsAccordingly()
throws Exception {
this.spring.configLocations(this.xml("CiRegexMatcherAuthorizationManager")).autowire();
// @formatter:off
this.mvc.perform(get("/path").with(userCredentials()))
.andExpect(status().isForbidden());
this.mvc.perform(get("/PATH").with(userCredentials()))
.andExpect(status().isForbidden());
// @formatter:on
assertThat(this.spring.getContext().getBean(AuthorizationManager.class)).isNotNull();
}
@Test @Test
public void requestWhenUsingFilterAllDispatcherTypesAndAuthorizationManagerThenAuthorizesRequestsAccordingly() public void requestWhenUsingFilterAllDispatcherTypesAndAuthorizationManagerThenAuthorizesRequestsAccordingly()
throws Exception { throws Exception {
@@ -275,7 +275,7 @@ class ServerJwtDslTests {
} }
class NullConverter: Converter<Jwt, Mono<AbstractAuthenticationToken>> { class NullConverter: Converter<Jwt, Mono<AbstractAuthenticationToken>> {
override fun convert(source: Jwt): Mono<AbstractAuthenticationToken> { override fun convert(source: Jwt): Mono<AbstractAuthenticationToken>? {
return Mono.empty() return Mono.empty()
} }
@@ -1,37 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2004-present the original author or authors.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/security"
xsi:schemaLocation="
http://www.springframework.org/schema/security
https://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<http auto-config="true">
<headers/>
<intercept-url pattern="/**" access="permitAll"/>
</http>
<b:bean class="org.springframework.security.config.http.HttpHeadersConfigTests.EagerHeadersBeanPostProcessor"/>
<b:bean name="simple" class="org.springframework.security.config.http.HttpHeadersConfigTests.SimpleController"/>
<b:import resource="userservice.xml"/>
</b:beans>
@@ -24,7 +24,7 @@
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd"> https://www.springframework.org/schema/beans/spring-beans.xsd">
<http request-matcher="path" use-authorization-manager="false"> <http request-matcher="ant" use-authorization-manager="false">
<intercept-url pattern="/path" access="denyAll" servlet-path="/spring"/> <intercept-url pattern="/path" access="denyAll" servlet-path="/spring"/>
<http-basic/> <http-basic/>
</http> </http>
@@ -24,7 +24,7 @@
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd"> https://www.springframework.org/schema/beans/spring-beans.xsd">
<http request-matcher="path"> <http request-matcher="ant">
<intercept-url pattern="/path" access="denyAll" servlet-path="/spring"/> <intercept-url pattern="/path" access="denyAll" servlet-path="/spring"/>
<http-basic/> <http-basic/>
</http> </http>
@@ -1,36 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2004-present the original author or authors.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/security"
xsi:schemaLocation="
http://www.springframework.org/schema/security
https://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<http request-matcher="ciRegex" use-authorization-manager="false">
<intercept-url pattern="\A/PATH\Z" access="denyAll"/>
<intercept-url pattern="\A/.*\Z" access="permitAll"/>
<http-basic/>
</http>
<b:bean name="path" class="org.springframework.security.config.http.InterceptUrlConfigTests.PathController"/>
<b:import resource="userservice.xml"/>
</b:beans>
@@ -1,36 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2004-present the original author or authors.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/security"
xsi:schemaLocation="
http://www.springframework.org/schema/security
https://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<http request-matcher="ciRegex">
<intercept-url pattern="\A/PATH\Z" access="denyAll"/>
<intercept-url pattern="\A/.*\Z" access="permitAll"/>
<http-basic/>
</http>
<b:bean name="path" class="org.springframework.security.config.http.InterceptUrlConfigTests.PathController"/>
<b:import resource="userservice.xml"/>
</b:beans>
@@ -26,11 +26,8 @@
<http use-authorization-manager="false"> <http use-authorization-manager="false">
<intercept-url pattern="/path" access="denyAll" servlet-path="/spring"/> <intercept-url pattern="/path" access="denyAll" servlet-path="/spring"/>
<intercept-url pattern="/**" access="permitAll"/>
<http-basic/> <http-basic/>
</http> </http>
<b:bean name="path" class="org.springframework.security.config.http.InterceptUrlConfigTests.PathController"/>
<b:import resource="userservice.xml"/> <b:import resource="userservice.xml"/>
</b:beans> </b:beans>
@@ -26,11 +26,8 @@
<http> <http>
<intercept-url pattern="/path" access="denyAll" servlet-path="/spring"/> <intercept-url pattern="/path" access="denyAll" servlet-path="/spring"/>
<intercept-url pattern="/**" access="permitAll"/>
<http-basic/> <http-basic/>
</http> </http>
<b:bean name="path" class="org.springframework.security.config.http.InterceptUrlConfigTests.PathController"/>
<b:import resource="userservice.xml"/> <b:import resource="userservice.xml"/>
</b:beans> </b:beans>
@@ -27,7 +27,7 @@
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd"> https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<http auto-config="true" request-matcher="path" use-authorization-manager="false"> <http auto-config="true" request-matcher="mvc" use-authorization-manager="false">
<intercept-url pattern="/path" access="denyAll"/> <intercept-url pattern="/path" access="denyAll"/>
<http-basic/> <http-basic/>
</http> </http>
@@ -27,7 +27,7 @@
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd"> https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<http auto-config="true" request-matcher="path"> <http auto-config="true" request-matcher="mvc">
<intercept-url pattern="/path" access="denyAll"/> <intercept-url pattern="/path" access="denyAll"/>
<http-basic/> <http-basic/>
</http> </http>
@@ -27,7 +27,7 @@
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd"> https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<http auto-config="true" request-matcher="path" use-authorization-manager="false"> <http auto-config="true" request-matcher="mvc" use-authorization-manager="false">
<intercept-url pattern="/path" access="denyAll" servlet-path="/spring"/> <intercept-url pattern="/path" access="denyAll" servlet-path="/spring"/>
<http-basic/> <http-basic/>
</http> </http>
@@ -27,7 +27,7 @@
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd"> https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<http auto-config="true" request-matcher="path"> <http auto-config="true" request-matcher="mvc">
<intercept-url pattern="/path" access="denyAll" servlet-path="/spring"/> <intercept-url pattern="/path" access="denyAll" servlet-path="/spring"/>
<http-basic/> <http-basic/>
</http> </http>
@@ -1,36 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2004-present the original author or authors.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/security"
xsi:schemaLocation="
http://www.springframework.org/schema/security
https://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<http request-matcher="regex" use-authorization-manager="false">
<intercept-url pattern="\A/path\Z" access="denyAll"/>
<intercept-url pattern="\A/.*\Z" access="permitAll"/>
<http-basic/>
</http>
<b:bean name="path" class="org.springframework.security.config.http.InterceptUrlConfigTests.PathController"/>
<b:import resource="userservice.xml"/>
</b:beans>
@@ -1,36 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2004-present the original author or authors.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/security"
xsi:schemaLocation="
http://www.springframework.org/schema/security
https://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<http request-matcher="regex">
<intercept-url pattern="\A/path\Z" access="denyAll"/>
<intercept-url pattern="\A/.*\Z" access="permitAll"/>
<http-basic/>
</http>
<b:bean name="path" class="org.springframework.security.config.http.InterceptUrlConfigTests.PathController"/>
<b:import resource="userservice.xml"/>
</b:beans>

Some files were not shown because too many files have changed in this diff Show More