Revert unnecessary merges on 6.0.x
This commit removes unnecessary main-branch merges starting from8750608b5b
and adds the following needed commit(s) that were made afterward: -5dce82c48b
This commit is contained in:
parent
e9d4223402
commit
9db33f33c7
|
@ -1,92 +0,0 @@
|
|||
version: 2
|
||||
|
||||
registries:
|
||||
spring-milestones:
|
||||
type: maven-repository
|
||||
url: https://repo.spring.io/milestone
|
||||
|
||||
updates:
|
||||
|
||||
- package-ecosystem: "gradle"
|
||||
target-branch: "main"
|
||||
milestone: 319 # 6.2.x
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
time: "03:00"
|
||||
timezone: "Etc/UTC"
|
||||
labels: [ "type: dependency-upgrade" ]
|
||||
registries:
|
||||
- "spring-milestones"
|
||||
ignore:
|
||||
- dependency-name: "com.nimbusds:nimbus-jose-jwt" # nimbus-jose-jwt gets updated when oauth2-oidc-sdk is updated to ensure consistency
|
||||
- dependency-name: "org.python:jython" # jython updates break integration tests
|
||||
- dependency-name: "org.apache.directory.server:*" # ApacheDS version > 1.5.5 contains break changes
|
||||
- dependency-name: "org.junit:junit-bom"
|
||||
update-types: [ "version-update:semver-major" ]
|
||||
- dependency-name: "org.mockito:mockito-bom"
|
||||
update-types: [ "version-update:semver-major" ]
|
||||
- dependency-name: "*"
|
||||
update-types: [ "version-update:semver-major", "version-update:semver-minor" ]
|
||||
|
||||
- package-ecosystem: "gradle"
|
||||
target-branch: "6.1.x"
|
||||
milestone: 318 # 6.1.x
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
time: "03:00"
|
||||
timezone: "Etc/UTC"
|
||||
labels: [ "type: dependency-upgrade" ]
|
||||
ignore:
|
||||
- dependency-name: "com.nimbusds:nimbus-jose-jwt" # nimbus-jose-jwt gets updated when oauth2-oidc-sdk is updated to ensure consistency
|
||||
- dependency-name: "org.python:jython" # jython updates break integration tests
|
||||
- dependency-name: "org.apache.directory.server:*" # ApacheDS version > 1.5.5 contains break changes
|
||||
- dependency-name: "org.junit:junit-bom"
|
||||
update-types: [ "version-update:semver-major" ]
|
||||
- dependency-name: "org.mockito:mockito-bom"
|
||||
update-types: [ "version-update:semver-major" ]
|
||||
- dependency-name: "*"
|
||||
update-types: [ "version-update:semver-major", "version-update:semver-minor" ]
|
||||
|
||||
- package-ecosystem: "gradle"
|
||||
target-branch: "6.0.x"
|
||||
milestone: 143 # 6.0.x
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
time: "03:00"
|
||||
timezone: "Etc/UTC"
|
||||
labels: [ "type: dependency-upgrade" ]
|
||||
ignore:
|
||||
- dependency-name: "com.nimbusds:nimbus-jose-jwt" # nimbus-jose-jwt gets updated when oauth2-oidc-sdk is updated to ensure consistency
|
||||
- dependency-name: "org.python:jython" # jython updates break integration tests
|
||||
- dependency-name: "org.apache.directory.server:*" # ApacheDS version > 1.5.5 contains break changes
|
||||
- dependency-name: "org.junit:junit-bom"
|
||||
update-types: [ "version-update:semver-major" ]
|
||||
- dependency-name: "org.mockito:mockito-bom"
|
||||
update-types: [ "version-update:semver-major" ]
|
||||
- dependency-name: "*"
|
||||
update-types: [ "version-update:semver-major", "version-update:semver-minor" ]
|
||||
|
||||
- package-ecosystem: "gradle"
|
||||
target-branch: "5.8.x"
|
||||
milestone: 246 # 5.8.x
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
time: "03:00"
|
||||
timezone: "Etc/UTC"
|
||||
labels: [ "type: dependency-upgrade" ]
|
||||
ignore:
|
||||
- dependency-name: "com.nimbusds:nimbus-jose-jwt" # nimbus-jose-jwt gets updated when oauth2-oidc-sdk is updated to ensure consistency
|
||||
- dependency-name: "org.python:jython" # jython updates break integration tests
|
||||
- dependency-name: "org.apache.directory.server:*" # ApacheDS version > 1.5.5 contains break changes
|
||||
- dependency-name: "io.mockk:mockk" # mockk updates break tests
|
||||
- dependency-name: "org.opensaml:*" # org.opensaml maintains two different versions, so it must be updated manually
|
||||
- dependency-name: "org.junit:junit-bom"
|
||||
update-types: [ "version-update:semver-major" ]
|
||||
- dependency-name: "org.mockito:mockito-bom"
|
||||
update-types: [ "version-update:semver-major" ]
|
||||
- dependency-name: "*"
|
||||
update-types: [ "version-update:semver-major", "version-update:semver-minor" ]
|
|
@ -69,13 +69,6 @@ jobs:
|
|||
snapshot_tests:
|
||||
name: Test against snapshots
|
||||
needs: [prerequisites]
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- java-version: '21-ea'
|
||||
toolchain: '21'
|
||||
- java-version: '17'
|
||||
toolchain: '17'
|
||||
runs-on: ubuntu-latest
|
||||
if: needs.prerequisites.outputs.runjobs
|
||||
steps:
|
||||
|
@ -83,14 +76,14 @@ jobs:
|
|||
- name: Set up gradle
|
||||
uses: spring-io/spring-gradle-build-action@v1
|
||||
with:
|
||||
java-version: ${{ matrix.java-version }}
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
- name: Snapshot Tests
|
||||
run: |
|
||||
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
|
||||
export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD"
|
||||
export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY"
|
||||
./gradlew test --refresh-dependencies -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" -PforceMavenRepositories=snapshot -PisOverrideVersionCatalog -PtestToolchain=${{ matrix.toolchain }} -PspringFrameworkVersion='6.1.+' -PreactorVersion='2023.0.+' -PspringDataVersion='2023.1.+' -PlocksDisabled --stacktrace
|
||||
./gradlew test --refresh-dependencies -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" -PforceMavenRepositories=snapshot -PisOverrideVersionCatalog -PspringFrameworkVersion='6.0.+' -PreactorVersion='2022.0.+' -PspringDataVersion='2022.0.+' -PlocksDisabled --stacktrace
|
||||
check_samples:
|
||||
name: Check Samples project
|
||||
needs: [prerequisites]
|
||||
|
|
|
@ -7,8 +7,8 @@ on:
|
|||
tags: '**'
|
||||
repository_dispatch:
|
||||
types: request-build-reference # legacy
|
||||
#schedule:
|
||||
#- cron: '0 10 * * *' # Once per day at 10am UTC
|
||||
schedule:
|
||||
- cron: '0 10 * * *' # Once per day at 10am UTC
|
||||
workflow_dispatch:
|
||||
permissions: read-all
|
||||
jobs:
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
name: Execute Gradle Wrapper Upgrade
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 2 * * *' # 2am UTC
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
upgrade_wrapper:
|
||||
name: Execution
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Git configuration
|
||||
env:
|
||||
TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
git config --global url."https://unused-username:${TOKEN}@github.com/".insteadOf "https://github.com/"
|
||||
git config --global user.name 'github-actions[bot]'
|
||||
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
- name: Set up Gradle
|
||||
uses: gradle/gradle-build-action@v2
|
||||
- name: Upgrade Wrappers
|
||||
run: ./gradlew clean upgradeGradleWrapperAll --continue -Porg.gradle.java.installations.auto-download=false
|
||||
env:
|
||||
WRAPPER_UPGRADE_GIT_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@ -11,7 +11,7 @@ jobs:
|
|||
strategy:
|
||||
matrix:
|
||||
# List of active maintenance branches.
|
||||
branch: [ main, 6.1.x, 6.0.x, 5.8.x ]
|
||||
branch: [ main, 6.0.x, 5.8.x, 5.7.x, 5.6.x ]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
|
|
@ -1,154 +1,232 @@
|
|||
= Contributing to Spring Security
|
||||
_Have something you'd like to contribute to the framework? We welcome pull requests, but ask that you carefully read this document first to understand how best to submit them; what kind of changes are likely to be accepted; and what to expect from the Spring Security team when evaluating your submission._
|
||||
|
||||
First off, thank you for taking the time to contribute! :+1: :tada:
|
||||
_Please refer back to this document as a checklist before issuing any pull request; this will save time for everyone!_
|
||||
|
||||
== Table of Contents
|
||||
= Code of Conduct
|
||||
|
||||
* <<code-of-conduct>>
|
||||
* <<how-to-contribute>>
|
||||
* <<ask-questions>>
|
||||
* <<find-an-issue>>
|
||||
* <<create-an-issue>>
|
||||
* <<issue-lifecycle>>
|
||||
* <<submit-a-pull-request>>
|
||||
* <<build-from-source>>
|
||||
* <<code-style>>
|
||||
Please see our https://github.com/spring-projects/.github/blob/main/CODE_OF_CONDUCT.md[code of conduct].
|
||||
|
||||
[[code-of-conduct]]
|
||||
== Code of Conduct
|
||||
= Similar but different
|
||||
|
||||
This project is governed by the https://github.com/spring-projects/.github/blob/main/CODE_OF_CONDUCT.md[Spring code of conduct].
|
||||
By participating you are expected to uphold this code.
|
||||
Please report unacceptable behavior to spring-code-of-conduct@pivotal.io.
|
||||
Each Spring module is slightly different from one another in terms of team size, number of issues, etc. Therefore, each project is managed slightly different. You will notice that this document is very similar to the https://github.com/spring-projects/spring-framework/wiki/Contributor-guidelines[Spring Framework Contributor guidelines]. However, there are some subtle differences between the two documents, so please be sure to read this document thoroughly.
|
||||
|
||||
[[how-to-contribute]]
|
||||
== How to Contribute
|
||||
= Importing into IDE
|
||||
|
||||
[[ask-questions]]
|
||||
=== Ask Questions
|
||||
The following provides information on setting up a development environment that can run the sample in https://www.springsource.org/sts[Spring Tool Suite 3.6.0+]. Other IDE's should work using Gradle's IDE support, but have not been tested.
|
||||
|
||||
If you have a question, check Stack Overflow using
|
||||
https://stackoverflow.com/questions/tagged/spring-security+or+spring-ldap+or+spring-authorization-server+or+spring-session?tab=Newest[this list of tags].
|
||||
Find an existing discussion, or start a new one if necessary.
|
||||
* IDE Setup
|
||||
** Install Spring Tool Suite 3.6.0+
|
||||
** You will need the following plugins installed (can be found on the Extensions Page)
|
||||
*** Gradle Eclipse
|
||||
*** Groovy Eclipse
|
||||
* Importing the project into Spring Tool Suite
|
||||
** File -> Import… -> Gradle Project
|
||||
|
||||
If you believe there is an issue, search through https://github.com/spring-projects/spring-security/issues[existing issues] trying a few different ways to find discussions, past or current, that are related to the issue.
|
||||
Reading those discussions helps you to learn about the issue, and helps us to make a decision.
|
||||
As of new versions of Spring Tool Suite, you might need to install Groovy Eclipse pointing directly to the updated plugin location. To install Groovy Eclipse on Spring Tool Suite based on Eclipse Oxigen you must do the following steps:
|
||||
|
||||
[[find-an-issue]]
|
||||
=== Find an Existing Issue
|
||||
Help -> Install New Software… -> Add the following URL into _Work with_ field:
|
||||
https://dist.springsource.org/snapshot/GRECLIPSE/e4.7/[https://dist.springsource.org/snapshot/GRECLIPSE/e4.7/]
|
||||
|
||||
There are many issues in Spring Security with the labels https://github.com/spring-projects/spring-security/issues?q=is%3Aissue+is%3Aopen+label%3A%22status%3A+ideal-for-contribution%22[`ideal-for-contribution`] or https://github.com/spring-projects/spring-security/issues?q=is%3Aissue+is%3Aopen+label%3A%22status%3A+first-timers-only%22[`first-timers-only`] that are a great way to contribute to a discussion or <<submit-a-pull-request,to a PR>>.
|
||||
You can volunteer by commenting on these tickets, and we will assign them to you.
|
||||
= Understand the basics
|
||||
|
||||
[[create-an-issue]]
|
||||
=== Create an Issue
|
||||
Not sure what a pull request is, or how to submit one? Take a look at GitHub's excellent https://help.github.com/articles/using-pull-requests[help documentation first].
|
||||
|
||||
Reporting an issue or making a feature request is a great way to contribute.
|
||||
Your feedback and the conversations that result from it provide a continuous flow of ideas.
|
||||
However, before creating a ticket, please take the time to <<ask-questions,ask and research>> first.
|
||||
= Search GitHub issues; create an issue if necessary
|
||||
|
||||
If you create an issue after a discussion on Stack Overflow, please provide a description in the issue instead of simply referring to Stack Overflow.
|
||||
The issue tracker is an important place of record for design discussions and should be self-sufficient.
|
||||
Is there already an issue that addresses your concern? Do a bit of searching in our https://github.com/spring-projects/spring-security/issues[GitHub issues] to see if you can find something similar. If not, please create a new issue before submitting a pull request unless the change is not a user facing issue.
|
||||
|
||||
Once you're ready, create an issue on https://github.com/spring-projects/spring-security/issues[GitHub].
|
||||
= Discuss non-trivial contribution ideas with committers
|
||||
|
||||
Many issues are caused by subtle behavior, typos, and unintended configuration.
|
||||
Creating a https://stackoverflow.com/help/minimal-reproducible-example[Minimal Reproducible Example] (starting with https://start.spring.io for example) of the problem helps the team quickly triage your issue and get to the core of the problem.
|
||||
If you're considering anything more than correcting a typo or fixing a minor bug, please discuss it on the https://gitter.im/spring-projects/spring-security[Spring Security Gitter] before submitting a pull request. We're happy to provide guidance but please spend an hour or two researching the subject on your own including searching the forums for prior discussions.
|
||||
|
||||
We love contributors, and we may ask you to <<submit-a-pull-request,submit a PR with a fix>>.
|
||||
= Sign the Contributor License Agreement
|
||||
|
||||
[[issue-lifecycle]]
|
||||
=== Issue Lifecycle
|
||||
If you have not previously done so, please fill out and submit the https://cla.pivotal.io/sign/spring[Contributor License Agreement].
|
||||
|
||||
When an issue is first created, it is flagged `waiting-for-triage` waiting for a team member to triage it.
|
||||
Once the issue has been reviewed, the team may ask for further information if needed, and based on the findings, the issue is either assigned a target branch (or no branch if a feature) or is closed with a specific status.
|
||||
The target branch is https://spring.io/projects/spring-security#support[the earliest supported branch] where <<choose-a-branch,the change will be applied>>.
|
||||
= Create your branch from oldest maintenance branch
|
||||
|
||||
When a fix is ready, the issue is closed and may still be re-opened until the fix is released.
|
||||
After that the issue will typically no longer be reopened.
|
||||
In rare cases if the issue was not at all fixed, the issue may be re-opened.
|
||||
In most cases however any follow-up reports will need to be created as new issues with a fresh description.
|
||||
Create your topic branch to be submitted as a pull request from the oldest impacted and supported maintenance branch.
|
||||
You can find the supported versions by looking at the https://github.com/spring-projects/spring-security/milestones[milestones page].
|
||||
Switch to a branch named `<major>.<minor>.x` from the smallest milestone in the format of `<major>.<minor>.<patch>(-<prerelease>)`.
|
||||
The spring team will ensure the code gets merged forward into additional branches.
|
||||
|
||||
[[build-from-source]]
|
||||
=== Build from Source
|
||||
= Use short branch names
|
||||
|
||||
See https://github.com/spring-projects/spring-security/tree/main#building-from-source[Build from Source] for instructions on how to check out, build, and import the Spring Security source code into your IDE.
|
||||
Branches used when submitting pull requests should preferably be named according to GitHub issues, e.g. `gh-1234` or `gh-1234-fix-npe`. Otherwise, use succinct, lower-case, dash (`-`) delimited names, such as `fix-warnings` or `fix-typo`. This is important, because branch names show up in the merge commits that result from accepting pull requests, and should be as expressive and concise as possible.
|
||||
|
||||
[[code-style]]
|
||||
=== Source Code Style
|
||||
= Keep commits focused
|
||||
|
||||
The wiki pages https://github.com/spring-projects/spring-framework/wiki/Code-Style[Code Style] and https://github.com/spring-projects/spring-framework/wiki/IntelliJ-IDEA-Editor-Settings[IntelliJ IDEA Editor Settings] define the source file coding standards we use along with some IDEA editor settings we customize.
|
||||
Remember each ticket should be focused on a single item of interest since the tickets are used to produce the changelog. Since each commit should be tied to a single GitHub issue, ensure that your commits are focused. For example, do not include an update to a transitive library in your commit unless the GitHub is to update the library. Reviewing your commits is essential before sending a pull request.
|
||||
|
||||
To format the code as well as check the style, run `./gradle format check`.
|
||||
= Mind the whitespace
|
||||
|
||||
[[submit-a-pull-request]]
|
||||
=== Submit a Pull Request
|
||||
Please carefully follow the whitespace and formatting conventions already present in the framework.
|
||||
|
||||
We are excited for your pull request! :heart:
|
||||
. Tabs, not spaces
|
||||
. Unix (LF), not dos (CRLF) line endings
|
||||
. Eliminate all trailing whitespace
|
||||
. Aim to wrap code at 120 characters, but favor readability over wrapping
|
||||
. Preserve existing formatting; i.e. do not reformat code for its own sake
|
||||
. Search the codebase using `git grep` and other tools to discover common naming conventions, etc.
|
||||
. UTF-8 encoding for Java sources and XML files
|
||||
|
||||
Please do your best to follow these steps.
|
||||
Don't worry if you don't get them all correct the first time, we will help you.
|
||||
Whitespace management tips
|
||||
|
||||
. You can use the https://marketplace.eclipse.org/content/anyedit-tools[AnyEdit Eclipse plugin] to ensure spaces are used and to clean up trailing whitespaces.
|
||||
. Use Git's `pre-commit.sample` hook to prevent invalid whitespace from being pushed out. You can enable it by moving `.git/hooks/pre-commit.sample` to `.git/hooks/pre-commit` and ensuring it is executable. For more information on hooks refer to https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks[https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks].
|
||||
|
||||
= Add Apache license header to all new classes
|
||||
|
||||
[[sign-cla]]
|
||||
1. If you have not previously done so, please sign the https://cla.spring.io/sign/spring[Contributor License Agreement].
|
||||
You will be reminded automatically when you submit the PR.
|
||||
[[create-an-issue]]
|
||||
1. Must you https://github.com/spring-projects/spring-security/issues/new/choose[create an issue] first? No, but it is recommended for features and larger bug fixes. It's easier discuss with the team first to determine the right fix or enhancement.
|
||||
For typos and straightforward bug fixes, starting with a pull request is encouraged.
|
||||
Please include a description for context and motivation.
|
||||
Note that the team may close your pull request if it's not a fit for the project.
|
||||
[[choose-a-branch]]
|
||||
1. Always check out the branch indicated in the milestone and submit pull requests against it (for example, for milestone `5.8.3` use the `5.8.x` branch).
|
||||
If there is no milestone, choose `main`.
|
||||
Once merged, the fix will be forwarded-ported to applicable branches including `main`.
|
||||
[[create-a-local-branch]]
|
||||
1. Create a local branch
|
||||
If this is for an issue, consider a branch name with the issue number, like `gh-22276`.
|
||||
[[write-tests]]
|
||||
1. Add JUnit Tests for your changes
|
||||
[[update-copyright]]
|
||||
1. In all files you edited, if the copyright header is of the form 2002-20xx, update the final copyright year to the current year.
|
||||
[[add-since]]
|
||||
1. If on `main`, add `@since` JavaDoc attributes to new public APIs that your PR adds
|
||||
[[change-rnc]]
|
||||
1. If you are updating the XSD, please instead update the RNC file and then run `./gradlew :spring-security-config:rncToXsd`.
|
||||
[[format-code]]
|
||||
1. For each commit, build the code using `./gradlew format check`.
|
||||
This command ensures the code meets most of <<code-style,the style guide>>; a notable exception is import order.
|
||||
[[commit-atomically]]
|
||||
1. Choose the granularity of your commits consciously and squash commits that represent
|
||||
multiple edits or corrections of the same logical change.
|
||||
See https://git-scm.com/book/en/Git-Tools-Rewriting-History[Rewriting History section of Pro Git] for an overview of streamlining the commit history.
|
||||
[[format-commit-messages]]
|
||||
1. Format commit messages using 55 characters for the subject line, 72 characters per line
|
||||
for the description, followed by the issue fixed, for example, `Closes gh-22276`.
|
||||
See the https://git-scm.com/book/en/Distributed-Git-Contributing-to-a-Project#Commit-Guidelines[Commit Guidelines section of Pro Git] for best practices around commit messages, and use `git log` to see some examples.
|
||||
Present tense is preferred.
|
||||
+
|
||||
[indent=0]
|
||||
----
|
||||
Address NullPointerException
|
||||
/*
|
||||
* Copyright 2002-2020 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.
|
||||
*/
|
||||
|
||||
Closes gh-22276
|
||||
----
|
||||
[[reference-issue]]
|
||||
1. If there is a prior issue, reference the GitHub issue number in the description of the pull request.
|
||||
+
|
||||
[indent=0]
|
||||
----
|
||||
Closes gh-22276
|
||||
package ...;
|
||||
----
|
||||
|
||||
If accepted, your contribution may be heavily modified as needed prior to merging.
|
||||
You will likely retain author attribution for your Git commits granted that the bulk of your changes remain intact.
|
||||
You may also be asked to rework the submission.
|
||||
= Update Apache license header to modified files as necessary
|
||||
|
||||
If asked to make corrections, simply push the changes against the same branch, and your pull request will be updated.
|
||||
In other words, you do not need to create a new pull request when asked to make changes.
|
||||
When it is time to merge, you'll be asked to squash your commits.
|
||||
Always check the date range in the license header. For example, if you've modified a file in 2020 whose header still reads
|
||||
|
||||
==== Participate in Reviews
|
||||
----
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
----
|
||||
|
||||
Helping to review pull requests is another great way to contribute.
|
||||
Your feedback can help to shape the implementation of new features.
|
||||
When reviewing pull requests, however, please refrain from approving or rejecting a PR unless you are a core committer for Spring Security.
|
||||
then be sure to update it to the current year appropriately (e.g. 2020)
|
||||
|
||||
----
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
----
|
||||
|
||||
= Use @since tags for newly-added public API types and methods
|
||||
|
||||
Example:
|
||||
|
||||
----
|
||||
/**
|
||||
* …
|
||||
*
|
||||
* @author First Last
|
||||
* @since 5.4
|
||||
* @see …
|
||||
*/
|
||||
----
|
||||
|
||||
= Submit JUnit test cases for all behavior changes
|
||||
|
||||
Search the codebase to find related unit tests and add additional `@Test` methods within.
|
||||
|
||||
. Any new tests should end in the name `Tests` (note this is plural). For example, a valid name would be `FilterChainProxyTests`. An invalid name would be `FilterChainProxyTest`.
|
||||
. New test methods should not start with test. This is an old JUnit3 convention and is not necessary since the method is annotated with `@Test`.
|
||||
|
||||
= Update spring-security-x.y.rnc for schema changes
|
||||
|
||||
Update the https://www.relaxng.org[RELAX NG] schema `spring-security-x.y.rnc` instead of `spring-security-x.y.xsd` if you contribute changes to supported XML configuration. The XML schema file can be generated the following Gradle task:
|
||||
|
||||
----
|
||||
./gradlew :spring-security-config:rncToXsd
|
||||
----
|
||||
|
||||
Changes to the XML schema will be overwritten by the Gradle build task.
|
||||
|
||||
= Squash commits
|
||||
|
||||
Use `git rebase --interactive`, `git add --patch` and other tools to "squash" multiple commits into atomic changes. In addition to the man pages for `git`, there are https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History[many resources online] to help you understand how these tools work.
|
||||
|
||||
= Use real name in git commits
|
||||
|
||||
Please configure Git to use your real first and last name for any commits you intend to submit as pull requests. Make sure the name is properly capitalized as submitted to the https://cla.pivotal.io[Pivotal Contributor License Agreement]:
|
||||
|
||||
----
|
||||
First Last <user@mail.com>
|
||||
----
|
||||
|
||||
This helps ensure traceability against the CLA, and also goes a long way to ensuring useful output from tools like Git shortlog and others.
|
||||
|
||||
You can configure this globally:
|
||||
|
||||
----
|
||||
git config --global user.name "First Last"
|
||||
git config --global user.email user@example.com
|
||||
----
|
||||
|
||||
or locally for the current repository by omitting the `--global` flag:
|
||||
|
||||
----
|
||||
git config user.name "First Last"
|
||||
git config user.email user@example.com
|
||||
----
|
||||
|
||||
= Format commit messages
|
||||
|
||||
. Keep the subject line to 50 characters or less if possible
|
||||
. Do not end the subject line with a period
|
||||
. In the body of the commit message, explain how things worked before this commit, what has changed, and how things work now
|
||||
. Include `Closes gh-<issue-number>` at the end if this fixes a GitHub issue
|
||||
. Avoid markdown, including back-ticks identifying code
|
||||
|
||||
Example:
|
||||
|
||||
----
|
||||
Short (50 chars or less) summary of changes
|
||||
|
||||
More detailed explanatory text, if necessary. Wrap it to about 72
|
||||
characters or so. In some contexts, the first line is treated as the
|
||||
subject of an email and the rest of the text as the body. The blank
|
||||
line separating the summary from the body is critical (unless you omit
|
||||
the body entirely); tools like rebase can get confused if you run the
|
||||
two together.
|
||||
|
||||
Further paragraphs come after blank lines.
|
||||
|
||||
- Bullet points are okay, too
|
||||
|
||||
- Typically a hyphen or asterisk is used for the bullet, preceded by a
|
||||
single space, with blank lines in between, but conventions vary here
|
||||
|
||||
Closes gh-123
|
||||
----
|
||||
|
||||
|
||||
= Run all tests prior to submission
|
||||
|
||||
----
|
||||
./gradlew clean build integrationTest
|
||||
----
|
||||
|
||||
= Submit your pull request
|
||||
|
||||
*Subject line:*
|
||||
|
||||
Follow the same conventions for pull request subject lines as mentioned above for commit message subject lines.
|
||||
|
||||
*In the body:*
|
||||
|
||||
. Explain your use case. What led you to submit this change? Why were existing mechanisms in the framework insufficient? Make a case that this is a general-purpose problem and that yours is a general-purpose solution, etc
|
||||
. Add any additional information and ask questions; start a conversation, or continue one from GitHub Issues
|
||||
. Mention any GitHub Issues
|
||||
. Also mention that you have submitted the CLA as described above
|
||||
Note that for pull requests containing a single commit, GitHub will default the subject line and body of the pull request to match the subject line and body of the commit message. This is fine, but please also include the items above in the body of the request.
|
||||
|
||||
= Mention your pull request on the associated GitHub issue
|
||||
|
||||
Add a comment to the associated GitHub issue(s) linking to your new pull request.
|
||||
|
||||
= Expect discussion and rework
|
||||
|
||||
The Spring team takes a very conservative approach to accepting contributions to the framework. This is to keep code quality and stability as high as possible, and to keep complexity at a minimum. Your changes, if accepted, may be heavily modified prior to merging. You will retain "Author:" attribution for your Git commits granted that the bulk of your changes remain intact. You may be asked to rework the submission for style (as explained above) and/or substance. Again, we strongly recommend discussing any serious submissions with the Spring Framework team prior to engaging in serious development work.
|
||||
|
||||
Note that you can always force push (`git push -f`) reworked / rebased commits against the branch used to submit your pull request. i.e. you do not need to issue a new pull request when asked to make changes.
|
||||
|
|
|
@ -59,9 +59,9 @@ The scheduled release process currently runs every Monday but only releases when
|
|||
The automated release process occurs on the following branches:
|
||||
|
||||
* `main`
|
||||
* `6.0.x`
|
||||
* `5.8.x`
|
||||
* `5.7.x`
|
||||
* `5.6.x`
|
||||
|
||||
For each of the above branches, the automated process performs the following checks before proceeding with the release:
|
||||
|
||||
|
|
|
@ -118,7 +118,8 @@ public class AclPermissionEvaluator implements PermissionEvaluator {
|
|||
if (permission instanceof Permission[]) {
|
||||
return Arrays.asList((Permission[]) permission);
|
||||
}
|
||||
if (permission instanceof String permString) {
|
||||
if (permission instanceof String) {
|
||||
String permString = (String) permission;
|
||||
Permission p = buildPermission(permString);
|
||||
if (p != null) {
|
||||
return Arrays.asList(p);
|
||||
|
|
|
@ -56,9 +56,10 @@ public abstract class AbstractPermission implements Permission {
|
|||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (!(obj instanceof Permission other)) {
|
||||
if (!(obj instanceof Permission)) {
|
||||
return false;
|
||||
}
|
||||
Permission other = (Permission) obj;
|
||||
return (this.mask == other.getMask());
|
||||
}
|
||||
|
||||
|
|
|
@ -112,7 +112,7 @@ public class JdbcAclServiceTests {
|
|||
given(this.jdbcOperations.query(anyString(), eq(args), any(RowMapper.class))).willReturn(result);
|
||||
ObjectIdentity objectIdentity = new ObjectIdentityImpl(MockLongIdDomainObject.class, 1L);
|
||||
List<ObjectIdentity> objectIdentities = this.aclService.findChildren(objectIdentity);
|
||||
assertThat(objectIdentities).hasSize(1);
|
||||
assertThat(objectIdentities.size()).isEqualTo(1);
|
||||
assertThat(objectIdentities.get(0).getIdentifier()).isEqualTo("5577");
|
||||
}
|
||||
|
||||
|
@ -127,7 +127,7 @@ public class JdbcAclServiceTests {
|
|||
public void findChildrenWithoutIdType() {
|
||||
ObjectIdentity objectIdentity = new ObjectIdentityImpl(MockLongIdDomainObject.class, 4711L);
|
||||
List<ObjectIdentity> objectIdentities = this.aclServiceIntegration.findChildren(objectIdentity);
|
||||
assertThat(objectIdentities).hasSize(1);
|
||||
assertThat(objectIdentities.size()).isEqualTo(1);
|
||||
assertThat(objectIdentities.get(0).getType()).isEqualTo(MockUntypedIdDomainObject.class.getName());
|
||||
assertThat(objectIdentities.get(0).getIdentifier()).isEqualTo(5000L);
|
||||
}
|
||||
|
@ -143,7 +143,7 @@ public class JdbcAclServiceTests {
|
|||
public void findChildrenOfIdTypeLong() {
|
||||
ObjectIdentity objectIdentity = new ObjectIdentityImpl("location", "US-PAL");
|
||||
List<ObjectIdentity> objectIdentities = this.aclServiceIntegration.findChildren(objectIdentity);
|
||||
assertThat(objectIdentities).hasSize(2);
|
||||
assertThat(objectIdentities.size()).isEqualTo(2);
|
||||
assertThat(objectIdentities.get(0).getType()).isEqualTo(MockLongIdDomainObject.class.getName());
|
||||
assertThat(objectIdentities.get(0).getIdentifier()).isEqualTo(4711L);
|
||||
assertThat(objectIdentities.get(1).getType()).isEqualTo(MockLongIdDomainObject.class.getName());
|
||||
|
@ -155,7 +155,7 @@ public class JdbcAclServiceTests {
|
|||
ObjectIdentity objectIdentity = new ObjectIdentityImpl("location", "US");
|
||||
this.aclServiceIntegration.setAclClassIdSupported(true);
|
||||
List<ObjectIdentity> objectIdentities = this.aclServiceIntegration.findChildren(objectIdentity);
|
||||
assertThat(objectIdentities).hasSize(1);
|
||||
assertThat(objectIdentities.size()).isEqualTo(1);
|
||||
assertThat(objectIdentities.get(0).getType()).isEqualTo("location");
|
||||
assertThat(objectIdentities.get(0).getIdentifier()).isEqualTo("US-PAL");
|
||||
}
|
||||
|
@ -165,7 +165,7 @@ public class JdbcAclServiceTests {
|
|||
ObjectIdentity objectIdentity = new ObjectIdentityImpl(MockUntypedIdDomainObject.class, 5000L);
|
||||
this.aclServiceIntegration.setAclClassIdSupported(true);
|
||||
List<ObjectIdentity> objectIdentities = this.aclServiceIntegration.findChildren(objectIdentity);
|
||||
assertThat(objectIdentities).hasSize(1);
|
||||
assertThat(objectIdentities.size()).isEqualTo(1);
|
||||
assertThat(objectIdentities.get(0).getType()).isEqualTo("costcenter");
|
||||
assertThat(objectIdentities.get(0).getIdentifier())
|
||||
.isEqualTo(UUID.fromString("25d93b3f-c3aa-4814-9d5e-c7c96ced7762"));
|
||||
|
@ -186,7 +186,7 @@ public class JdbcAclServiceTests {
|
|||
ObjectIdentity objectIdentity = new ObjectIdentityImpl("location", "US");
|
||||
this.aclServiceIntegration.setAclClassIdSupported(true);
|
||||
List<ObjectIdentity> objectIdentities = this.aclServiceIntegration.findChildren(objectIdentity);
|
||||
assertThat(objectIdentities).hasSize(1);
|
||||
assertThat(objectIdentities.size()).isEqualTo(1);
|
||||
assertThat(objectIdentities.get(0).getType()).isEqualTo("location");
|
||||
assertThat(objectIdentities.get(0).getIdentifier()).isEqualTo("prefix:US-PAL");
|
||||
}
|
||||
|
|
|
@ -454,7 +454,7 @@ public class JdbcMutableAclServiceTests {
|
|||
CustomSid customSid = new CustomSid("Custom sid");
|
||||
given(customJdbcMutableAclService.createOrRetrieveSidPrimaryKey("Custom sid", false, false)).willReturn(1L);
|
||||
Long result = customJdbcMutableAclService.createOrRetrieveSidPrimaryKey(customSid, false);
|
||||
assertThat(Long.valueOf(1L)).isEqualTo(result);
|
||||
assertThat(new Long(1L)).isEqualTo(result);
|
||||
}
|
||||
|
||||
protected Authentication getAuth() {
|
||||
|
|
|
@ -120,9 +120,9 @@ public class SidTests {
|
|||
PrincipalSid principalSid = new PrincipalSid(authentication);
|
||||
GrantedAuthority ga = new SimpleGrantedAuthority("ROLE_TEST");
|
||||
GrantedAuthoritySid gaSid = new GrantedAuthoritySid(ga);
|
||||
assertThat("johndoe").isEqualTo(principalSid.getPrincipal());
|
||||
assertThat("johndoe".equals(principalSid.getPrincipal())).isTrue();
|
||||
assertThat("scott".equals(principalSid.getPrincipal())).isFalse();
|
||||
assertThat("ROLE_TEST").isEqualTo(gaSid.getGrantedAuthority());
|
||||
assertThat("ROLE_TEST".equals(gaSid.getGrantedAuthority())).isTrue();
|
||||
assertThat("ROLE_TEST2".equals(gaSid.getGrantedAuthority())).isFalse();
|
||||
}
|
||||
|
||||
|
|
|
@ -29,4 +29,10 @@ dependencies {
|
|||
testAspect sourceSets.main.output
|
||||
}
|
||||
|
||||
sourceSets.main.aspectj.srcDir "src/main/java"
|
||||
sourceSets.main.java.srcDirs = files()
|
||||
|
||||
sourceSets.test.aspectj.srcDir "src/test/java"
|
||||
sourceSets.test.java.srcDirs = files()
|
||||
|
||||
compileAspectj.ajcOptions.outxmlfile = "META-INF/aop.xml"
|
||||
|
|
|
@ -143,8 +143,8 @@ public class AnnotationSecurityAspectTests {
|
|||
SecurityContextHolder.getContext().setAuthentication(this.anne);
|
||||
List<String> objects = this.prePostSecured.postFilterMethod();
|
||||
assertThat(objects).hasSize(2);
|
||||
assertThat(objects).contains("apple");
|
||||
assertThat(objects).contains("aubergine");
|
||||
assertThat(objects.contains("apple")).isTrue();
|
||||
assertThat(objects.contains("aubergine")).isTrue();
|
||||
}
|
||||
|
||||
private void configureForElAnnotations() {
|
||||
|
|
42
build.gradle
42
build.gradle
|
@ -14,10 +14,6 @@ buildscript {
|
|||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.org.gradle.wrapper.upgrade)
|
||||
}
|
||||
|
||||
apply plugin: 'io.spring.nohttp'
|
||||
apply plugin: 'locks'
|
||||
apply plugin: 's101'
|
||||
|
@ -39,7 +35,6 @@ ext.milestoneBuild = !(snapshotBuild || releaseBuild)
|
|||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url "https://repo.spring.io/milestone" }
|
||||
}
|
||||
|
||||
tasks.named("saganCreateRelease") {
|
||||
|
@ -91,31 +86,17 @@ tasks.named("dispatchGitHubWorkflow") {
|
|||
}
|
||||
}
|
||||
|
||||
def toolchainVersion() {
|
||||
if (project.hasProperty('testToolchain')) {
|
||||
return project.property('testToolchain').toString().toInteger()
|
||||
}
|
||||
return 17
|
||||
}
|
||||
|
||||
subprojects {
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(toolchainVersion())
|
||||
}
|
||||
plugins.withType(JavaPlugin) {
|
||||
project.sourceCompatibility=JavaVersion.VERSION_17
|
||||
}
|
||||
kotlin {
|
||||
jvmToolchain {
|
||||
languageVersion = JavaLanguageVersion.of(17)
|
||||
}
|
||||
}
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
tasks.withType(JavaCompile) {
|
||||
options.encoding = "UTF-8"
|
||||
options.compilerArgs.add("-parameters")
|
||||
options.release.set(17)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
allprojects {
|
||||
if (!['spring-security-bom', 'spring-security-docs'].contains(project.name)) {
|
||||
apply plugin: 'io.spring.javaformat'
|
||||
|
@ -140,6 +121,12 @@ allprojects {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
javaCompiler = javaToolchains.compilerFor {
|
||||
languageVersion = JavaLanguageVersion.of(17)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasProperty('buildScan')) {
|
||||
|
@ -163,12 +150,3 @@ tasks.register('cloneSamples', IncludeRepoTask) {
|
|||
s101 {
|
||||
configurationDirectory = project.file("etc/s101")
|
||||
}
|
||||
|
||||
wrapperUpgrade {
|
||||
gradle {
|
||||
'spring-security' {
|
||||
repo = 'spring-projects/spring-security'
|
||||
baseBranch = '6.0.x' // runs only on 6.0.x and the update is merged forward to main
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ sourceCompatibility = JavaVersion.VERSION_17
|
|||
repositories {
|
||||
gradlePluginPortal()
|
||||
mavenCentral()
|
||||
maven { url 'https://repo.spring.io/milestone' }
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
|
@ -112,7 +111,6 @@ dependencies {
|
|||
testImplementation libs.com.squareup.okhttp3.mockwebserver
|
||||
}
|
||||
|
||||
|
||||
tasks.named('test', Test).configure {
|
||||
onlyIf { !project.hasProperty("buildSrc.skipTests") }
|
||||
useJUnitPlatform()
|
||||
|
|
|
@ -4,6 +4,7 @@ import org.gradle.api.Action;
|
|||
import org.gradle.api.GradleException;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.Task;
|
||||
import org.gradle.api.tasks.TaskProvider;
|
||||
import org.gradle.language.base.plugins.LifecycleBasePlugin;
|
||||
|
||||
|
@ -26,8 +27,12 @@ public class AntoraVersionPlugin implements Plugin<Project> {
|
|||
project.getPlugins().withType(LifecycleBasePlugin.class, new Action<LifecycleBasePlugin>() {
|
||||
@Override
|
||||
public void execute(LifecycleBasePlugin lifecycleBasePlugin) {
|
||||
project.getTasks().named(LifecycleBasePlugin.CHECK_TASK_NAME)
|
||||
.configure(check -> check.dependsOn(antoraCheckVersion));
|
||||
project.getTasks().named(LifecycleBasePlugin.CHECK_TASK_NAME).configure(new Action<Task>() {
|
||||
@Override
|
||||
public void execute(Task check) {
|
||||
check.dependsOn(antoraCheckVersion);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
project.getTasks().register("antoraUpdateVersion", UpdateAntoraVersionTask.class, new Action<UpdateAntoraVersionTask>() {
|
||||
|
|
|
@ -35,7 +35,9 @@ public class CheckClasspathForProhibitedDependenciesPlugin implements Plugin<Pro
|
|||
@Override
|
||||
public void apply(Project project) {
|
||||
project.getPlugins().apply(CheckProhibitedDependenciesLifecyclePlugin.class);
|
||||
project.getPlugins().withType(JavaBasePlugin.class, javaBasePlugin -> configureProhibitedDependencyChecks(project));
|
||||
project.getPlugins().withType(JavaBasePlugin.class, javaBasePlugin -> {
|
||||
configureProhibitedDependencyChecks(project);
|
||||
});
|
||||
}
|
||||
|
||||
private void configureProhibitedDependencyChecks(Project project) {
|
||||
|
|
|
@ -34,6 +34,8 @@ public class CheckProhibitedDependenciesLifecyclePlugin implements Plugin<Projec
|
|||
task.setGroup(JavaBasePlugin.VERIFICATION_GROUP);
|
||||
task.setDescription("Checks both the compile/runtime classpath of every SourceSet for prohibited dependencies");
|
||||
});
|
||||
project.getTasks().named(JavaBasePlugin.CHECK_TASK_NAME, checkTask -> checkTask.dependsOn(checkProhibitedDependencies));
|
||||
project.getTasks().named(JavaBasePlugin.CHECK_TASK_NAME, checkTask -> {
|
||||
checkTask.dependsOn(checkProhibitedDependencies);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import org.gradle.api.Task;
|
|||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.artifacts.DependencySet;
|
||||
import org.gradle.api.artifacts.repositories.ExclusiveContentRepository;
|
||||
import org.gradle.api.artifacts.repositories.InclusiveRepositoryContentDescriptor;
|
||||
import org.gradle.api.artifacts.repositories.IvyArtifactRepository;
|
||||
import org.gradle.api.artifacts.repositories.IvyPatternRepositoryLayout;
|
||||
import org.gradle.api.tasks.JavaExec;
|
||||
|
@ -90,7 +91,12 @@ public class GitHubChangelogPlugin implements Plugin<Project> {
|
|||
@Override
|
||||
public void execute(ExclusiveContentRepository exclusiveContentRepository) {
|
||||
exclusiveContentRepository.forRepositories(repository);
|
||||
exclusiveContentRepository.filter(descriptor -> descriptor.includeGroup("spring-io"));
|
||||
exclusiveContentRepository.filter(new Action<InclusiveRepositoryContentDescriptor>() {
|
||||
@Override
|
||||
public void execute(InclusiveRepositoryContentDescriptor descriptor) {
|
||||
descriptor.includeGroup("spring-io");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -114,11 +114,11 @@ public final class SpringReleaseTrainSpec {
|
|||
}
|
||||
|
||||
public Builder train(int train) {
|
||||
this.train = switch (train) {
|
||||
case 1 -> Train.ONE;
|
||||
case 2 -> Train.TWO;
|
||||
default -> throw new IllegalArgumentException("Invalid train: " + train);
|
||||
};
|
||||
switch (train) {
|
||||
case 1: this.train = Train.ONE; break;
|
||||
case 2: this.train = Train.TWO; break;
|
||||
default: throw new IllegalArgumentException("Invalid train: " + train);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -156,13 +156,13 @@ public final class SpringReleaseTrainSpec {
|
|||
}
|
||||
|
||||
public Builder weekOfMonth(int weekOfMonth) {
|
||||
this.weekOfMonth = switch (weekOfMonth) {
|
||||
case 1 -> WeekOfMonth.FIRST;
|
||||
case 2 -> WeekOfMonth.SECOND;
|
||||
case 3 -> WeekOfMonth.THIRD;
|
||||
case 4 -> WeekOfMonth.FOURTH;
|
||||
default -> throw new IllegalArgumentException("Invalid weekOfMonth: " + weekOfMonth);
|
||||
};
|
||||
switch (weekOfMonth) {
|
||||
case 1: this.weekOfMonth = WeekOfMonth.FIRST; break;
|
||||
case 2: this.weekOfMonth = WeekOfMonth.SECOND; break;
|
||||
case 3: this.weekOfMonth = WeekOfMonth.THIRD; break;
|
||||
case 4: this.weekOfMonth = WeekOfMonth.FOURTH; break;
|
||||
default: throw new IllegalArgumentException("Invalid weekOfMonth: " + weekOfMonth);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -172,14 +172,14 @@ public final class SpringReleaseTrainSpec {
|
|||
}
|
||||
|
||||
public Builder dayOfWeek(int dayOfWeek) {
|
||||
this.dayOfWeek = switch (dayOfWeek) {
|
||||
case 1 -> DayOfWeek.MONDAY;
|
||||
case 2 -> DayOfWeek.TUESDAY;
|
||||
case 3 -> DayOfWeek.WEDNESDAY;
|
||||
case 4 -> DayOfWeek.THURSDAY;
|
||||
case 5 -> DayOfWeek.FRIDAY;
|
||||
default -> throw new IllegalArgumentException("Invalid dayOfWeek: " + dayOfWeek);
|
||||
};
|
||||
switch (dayOfWeek) {
|
||||
case 1: this.dayOfWeek = DayOfWeek.MONDAY; break;
|
||||
case 2: this.dayOfWeek = DayOfWeek.TUESDAY; break;
|
||||
case 3: this.dayOfWeek = DayOfWeek.WEDNESDAY; break;
|
||||
case 4: this.dayOfWeek = DayOfWeek.THURSDAY; break;
|
||||
case 5: this.dayOfWeek = DayOfWeek.FRIDAY; break;
|
||||
default: throw new IllegalArgumentException("Invalid dayOfWeek: " + dayOfWeek);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ import org.gradle.api.Plugin;
|
|||
import org.gradle.api.Project;
|
||||
import org.gradle.api.publish.Publication;
|
||||
import org.gradle.api.publish.PublishingExtension;
|
||||
import org.gradle.api.publish.plugins.PublishingPlugin;
|
||||
import org.gradle.plugins.signing.SigningExtension;
|
||||
import org.gradle.plugins.signing.SigningPlugin;
|
||||
|
||||
|
@ -43,7 +44,12 @@ public class SpringSigningPlugin implements Plugin<Project> {
|
|||
|
||||
private void sign(Project project) {
|
||||
SigningExtension signing = project.getExtensions().findByType(SigningExtension.class);
|
||||
signing.setRequired((Callable<Boolean>) () -> project.getGradle().getTaskGraph().hasTask("publishArtifacts"));
|
||||
signing.setRequired(new Callable<Boolean>() {
|
||||
@Override
|
||||
public Boolean call() throws Exception {
|
||||
return project.getGradle().getTaskGraph().hasTask("publishArtifacts");
|
||||
}
|
||||
});
|
||||
String signingKeyId = (String) project.findProperty("signingKeyId");
|
||||
String signingKey = (String) project.findProperty("signingKey");
|
||||
String signingPassword = (String) project.findProperty("signingPassword");
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
apply plugin: 'io.spring.convention.spring-module'
|
||||
|
||||
dependencies {
|
||||
management platform(project(":spring-security-dependencies"))
|
||||
api project(':spring-security-core')
|
||||
api project(':spring-security-web')
|
||||
api 'org.apereo.cas.client:cas-client-core'
|
||||
api 'org.springframework:spring-beans'
|
||||
api 'org.springframework:spring-context'
|
||||
api 'org.springframework:spring-core'
|
||||
api 'org.springframework:spring-web'
|
||||
|
||||
optional 'com.fasterxml.jackson.core:jackson-databind'
|
||||
|
||||
provided 'jakarta.servlet:jakarta.servlet-api'
|
||||
|
||||
testImplementation "org.assertj:assertj-core"
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-api"
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-params"
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-engine"
|
||||
testImplementation "org.mockito:mockito-core"
|
||||
testImplementation "org.mockito:mockito-junit-jupiter"
|
||||
testImplementation "org.springframework:spring-test"
|
||||
testImplementation 'org.skyscreamer:jsonassert'
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
/*
|
||||
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.cas;
|
||||
|
||||
/**
|
||||
* Sets the appropriate parameters for CAS's implementation of SAML (which is not
|
||||
* guaranteed to be actually SAML compliant).
|
||||
*
|
||||
* @author Scott Battaglia
|
||||
* @since 3.0
|
||||
*/
|
||||
public final class SamlServiceProperties extends ServiceProperties {
|
||||
|
||||
public static final String DEFAULT_SAML_ARTIFACT_PARAMETER = "SAMLart";
|
||||
|
||||
public static final String DEFAULT_SAML_SERVICE_PARAMETER = "TARGET";
|
||||
|
||||
public SamlServiceProperties() {
|
||||
super.setArtifactParameter(DEFAULT_SAML_ARTIFACT_PARAMETER);
|
||||
super.setServiceParameter(DEFAULT_SAML_SERVICE_PARAMETER);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,132 +0,0 @@
|
|||
/*
|
||||
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.cas;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Stores properties related to this CAS service.
|
||||
* <p>
|
||||
* Each web application capable of processing CAS tickets is known as a service. This
|
||||
* class stores the properties that are relevant to the local CAS service, being the
|
||||
* application that is being secured by Spring Security.
|
||||
*
|
||||
* @author Ben Alex
|
||||
*/
|
||||
public class ServiceProperties implements InitializingBean {
|
||||
|
||||
public static final String DEFAULT_CAS_ARTIFACT_PARAMETER = "ticket";
|
||||
|
||||
public static final String DEFAULT_CAS_SERVICE_PARAMETER = "service";
|
||||
|
||||
private String service;
|
||||
|
||||
private boolean authenticateAllArtifacts;
|
||||
|
||||
private boolean sendRenew = false;
|
||||
|
||||
private String artifactParameter = DEFAULT_CAS_ARTIFACT_PARAMETER;
|
||||
|
||||
private String serviceParameter = DEFAULT_CAS_SERVICE_PARAMETER;
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
Assert.hasLength(this.service, "service cannot be empty.");
|
||||
Assert.hasLength(this.artifactParameter, "artifactParameter cannot be empty.");
|
||||
Assert.hasLength(this.serviceParameter, "serviceParameter cannot be empty.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the service the user is authenticating to.
|
||||
* <p>
|
||||
* This service is the callback URL belonging to the local Spring Security System for
|
||||
* Spring secured application. For example,
|
||||
*
|
||||
* <pre>
|
||||
* https://www.mycompany.com/application/login/cas
|
||||
* </pre>
|
||||
* @return the URL of the service the user is authenticating to
|
||||
*/
|
||||
public final String getService() {
|
||||
return this.service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the <code>renew</code> parameter should be sent to the CAS login
|
||||
* URL and CAS validation URL.
|
||||
* <p>
|
||||
* If <code>true</code>, it will force CAS to authenticate the user again (even if the
|
||||
* user has previously authenticated). During ticket validation it will require the
|
||||
* ticket was generated as a consequence of an explicit login. High security
|
||||
* applications would probably set this to <code>true</code>. Defaults to
|
||||
* <code>false</code>, providing automated single sign on.
|
||||
* @return whether to send the <code>renew</code> parameter to CAS
|
||||
*/
|
||||
public final boolean isSendRenew() {
|
||||
return this.sendRenew;
|
||||
}
|
||||
|
||||
public final void setSendRenew(final boolean sendRenew) {
|
||||
this.sendRenew = sendRenew;
|
||||
}
|
||||
|
||||
public final void setService(final String service) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
public final String getArtifactParameter() {
|
||||
return this.artifactParameter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the Request Parameter to look for when attempting to see if a CAS ticket
|
||||
* was sent from the server.
|
||||
* @param artifactParameter the id to use. Default is "ticket".
|
||||
*/
|
||||
public final void setArtifactParameter(final String artifactParameter) {
|
||||
this.artifactParameter = artifactParameter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the Request parameter to look for when attempting to send a request to
|
||||
* CAS.
|
||||
* @return the service parameter to use. Default is "service".
|
||||
*/
|
||||
public final String getServiceParameter() {
|
||||
return this.serviceParameter;
|
||||
}
|
||||
|
||||
public final void setServiceParameter(final String serviceParameter) {
|
||||
this.serviceParameter = serviceParameter;
|
||||
}
|
||||
|
||||
public final boolean isAuthenticateAllArtifacts() {
|
||||
return this.authenticateAllArtifacts;
|
||||
}
|
||||
|
||||
/**
|
||||
* If true, then any non-null artifact (ticket) should be authenticated. Additionally,
|
||||
* the service will be determined dynamically in order to ensure the service matches
|
||||
* the expected value for this artifact.
|
||||
* @param authenticateAllArtifacts
|
||||
*/
|
||||
public final void setAuthenticateAllArtifacts(final boolean authenticateAllArtifacts) {
|
||||
this.authenticateAllArtifacts = authenticateAllArtifacts;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
/*
|
||||
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.cas.authentication;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.apereo.cas.client.validation.Assertion;
|
||||
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.core.SpringSecurityCoreVersion;
|
||||
|
||||
/**
|
||||
* Temporary authentication object needed to load the user details service.
|
||||
*
|
||||
* @author Scott Battaglia
|
||||
* @since 3.0
|
||||
*/
|
||||
public final class CasAssertionAuthenticationToken extends AbstractAuthenticationToken {
|
||||
|
||||
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
|
||||
|
||||
private final Assertion assertion;
|
||||
|
||||
private final String ticket;
|
||||
|
||||
public CasAssertionAuthenticationToken(final Assertion assertion, final String ticket) {
|
||||
super(new ArrayList<>());
|
||||
this.assertion = assertion;
|
||||
this.ticket = ticket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getPrincipal() {
|
||||
return this.assertion.getPrincipal().getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getCredentials() {
|
||||
return this.ticket;
|
||||
}
|
||||
|
||||
public Assertion getAssertion() {
|
||||
return this.assertion;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,235 +0,0 @@
|
|||
/*
|
||||
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.cas.authentication;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apereo.cas.client.validation.Assertion;
|
||||
import org.apereo.cas.client.validation.TicketValidationException;
|
||||
import org.apereo.cas.client.validation.TicketValidator;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.MessageSourceAware;
|
||||
import org.springframework.context.support.MessageSourceAccessor;
|
||||
import org.springframework.core.log.LogMessage;
|
||||
import org.springframework.security.authentication.AccountStatusUserDetailsChecker;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.cas.ServiceProperties;
|
||||
import org.springframework.security.cas.web.authentication.ServiceAuthenticationDetails;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.SpringSecurityMessageSource;
|
||||
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
|
||||
import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper;
|
||||
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper;
|
||||
import org.springframework.security.core.userdetails.UserDetailsChecker;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An {@link AuthenticationProvider} implementation that integrates with JA-SIG Central
|
||||
* Authentication Service (CAS).
|
||||
* <p>
|
||||
* This <code>AuthenticationProvider</code> is capable of validating
|
||||
* {@link CasServiceTicketAuthenticationToken} requests which contain a
|
||||
* <code>principal</code> name equal to either
|
||||
* {@link CasServiceTicketAuthenticationToken#CAS_STATEFUL_IDENTIFIER} or
|
||||
* {@link CasServiceTicketAuthenticationToken#CAS_STATELESS_IDENTIFIER}. It can also
|
||||
* validate a previously created {@link CasAuthenticationToken}.
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @author Scott Battaglia
|
||||
*/
|
||||
public class CasAuthenticationProvider implements AuthenticationProvider, InitializingBean, MessageSourceAware {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(CasAuthenticationProvider.class);
|
||||
|
||||
private AuthenticationUserDetailsService<CasAssertionAuthenticationToken> authenticationUserDetailsService;
|
||||
|
||||
private final UserDetailsChecker userDetailsChecker = new AccountStatusUserDetailsChecker();
|
||||
|
||||
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
|
||||
|
||||
private StatelessTicketCache statelessTicketCache = new NullStatelessTicketCache();
|
||||
|
||||
private String key;
|
||||
|
||||
private TicketValidator ticketValidator;
|
||||
|
||||
private ServiceProperties serviceProperties;
|
||||
|
||||
private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
Assert.notNull(this.authenticationUserDetailsService, "An authenticationUserDetailsService must be set");
|
||||
Assert.notNull(this.ticketValidator, "A ticketValidator must be set");
|
||||
Assert.notNull(this.statelessTicketCache, "A statelessTicketCache must be set");
|
||||
Assert.hasText(this.key,
|
||||
"A Key is required so CasAuthenticationProvider can identify tokens it previously authenticated");
|
||||
Assert.notNull(this.messages, "A message source must be set");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||
if (!supports(authentication.getClass())) {
|
||||
return null;
|
||||
}
|
||||
// If an existing CasAuthenticationToken, just check we created it
|
||||
if (authentication instanceof CasAuthenticationToken) {
|
||||
if (this.key.hashCode() != ((CasAuthenticationToken) authentication).getKeyHash()) {
|
||||
throw new BadCredentialsException(this.messages.getMessage("CasAuthenticationProvider.incorrectKey",
|
||||
"The presented CasAuthenticationToken does not contain the expected key"));
|
||||
}
|
||||
return authentication;
|
||||
}
|
||||
|
||||
// Ensure credentials are presented
|
||||
if ((authentication.getCredentials() == null) || "".equals(authentication.getCredentials())) {
|
||||
throw new BadCredentialsException(this.messages.getMessage("CasAuthenticationProvider.noServiceTicket",
|
||||
"Failed to provide a CAS service ticket to validate"));
|
||||
}
|
||||
|
||||
boolean stateless = (authentication instanceof CasServiceTicketAuthenticationToken token
|
||||
&& token.isStateless());
|
||||
CasAuthenticationToken result = null;
|
||||
|
||||
if (stateless) {
|
||||
// Try to obtain from cache
|
||||
result = this.statelessTicketCache.getByTicketId(authentication.getCredentials().toString());
|
||||
}
|
||||
if (result == null) {
|
||||
result = this.authenticateNow(authentication);
|
||||
result.setDetails(authentication.getDetails());
|
||||
}
|
||||
if (stateless) {
|
||||
// Add to cache
|
||||
this.statelessTicketCache.putTicketInCache(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private CasAuthenticationToken authenticateNow(final Authentication authentication) throws AuthenticationException {
|
||||
try {
|
||||
Assertion assertion = this.ticketValidator.validate(authentication.getCredentials().toString(),
|
||||
getServiceUrl(authentication));
|
||||
UserDetails userDetails = loadUserByAssertion(assertion);
|
||||
this.userDetailsChecker.check(userDetails);
|
||||
return new CasAuthenticationToken(this.key, userDetails, authentication.getCredentials(),
|
||||
this.authoritiesMapper.mapAuthorities(userDetails.getAuthorities()), userDetails, assertion);
|
||||
}
|
||||
catch (TicketValidationException ex) {
|
||||
throw new BadCredentialsException(ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the serviceUrl. If the {@link Authentication#getDetails()} is an instance of
|
||||
* {@link ServiceAuthenticationDetails}, then
|
||||
* {@link ServiceAuthenticationDetails#getServiceUrl()} is used. Otherwise, the
|
||||
* {@link ServiceProperties#getService()} is used.
|
||||
* @param authentication
|
||||
* @return
|
||||
*/
|
||||
private String getServiceUrl(Authentication authentication) {
|
||||
String serviceUrl;
|
||||
if (authentication.getDetails() instanceof ServiceAuthenticationDetails) {
|
||||
return ((ServiceAuthenticationDetails) authentication.getDetails()).getServiceUrl();
|
||||
}
|
||||
Assert.state(this.serviceProperties != null,
|
||||
"serviceProperties cannot be null unless Authentication.getDetails() implements ServiceAuthenticationDetails.");
|
||||
Assert.state(this.serviceProperties.getService() != null,
|
||||
"serviceProperties.getService() cannot be null unless Authentication.getDetails() implements ServiceAuthenticationDetails.");
|
||||
serviceUrl = this.serviceProperties.getService();
|
||||
logger.debug(LogMessage.format("serviceUrl = %s", serviceUrl));
|
||||
return serviceUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Template method for retrieving the UserDetails based on the assertion. Default is
|
||||
* to call configured userDetailsService and pass the username. Deployers can override
|
||||
* this method and retrieve the user based on any criteria they desire.
|
||||
* @param assertion The CAS Assertion.
|
||||
* @return the UserDetails.
|
||||
*/
|
||||
protected UserDetails loadUserByAssertion(final Assertion assertion) {
|
||||
final CasAssertionAuthenticationToken token = new CasAssertionAuthenticationToken(assertion, "");
|
||||
return this.authenticationUserDetailsService.loadUserDetails(token);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
/**
|
||||
* Sets the UserDetailsService to use. This is a convenience method to invoke
|
||||
*/
|
||||
public void setUserDetailsService(final UserDetailsService userDetailsService) {
|
||||
this.authenticationUserDetailsService = new UserDetailsByNameServiceWrapper(userDetailsService);
|
||||
}
|
||||
|
||||
public void setAuthenticationUserDetailsService(
|
||||
final AuthenticationUserDetailsService<CasAssertionAuthenticationToken> authenticationUserDetailsService) {
|
||||
this.authenticationUserDetailsService = authenticationUserDetailsService;
|
||||
}
|
||||
|
||||
public void setServiceProperties(final ServiceProperties serviceProperties) {
|
||||
this.serviceProperties = serviceProperties;
|
||||
}
|
||||
|
||||
protected String getKey() {
|
||||
return this.key;
|
||||
}
|
||||
|
||||
public void setKey(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public StatelessTicketCache getStatelessTicketCache() {
|
||||
return this.statelessTicketCache;
|
||||
}
|
||||
|
||||
protected TicketValidator getTicketValidator() {
|
||||
return this.ticketValidator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMessageSource(final MessageSource messageSource) {
|
||||
this.messages = new MessageSourceAccessor(messageSource);
|
||||
}
|
||||
|
||||
public void setStatelessTicketCache(final StatelessTicketCache statelessTicketCache) {
|
||||
this.statelessTicketCache = statelessTicketCache;
|
||||
}
|
||||
|
||||
public void setTicketValidator(final TicketValidator ticketValidator) {
|
||||
this.ticketValidator = ticketValidator;
|
||||
}
|
||||
|
||||
public void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) {
|
||||
this.authoritiesMapper = authoritiesMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(final Class<?> authentication) {
|
||||
return (CasServiceTicketAuthenticationToken.class.isAssignableFrom(authentication))
|
||||
|| (CasAuthenticationToken.class.isAssignableFrom(authentication))
|
||||
|| (CasAssertionAuthenticationToken.class.isAssignableFrom(authentication));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,173 +0,0 @@
|
|||
/*
|
||||
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.cas.authentication;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.apereo.cas.client.validation.Assertion;
|
||||
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.SpringSecurityCoreVersion;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* Represents a successful CAS <code>Authentication</code>.
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @author Scott Battaglia
|
||||
*/
|
||||
public class CasAuthenticationToken extends AbstractAuthenticationToken implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
|
||||
|
||||
private final Object credentials;
|
||||
|
||||
private final Object principal;
|
||||
|
||||
private final UserDetails userDetails;
|
||||
|
||||
private final int keyHash;
|
||||
|
||||
private final Assertion assertion;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param key to identify if this object made by a given
|
||||
* {@link CasAuthenticationProvider}
|
||||
* @param principal typically the UserDetails object (cannot be <code>null</code>)
|
||||
* @param credentials the service/proxy ticket ID from CAS (cannot be
|
||||
* <code>null</code>)
|
||||
* @param authorities the authorities granted to the user (from the
|
||||
* {@link org.springframework.security.core.userdetails.UserDetailsService}) (cannot
|
||||
* be <code>null</code>)
|
||||
* @param userDetails the user details (from the
|
||||
* {@link org.springframework.security.core.userdetails.UserDetailsService}) (cannot
|
||||
* be <code>null</code>)
|
||||
* @param assertion the assertion returned from the CAS servers. It contains the
|
||||
* principal and how to obtain a proxy ticket for the user.
|
||||
* @throws IllegalArgumentException if a <code>null</code> was passed
|
||||
*/
|
||||
public CasAuthenticationToken(final String key, final Object principal, final Object credentials,
|
||||
final Collection<? extends GrantedAuthority> authorities, final UserDetails userDetails,
|
||||
final Assertion assertion) {
|
||||
this(extractKeyHash(key), principal, credentials, authorities, userDetails, assertion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Private constructor for Jackson Deserialization support
|
||||
* @param keyHash hashCode of provided key to identify if this object made by a given
|
||||
* {@link CasAuthenticationProvider}
|
||||
* @param principal typically the UserDetails object (cannot be <code>null</code>)
|
||||
* @param credentials the service/proxy ticket ID from CAS (cannot be
|
||||
* <code>null</code>)
|
||||
* @param authorities the authorities granted to the user (from the
|
||||
* {@link org.springframework.security.core.userdetails.UserDetailsService}) (cannot
|
||||
* be <code>null</code>)
|
||||
* @param userDetails the user details (from the
|
||||
* {@link org.springframework.security.core.userdetails.UserDetailsService}) (cannot
|
||||
* be <code>null</code>)
|
||||
* @param assertion the assertion returned from the CAS servers. It contains the
|
||||
* principal and how to obtain a proxy ticket for the user.
|
||||
* @throws IllegalArgumentException if a <code>null</code> was passed
|
||||
* @since 4.2
|
||||
*/
|
||||
private CasAuthenticationToken(final Integer keyHash, final Object principal, final Object credentials,
|
||||
final Collection<? extends GrantedAuthority> authorities, final UserDetails userDetails,
|
||||
final Assertion assertion) {
|
||||
super(authorities);
|
||||
if ((principal == null) || "".equals(principal) || (credentials == null) || "".equals(credentials)
|
||||
|| (authorities == null) || (userDetails == null) || (assertion == null)) {
|
||||
throw new IllegalArgumentException("Cannot pass null or empty values to constructor");
|
||||
}
|
||||
this.keyHash = keyHash;
|
||||
this.principal = principal;
|
||||
this.credentials = credentials;
|
||||
this.userDetails = userDetails;
|
||||
this.assertion = assertion;
|
||||
setAuthenticated(true);
|
||||
}
|
||||
|
||||
private static Integer extractKeyHash(String key) {
|
||||
Assert.hasLength(key, "key cannot be null or empty");
|
||||
return key.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (!super.equals(obj)) {
|
||||
return false;
|
||||
}
|
||||
if (obj instanceof CasAuthenticationToken) {
|
||||
CasAuthenticationToken test = (CasAuthenticationToken) obj;
|
||||
if (!this.assertion.equals(test.getAssertion())) {
|
||||
return false;
|
||||
}
|
||||
if (this.getKeyHash() != test.getKeyHash()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = super.hashCode();
|
||||
result = 31 * result + this.credentials.hashCode();
|
||||
result = 31 * result + this.principal.hashCode();
|
||||
result = 31 * result + this.userDetails.hashCode();
|
||||
result = 31 * result + this.keyHash;
|
||||
result = 31 * result + ObjectUtils.nullSafeHashCode(this.assertion);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getCredentials() {
|
||||
return this.credentials;
|
||||
}
|
||||
|
||||
public int getKeyHash() {
|
||||
return this.keyHash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getPrincipal() {
|
||||
return this.principal;
|
||||
}
|
||||
|
||||
public Assertion getAssertion() {
|
||||
return this.assertion;
|
||||
}
|
||||
|
||||
public UserDetails getUserDetails() {
|
||||
return this.userDetails;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(super.toString());
|
||||
sb.append(" Assertion: ").append(this.assertion);
|
||||
sb.append(" Credentials (Service/Proxy Ticket): ").append(this.credentials);
|
||||
return (sb.toString());
|
||||
}
|
||||
|
||||
}
|
|
@ -1,112 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.cas.authentication;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.SpringSecurityCoreVersion;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An {@link org.springframework.security.core.Authentication} implementation that is
|
||||
* designed to process CAS service ticket.
|
||||
*
|
||||
* @author Hal Deadman
|
||||
* @since 6.1
|
||||
*/
|
||||
public class CasServiceTicketAuthenticationToken extends AbstractAuthenticationToken {
|
||||
|
||||
static final String CAS_STATELESS_IDENTIFIER = "_cas_stateless_";
|
||||
|
||||
static final String CAS_STATEFUL_IDENTIFIER = "_cas_stateful_";
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
|
||||
|
||||
private final String identifier;
|
||||
|
||||
private Object credentials;
|
||||
|
||||
/**
|
||||
* This constructor can be safely used by any code that wishes to create a
|
||||
* <code>CasServiceTicketAuthenticationToken</code>, as the {@link #isAuthenticated()}
|
||||
* will return <code>false</code>.
|
||||
*
|
||||
*/
|
||||
public CasServiceTicketAuthenticationToken(String identifier, Object credentials) {
|
||||
super(null);
|
||||
this.identifier = identifier;
|
||||
this.credentials = credentials;
|
||||
setAuthenticated(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* This constructor should only be used by <code>AuthenticationManager</code> or
|
||||
* <code>AuthenticationProvider</code> implementations that are satisfied with
|
||||
* producing a trusted (i.e. {@link #isAuthenticated()} = <code>true</code>)
|
||||
* authentication token.
|
||||
* @param identifier
|
||||
* @param credentials
|
||||
* @param authorities
|
||||
*/
|
||||
public CasServiceTicketAuthenticationToken(String identifier, Object credentials,
|
||||
Collection<? extends GrantedAuthority> authorities) {
|
||||
super(authorities);
|
||||
this.identifier = identifier;
|
||||
this.credentials = credentials;
|
||||
super.setAuthenticated(true);
|
||||
}
|
||||
|
||||
public static CasServiceTicketAuthenticationToken stateful(Object credentials) {
|
||||
return new CasServiceTicketAuthenticationToken(CAS_STATEFUL_IDENTIFIER, credentials);
|
||||
}
|
||||
|
||||
public static CasServiceTicketAuthenticationToken stateless(Object credentials) {
|
||||
return new CasServiceTicketAuthenticationToken(CAS_STATELESS_IDENTIFIER, credentials);
|
||||
}
|
||||
|
||||
public boolean isStateless() {
|
||||
return CAS_STATELESS_IDENTIFIER.equals(this.identifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getCredentials() {
|
||||
return this.credentials;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getPrincipal() {
|
||||
return this.identifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
|
||||
Assert.isTrue(!isAuthenticated,
|
||||
"Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
|
||||
super.setAuthenticated(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eraseCredentials() {
|
||||
super.eraseCredentials();
|
||||
this.credentials = null;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
/*
|
||||
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.cas.authentication;
|
||||
|
||||
/**
|
||||
* Implementation of @link {@link StatelessTicketCache} that has no backing cache. Useful
|
||||
* in instances where storing of tickets for stateless session management is not required.
|
||||
* <p>
|
||||
* This is the default StatelessTicketCache of the @link {@link CasAuthenticationProvider}
|
||||
* to eliminate the unnecessary dependency on EhCache that applications have even if they
|
||||
* are not using the stateless session management.
|
||||
*
|
||||
* @author Scott Battaglia
|
||||
* @see CasAuthenticationProvider
|
||||
*/
|
||||
public final class NullStatelessTicketCache implements StatelessTicketCache {
|
||||
|
||||
/**
|
||||
* @return null since we are not storing any tickets.
|
||||
*/
|
||||
@Override
|
||||
public CasAuthenticationToken getByTicketId(final String serviceTicket) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a no-op since we are not storing tickets.
|
||||
*/
|
||||
@Override
|
||||
public void putTicketInCache(final CasAuthenticationToken token) {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a no-op since we are not storing tickets.
|
||||
*/
|
||||
@Override
|
||||
public void removeTicketFromCache(final CasAuthenticationToken token) {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a no-op since we are not storing tickets.
|
||||
*/
|
||||
@Override
|
||||
public void removeTicketFromCache(final String serviceTicket) {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.cas.authentication;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.cache.Cache;
|
||||
import org.springframework.core.log.LogMessage;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Caches tickets using a Spring IoC defined {@link Cache}.
|
||||
*
|
||||
* @author Marten Deinum
|
||||
* @since 3.2
|
||||
*
|
||||
*/
|
||||
public class SpringCacheBasedTicketCache implements StatelessTicketCache {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(SpringCacheBasedTicketCache.class);
|
||||
|
||||
private final Cache cache;
|
||||
|
||||
public SpringCacheBasedTicketCache(Cache cache) {
|
||||
Assert.notNull(cache, "cache mandatory");
|
||||
this.cache = cache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CasAuthenticationToken getByTicketId(final String serviceTicket) {
|
||||
final Cache.ValueWrapper element = (serviceTicket != null) ? this.cache.get(serviceTicket) : null;
|
||||
logger.debug(LogMessage.of(() -> "Cache hit: " + (element != null) + "; service ticket: " + serviceTicket));
|
||||
return (element != null) ? (CasAuthenticationToken) element.get() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putTicketInCache(final CasAuthenticationToken token) {
|
||||
String key = token.getCredentials().toString();
|
||||
logger.debug(LogMessage.of(() -> "Cache put: " + key));
|
||||
this.cache.put(key, token);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeTicketFromCache(final CasAuthenticationToken token) {
|
||||
logger.debug(LogMessage.of(() -> "Cache remove: " + token.getCredentials().toString()));
|
||||
this.removeTicketFromCache(token.getCredentials().toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeTicketFromCache(final String serviceTicket) {
|
||||
this.cache.evict(serviceTicket);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,110 +0,0 @@
|
|||
/*
|
||||
* Copyright 2004 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.cas.authentication;
|
||||
|
||||
/**
|
||||
* Caches CAS service tickets and CAS proxy tickets for stateless connections.
|
||||
*
|
||||
* <p>
|
||||
* When a service ticket or proxy ticket is validated against the CAS server, it is unable
|
||||
* to be used again. Most types of callers are stateful and are associated with a given
|
||||
* <code>HttpSession</code>. This allows the affirmative CAS validation outcome to be
|
||||
* stored in the <code>HttpSession</code>, meaning the removal of the ticket from the CAS
|
||||
* server is not an issue.
|
||||
* </p>
|
||||
*
|
||||
* <P>
|
||||
* Stateless callers, such as remoting protocols, cannot take advantage of
|
||||
* <code>HttpSession</code>. If the stateless caller is located a significant network
|
||||
* distance from the CAS server, acquiring a fresh service ticket or proxy ticket for each
|
||||
* invocation would be expensive.
|
||||
* </p>
|
||||
*
|
||||
* <P>
|
||||
* To avoid this issue with stateless callers, it is expected stateless callers will
|
||||
* obtain a single service ticket or proxy ticket, and then present this same ticket to
|
||||
* the Spring Security secured application on each occasion. As no
|
||||
* <code>HttpSession</code> is available for such callers, the affirmative CAS validation
|
||||
* outcome cannot be stored in this location.
|
||||
* </p>
|
||||
*
|
||||
* <P>
|
||||
* The <code>StatelessTicketCache</code> enables the service tickets and proxy tickets
|
||||
* belonging to stateless callers to be placed in a cache. This in-memory cache stores the
|
||||
* <code>CasAuthenticationToken</code>, effectively providing the same capability as a
|
||||
* <code>HttpSession</code> with the ticket identifier being the key rather than a session
|
||||
* identifier.
|
||||
* </p>
|
||||
*
|
||||
* <P>
|
||||
* Implementations should provide a reasonable timeout on stored entries, such that the
|
||||
* stateless caller are not required to unnecessarily acquire fresh CAS service tickets or
|
||||
* proxy tickets.
|
||||
* </p>
|
||||
*
|
||||
* @author Ben Alex
|
||||
*/
|
||||
public interface StatelessTicketCache {
|
||||
|
||||
/**
|
||||
* Retrieves the <code>CasAuthenticationToken</code> associated with the specified
|
||||
* ticket.
|
||||
*
|
||||
* <P>
|
||||
* If not found, returns a <code>null</code><code>CasAuthenticationToken</code>.
|
||||
* </p>
|
||||
* @return the fully populated authentication token
|
||||
*/
|
||||
CasAuthenticationToken getByTicketId(String serviceTicket);
|
||||
|
||||
/**
|
||||
* Adds the specified <code>CasAuthenticationToken</code> to the cache.
|
||||
*
|
||||
* <P>
|
||||
* The {@link CasAuthenticationToken#getCredentials()} method is used to retrieve the
|
||||
* service ticket number.
|
||||
* </p>
|
||||
* @param token to be added to the cache
|
||||
*/
|
||||
void putTicketInCache(CasAuthenticationToken token);
|
||||
|
||||
/**
|
||||
* Removes the specified ticket from the cache, as per
|
||||
* {@link #removeTicketFromCache(String)}.
|
||||
*
|
||||
* <P>
|
||||
* Implementations should use {@link CasAuthenticationToken#getCredentials()} to
|
||||
* obtain the ticket and then delegate to the {@link #removeTicketFromCache(String)}
|
||||
* method.
|
||||
* </p>
|
||||
* @param token to be removed
|
||||
*/
|
||||
void removeTicketFromCache(CasAuthenticationToken token);
|
||||
|
||||
/**
|
||||
* Removes the specified ticket from the cache, meaning that future calls will require
|
||||
* a new service ticket.
|
||||
*
|
||||
* <P>
|
||||
* This is in case applications wish to provide a session termination capability for
|
||||
* their stateless clients.
|
||||
* </p>
|
||||
* @param serviceTicket to be removed
|
||||
*/
|
||||
void removeTicketFromCache(String serviceTicket);
|
||||
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* An {@code AuthenticationProvider} that can process CAS service tickets and proxy
|
||||
* tickets.
|
||||
*/
|
||||
package org.springframework.security.cas.authentication;
|
|
@ -1,69 +0,0 @@
|
|||
/*
|
||||
* Copyright 2015-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.cas.jackson2;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import org.apereo.cas.client.authentication.AttributePrincipal;
|
||||
|
||||
/**
|
||||
* Helps in jackson deserialization of class
|
||||
* {@link org.apereo.cas.client.validation.AssertionImpl}, which is used with
|
||||
* {@link org.springframework.security.cas.authentication.CasAuthenticationToken}. To use
|
||||
* this class we need to register with
|
||||
* {@link com.fasterxml.jackson.databind.ObjectMapper}. Type information will be stored
|
||||
* in @class property.
|
||||
* <p>
|
||||
* <pre>
|
||||
* ObjectMapper mapper = new ObjectMapper();
|
||||
* mapper.registerModule(new CasJackson2Module());
|
||||
* </pre>
|
||||
*
|
||||
* @author Jitendra Singh
|
||||
* @since 4.2
|
||||
* @see CasJackson2Module
|
||||
* @see org.springframework.security.jackson2.SecurityJackson2Modules
|
||||
*/
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
|
||||
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
|
||||
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
class AssertionImplMixin {
|
||||
|
||||
/**
|
||||
* Mixin Constructor helps in deserialize
|
||||
* {@link org.apereo.cas.client.validation.AssertionImpl}
|
||||
* @param principal the Principal to associate with the Assertion.
|
||||
* @param validFromDate when the assertion is valid from.
|
||||
* @param validUntilDate when the assertion is valid to.
|
||||
* @param authenticationDate when the assertion is authenticated.
|
||||
* @param attributes the key/value pairs for this attribute.
|
||||
*/
|
||||
@JsonCreator
|
||||
AssertionImplMixin(@JsonProperty("principal") AttributePrincipal principal,
|
||||
@JsonProperty("validFromDate") Date validFromDate, @JsonProperty("validUntilDate") Date validUntilDate,
|
||||
@JsonProperty("authenticationDate") Date authenticationDate,
|
||||
@JsonProperty("attributes") Map<String, Object> attributes) {
|
||||
}
|
||||
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
/*
|
||||
* Copyright 2015-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.cas.jackson2;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import org.apereo.cas.client.proxy.ProxyRetriever;
|
||||
|
||||
/**
|
||||
* Helps in deserialize
|
||||
* {@link org.apereo.cas.client.authentication.AttributePrincipalImpl} which is used with
|
||||
* {@link org.springframework.security.cas.authentication.CasAuthenticationToken}. Type
|
||||
* information will be stored in property named @class.
|
||||
* <p>
|
||||
* <pre>
|
||||
* ObjectMapper mapper = new ObjectMapper();
|
||||
* mapper.registerModule(new CasJackson2Module());
|
||||
* </pre>
|
||||
*
|
||||
* @author Jitendra Singh
|
||||
* @since 4.2
|
||||
* @see CasJackson2Module
|
||||
* @see org.springframework.security.jackson2.SecurityJackson2Modules
|
||||
*/
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
|
||||
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
|
||||
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
class AttributePrincipalImplMixin {
|
||||
|
||||
/**
|
||||
* Mixin Constructor helps in deserialize
|
||||
* {@link org.apereo.cas.client.authentication.AttributePrincipalImpl}
|
||||
* @param name the unique identifier for the principal.
|
||||
* @param attributes the key/value pairs for this principal.
|
||||
* @param proxyGrantingTicket the ticket associated with this principal.
|
||||
* @param proxyRetriever the ProxyRetriever implementation to call back to the CAS
|
||||
* server.
|
||||
*/
|
||||
@JsonCreator
|
||||
AttributePrincipalImplMixin(@JsonProperty("name") String name,
|
||||
@JsonProperty("attributes") Map<String, Object> attributes,
|
||||
@JsonProperty("proxyGrantingTicket") String proxyGrantingTicket,
|
||||
@JsonProperty("proxyRetriever") ProxyRetriever proxyRetriever) {
|
||||
}
|
||||
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
/*
|
||||
* Copyright 2015-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.cas.jackson2;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import org.apereo.cas.client.validation.Assertion;
|
||||
|
||||
import org.springframework.security.cas.authentication.CasAuthenticationProvider;
|
||||
import org.springframework.security.cas.authentication.CasAuthenticationToken;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
/**
|
||||
* Mixin class which helps in deserialize
|
||||
* {@link org.springframework.security.cas.authentication.CasAuthenticationToken} using
|
||||
* jackson. Two more dependent classes needs to register along with this mixin class.
|
||||
* <ol>
|
||||
* <li>{@link org.springframework.security.cas.jackson2.AssertionImplMixin}</li>
|
||||
* <li>{@link org.springframework.security.cas.jackson2.AttributePrincipalImplMixin}</li>
|
||||
* </ol>
|
||||
*
|
||||
* <p>
|
||||
*
|
||||
* <pre>
|
||||
* ObjectMapper mapper = new ObjectMapper();
|
||||
* mapper.registerModule(new CasJackson2Module());
|
||||
* </pre>
|
||||
*
|
||||
* @author Jitendra Singh
|
||||
* @since 4.2
|
||||
* @see CasJackson2Module
|
||||
* @see org.springframework.security.jackson2.SecurityJackson2Modules
|
||||
*/
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
|
||||
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, isGetterVisibility = JsonAutoDetect.Visibility.NONE,
|
||||
getterVisibility = JsonAutoDetect.Visibility.NONE, creatorVisibility = JsonAutoDetect.Visibility.ANY)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
class CasAuthenticationTokenMixin {
|
||||
|
||||
/**
|
||||
* Mixin Constructor helps in deserialize {@link CasAuthenticationToken}
|
||||
* @param keyHash hashCode of provided key to identify if this object made by a given
|
||||
* {@link CasAuthenticationProvider}
|
||||
* @param principal typically the UserDetails object (cannot be <code>null</code>)
|
||||
* @param credentials the service/proxy ticket ID from CAS (cannot be
|
||||
* <code>null</code>)
|
||||
* @param authorities the authorities granted to the user (from the
|
||||
* {@link org.springframework.security.core.userdetails.UserDetailsService}) (cannot
|
||||
* be <code>null</code>)
|
||||
* @param userDetails the user details (from the
|
||||
* {@link org.springframework.security.core.userdetails.UserDetailsService}) (cannot
|
||||
* be <code>null</code>)
|
||||
* @param assertion the assertion returned from the CAS servers. It contains the
|
||||
* principal and how to obtain a proxy ticket for the user.
|
||||
*/
|
||||
@JsonCreator
|
||||
CasAuthenticationTokenMixin(@JsonProperty("keyHash") Integer keyHash, @JsonProperty("principal") Object principal,
|
||||
@JsonProperty("credentials") Object credentials,
|
||||
@JsonProperty("authorities") Collection<? extends GrantedAuthority> authorities,
|
||||
@JsonProperty("userDetails") UserDetails userDetails, @JsonProperty("assertion") Assertion assertion) {
|
||||
}
|
||||
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
/*
|
||||
* Copyright 2015-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.cas.jackson2;
|
||||
|
||||
import com.fasterxml.jackson.core.Version;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
import org.apereo.cas.client.authentication.AttributePrincipalImpl;
|
||||
import org.apereo.cas.client.validation.AssertionImpl;
|
||||
|
||||
import org.springframework.security.cas.authentication.CasAuthenticationToken;
|
||||
import org.springframework.security.jackson2.SecurityJackson2Modules;
|
||||
|
||||
/**
|
||||
* Jackson module for spring-security-cas. This module register
|
||||
* {@link AssertionImplMixin}, {@link AttributePrincipalImplMixin} and
|
||||
* {@link CasAuthenticationTokenMixin}. If no default typing enabled by default then it'll
|
||||
* enable it because typing info is needed to properly serialize/deserialize objects. In
|
||||
* order to use this module just add this module into your ObjectMapper configuration.
|
||||
*
|
||||
* <pre>
|
||||
* ObjectMapper mapper = new ObjectMapper();
|
||||
* mapper.registerModule(new CasJackson2Module());
|
||||
* </pre> <b>Note: use {@link SecurityJackson2Modules#getModules(ClassLoader)} to get list
|
||||
* of all security modules on the classpath.</b>
|
||||
*
|
||||
* @author Jitendra Singh.
|
||||
* @since 4.2
|
||||
* @see org.springframework.security.jackson2.SecurityJackson2Modules
|
||||
*/
|
||||
public class CasJackson2Module extends SimpleModule {
|
||||
|
||||
public CasJackson2Module() {
|
||||
super(CasJackson2Module.class.getName(), new Version(1, 0, 0, null, null, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setupModule(SetupContext context) {
|
||||
SecurityJackson2Modules.enableDefaultTyping(context.getOwner());
|
||||
context.setMixInAnnotations(AssertionImpl.class, AssertionImplMixin.class);
|
||||
context.setMixInAnnotations(AttributePrincipalImpl.class, AttributePrincipalImplMixin.class);
|
||||
context.setMixInAnnotations(CasAuthenticationToken.class, CasAuthenticationTokenMixin.class);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Spring Security support for Apereo's Central Authentication Service
|
||||
* (<a href="https://github.com/apereo/cas">CAS</a>).
|
||||
*/
|
||||
package org.springframework.security.cas;
|
|
@ -1,51 +0,0 @@
|
|||
/*
|
||||
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.cas.userdetails;
|
||||
|
||||
import org.apereo.cas.client.validation.Assertion;
|
||||
|
||||
import org.springframework.security.cas.authentication.CasAssertionAuthenticationToken;
|
||||
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
/**
|
||||
* Abstract class for using the provided CAS assertion to construct a new User object.
|
||||
* This generally is most useful when combined with a SAML-based response from the CAS
|
||||
* Server/client.
|
||||
*
|
||||
* @author Scott Battaglia
|
||||
* @since 3.0
|
||||
*/
|
||||
public abstract class AbstractCasAssertionUserDetailsService
|
||||
implements AuthenticationUserDetailsService<CasAssertionAuthenticationToken> {
|
||||
|
||||
@Override
|
||||
public final UserDetails loadUserDetails(final CasAssertionAuthenticationToken token) {
|
||||
return loadUserDetails(token.getAssertion());
|
||||
}
|
||||
|
||||
/**
|
||||
* Protected template method for construct a
|
||||
* {@link org.springframework.security.core.userdetails.UserDetails} via the supplied
|
||||
* CAS assertion.
|
||||
* @param assertion the assertion to use to construct the new UserDetails. CANNOT be
|
||||
* NULL.
|
||||
* @return the newly constructed UserDetails.
|
||||
*/
|
||||
protected abstract UserDetails loadUserDetails(Assertion assertion);
|
||||
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
/*
|
||||
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.cas.userdetails;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apereo.cas.client.validation.Assertion;
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Populates the {@link org.springframework.security.core.GrantedAuthority}s for a user by
|
||||
* reading a list of attributes that were returned as part of the CAS response. Each
|
||||
* attribute is read and each value of the attribute is turned into a GrantedAuthority. If
|
||||
* the attribute has no value then its not added.
|
||||
*
|
||||
* @author Scott Battaglia
|
||||
* @since 3.0
|
||||
*/
|
||||
public final class GrantedAuthorityFromAssertionAttributesUserDetailsService
|
||||
extends AbstractCasAssertionUserDetailsService {
|
||||
|
||||
private static final String NON_EXISTENT_PASSWORD_VALUE = "NO_PASSWORD";
|
||||
|
||||
private final String[] attributes;
|
||||
|
||||
private boolean convertToUpperCase = true;
|
||||
|
||||
public GrantedAuthorityFromAssertionAttributesUserDetailsService(final String[] attributes) {
|
||||
Assert.notNull(attributes, "attributes cannot be null.");
|
||||
Assert.isTrue(attributes.length > 0, "At least one attribute is required to retrieve roles from.");
|
||||
this.attributes = attributes;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
protected UserDetails loadUserDetails(final Assertion assertion) {
|
||||
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
|
||||
for (String attribute : this.attributes) {
|
||||
Object value = assertion.getPrincipal().getAttributes().get(attribute);
|
||||
if (value != null) {
|
||||
if (value instanceof List) {
|
||||
for (Object o : (List<?>) value) {
|
||||
grantedAuthorities.add(createSimpleGrantedAuthority(o));
|
||||
}
|
||||
}
|
||||
else {
|
||||
grantedAuthorities.add(createSimpleGrantedAuthority(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
return new User(assertion.getPrincipal().getName(), NON_EXISTENT_PASSWORD_VALUE, true, true, true, true,
|
||||
grantedAuthorities);
|
||||
}
|
||||
|
||||
private SimpleGrantedAuthority createSimpleGrantedAuthority(Object o) {
|
||||
return new SimpleGrantedAuthority(this.convertToUpperCase ? o.toString().toUpperCase() : o.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the returned attribute values to uppercase values.
|
||||
* @param convertToUpperCase true if it should convert, false otherwise.
|
||||
*/
|
||||
public void setConvertToUpperCase(final boolean convertToUpperCase) {
|
||||
this.convertToUpperCase = convertToUpperCase;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,152 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.cas.web;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.apereo.cas.client.util.CommonUtils;
|
||||
import org.apereo.cas.client.util.WebUtils;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.security.cas.ServiceProperties;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
import org.springframework.security.web.DefaultRedirectStrategy;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Used by the <code>ExceptionTranslationFilter</code> to commence authentication via the
|
||||
* JA-SIG Central Authentication Service (CAS).
|
||||
* <p>
|
||||
* The user's browser will be redirected to the JA-SIG CAS enterprise-wide login page.
|
||||
* This page is specified by the <code>loginUrl</code> property. Once login is complete,
|
||||
* the CAS login page will redirect to the page indicated by the <code>service</code>
|
||||
* property. The <code>service</code> is a HTTP URL belonging to the current application.
|
||||
* The <code>service</code> URL is monitored by the {@link CasAuthenticationFilter}, which
|
||||
* will validate the CAS login was successful.
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @author Scott Battaglia
|
||||
*/
|
||||
public class CasAuthenticationEntryPoint implements AuthenticationEntryPoint, InitializingBean {
|
||||
|
||||
private ServiceProperties serviceProperties;
|
||||
|
||||
private String loginUrl;
|
||||
|
||||
/**
|
||||
* Determines whether the Service URL should include the session id for the specific
|
||||
* user. As of CAS 3.0.5, the session id will automatically be stripped. However,
|
||||
* older versions of CAS (i.e. CAS 2), do not automatically strip the session
|
||||
* identifier (this is a bug on the part of the older server implementations), so an
|
||||
* option to disable the session encoding is provided for backwards compatibility.
|
||||
*
|
||||
* By default, encoding is enabled.
|
||||
*/
|
||||
private boolean encodeServiceUrlWithSessionId = true;
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
Assert.hasLength(this.loginUrl, "loginUrl must be specified");
|
||||
Assert.notNull(this.serviceProperties, "serviceProperties must be specified");
|
||||
Assert.notNull(this.serviceProperties.getService(), "serviceProperties.getService() cannot be null.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void commence(final HttpServletRequest servletRequest, HttpServletResponse response,
|
||||
AuthenticationException authenticationException) throws IOException {
|
||||
String urlEncodedService = createServiceUrl(servletRequest, response);
|
||||
String redirectUrl = createRedirectUrl(urlEncodedService);
|
||||
preCommence(servletRequest, response);
|
||||
new DefaultRedirectStrategy().sendRedirect(servletRequest, response, redirectUrl);
|
||||
// response.sendRedirect(redirectUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new Service Url. The default implementation relies on the CAS client
|
||||
* to do the bulk of the work.
|
||||
* @param request the HttpServletRequest
|
||||
* @param response the HttpServlet Response
|
||||
* @return the constructed service url. CANNOT be NULL.
|
||||
*/
|
||||
protected String createServiceUrl(HttpServletRequest request, HttpServletResponse response) {
|
||||
return WebUtils.constructServiceUrl(null, response, this.serviceProperties.getService(), null,
|
||||
this.serviceProperties.getArtifactParameter(), this.encodeServiceUrlWithSessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the Url for Redirection to the CAS server. Default implementation relies
|
||||
* on the CAS client to do the bulk of the work.
|
||||
* @param serviceUrl the service url that should be included.
|
||||
* @return the redirect url. CANNOT be NULL.
|
||||
*/
|
||||
protected String createRedirectUrl(String serviceUrl) {
|
||||
return CommonUtils.constructRedirectUrl(this.loginUrl, this.serviceProperties.getServiceParameter(), serviceUrl,
|
||||
this.serviceProperties.isSendRenew(), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Template method for you to do your own pre-processing before the redirect occurs.
|
||||
* @param request the HttpServletRequest
|
||||
* @param response the HttpServletResponse
|
||||
*/
|
||||
protected void preCommence(HttpServletRequest request, HttpServletResponse response) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The enterprise-wide CAS login URL. Usually something like
|
||||
* <code>https://www.mycompany.com/cas/login</code>.
|
||||
* @return the enterprise-wide CAS login URL
|
||||
*/
|
||||
public final String getLoginUrl() {
|
||||
return this.loginUrl;
|
||||
}
|
||||
|
||||
public final ServiceProperties getServiceProperties() {
|
||||
return this.serviceProperties;
|
||||
}
|
||||
|
||||
public final void setLoginUrl(String loginUrl) {
|
||||
this.loginUrl = loginUrl;
|
||||
}
|
||||
|
||||
public final void setServiceProperties(ServiceProperties serviceProperties) {
|
||||
this.serviceProperties = serviceProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to encode the service url with the session id or not.
|
||||
* @param encodeServiceUrlWithSessionId whether to encode the service url with the
|
||||
* session id or not.
|
||||
*/
|
||||
public final void setEncodeServiceUrlWithSessionId(boolean encodeServiceUrlWithSessionId) {
|
||||
this.encodeServiceUrlWithSessionId = encodeServiceUrlWithSessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to encode the service url with the session id or not.
|
||||
* @return whether to encode the service url with the session id or not.
|
||||
*
|
||||
*/
|
||||
protected boolean getEncodeServiceUrlWithSessionId() {
|
||||
return this.encodeServiceUrlWithSessionId;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,395 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.cas.web;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.apereo.cas.client.proxy.ProxyGrantingTicketStorage;
|
||||
import org.apereo.cas.client.util.WebUtils;
|
||||
import org.apereo.cas.client.validation.TicketValidator;
|
||||
|
||||
import org.springframework.core.log.LogMessage;
|
||||
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||
import org.springframework.security.authentication.AuthenticationDetailsSource;
|
||||
import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent;
|
||||
import org.springframework.security.cas.ServiceProperties;
|
||||
import org.springframework.security.cas.authentication.CasServiceTicketAuthenticationToken;
|
||||
import org.springframework.security.cas.web.authentication.ServiceAuthenticationDetails;
|
||||
import org.springframework.security.cas.web.authentication.ServiceAuthenticationDetailsSource;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.context.SecurityContextHolderStrategy;
|
||||
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
|
||||
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
|
||||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
|
||||
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
|
||||
import org.springframework.security.web.context.SecurityContextRepository;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Processes a CAS service ticket, obtains proxy granting tickets, and processes proxy
|
||||
* tickets.
|
||||
* <h2>Service Tickets</h2>
|
||||
* <p>
|
||||
* A service ticket consists of an opaque ticket string. It arrives at this filter by the
|
||||
* user's browser successfully authenticating using CAS, and then receiving a HTTP
|
||||
* redirect to a <code>service</code>. The opaque ticket string is presented in the
|
||||
* <code>ticket</code> request parameter.
|
||||
* <p>
|
||||
* This filter monitors the <code>service</code> URL so it can receive the service ticket
|
||||
* and process it. By default this filter processes the URL <tt>/login/cas</tt>. When
|
||||
* processing this URL, the value of {@link ServiceProperties#getService()} is used as the
|
||||
* <tt>service</tt> when validating the <code>ticket</code>. This means that it is
|
||||
* important that {@link ServiceProperties#getService()} specifies the same value as the
|
||||
* <tt>filterProcessesUrl</tt>.
|
||||
* <p>
|
||||
* Processing the service ticket involves creating a
|
||||
* <code>CasServiceTicketAuthenticationToken</code> which uses
|
||||
* {@link CasServiceTicketAuthenticationToken#CAS_STATEFUL_IDENTIFIER} for the
|
||||
* <code>principal</code> and the opaque ticket string as the <code>credentials</code>.
|
||||
* <h2>Obtaining Proxy Granting Tickets</h2>
|
||||
* <p>
|
||||
* If specified, the filter can also monitor the <code>proxyReceptorUrl</code>. The filter
|
||||
* will respond to requests matching this url so that the CAS Server can provide a PGT to
|
||||
* the filter. Note that in addition to the <code>proxyReceptorUrl</code> a non-null
|
||||
* <code>proxyGrantingTicketStorage</code> must be provided in order for the filter to
|
||||
* respond to proxy receptor requests. By configuring a shared
|
||||
* {@link ProxyGrantingTicketStorage} between the {@link TicketValidator} and the
|
||||
* CasAuthenticationFilter one can have the CasAuthenticationFilter handle the proxying
|
||||
* requirements for CAS.
|
||||
* <h2>Proxy Tickets</h2>
|
||||
* <p>
|
||||
* The filter can process tickets present on any url. This is useful when wanting to
|
||||
* process proxy tickets. In order for proxy tickets to get processed
|
||||
* {@link ServiceProperties#isAuthenticateAllArtifacts()} must return <code>true</code>.
|
||||
* Additionally, if the request is already authenticated, authentication will <b>not</b>
|
||||
* occur. Last, {@link AuthenticationDetailsSource#buildDetails(Object)} must return a
|
||||
* {@link ServiceAuthenticationDetails}. This can be accomplished using the
|
||||
* {@link ServiceAuthenticationDetailsSource}. In this case
|
||||
* {@link ServiceAuthenticationDetails#getServiceUrl()} will be used for the service url.
|
||||
* <p>
|
||||
* Processing the proxy ticket involves creating a
|
||||
* <code>CasServiceTicketAuthenticationToken</code> which uses
|
||||
* {@link CasServiceTicketAuthenticationToken#CAS_STATELESS_IDENTIFIER} for the
|
||||
* <code>principal</code> and the opaque ticket string as the <code>credentials</code>.
|
||||
* When a proxy ticket is successfully authenticated, the FilterChain continues and the
|
||||
* <code>authenticationSuccessHandler</code> is not used.
|
||||
* <h2>Notes about the <code>AuthenticationManager</code></h2>
|
||||
* <p>
|
||||
* The configured <code>AuthenticationManager</code> is expected to provide a provider
|
||||
* that can recognise <code>CasServiceTicketAuthenticationToken</code>s containing this
|
||||
* special <code>principal</code> name, and process them accordingly by validation with
|
||||
* the CAS server. Additionally, it should be capable of using the result of
|
||||
* {@link ServiceAuthenticationDetails#getServiceUrl()} as the service when validating the
|
||||
* ticket.
|
||||
* <h2>Example Configuration</h2>
|
||||
* <p>
|
||||
* An example configuration that supports service tickets, obtaining proxy granting
|
||||
* tickets, and proxy tickets is illustrated below:
|
||||
*
|
||||
* <pre>
|
||||
* <b:bean id="serviceProperties"
|
||||
* class="org.springframework.security.cas.ServiceProperties"
|
||||
* p:service="https://service.example.com/cas-sample/login/cas"
|
||||
* p:authenticateAllArtifacts="true"/>
|
||||
* <b:bean id="casEntryPoint"
|
||||
* class="org.springframework.security.cas.web.CasAuthenticationEntryPoint"
|
||||
* p:serviceProperties-ref="serviceProperties" p:loginUrl="https://login.example.org/cas/login" />
|
||||
* <b:bean id="casFilter"
|
||||
* class="org.springframework.security.cas.web.CasAuthenticationFilter"
|
||||
* p:authenticationManager-ref="authManager"
|
||||
* p:serviceProperties-ref="serviceProperties"
|
||||
* p:proxyGrantingTicketStorage-ref="pgtStorage"
|
||||
* p:proxyReceptorUrl="/login/cas/proxyreceptor">
|
||||
* <b:property name="authenticationDetailsSource">
|
||||
* <b:bean class="org.springframework.security.cas.web.authentication.ServiceAuthenticationDetailsSource"/>
|
||||
* </b:property>
|
||||
* <b:property name="authenticationFailureHandler">
|
||||
* <b:bean class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler"
|
||||
* p:defaultFailureUrl="/casfailed.jsp"/>
|
||||
* </b:property>
|
||||
* </b:bean>
|
||||
* <!--
|
||||
* NOTE: In a real application you should not use an in memory implementation. You will also want
|
||||
* to ensure to clean up expired tickets by calling ProxyGrantingTicketStorage.cleanup()
|
||||
* -->
|
||||
* <b:bean id="pgtStorage" class="org.apereo.cas.client.proxy.ProxyGrantingTicketStorageImpl"/>
|
||||
* <b:bean id="casAuthProvider" class="org.springframework.security.cas.authentication.CasAuthenticationProvider"
|
||||
* p:serviceProperties-ref="serviceProperties"
|
||||
* p:key="casAuthProviderKey">
|
||||
* <b:property name="authenticationUserDetailsService">
|
||||
* <b:bean
|
||||
* class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
|
||||
* <b:constructor-arg ref="userService" />
|
||||
* </b:bean>
|
||||
* </b:property>
|
||||
* <b:property name="ticketValidator">
|
||||
* <b:bean
|
||||
* class="org.apereo.cas.client.validation.Cas20ProxyTicketValidator"
|
||||
* p:acceptAnyProxy="true"
|
||||
* p:proxyCallbackUrl="https://service.example.com/cas-sample/login/cas/proxyreceptor"
|
||||
* p:proxyGrantingTicketStorage-ref="pgtStorage">
|
||||
* <b:constructor-arg value="https://login.example.org/cas" />
|
||||
* </b:bean>
|
||||
* </b:property>
|
||||
* <b:property name="statelessTicketCache">
|
||||
* <b:bean class="org.springframework.security.cas.authentication.EhCacheBasedTicketCache">
|
||||
* <b:property name="cache">
|
||||
* <b:bean class="net.sf.ehcache.Cache"
|
||||
* init-method="initialise"
|
||||
* destroy-method="dispose">
|
||||
* <b:constructor-arg value="casTickets"/>
|
||||
* <b:constructor-arg value="50"/>
|
||||
* <b:constructor-arg value="true"/>
|
||||
* <b:constructor-arg value="false"/>
|
||||
* <b:constructor-arg value="3600"/>
|
||||
* <b:constructor-arg value="900"/>
|
||||
* </b:bean>
|
||||
* </b:property>
|
||||
* </b:bean>
|
||||
* </b:property>
|
||||
* </b:bean>
|
||||
* </pre>
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @author Rob Winch
|
||||
*/
|
||||
public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
|
||||
|
||||
/**
|
||||
* The last portion of the receptor url, i.e. /proxy/receptor
|
||||
*/
|
||||
private RequestMatcher proxyReceptorMatcher;
|
||||
|
||||
/**
|
||||
* The backing storage to store ProxyGrantingTicket requests.
|
||||
*/
|
||||
private ProxyGrantingTicketStorage proxyGrantingTicketStorage;
|
||||
|
||||
private String artifactParameter = ServiceProperties.DEFAULT_CAS_ARTIFACT_PARAMETER;
|
||||
|
||||
private boolean authenticateAllArtifacts;
|
||||
|
||||
private AuthenticationFailureHandler proxyFailureHandler = new SimpleUrlAuthenticationFailureHandler();
|
||||
|
||||
private SecurityContextRepository securityContextRepository = new HttpSessionSecurityContextRepository();
|
||||
|
||||
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
|
||||
.getContextHolderStrategy();
|
||||
|
||||
public CasAuthenticationFilter() {
|
||||
super("/login/cas");
|
||||
setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler());
|
||||
setSecurityContextRepository(this.securityContextRepository);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
|
||||
FilterChain chain, Authentication authResult) throws IOException, ServletException {
|
||||
boolean continueFilterChain = proxyTicketRequest(serviceTicketRequest(request, response), request);
|
||||
if (!continueFilterChain) {
|
||||
super.successfulAuthentication(request, response, chain, authResult);
|
||||
return;
|
||||
}
|
||||
this.logger.debug(
|
||||
LogMessage.format("Authentication success. Updating SecurityContextHolder to contain: %s", authResult));
|
||||
|
||||
SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();
|
||||
context.setAuthentication(authResult);
|
||||
this.securityContextHolderStrategy.setContext(context);
|
||||
this.securityContextRepository.saveContext(context, request, response);
|
||||
if (this.eventPublisher != null) {
|
||||
this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
|
||||
}
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
|
||||
throws AuthenticationException, IOException {
|
||||
// if the request is a proxy request process it and return null to indicate the
|
||||
// request has been processed
|
||||
if (proxyReceptorRequest(request)) {
|
||||
this.logger.debug("Responding to proxy receptor request");
|
||||
WebUtils.readAndRespondToProxyReceptorRequest(request, response, this.proxyGrantingTicketStorage);
|
||||
return null;
|
||||
}
|
||||
String serviceTicket = obtainArtifact(request);
|
||||
if (serviceTicket == null) {
|
||||
this.logger.debug("Failed to obtain an artifact (cas ticket)");
|
||||
serviceTicket = "";
|
||||
}
|
||||
boolean serviceTicketRequest = serviceTicketRequest(request, response);
|
||||
CasServiceTicketAuthenticationToken authRequest = serviceTicketRequest
|
||||
? CasServiceTicketAuthenticationToken.stateful(serviceTicket)
|
||||
: CasServiceTicketAuthenticationToken.stateless(serviceTicket);
|
||||
authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
|
||||
return this.getAuthenticationManager().authenticate(authRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* If present, gets the artifact (CAS ticket) from the {@link HttpServletRequest}.
|
||||
* @param request
|
||||
* @return if present the artifact from the {@link HttpServletRequest}, else null
|
||||
*/
|
||||
protected String obtainArtifact(HttpServletRequest request) {
|
||||
return request.getParameter(this.artifactParameter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overridden to provide proxying capabilities.
|
||||
*/
|
||||
@Override
|
||||
protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) {
|
||||
final boolean serviceTicketRequest = serviceTicketRequest(request, response);
|
||||
final boolean result = serviceTicketRequest || proxyReceptorRequest(request)
|
||||
|| (proxyTicketRequest(serviceTicketRequest, request));
|
||||
if (this.logger.isDebugEnabled()) {
|
||||
this.logger.debug("requiresAuthentication = " + result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link AuthenticationFailureHandler} for proxy requests.
|
||||
* @param proxyFailureHandler
|
||||
*/
|
||||
public final void setProxyAuthenticationFailureHandler(AuthenticationFailureHandler proxyFailureHandler) {
|
||||
Assert.notNull(proxyFailureHandler, "proxyFailureHandler cannot be null");
|
||||
this.proxyFailureHandler = proxyFailureHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the {@link AuthenticationFailureHandler} to distinguish between handling
|
||||
* proxy ticket authentication failures and service ticket failures.
|
||||
*/
|
||||
@Override
|
||||
public final void setAuthenticationFailureHandler(AuthenticationFailureHandler failureHandler) {
|
||||
super.setAuthenticationFailureHandler(new CasAuthenticationFailureHandler(failureHandler));
|
||||
}
|
||||
|
||||
public final void setProxyReceptorUrl(final String proxyReceptorUrl) {
|
||||
this.proxyReceptorMatcher = new AntPathRequestMatcher("/**" + proxyReceptorUrl);
|
||||
}
|
||||
|
||||
public final void setProxyGrantingTicketStorage(final ProxyGrantingTicketStorage proxyGrantingTicketStorage) {
|
||||
this.proxyGrantingTicketStorage = proxyGrantingTicketStorage;
|
||||
}
|
||||
|
||||
public final void setServiceProperties(final ServiceProperties serviceProperties) {
|
||||
this.artifactParameter = serviceProperties.getArtifactParameter();
|
||||
this.authenticateAllArtifacts = serviceProperties.isAuthenticateAllArtifacts();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the request is elgible to process a service ticket. This method exists
|
||||
* for readability.
|
||||
* @param request
|
||||
* @param response
|
||||
* @return
|
||||
*/
|
||||
private boolean serviceTicketRequest(HttpServletRequest request, HttpServletResponse response) {
|
||||
boolean result = super.requiresAuthentication(request, response);
|
||||
this.logger.debug(LogMessage.format("serviceTicketRequest = %s", result));
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the request is elgible to process a proxy ticket.
|
||||
* @param request
|
||||
* @return
|
||||
*/
|
||||
private boolean proxyTicketRequest(boolean serviceTicketRequest, HttpServletRequest request) {
|
||||
if (serviceTicketRequest) {
|
||||
return false;
|
||||
}
|
||||
boolean result = this.authenticateAllArtifacts && obtainArtifact(request) != null && !authenticated();
|
||||
this.logger.debug(LogMessage.format("proxyTicketRequest = %s", result));
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a user is already authenticated.
|
||||
* @return
|
||||
*/
|
||||
private boolean authenticated() {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
return authentication != null && authentication.isAuthenticated()
|
||||
&& !(authentication instanceof AnonymousAuthenticationToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the request is elgible to be processed as the proxy receptor.
|
||||
* @param request
|
||||
* @return
|
||||
*/
|
||||
private boolean proxyReceptorRequest(HttpServletRequest request) {
|
||||
final boolean result = proxyReceptorConfigured() && this.proxyReceptorMatcher.matches(request);
|
||||
this.logger.debug(LogMessage.format("proxyReceptorRequest = %s", result));
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the {@link CasAuthenticationFilter} is configured to handle the proxy
|
||||
* receptor requests.
|
||||
* @return
|
||||
*/
|
||||
private boolean proxyReceptorConfigured() {
|
||||
final boolean result = this.proxyGrantingTicketStorage != null && this.proxyReceptorMatcher != null;
|
||||
this.logger.debug(LogMessage.format("proxyReceptorConfigured = %s", result));
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper for the AuthenticationFailureHandler that will flex the
|
||||
* {@link AuthenticationFailureHandler} that is used. The value
|
||||
* {@link CasAuthenticationFilter#setProxyAuthenticationFailureHandler(AuthenticationFailureHandler)}
|
||||
* will be used for proxy requests that fail. The value
|
||||
* {@link CasAuthenticationFilter#setAuthenticationFailureHandler(AuthenticationFailureHandler)}
|
||||
* will be used for service tickets that fail.
|
||||
*/
|
||||
private class CasAuthenticationFailureHandler implements AuthenticationFailureHandler {
|
||||
|
||||
private final AuthenticationFailureHandler serviceTicketFailureHandler;
|
||||
|
||||
CasAuthenticationFailureHandler(AuthenticationFailureHandler failureHandler) {
|
||||
Assert.notNull(failureHandler, "failureHandler");
|
||||
this.serviceTicketFailureHandler = failureHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
|
||||
AuthenticationException exception) throws IOException, ServletException {
|
||||
if (serviceTicketRequest(request, response)) {
|
||||
this.serviceTicketFailureHandler.onAuthenticationFailure(request, response, exception);
|
||||
}
|
||||
else {
|
||||
CasAuthenticationFilter.this.proxyFailureHandler.onAuthenticationFailure(request, response, exception);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,146 +0,0 @@
|
|||
/*
|
||||
* Copyright 2011-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.cas.web.authentication;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetails;
|
||||
import org.springframework.security.web.util.UrlUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A default implementation of {@link ServiceAuthenticationDetails} that figures out the
|
||||
* value for {@link #getServiceUrl()} by inspecting the current {@link HttpServletRequest}
|
||||
* and using the current URL minus the artifact and the corresponding value.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
final class DefaultServiceAuthenticationDetails extends WebAuthenticationDetails
|
||||
implements ServiceAuthenticationDetails {
|
||||
|
||||
private static final long serialVersionUID = 6192409090610517700L;
|
||||
|
||||
private final String serviceUrl;
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
* @param request the current {@link HttpServletRequest} to obtain the
|
||||
* {@link #getServiceUrl()} from.
|
||||
* @param artifactPattern the {@link Pattern} that will be used to clean up the query
|
||||
* string from containing the artifact name and value. This can be created using
|
||||
* {@link #createArtifactPattern(String)}.
|
||||
*/
|
||||
DefaultServiceAuthenticationDetails(String casService, HttpServletRequest request, Pattern artifactPattern)
|
||||
throws MalformedURLException {
|
||||
super(request);
|
||||
URL casServiceUrl = new URL(casService);
|
||||
int port = getServicePort(casServiceUrl);
|
||||
final String query = getQueryString(request, artifactPattern);
|
||||
this.serviceUrl = UrlUtils.buildFullRequestUrl(casServiceUrl.getProtocol(), casServiceUrl.getHost(), port,
|
||||
request.getRequestURI(), query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current URL minus the artifact parameter and its value, if present.
|
||||
* @see org.springframework.security.cas.web.authentication.ServiceAuthenticationDetails#getServiceUrl()
|
||||
*/
|
||||
@Override
|
||||
public String getServiceUrl() {
|
||||
return this.serviceUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!super.equals(obj) || !(obj instanceof DefaultServiceAuthenticationDetails)) {
|
||||
return false;
|
||||
}
|
||||
ServiceAuthenticationDetails that = (ServiceAuthenticationDetails) obj;
|
||||
return this.serviceUrl.equals(that.getServiceUrl());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = super.hashCode();
|
||||
result = prime * result + this.serviceUrl.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append(super.toString());
|
||||
result.append("ServiceUrl: ");
|
||||
result.append(this.serviceUrl);
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* If present, removes the artifactParameterName and the corresponding value from the
|
||||
* query String.
|
||||
* @param request
|
||||
* @return the query String minus the artifactParameterName and the corresponding
|
||||
* value.
|
||||
*/
|
||||
private String getQueryString(final HttpServletRequest request, final Pattern artifactPattern) {
|
||||
final String query = request.getQueryString();
|
||||
if (query == null) {
|
||||
return null;
|
||||
}
|
||||
String result = artifactPattern.matcher(query).replaceFirst("");
|
||||
if (result.length() == 0) {
|
||||
return null;
|
||||
}
|
||||
// strip off the trailing & only if the artifact was the first query param
|
||||
return result.startsWith("&") ? result.substring(1) : result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link Pattern} that can be passed into the constructor. This allows the
|
||||
* {@link Pattern} to be reused for every instance of
|
||||
* {@link DefaultServiceAuthenticationDetails}.
|
||||
* @param artifactParameterName
|
||||
* @return
|
||||
*/
|
||||
static Pattern createArtifactPattern(String artifactParameterName) {
|
||||
Assert.hasLength(artifactParameterName, "artifactParameterName is expected to have a length");
|
||||
return Pattern.compile("&?" + Pattern.quote(artifactParameterName) + "=[^&]*");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the port from the casServiceURL ensuring to return the proper value if the
|
||||
* default port is being used.
|
||||
* @param casServiceUrl the casServerUrl to be used (i.e.
|
||||
* "https://example.com/context/login/cas")
|
||||
* @return the port that is configured for the casServerUrl
|
||||
*/
|
||||
private static int getServicePort(URL casServiceUrl) {
|
||||
int port = casServiceUrl.getPort();
|
||||
if (port == -1) {
|
||||
port = casServiceUrl.getDefaultPort();
|
||||
}
|
||||
return port;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
/*
|
||||
* Copyright 2011-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.cas.web.authentication;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.springframework.security.cas.ServiceProperties;
|
||||
import org.springframework.security.cas.authentication.CasAuthenticationProvider;
|
||||
import org.springframework.security.core.Authentication;
|
||||
|
||||
/**
|
||||
* In order for the {@link CasAuthenticationProvider} to provide the correct service url
|
||||
* to authenticate the ticket, the returned value of {@link Authentication#getDetails()}
|
||||
* should implement this interface when tickets can be sent to any URL rather than only
|
||||
* {@link ServiceProperties#getService()}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @see ServiceAuthenticationDetailsSource
|
||||
*/
|
||||
public interface ServiceAuthenticationDetails extends Serializable {
|
||||
|
||||
/**
|
||||
* Gets the absolute service url (i.e. https://example.com/service/).
|
||||
* @return the service url. Cannot be <code>null</code>.
|
||||
*/
|
||||
String getServiceUrl();
|
||||
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
/*
|
||||
* Copyright 2011-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.cas.web.authentication;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.security.authentication.AuthenticationDetailsSource;
|
||||
import org.springframework.security.cas.ServiceProperties;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* The {@code AuthenticationDetailsSource} that is set on the
|
||||
* {@code CasAuthenticationFilter} should return a value that implements
|
||||
* {@code ServiceAuthenticationDetails} if the application needs to authenticate dynamic
|
||||
* service urls. The
|
||||
* {@code ServiceAuthenticationDetailsSource#buildDetails(HttpServletRequest)} creates a
|
||||
* default {@code ServiceAuthenticationDetails}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
public class ServiceAuthenticationDetailsSource
|
||||
implements AuthenticationDetailsSource<HttpServletRequest, ServiceAuthenticationDetails> {
|
||||
|
||||
private final Pattern artifactPattern;
|
||||
|
||||
private ServiceProperties serviceProperties;
|
||||
|
||||
/**
|
||||
* Creates an implementation that uses the specified ServiceProperties and the default
|
||||
* CAS artifactParameterName.
|
||||
* @param serviceProperties The ServiceProperties to use to construct the serviceUrl.
|
||||
*/
|
||||
public ServiceAuthenticationDetailsSource(ServiceProperties serviceProperties) {
|
||||
this(serviceProperties, ServiceProperties.DEFAULT_CAS_ARTIFACT_PARAMETER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an implementation that uses the specified artifactParameterName
|
||||
* @param serviceProperties The ServiceProperties to use to construct the serviceUrl.
|
||||
* @param artifactParameterName the artifactParameterName that is removed from the
|
||||
* current URL. The result becomes the service url. Cannot be null and cannot be an
|
||||
* empty String.
|
||||
*/
|
||||
public ServiceAuthenticationDetailsSource(ServiceProperties serviceProperties, String artifactParameterName) {
|
||||
Assert.notNull(serviceProperties, "serviceProperties cannot be null");
|
||||
this.serviceProperties = serviceProperties;
|
||||
this.artifactPattern = DefaultServiceAuthenticationDetails.createArtifactPattern(artifactParameterName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param context the {@code HttpServletRequest} object.
|
||||
* @return the {@code ServiceAuthenticationDetails} containing information about the
|
||||
* current request
|
||||
*/
|
||||
@Override
|
||||
public ServiceAuthenticationDetails buildDetails(HttpServletRequest context) {
|
||||
try {
|
||||
return new DefaultServiceAuthenticationDetails(this.serviceProperties.getService(), context,
|
||||
this.artifactPattern);
|
||||
}
|
||||
catch (MalformedURLException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Authentication processing mechanisms which respond to the submission of authentication
|
||||
* credentials using CAS.
|
||||
*/
|
||||
package org.springframework.security.cas.web.authentication;
|
|
@ -1,20 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Authenticates standard web browser users via CAS.
|
||||
*/
|
||||
package org.springframework.security.cas.web;
|
|
@ -1,45 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.cas.authentication;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apereo.cas.client.validation.Assertion;
|
||||
import org.apereo.cas.client.validation.AssertionImpl;
|
||||
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
|
||||
/**
|
||||
* @author Scott Battaglia
|
||||
* @since 2.0
|
||||
*
|
||||
*/
|
||||
public abstract class AbstractStatelessTicketCacheTests {
|
||||
|
||||
protected CasAuthenticationToken getToken() {
|
||||
List<String> proxyList = new ArrayList<>();
|
||||
proxyList.add("https://localhost/newPortal/login/cas");
|
||||
User user = new User("rod", "password", true, true, true, true,
|
||||
AuthorityUtils.createAuthorityList("ROLE_ONE", "ROLE_TWO"));
|
||||
final Assertion assertion = new AssertionImpl("rod");
|
||||
return new CasAuthenticationToken("key", user, "ST-0-ER94xMJmn6pha35CQRoZ",
|
||||
AuthorityUtils.createAuthorityList("ROLE_ONE", "ROLE_TWO"), user, assertion);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,376 +0,0 @@
|
|||
/*
|
||||
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.cas.authentication;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apereo.cas.client.validation.Assertion;
|
||||
import org.apereo.cas.client.validation.AssertionImpl;
|
||||
import org.apereo.cas.client.validation.TicketValidator;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.cas.ServiceProperties;
|
||||
import org.springframework.security.cas.web.authentication.ServiceAuthenticationDetails;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetails;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests {@link CasAuthenticationProvider}.
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @author Scott Battaglia
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public class CasAuthenticationProviderTests {
|
||||
|
||||
private UserDetails makeUserDetails() {
|
||||
return new User("user", "password", true, true, true, true,
|
||||
AuthorityUtils.createAuthorityList("ROLE_ONE", "ROLE_TWO"));
|
||||
}
|
||||
|
||||
private UserDetails makeUserDetailsFromAuthoritiesPopulator() {
|
||||
return new User("user", "password", true, true, true, true,
|
||||
AuthorityUtils.createAuthorityList("ROLE_A", "ROLE_B"));
|
||||
}
|
||||
|
||||
private ServiceProperties makeServiceProperties() {
|
||||
final ServiceProperties serviceProperties = new ServiceProperties();
|
||||
serviceProperties.setSendRenew(false);
|
||||
serviceProperties.setService("http://test.com");
|
||||
return serviceProperties;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void statefulAuthenticationIsSuccessful() throws Exception {
|
||||
CasAuthenticationProvider cap = new CasAuthenticationProvider();
|
||||
cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
|
||||
cap.setKey("qwerty");
|
||||
StatelessTicketCache cache = new MockStatelessTicketCache();
|
||||
cap.setStatelessTicketCache(cache);
|
||||
cap.setServiceProperties(makeServiceProperties());
|
||||
cap.setTicketValidator(new MockTicketValidator(true));
|
||||
cap.afterPropertiesSet();
|
||||
CasServiceTicketAuthenticationToken token = CasServiceTicketAuthenticationToken.stateful("ST-123");
|
||||
token.setDetails("details");
|
||||
Authentication result = cap.authenticate(token);
|
||||
// Confirm ST-123 was NOT added to the cache
|
||||
assertThat(cache.getByTicketId("ST-456") == null).isTrue();
|
||||
if (!(result instanceof CasAuthenticationToken)) {
|
||||
fail("Should have returned a CasAuthenticationToken");
|
||||
}
|
||||
CasAuthenticationToken casResult = (CasAuthenticationToken) result;
|
||||
assertThat(casResult.getPrincipal()).isEqualTo(makeUserDetailsFromAuthoritiesPopulator());
|
||||
assertThat(casResult.getCredentials()).isEqualTo("ST-123");
|
||||
assertThat(casResult.getAuthorities()).contains(new SimpleGrantedAuthority("ROLE_A"));
|
||||
assertThat(casResult.getAuthorities()).contains(new SimpleGrantedAuthority("ROLE_B"));
|
||||
assertThat(casResult.getKeyHash()).isEqualTo(cap.getKey().hashCode());
|
||||
assertThat(casResult.getDetails()).isEqualTo("details");
|
||||
// Now confirm the CasAuthenticationToken is automatically re-accepted.
|
||||
// To ensure TicketValidator not called again, set it to deliver an exception...
|
||||
cap.setTicketValidator(new MockTicketValidator(false));
|
||||
Authentication laterResult = cap.authenticate(result);
|
||||
assertThat(laterResult).isEqualTo(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void statelessAuthenticationIsSuccessful() throws Exception {
|
||||
CasAuthenticationProvider cap = new CasAuthenticationProvider();
|
||||
cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
|
||||
cap.setKey("qwerty");
|
||||
StatelessTicketCache cache = new MockStatelessTicketCache();
|
||||
cap.setStatelessTicketCache(cache);
|
||||
cap.setTicketValidator(new MockTicketValidator(true));
|
||||
cap.setServiceProperties(makeServiceProperties());
|
||||
cap.afterPropertiesSet();
|
||||
CasServiceTicketAuthenticationToken token = CasServiceTicketAuthenticationToken.stateless("ST-456");
|
||||
token.setDetails("details");
|
||||
Authentication result = cap.authenticate(token);
|
||||
// Confirm ST-456 was added to the cache
|
||||
assertThat(cache.getByTicketId("ST-456") != null).isTrue();
|
||||
if (!(result instanceof CasAuthenticationToken)) {
|
||||
fail("Should have returned a CasAuthenticationToken");
|
||||
}
|
||||
assertThat(result.getPrincipal()).isEqualTo(makeUserDetailsFromAuthoritiesPopulator());
|
||||
assertThat(result.getCredentials()).isEqualTo("ST-456");
|
||||
assertThat(result.getDetails()).isEqualTo("details");
|
||||
// Now try to authenticate again. To ensure TicketValidator not
|
||||
// called again, set it to deliver an exception...
|
||||
cap.setTicketValidator(new MockTicketValidator(false));
|
||||
// Previously created CasServiceTicketAuthenticationToken is OK
|
||||
Authentication newResult = cap.authenticate(token);
|
||||
assertThat(newResult.getPrincipal()).isEqualTo(makeUserDetailsFromAuthoritiesPopulator());
|
||||
assertThat(newResult.getCredentials()).isEqualTo("ST-456");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateAllNullService() throws Exception {
|
||||
String serviceUrl = "https://service/context";
|
||||
ServiceAuthenticationDetails details = mock(ServiceAuthenticationDetails.class);
|
||||
given(details.getServiceUrl()).willReturn(serviceUrl);
|
||||
TicketValidator validator = mock(TicketValidator.class);
|
||||
given(validator.validate(any(String.class), any(String.class))).willReturn(new AssertionImpl("rod"));
|
||||
ServiceProperties serviceProperties = makeServiceProperties();
|
||||
serviceProperties.setAuthenticateAllArtifacts(true);
|
||||
CasAuthenticationProvider cap = new CasAuthenticationProvider();
|
||||
cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
|
||||
cap.setKey("qwerty");
|
||||
cap.setTicketValidator(validator);
|
||||
cap.setServiceProperties(serviceProperties);
|
||||
cap.afterPropertiesSet();
|
||||
String ticket = "ST-456";
|
||||
CasServiceTicketAuthenticationToken token = CasServiceTicketAuthenticationToken.stateless(ticket);
|
||||
Authentication result = cap.authenticate(token);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateAllAuthenticationIsSuccessful() throws Exception {
|
||||
String serviceUrl = "https://service/context";
|
||||
ServiceAuthenticationDetails details = mock(ServiceAuthenticationDetails.class);
|
||||
given(details.getServiceUrl()).willReturn(serviceUrl);
|
||||
TicketValidator validator = mock(TicketValidator.class);
|
||||
given(validator.validate(any(String.class), any(String.class))).willReturn(new AssertionImpl("rod"));
|
||||
ServiceProperties serviceProperties = makeServiceProperties();
|
||||
serviceProperties.setAuthenticateAllArtifacts(true);
|
||||
CasAuthenticationProvider cap = new CasAuthenticationProvider();
|
||||
cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
|
||||
cap.setKey("qwerty");
|
||||
cap.setTicketValidator(validator);
|
||||
cap.setServiceProperties(serviceProperties);
|
||||
cap.afterPropertiesSet();
|
||||
String ticket = "ST-456";
|
||||
CasServiceTicketAuthenticationToken token = CasServiceTicketAuthenticationToken.stateless(ticket);
|
||||
Authentication result = cap.authenticate(token);
|
||||
verify(validator).validate(ticket, serviceProperties.getService());
|
||||
serviceProperties.setAuthenticateAllArtifacts(true);
|
||||
result = cap.authenticate(token);
|
||||
verify(validator, times(2)).validate(ticket, serviceProperties.getService());
|
||||
token.setDetails(details);
|
||||
result = cap.authenticate(token);
|
||||
verify(validator).validate(ticket, serviceUrl);
|
||||
serviceProperties.setAuthenticateAllArtifacts(false);
|
||||
serviceProperties.setService(null);
|
||||
cap.setServiceProperties(serviceProperties);
|
||||
cap.afterPropertiesSet();
|
||||
result = cap.authenticate(token);
|
||||
verify(validator, times(2)).validate(ticket, serviceUrl);
|
||||
token.setDetails(new WebAuthenticationDetails(new MockHttpServletRequest()));
|
||||
assertThatIllegalStateException().isThrownBy(() -> cap.authenticate(token));
|
||||
cap.setServiceProperties(null);
|
||||
cap.afterPropertiesSet();
|
||||
assertThatIllegalStateException().isThrownBy(() -> cap.authenticate(token));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void missingTicketIdIsDetected() throws Exception {
|
||||
CasAuthenticationProvider cap = new CasAuthenticationProvider();
|
||||
cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
|
||||
cap.setKey("qwerty");
|
||||
StatelessTicketCache cache = new MockStatelessTicketCache();
|
||||
cap.setStatelessTicketCache(cache);
|
||||
cap.setTicketValidator(new MockTicketValidator(true));
|
||||
cap.setServiceProperties(makeServiceProperties());
|
||||
cap.afterPropertiesSet();
|
||||
CasServiceTicketAuthenticationToken token = CasServiceTicketAuthenticationToken.stateful("");
|
||||
assertThatExceptionOfType(BadCredentialsException.class).isThrownBy(() -> cap.authenticate(token));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidKeyIsDetected() throws Exception {
|
||||
final Assertion assertion = new AssertionImpl("test");
|
||||
CasAuthenticationProvider cap = new CasAuthenticationProvider();
|
||||
cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
|
||||
cap.setKey("qwerty");
|
||||
StatelessTicketCache cache = new MockStatelessTicketCache();
|
||||
cap.setStatelessTicketCache(cache);
|
||||
cap.setTicketValidator(new MockTicketValidator(true));
|
||||
cap.setServiceProperties(makeServiceProperties());
|
||||
cap.afterPropertiesSet();
|
||||
CasAuthenticationToken token = new CasAuthenticationToken("WRONG_KEY", makeUserDetails(), "credentials",
|
||||
AuthorityUtils.createAuthorityList("XX"), makeUserDetails(), assertion);
|
||||
assertThatExceptionOfType(BadCredentialsException.class).isThrownBy(() -> cap.authenticate(token));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void detectsMissingAuthoritiesPopulator() throws Exception {
|
||||
CasAuthenticationProvider cap = new CasAuthenticationProvider();
|
||||
cap.setKey("qwerty");
|
||||
cap.setStatelessTicketCache(new MockStatelessTicketCache());
|
||||
cap.setTicketValidator(new MockTicketValidator(true));
|
||||
cap.setServiceProperties(makeServiceProperties());
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> cap.afterPropertiesSet());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void detectsMissingKey() throws Exception {
|
||||
CasAuthenticationProvider cap = new CasAuthenticationProvider();
|
||||
cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
|
||||
cap.setStatelessTicketCache(new MockStatelessTicketCache());
|
||||
cap.setTicketValidator(new MockTicketValidator(true));
|
||||
cap.setServiceProperties(makeServiceProperties());
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> cap.afterPropertiesSet());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void detectsMissingStatelessTicketCache() throws Exception {
|
||||
CasAuthenticationProvider cap = new CasAuthenticationProvider();
|
||||
// set this explicitly to null to test failure
|
||||
cap.setStatelessTicketCache(null);
|
||||
cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
|
||||
cap.setKey("qwerty");
|
||||
cap.setTicketValidator(new MockTicketValidator(true));
|
||||
cap.setServiceProperties(makeServiceProperties());
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> cap.afterPropertiesSet());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void detectsMissingTicketValidator() throws Exception {
|
||||
CasAuthenticationProvider cap = new CasAuthenticationProvider();
|
||||
cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
|
||||
cap.setKey("qwerty");
|
||||
cap.setStatelessTicketCache(new MockStatelessTicketCache());
|
||||
cap.setServiceProperties(makeServiceProperties());
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> cap.afterPropertiesSet());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void gettersAndSettersMatch() throws Exception {
|
||||
CasAuthenticationProvider cap = new CasAuthenticationProvider();
|
||||
cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
|
||||
cap.setKey("qwerty");
|
||||
cap.setStatelessTicketCache(new MockStatelessTicketCache());
|
||||
cap.setTicketValidator(new MockTicketValidator(true));
|
||||
cap.setServiceProperties(makeServiceProperties());
|
||||
cap.afterPropertiesSet();
|
||||
// TODO disabled because why do we need to expose this?
|
||||
// assertThat(cap.getUserDetailsService() != null).isTrue();
|
||||
assertThat(cap.getKey()).isEqualTo("qwerty");
|
||||
assertThat(cap.getStatelessTicketCache() != null).isTrue();
|
||||
assertThat(cap.getTicketValidator() != null).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ignoresClassesItDoesNotSupport() throws Exception {
|
||||
CasAuthenticationProvider cap = new CasAuthenticationProvider();
|
||||
cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
|
||||
cap.setKey("qwerty");
|
||||
cap.setStatelessTicketCache(new MockStatelessTicketCache());
|
||||
cap.setTicketValidator(new MockTicketValidator(true));
|
||||
cap.setServiceProperties(makeServiceProperties());
|
||||
cap.afterPropertiesSet();
|
||||
TestingAuthenticationToken token = new TestingAuthenticationToken("user", "password", "ROLE_A");
|
||||
assertThat(cap.supports(TestingAuthenticationToken.class)).isFalse();
|
||||
// Try it anyway
|
||||
assertThat(cap.authenticate(token)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ignoresUsernamePasswordAuthenticationTokensWithoutCasIdentifiersAsPrincipal() throws Exception {
|
||||
CasAuthenticationProvider cap = new CasAuthenticationProvider();
|
||||
cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
|
||||
cap.setKey("qwerty");
|
||||
cap.setStatelessTicketCache(new MockStatelessTicketCache());
|
||||
cap.setTicketValidator(new MockTicketValidator(true));
|
||||
cap.setServiceProperties(makeServiceProperties());
|
||||
cap.afterPropertiesSet();
|
||||
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("some_normal_user",
|
||||
"password", AuthorityUtils.createAuthorityList("ROLE_A"));
|
||||
assertThat(cap.authenticate(token)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void supportsRequiredTokens() {
|
||||
CasAuthenticationProvider cap = new CasAuthenticationProvider();
|
||||
assertThat(cap.supports(CasServiceTicketAuthenticationToken.class)).isTrue();
|
||||
assertThat(cap.supports(CasAuthenticationToken.class)).isTrue();
|
||||
}
|
||||
|
||||
private class MockAuthoritiesPopulator implements AuthenticationUserDetailsService {
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserDetails(final Authentication token) throws UsernameNotFoundException {
|
||||
return makeUserDetailsFromAuthoritiesPopulator();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class MockStatelessTicketCache implements StatelessTicketCache {
|
||||
|
||||
private Map<String, CasAuthenticationToken> cache = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public CasAuthenticationToken getByTicketId(String serviceTicket) {
|
||||
return this.cache.get(serviceTicket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putTicketInCache(CasAuthenticationToken token) {
|
||||
this.cache.put(token.getCredentials().toString(), token);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeTicketFromCache(CasAuthenticationToken token) {
|
||||
throw new UnsupportedOperationException("mock method not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeTicketFromCache(String serviceTicket) {
|
||||
throw new UnsupportedOperationException("mock method not implemented");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class MockTicketValidator implements TicketValidator {
|
||||
|
||||
private boolean returnTicket;
|
||||
|
||||
MockTicketValidator(boolean returnTicket) {
|
||||
this.returnTicket = returnTicket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Assertion validate(final String ticket, final String service) {
|
||||
if (this.returnTicket) {
|
||||
return new AssertionImpl("rod");
|
||||
}
|
||||
throw new BadCredentialsException("As requested from mock");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,158 +0,0 @@
|
|||
/*
|
||||
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.cas.authentication;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.apereo.cas.client.validation.Assertion;
|
||||
import org.apereo.cas.client.validation.AssertionImpl;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Tests {@link CasAuthenticationToken}.
|
||||
*
|
||||
* @author Ben Alex
|
||||
*/
|
||||
public class CasAuthenticationTokenTests {
|
||||
|
||||
private final List<GrantedAuthority> ROLES = AuthorityUtils.createAuthorityList("ROLE_ONE", "ROLE_TWO");
|
||||
|
||||
private UserDetails makeUserDetails() {
|
||||
return makeUserDetails("user");
|
||||
}
|
||||
|
||||
private UserDetails makeUserDetails(final String name) {
|
||||
return new User(name, "password", true, true, true, true, this.ROLES);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructorRejectsNulls() {
|
||||
Assertion assertion = new AssertionImpl("test");
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new CasAuthenticationToken(null, makeUserDetails(),
|
||||
"Password", this.ROLES, makeUserDetails(), assertion));
|
||||
assertThatIllegalArgumentException().isThrownBy(
|
||||
() -> new CasAuthenticationToken("key", null, "Password", this.ROLES, makeUserDetails(), assertion));
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new CasAuthenticationToken("key", makeUserDetails(), null,
|
||||
this.ROLES, makeUserDetails(), assertion));
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new CasAuthenticationToken("key", makeUserDetails(),
|
||||
"Password", this.ROLES, makeUserDetails(), null));
|
||||
assertThatIllegalArgumentException().isThrownBy(
|
||||
() -> new CasAuthenticationToken("key", makeUserDetails(), "Password", this.ROLES, null, assertion));
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new CasAuthenticationToken("key", makeUserDetails(),
|
||||
"Password", AuthorityUtils.createAuthorityList("ROLE_1", null), makeUserDetails(), assertion));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenEmptyKeyThenThrowsException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(
|
||||
() -> new CasAuthenticationToken("", "user", "password", Collections.<GrantedAuthority>emptyList(),
|
||||
new User("user", "password", Collections.<GrantedAuthority>emptyList()), null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEqualsWhenEqual() {
|
||||
final Assertion assertion = new AssertionImpl("test");
|
||||
CasAuthenticationToken token1 = new CasAuthenticationToken("key", makeUserDetails(), "Password", this.ROLES,
|
||||
makeUserDetails(), assertion);
|
||||
CasAuthenticationToken token2 = new CasAuthenticationToken("key", makeUserDetails(), "Password", this.ROLES,
|
||||
makeUserDetails(), assertion);
|
||||
assertThat(token2).isEqualTo(token1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetters() {
|
||||
// Build the proxy list returned in the ticket from CAS
|
||||
final Assertion assertion = new AssertionImpl("test");
|
||||
CasAuthenticationToken token = new CasAuthenticationToken("key", makeUserDetails(), "Password", this.ROLES,
|
||||
makeUserDetails(), assertion);
|
||||
assertThat(token.getKeyHash()).isEqualTo("key".hashCode());
|
||||
assertThat(token.getPrincipal()).isEqualTo(makeUserDetails());
|
||||
assertThat(token.getCredentials()).isEqualTo("Password");
|
||||
assertThat(token.getAuthorities()).contains(new SimpleGrantedAuthority("ROLE_ONE"));
|
||||
assertThat(token.getAuthorities()).contains(new SimpleGrantedAuthority("ROLE_TWO"));
|
||||
assertThat(token.getAssertion()).isEqualTo(assertion);
|
||||
assertThat(token.getUserDetails().getUsername()).isEqualTo(makeUserDetails().getUsername());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoArgConstructorDoesntExist() {
|
||||
assertThatExceptionOfType(NoSuchMethodException.class)
|
||||
.isThrownBy(() -> CasAuthenticationToken.class.getDeclaredConstructor((Class[]) null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotEqualsDueToAbstractParentEqualsCheck() {
|
||||
final Assertion assertion = new AssertionImpl("test");
|
||||
CasAuthenticationToken token1 = new CasAuthenticationToken("key", makeUserDetails(), "Password", this.ROLES,
|
||||
makeUserDetails(), assertion);
|
||||
CasAuthenticationToken token2 = new CasAuthenticationToken("key", makeUserDetails("OTHER_NAME"), "Password",
|
||||
this.ROLES, makeUserDetails(), assertion);
|
||||
assertThat(!token1.equals(token2)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotEqualsDueToKey() {
|
||||
final Assertion assertion = new AssertionImpl("test");
|
||||
CasAuthenticationToken token1 = new CasAuthenticationToken("key", makeUserDetails(), "Password", this.ROLES,
|
||||
makeUserDetails(), assertion);
|
||||
CasAuthenticationToken token2 = new CasAuthenticationToken("DIFFERENT_KEY", makeUserDetails(), "Password",
|
||||
this.ROLES, makeUserDetails(), assertion);
|
||||
assertThat(!token1.equals(token2)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotEqualsDueToAssertion() {
|
||||
final Assertion assertion = new AssertionImpl("test");
|
||||
final Assertion assertion2 = new AssertionImpl("test");
|
||||
CasAuthenticationToken token1 = new CasAuthenticationToken("key", makeUserDetails(), "Password", this.ROLES,
|
||||
makeUserDetails(), assertion);
|
||||
CasAuthenticationToken token2 = new CasAuthenticationToken("key", makeUserDetails(), "Password", this.ROLES,
|
||||
makeUserDetails(), assertion2);
|
||||
assertThat(!token1.equals(token2)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetAuthenticated() {
|
||||
final Assertion assertion = new AssertionImpl("test");
|
||||
CasAuthenticationToken token = new CasAuthenticationToken("key", makeUserDetails(), "Password", this.ROLES,
|
||||
makeUserDetails(), assertion);
|
||||
assertThat(token.isAuthenticated()).isTrue();
|
||||
token.setAuthenticated(false);
|
||||
assertThat(!token.isAuthenticated()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToString() {
|
||||
final Assertion assertion = new AssertionImpl("test");
|
||||
CasAuthenticationToken token = new CasAuthenticationToken("key", makeUserDetails(), "Password", this.ROLES,
|
||||
makeUserDetails(), assertion);
|
||||
String result = token.toString();
|
||||
assertThat(result.lastIndexOf("Credentials (Service/Proxy Ticket):") != -1).isTrue();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
/*
|
||||
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.cas.authentication;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Test cases for the @link {@link NullStatelessTicketCache}
|
||||
*
|
||||
* @author Scott Battaglia
|
||||
*
|
||||
*/
|
||||
public class NullStatelessTicketCacheTests extends AbstractStatelessTicketCacheTests {
|
||||
|
||||
private StatelessTicketCache cache = new NullStatelessTicketCache();
|
||||
|
||||
@Test
|
||||
public void testGetter() {
|
||||
assertThat(this.cache.getByTicketId(null)).isNull();
|
||||
assertThat(this.cache.getByTicketId("test")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInsertAndGet() {
|
||||
final CasAuthenticationToken token = getToken();
|
||||
this.cache.putTicketInCache(token);
|
||||
assertThat(this.cache.getByTicketId((String) token.getCredentials())).isNull();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
/*
|
||||
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.cas.authentication;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Tests
|
||||
* {@link org.springframework.security.cas.authentication.SpringCacheBasedTicketCache}.
|
||||
*
|
||||
* @author Marten Deinum
|
||||
* @since 3.2
|
||||
*/
|
||||
public class SpringCacheBasedTicketCacheTests extends AbstractStatelessTicketCacheTests {
|
||||
|
||||
private static CacheManager cacheManager;
|
||||
|
||||
@BeforeAll
|
||||
public static void initCacheManaer() {
|
||||
cacheManager = new ConcurrentMapCacheManager();
|
||||
cacheManager.getCache("castickets");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCacheOperation() throws Exception {
|
||||
SpringCacheBasedTicketCache cache = new SpringCacheBasedTicketCache(cacheManager.getCache("castickets"));
|
||||
final CasAuthenticationToken token = getToken();
|
||||
// Check it gets stored in the cache
|
||||
cache.putTicketInCache(token);
|
||||
assertThat(cache.getByTicketId("ST-0-ER94xMJmn6pha35CQRoZ")).isEqualTo(token);
|
||||
// Check it gets removed from the cache
|
||||
cache.removeTicketFromCache(getToken());
|
||||
assertThat(cache.getByTicketId("ST-0-ER94xMJmn6pha35CQRoZ")).isNull();
|
||||
// Check it doesn't return values for null or unknown service tickets
|
||||
assertThat(cache.getByTicketId(null)).isNull();
|
||||
assertThat(cache.getByTicketId("UNKNOWN_SERVICE_TICKET")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartupDetectsMissingCache() throws Exception {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new SpringCacheBasedTicketCache(null));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,154 +0,0 @@
|
|||
/*
|
||||
* Copyright 2015-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.cas.jackson2;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apereo.cas.client.authentication.AttributePrincipalImpl;
|
||||
import org.apereo.cas.client.validation.Assertion;
|
||||
import org.apereo.cas.client.validation.AssertionImpl;
|
||||
import org.json.JSONException;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.skyscreamer.jsonassert.JSONAssert;
|
||||
|
||||
import org.springframework.security.cas.authentication.CasAuthenticationToken;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.jackson2.SecurityJackson2Modules;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Jitendra Singh
|
||||
* @since 4.2
|
||||
*/
|
||||
public class CasAuthenticationTokenMixinTests {
|
||||
|
||||
private static final String KEY = "casKey";
|
||||
|
||||
private static final String PASSWORD = "\"1234\"";
|
||||
|
||||
private static final Date START_DATE = new Date();
|
||||
|
||||
private static final Date END_DATE = new Date();
|
||||
|
||||
public static final String AUTHORITY_JSON = "{\"@class\": \"org.springframework.security.core.authority.SimpleGrantedAuthority\", \"authority\": \"ROLE_USER\"}";
|
||||
|
||||
public static final String AUTHORITIES_SET_JSON = "[\"java.util.Collections$UnmodifiableSet\", [" + AUTHORITY_JSON
|
||||
+ "]]";
|
||||
|
||||
public static final String AUTHORITIES_ARRAYLIST_JSON = "[\"java.util.Collections$UnmodifiableRandomAccessList\", ["
|
||||
+ AUTHORITY_JSON + "]]";
|
||||
|
||||
// @formatter:off
|
||||
public static final String USER_JSON = "{"
|
||||
+ "\"@class\": \"org.springframework.security.core.userdetails.User\", "
|
||||
+ "\"username\": \"admin\","
|
||||
+ " \"password\": " + PASSWORD + ", "
|
||||
+ "\"accountNonExpired\": true, "
|
||||
+ "\"accountNonLocked\": true, "
|
||||
+ "\"credentialsNonExpired\": true, "
|
||||
+ "\"enabled\": true, "
|
||||
+ "\"authorities\": " + AUTHORITIES_SET_JSON
|
||||
+ "}";
|
||||
// @formatter:on
|
||||
private static final String CAS_TOKEN_JSON = "{"
|
||||
+ "\"@class\": \"org.springframework.security.cas.authentication.CasAuthenticationToken\", "
|
||||
+ "\"keyHash\": " + KEY.hashCode() + "," + "\"principal\": " + USER_JSON + ", " + "\"credentials\": "
|
||||
+ PASSWORD + ", " + "\"authorities\": " + AUTHORITIES_ARRAYLIST_JSON + "," + "\"userDetails\": " + USER_JSON
|
||||
+ "," + "\"authenticated\": true, " + "\"details\": null," + "\"assertion\": {"
|
||||
+ "\"@class\": \"org.apereo.cas.client.validation.AssertionImpl\", " + "\"principal\": {"
|
||||
+ "\"@class\": \"org.apereo.cas.client.authentication.AttributePrincipalImpl\", "
|
||||
+ "\"name\": \"assertName\", " + "\"attributes\": {\"@class\": \"java.util.Collections$EmptyMap\"}, "
|
||||
+ "\"proxyGrantingTicket\": null, " + "\"proxyRetriever\": null" + "}, "
|
||||
+ "\"validFromDate\": [\"java.util.Date\", " + START_DATE.getTime() + "], "
|
||||
+ "\"validUntilDate\": [\"java.util.Date\", " + END_DATE.getTime() + "],"
|
||||
+ "\"authenticationDate\": [\"java.util.Date\", " + START_DATE.getTime() + "], "
|
||||
+ "\"attributes\": {\"@class\": \"java.util.Collections$EmptyMap\"},"
|
||||
+ "\"context\": {\"@class\":\"java.util.HashMap\"}" + "}" + "}";
|
||||
|
||||
private static final String CAS_TOKEN_CLEARED_JSON = CAS_TOKEN_JSON.replaceFirst(PASSWORD, "null");
|
||||
|
||||
protected ObjectMapper mapper;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
this.mapper = new ObjectMapper();
|
||||
ClassLoader loader = getClass().getClassLoader();
|
||||
this.mapper.registerModules(SecurityJackson2Modules.getModules(loader));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serializeCasAuthenticationTest() throws JsonProcessingException, JSONException {
|
||||
CasAuthenticationToken token = createCasAuthenticationToken();
|
||||
String actualJson = this.mapper.writeValueAsString(token);
|
||||
JSONAssert.assertEquals(CAS_TOKEN_JSON, actualJson, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serializeCasAuthenticationTestAfterEraseCredentialInvoked()
|
||||
throws JsonProcessingException, JSONException {
|
||||
CasAuthenticationToken token = createCasAuthenticationToken();
|
||||
token.eraseCredentials();
|
||||
String actualJson = this.mapper.writeValueAsString(token);
|
||||
JSONAssert.assertEquals(CAS_TOKEN_CLEARED_JSON, actualJson, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deserializeCasAuthenticationTestAfterEraseCredentialInvoked() throws Exception {
|
||||
CasAuthenticationToken token = this.mapper.readValue(CAS_TOKEN_CLEARED_JSON, CasAuthenticationToken.class);
|
||||
assertThat(((UserDetails) token.getPrincipal()).getPassword()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deserializeCasAuthenticationTest() throws IOException {
|
||||
CasAuthenticationToken token = this.mapper.readValue(CAS_TOKEN_JSON, CasAuthenticationToken.class);
|
||||
assertThat(token).isNotNull();
|
||||
assertThat(token.getPrincipal()).isNotNull().isInstanceOf(User.class);
|
||||
assertThat(((User) token.getPrincipal()).getUsername()).isEqualTo("admin");
|
||||
assertThat(((User) token.getPrincipal()).getPassword()).isEqualTo("1234");
|
||||
assertThat(token.getUserDetails()).isNotNull().isInstanceOf(User.class);
|
||||
assertThat(token.getAssertion()).isNotNull().isInstanceOf(AssertionImpl.class);
|
||||
assertThat(token.getKeyHash()).isEqualTo(KEY.hashCode());
|
||||
assertThat(token.getUserDetails().getAuthorities()).extracting(GrantedAuthority::getAuthority)
|
||||
.containsOnly("ROLE_USER");
|
||||
assertThat(token.getAssertion().getAuthenticationDate()).isEqualTo(START_DATE);
|
||||
assertThat(token.getAssertion().getValidFromDate()).isEqualTo(START_DATE);
|
||||
assertThat(token.getAssertion().getValidUntilDate()).isEqualTo(END_DATE);
|
||||
assertThat(token.getAssertion().getPrincipal().getName()).isEqualTo("assertName");
|
||||
assertThat(token.getAssertion().getAttributes()).hasSize(0);
|
||||
}
|
||||
|
||||
private CasAuthenticationToken createCasAuthenticationToken() {
|
||||
User principal = new User("admin", "1234", Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")));
|
||||
Collection<? extends GrantedAuthority> authorities = Collections
|
||||
.singletonList(new SimpleGrantedAuthority("ROLE_USER"));
|
||||
Assertion assertion = new AssertionImpl(new AttributePrincipalImpl("assertName"), START_DATE, END_DATE,
|
||||
START_DATE, Collections.<String, Object>emptyMap());
|
||||
return new CasAuthenticationToken(KEY, principal, principal.getPassword(), authorities,
|
||||
new User("admin", "1234", authorities), assertion);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.cas.userdetails;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apereo.cas.client.authentication.AttributePrincipal;
|
||||
import org.apereo.cas.client.validation.Assertion;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.security.cas.authentication.CasAssertionAuthenticationToken;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* @author Luke Taylor
|
||||
*/
|
||||
public class GrantedAuthorityFromAssertionAttributesUserDetailsServiceTests {
|
||||
|
||||
@Test
|
||||
public void correctlyExtractsNamedAttributesFromAssertionAndConvertsThemToAuthorities() {
|
||||
GrantedAuthorityFromAssertionAttributesUserDetailsService uds = new GrantedAuthorityFromAssertionAttributesUserDetailsService(
|
||||
new String[] { "a", "b", "c", "d" });
|
||||
uds.setConvertToUpperCase(false);
|
||||
Assertion assertion = mock(Assertion.class);
|
||||
AttributePrincipal principal = mock(AttributePrincipal.class);
|
||||
Map<String, Object> attributes = new HashMap<>();
|
||||
attributes.put("a", Arrays.asList("role_a1", "role_a2"));
|
||||
attributes.put("b", "role_b");
|
||||
attributes.put("c", "role_c");
|
||||
attributes.put("d", null);
|
||||
attributes.put("someother", "unused");
|
||||
given(assertion.getPrincipal()).willReturn(principal);
|
||||
given(principal.getAttributes()).willReturn(attributes);
|
||||
given(principal.getName()).willReturn("somebody");
|
||||
CasAssertionAuthenticationToken token = new CasAssertionAuthenticationToken(assertion, "ticket");
|
||||
UserDetails user = uds.loadUserDetails(token);
|
||||
Set<String> roles = AuthorityUtils.authorityListToSet(user.getAuthorities());
|
||||
assertThat(roles).containsExactlyInAnyOrder("role_a1", "role_a2", "role_b", "role_c");
|
||||
}
|
||||
|
||||
}
|
|
@ -1,98 +0,0 @@
|
|||
/*
|
||||
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.cas.web;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.security.cas.ServiceProperties;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Tests {@link CasAuthenticationEntryPoint}.
|
||||
*
|
||||
* @author Ben Alex
|
||||
*/
|
||||
public class CasAuthenticationEntryPointTests {
|
||||
|
||||
@Test
|
||||
public void testDetectsMissingLoginFormUrl() throws Exception {
|
||||
CasAuthenticationEntryPoint ep = new CasAuthenticationEntryPoint();
|
||||
ep.setServiceProperties(new ServiceProperties());
|
||||
assertThatIllegalArgumentException().isThrownBy(ep::afterPropertiesSet)
|
||||
.withMessage("loginUrl must be specified");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDetectsMissingServiceProperties() throws Exception {
|
||||
CasAuthenticationEntryPoint ep = new CasAuthenticationEntryPoint();
|
||||
ep.setLoginUrl("https://cas/login");
|
||||
assertThatIllegalArgumentException().isThrownBy(ep::afterPropertiesSet)
|
||||
.withMessage("serviceProperties must be specified");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGettersSetters() {
|
||||
CasAuthenticationEntryPoint ep = new CasAuthenticationEntryPoint();
|
||||
ep.setLoginUrl("https://cas/login");
|
||||
assertThat(ep.getLoginUrl()).isEqualTo("https://cas/login");
|
||||
ep.setServiceProperties(new ServiceProperties());
|
||||
assertThat(ep.getServiceProperties() != null).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNormalOperationWithRenewFalse() throws Exception {
|
||||
ServiceProperties sp = new ServiceProperties();
|
||||
sp.setSendRenew(false);
|
||||
sp.setService("https://mycompany.com/bigWebApp/login/cas");
|
||||
CasAuthenticationEntryPoint ep = new CasAuthenticationEntryPoint();
|
||||
ep.setLoginUrl("https://cas/login");
|
||||
ep.setServiceProperties(sp);
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setRequestURI("/some_path");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
ep.afterPropertiesSet();
|
||||
ep.commence(request, response, null);
|
||||
assertThat(
|
||||
"https://cas/login?service=" + URLEncoder.encode("https://mycompany.com/bigWebApp/login/cas", "UTF-8"))
|
||||
.isEqualTo(response.getRedirectedUrl());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNormalOperationWithRenewTrue() throws Exception {
|
||||
ServiceProperties sp = new ServiceProperties();
|
||||
sp.setSendRenew(true);
|
||||
sp.setService("https://mycompany.com/bigWebApp/login/cas");
|
||||
CasAuthenticationEntryPoint ep = new CasAuthenticationEntryPoint();
|
||||
ep.setLoginUrl("https://cas/login");
|
||||
ep.setServiceProperties(sp);
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setRequestURI("/some_path");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
ep.afterPropertiesSet();
|
||||
ep.commence(request, response, null);
|
||||
assertThat("https://cas/login?service="
|
||||
+ URLEncoder.encode("https://mycompany.com/bigWebApp/login/cas", "UTF-8") + "&renew=true")
|
||||
.isEqualTo(response.getRedirectedUrl());
|
||||
}
|
||||
|
||||
}
|
|
@ -1,222 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.cas.web;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import org.apereo.cas.client.proxy.ProxyGrantingTicketStorage;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.mock.web.MockFilterChain;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.cas.ServiceProperties;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
||||
import org.springframework.security.web.context.SecurityContextRepository;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
/**
|
||||
* Tests {@link CasAuthenticationFilter}.
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @author Rob Winch
|
||||
*/
|
||||
public class CasAuthenticationFilterTests {
|
||||
|
||||
@AfterEach
|
||||
public void tearDown() {
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGettersSetters() {
|
||||
CasAuthenticationFilter filter = new CasAuthenticationFilter();
|
||||
filter.setProxyGrantingTicketStorage(mock(ProxyGrantingTicketStorage.class));
|
||||
filter.setProxyReceptorUrl("/someurl");
|
||||
filter.setServiceProperties(new ServiceProperties());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNormalOperation() throws Exception {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setServletPath("/login/cas");
|
||||
request.addParameter("ticket", "ST-0-ER94xMJmn6pha35CQRoZ");
|
||||
CasAuthenticationFilter filter = new CasAuthenticationFilter();
|
||||
filter.setAuthenticationManager((a) -> a);
|
||||
assertThat(filter.requiresAuthentication(request, new MockHttpServletResponse())).isTrue();
|
||||
Authentication result = filter.attemptAuthentication(request, new MockHttpServletResponse());
|
||||
assertThat(result != null).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNullServiceTicketHandledGracefully() throws Exception {
|
||||
CasAuthenticationFilter filter = new CasAuthenticationFilter();
|
||||
filter.setAuthenticationManager((a) -> {
|
||||
throw new BadCredentialsException("Rejected");
|
||||
});
|
||||
assertThatExceptionOfType(AuthenticationException.class).isThrownBy(
|
||||
() -> filter.attemptAuthentication(new MockHttpServletRequest(), new MockHttpServletResponse()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequiresAuthenticationFilterProcessUrl() {
|
||||
String url = "/login/cas";
|
||||
CasAuthenticationFilter filter = new CasAuthenticationFilter();
|
||||
filter.setFilterProcessesUrl(url);
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
request.setServletPath(url);
|
||||
assertThat(filter.requiresAuthentication(request, response)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequiresAuthenticationProxyRequest() {
|
||||
CasAuthenticationFilter filter = new CasAuthenticationFilter();
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
request.setServletPath("/pgtCallback");
|
||||
assertThat(filter.requiresAuthentication(request, response)).isFalse();
|
||||
filter.setProxyReceptorUrl(request.getServletPath());
|
||||
assertThat(filter.requiresAuthentication(request, response)).isFalse();
|
||||
filter.setProxyGrantingTicketStorage(mock(ProxyGrantingTicketStorage.class));
|
||||
assertThat(filter.requiresAuthentication(request, response)).isTrue();
|
||||
request.setServletPath("/other");
|
||||
assertThat(filter.requiresAuthentication(request, response)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequiresAuthenticationAuthAll() {
|
||||
ServiceProperties properties = new ServiceProperties();
|
||||
properties.setAuthenticateAllArtifacts(true);
|
||||
String url = "/login/cas";
|
||||
CasAuthenticationFilter filter = new CasAuthenticationFilter();
|
||||
filter.setFilterProcessesUrl(url);
|
||||
filter.setServiceProperties(properties);
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
request.setServletPath(url);
|
||||
assertThat(filter.requiresAuthentication(request, response)).isTrue();
|
||||
request.setServletPath("/other");
|
||||
assertThat(filter.requiresAuthentication(request, response)).isFalse();
|
||||
request.setParameter(properties.getArtifactParameter(), "value");
|
||||
assertThat(filter.requiresAuthentication(request, response)).isTrue();
|
||||
SecurityContextHolder.getContext()
|
||||
.setAuthentication(new AnonymousAuthenticationToken("key", "principal",
|
||||
AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS")));
|
||||
assertThat(filter.requiresAuthentication(request, response)).isTrue();
|
||||
SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken("un", "principal"));
|
||||
assertThat(filter.requiresAuthentication(request, response)).isTrue();
|
||||
SecurityContextHolder.getContext()
|
||||
.setAuthentication(new TestingAuthenticationToken("un", "principal", "ROLE_ANONYMOUS"));
|
||||
assertThat(filter.requiresAuthentication(request, response)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthenticateProxyUrl() throws Exception {
|
||||
CasAuthenticationFilter filter = new CasAuthenticationFilter();
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
request.setServletPath("/pgtCallback");
|
||||
filter.setProxyGrantingTicketStorage(mock(ProxyGrantingTicketStorage.class));
|
||||
filter.setProxyReceptorUrl(request.getServletPath());
|
||||
assertThat(filter.attemptAuthentication(request, response)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoFilterAuthenticateAll() throws Exception {
|
||||
AuthenticationSuccessHandler successHandler = mock(AuthenticationSuccessHandler.class);
|
||||
AuthenticationManager manager = mock(AuthenticationManager.class);
|
||||
Authentication authentication = new TestingAuthenticationToken("un", "pwd", "ROLE_USER");
|
||||
given(manager.authenticate(any(Authentication.class))).willReturn(authentication);
|
||||
ServiceProperties serviceProperties = new ServiceProperties();
|
||||
serviceProperties.setAuthenticateAllArtifacts(true);
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setParameter("ticket", "ST-1-123");
|
||||
request.setServletPath("/authenticate");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterChain chain = mock(FilterChain.class);
|
||||
CasAuthenticationFilter filter = new CasAuthenticationFilter();
|
||||
filter.setServiceProperties(serviceProperties);
|
||||
filter.setAuthenticationSuccessHandler(successHandler);
|
||||
filter.setProxyGrantingTicketStorage(mock(ProxyGrantingTicketStorage.class));
|
||||
filter.setAuthenticationManager(manager);
|
||||
filter.afterPropertiesSet();
|
||||
filter.doFilter(request, response, chain);
|
||||
assertThat(SecurityContextHolder.getContext().getAuthentication()).isNotNull()
|
||||
.withFailMessage("Authentication should not be null");
|
||||
verify(chain).doFilter(request, response);
|
||||
verifyNoInteractions(successHandler);
|
||||
// validate for when the filterProcessUrl matches
|
||||
filter.setFilterProcessesUrl(request.getServletPath());
|
||||
SecurityContextHolder.clearContext();
|
||||
filter.doFilter(request, response, chain);
|
||||
verifyNoMoreInteractions(chain);
|
||||
verify(successHandler).onAuthenticationSuccess(request, response, authentication);
|
||||
}
|
||||
|
||||
// SEC-1592
|
||||
@Test
|
||||
public void testChainNotInvokedForProxyReceptor() throws Exception {
|
||||
CasAuthenticationFilter filter = new CasAuthenticationFilter();
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterChain chain = mock(FilterChain.class);
|
||||
request.setServletPath("/pgtCallback");
|
||||
filter.setProxyGrantingTicketStorage(mock(ProxyGrantingTicketStorage.class));
|
||||
filter.setProxyReceptorUrl(request.getServletPath());
|
||||
filter.doFilter(request, response, chain);
|
||||
verifyNoInteractions(chain);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void successfulAuthenticationWhenProxyRequestThenSavesSecurityContext() throws Exception {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setParameter(ServiceProperties.DEFAULT_CAS_ARTIFACT_PARAMETER, "ticket");
|
||||
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
CasAuthenticationFilter filter = new CasAuthenticationFilter();
|
||||
ServiceProperties serviceProperties = new ServiceProperties();
|
||||
serviceProperties.setAuthenticateAllArtifacts(true);
|
||||
filter.setServiceProperties(serviceProperties);
|
||||
|
||||
SecurityContextRepository securityContextRepository = mock(SecurityContextRepository.class);
|
||||
ReflectionTestUtils.setField(filter, "securityContextRepository", securityContextRepository);
|
||||
|
||||
filter.successfulAuthentication(request, response, new MockFilterChain(), mock(Authentication.class));
|
||||
verify(securityContextRepository).saveContext(any(SecurityContext.class), eq(request), eq(response));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
/*
|
||||
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.cas.web;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.security.cas.SamlServiceProperties;
|
||||
import org.springframework.security.cas.ServiceProperties;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Tests {@link ServiceProperties}.
|
||||
*
|
||||
* @author Ben Alex
|
||||
*/
|
||||
public class ServicePropertiesTests {
|
||||
|
||||
@Test
|
||||
public void detectsMissingService() throws Exception {
|
||||
ServiceProperties sp = new ServiceProperties();
|
||||
assertThatIllegalArgumentException().isThrownBy(sp::afterPropertiesSet);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullServiceWhenAuthenticateAllTokens() throws Exception {
|
||||
ServiceProperties sp = new ServiceProperties();
|
||||
sp.setAuthenticateAllArtifacts(true);
|
||||
assertThatIllegalArgumentException().isThrownBy(sp::afterPropertiesSet);
|
||||
sp.setAuthenticateAllArtifacts(false);
|
||||
assertThatIllegalArgumentException().isThrownBy(sp::afterPropertiesSet);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGettersSetters() throws Exception {
|
||||
ServiceProperties[] sps = { new ServiceProperties(), new SamlServiceProperties() };
|
||||
for (ServiceProperties sp : sps) {
|
||||
sp.setSendRenew(false);
|
||||
assertThat(sp.isSendRenew()).isFalse();
|
||||
sp.setSendRenew(true);
|
||||
assertThat(sp.isSendRenew()).isTrue();
|
||||
sp.setArtifactParameter("notticket");
|
||||
assertThat(sp.getArtifactParameter()).isEqualTo("notticket");
|
||||
sp.setServiceParameter("notservice");
|
||||
assertThat(sp.getServiceParameter()).isEqualTo("notservice");
|
||||
sp.setService("https://mycompany.com/service");
|
||||
assertThat(sp.getService()).isEqualTo("https://mycompany.com/service");
|
||||
sp.afterPropertiesSet();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,132 +0,0 @@
|
|||
/*
|
||||
* Copyright 2011-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.cas.web.authentication;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.support.GenericXmlApplicationContext;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.security.cas.ServiceProperties;
|
||||
import org.springframework.security.web.util.UrlUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
*/
|
||||
public class DefaultServiceAuthenticationDetailsTests {
|
||||
|
||||
private DefaultServiceAuthenticationDetails details;
|
||||
|
||||
private MockHttpServletRequest request;
|
||||
|
||||
private Pattern artifactPattern;
|
||||
|
||||
private String casServiceUrl;
|
||||
|
||||
private ConfigurableApplicationContext context;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
this.casServiceUrl = "https://localhost:8443/j_spring_security_cas";
|
||||
this.request = new MockHttpServletRequest();
|
||||
this.request.setScheme("https");
|
||||
this.request.setServerName("localhost");
|
||||
this.request.setServerPort(8443);
|
||||
this.request.setRequestURI("/cas-sample/secure/");
|
||||
this.artifactPattern = DefaultServiceAuthenticationDetails
|
||||
.createArtifactPattern(ServiceProperties.DEFAULT_CAS_ARTIFACT_PARAMETER);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void cleanup() {
|
||||
if (this.context != null) {
|
||||
this.context.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getServiceUrlNullQuery() throws Exception {
|
||||
this.details = new DefaultServiceAuthenticationDetails(this.casServiceUrl, this.request, this.artifactPattern);
|
||||
assertThat(this.details.getServiceUrl()).isEqualTo(UrlUtils.buildFullRequestUrl(this.request));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getServiceUrlTicketOnlyParam() throws Exception {
|
||||
this.request.setQueryString("ticket=123");
|
||||
this.details = new DefaultServiceAuthenticationDetails(this.casServiceUrl, this.request, this.artifactPattern);
|
||||
String serviceUrl = this.details.getServiceUrl();
|
||||
this.request.setQueryString(null);
|
||||
assertThat(serviceUrl).isEqualTo(UrlUtils.buildFullRequestUrl(this.request));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getServiceUrlTicketFirstMultiParam() throws Exception {
|
||||
this.request.setQueryString("ticket=123&other=value");
|
||||
this.details = new DefaultServiceAuthenticationDetails(this.casServiceUrl, this.request, this.artifactPattern);
|
||||
String serviceUrl = this.details.getServiceUrl();
|
||||
this.request.setQueryString("other=value");
|
||||
assertThat(serviceUrl).isEqualTo(UrlUtils.buildFullRequestUrl(this.request));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getServiceUrlTicketLastMultiParam() throws Exception {
|
||||
this.request.setQueryString("other=value&ticket=123");
|
||||
this.details = new DefaultServiceAuthenticationDetails(this.casServiceUrl, this.request, this.artifactPattern);
|
||||
String serviceUrl = this.details.getServiceUrl();
|
||||
this.request.setQueryString("other=value");
|
||||
assertThat(serviceUrl).isEqualTo(UrlUtils.buildFullRequestUrl(this.request));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getServiceUrlTicketMiddleMultiParam() throws Exception {
|
||||
this.request.setQueryString("other=value&ticket=123&last=this");
|
||||
this.details = new DefaultServiceAuthenticationDetails(this.casServiceUrl, this.request, this.artifactPattern);
|
||||
String serviceUrl = this.details.getServiceUrl();
|
||||
this.request.setQueryString("other=value&last=this");
|
||||
assertThat(serviceUrl).isEqualTo(UrlUtils.buildFullRequestUrl(this.request));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getServiceUrlDoesNotUseHostHeader() throws Exception {
|
||||
this.casServiceUrl = "https://example.com/j_spring_security_cas";
|
||||
this.request.setServerName("evil.com");
|
||||
this.details = new DefaultServiceAuthenticationDetails(this.casServiceUrl, this.request, this.artifactPattern);
|
||||
assertThat(this.details.getServiceUrl()).isEqualTo("https://example.com/cas-sample/secure/");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getServiceUrlDoesNotUseHostHeaderExplicit() {
|
||||
this.casServiceUrl = "https://example.com/j_spring_security_cas";
|
||||
this.request.setServerName("evil.com");
|
||||
ServiceAuthenticationDetails details = loadServiceAuthenticationDetails(
|
||||
"defaultserviceauthenticationdetails-explicit.xml");
|
||||
assertThat(details.getServiceUrl()).isEqualTo("https://example.com/cas-sample/secure/");
|
||||
}
|
||||
|
||||
private ServiceAuthenticationDetails loadServiceAuthenticationDetails(String resourceName) {
|
||||
this.context = new GenericXmlApplicationContext(getClass(), resourceName);
|
||||
ServiceAuthenticationDetailsSource source = this.context.getBean(ServiceAuthenticationDetailsSource.class);
|
||||
return source.buildDetails(this.request);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<logger name="org.springframework.security" level="${sec.log.level:-WARN}"/>
|
||||
|
||||
|
||||
<root level="${root.level:-WARN}">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
|
||||
</configuration>
|
|
@ -1,23 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
|
||||
|
||||
<bean id="serviceProperties"
|
||||
class="org.springframework.security.cas.ServiceProperties">
|
||||
<property name="service"
|
||||
value="https://example.com/j_spring_security_cas"/>
|
||||
<property name="sendRenew" value="false"/>
|
||||
</bean>
|
||||
<bean id="serviceProperties2"
|
||||
class="org.springframework.security.cas.ServiceProperties">
|
||||
<property name="service"
|
||||
value="https://example2.com/j_spring_security_cas"/>
|
||||
<property name="sendRenew" value="false"/>
|
||||
</bean>
|
||||
|
||||
<bean class="org.springframework.security.cas.web.authentication.ServiceAuthenticationDetailsSource">
|
||||
<constructor-arg ref="serviceProperties"/>
|
||||
</bean>
|
||||
</beans>
|
|
@ -1,15 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
|
||||
|
||||
<bean id="serviceProperties"
|
||||
class="org.springframework.security.cas.ServiceProperties">
|
||||
<property name="service"
|
||||
value="https://example.com/j_spring_security_cas"/>
|
||||
<property name="sendRenew" value="false"/>
|
||||
</bean>
|
||||
|
||||
<bean class="org.springframework.security.cas.web.authentication.ServiceAuthenticationDetailsSource"/>
|
||||
</beans>
|
|
@ -39,7 +39,6 @@ dependencies {
|
|||
provided 'jakarta.servlet:jakarta.servlet-api'
|
||||
|
||||
testImplementation project(':spring-security-aspects')
|
||||
testImplementation project(':spring-security-cas')
|
||||
testImplementation project(':spring-security-test')
|
||||
testImplementation project(path : ':spring-security-core', configuration : 'tests')
|
||||
testImplementation project(path : ':spring-security-ldap', configuration : 'tests')
|
||||
|
@ -80,6 +79,7 @@ dependencies {
|
|||
testImplementation "org.hibernate.orm:hibernate-core"
|
||||
testImplementation 'org.hsqldb:hsqldb'
|
||||
testImplementation 'org.mockito:mockito-core'
|
||||
testImplementation "org.mockito:mockito-inline"
|
||||
testImplementation('org.seleniumhq.selenium:htmlunit-driver') {
|
||||
exclude group: 'commons-logging', module: 'commons-logging'
|
||||
exclude group: 'xml-apis', module: 'xml-apis'
|
||||
|
|
|
@ -294,7 +294,7 @@ public class RSocketMessageHandlerITests {
|
|||
|
||||
@MessageMapping({ "secure.send", "send" })
|
||||
Mono<Void> send(Mono<String> payload) {
|
||||
return payload.doOnNext(this::add).then(Mono.fromRunnable(this::doNotifyAll));
|
||||
return payload.doOnNext(this::add).then(Mono.fromRunnable(() -> doNotifyAll()));
|
||||
}
|
||||
|
||||
private synchronized void doNotifyAll() {
|
||||
|
|
|
@ -87,7 +87,7 @@ public class LdapUserServiceBeanDefinitionParserTests {
|
|||
|
||||
Set<String> authorities = AuthorityUtils.authorityListToSet(ben.getAuthorities());
|
||||
assertThat(authorities).hasSize(3);
|
||||
assertThat(authorities).contains("ROLE_DEVELOPERS");
|
||||
assertThat(authorities.contains("ROLE_DEVELOPERS")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -128,7 +128,7 @@ public class LdapUserServiceBeanDefinitionParserTests {
|
|||
|
||||
Set<String> authorities = AuthorityUtils.authorityListToSet(ben.getAuthorities());
|
||||
assertThat(authorities).hasSize(3);
|
||||
assertThat(authorities).contains("ROLE_DEVELOPER");
|
||||
assertThat(authorities.contains("ROLE_DEVELOPER")).isTrue();
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -96,7 +96,7 @@ public final class SecurityNamespaceHandler implements NamespaceHandler {
|
|||
pc.getReaderContext()
|
||||
.fatal("You cannot use a spring-security-2.0.xsd or spring-security-3.0.xsd or "
|
||||
+ "spring-security-3.1.xsd schema or spring-security-3.2.xsd schema or spring-security-4.0.xsd schema "
|
||||
+ "with Spring Security 6.2. Please update your schema declarations to the 6.2 schema.",
|
||||
+ "with Spring Security 6.0. Please update your schema declarations to the 6.0 schema.",
|
||||
element);
|
||||
}
|
||||
String name = pc.getDelegate().getLocalName(element);
|
||||
|
@ -221,7 +221,7 @@ public final class SecurityNamespaceHandler implements NamespaceHandler {
|
|||
|
||||
private boolean matchesVersionInternal(Element element) {
|
||||
String schemaLocation = element.getAttributeNS("http://www.w3.org/2001/XMLSchema-instance", "schemaLocation");
|
||||
return schemaLocation.matches("(?m).*spring-security-6\\.2.*.xsd.*")
|
||||
return schemaLocation.matches("(?m).*spring-security-6\\.0.*.xsd.*")
|
||||
|| schemaLocation.matches("(?m).*spring-security.xsd.*")
|
||||
|| !schemaLocation.matches("(?m).*spring-security.*");
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -27,7 +27,6 @@ import java.util.Map;
|
|||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.builders.WebSecurity;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.filter.DelegatingFilterProxy;
|
||||
|
@ -118,10 +117,7 @@ public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBui
|
|||
* @param configurer
|
||||
* @return the {@link SecurityConfigurerAdapter} for further customizations
|
||||
* @throws Exception
|
||||
* @deprecated For removal in 7.0. Use
|
||||
* {@link #with(SecurityConfigurerAdapter, Customizer)} instead.
|
||||
*/
|
||||
@Deprecated(since = "6.2", forRemoval = true)
|
||||
@SuppressWarnings("unchecked")
|
||||
public <C extends SecurityConfigurerAdapter<O, B>> C apply(C configurer) throws Exception {
|
||||
configurer.addObjectPostProcessor(this.objectPostProcessor);
|
||||
|
@ -143,23 +139,6 @@ public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBui
|
|||
return configurer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a {@link SecurityConfigurerAdapter} to this {@link SecurityBuilder} and
|
||||
* invokes {@link SecurityConfigurerAdapter#setBuilder(SecurityBuilder)}.
|
||||
* @param configurer
|
||||
* @return the {@link SecurityBuilder} for further customizations
|
||||
* @throws Exception
|
||||
* @since 6.2
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <C extends SecurityConfigurerAdapter<O, B>> B with(C configurer, Customizer<C> customizer) throws Exception {
|
||||
configurer.addObjectPostProcessor(this.objectPostProcessor);
|
||||
configurer.setBuilder((B) this);
|
||||
add(configurer);
|
||||
customizer.customize(configurer);
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an object that is shared by multiple {@link SecurityConfigurer}.
|
||||
* @param sharedType the Class to key the shared object by.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2013 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.
|
||||
|
@ -53,9 +53,7 @@ public abstract class SecurityConfigurerAdapter<O, B extends SecurityBuilder<O>>
|
|||
* Return the {@link SecurityBuilder} when done using the {@link SecurityConfigurer}.
|
||||
* This is useful for method chaining.
|
||||
* @return the {@link SecurityBuilder} for further customizations
|
||||
* @deprecated For removal in 7.0. Use the lambda based configuration instead.
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public B and() {
|
||||
return getBuilder();
|
||||
}
|
||||
|
|
|
@ -41,8 +41,7 @@ final class MethodSecuritySelector implements ImportSelector {
|
|||
|
||||
@Override
|
||||
public String[] selectImports(@NonNull AnnotationMetadata importMetadata) {
|
||||
if (!importMetadata.hasAnnotation(EnableMethodSecurity.class.getName())
|
||||
&& !importMetadata.hasMetaAnnotation(EnableMethodSecurity.class.getName())) {
|
||||
if (!importMetadata.hasAnnotation(EnableMethodSecurity.class.getName())) {
|
||||
return new String[0];
|
||||
}
|
||||
EnableMethodSecurity annotation = importMetadata.getAnnotations().get(EnableMethodSecurity.class).synthesize();
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -26,7 +26,6 @@ import java.util.Map;
|
|||
import jakarta.servlet.DispatcherType;
|
||||
import jakarta.servlet.ServletContext;
|
||||
import jakarta.servlet.ServletRegistration;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
@ -204,30 +203,11 @@ public abstract class AbstractRequestMatcherRegistry<C> {
|
|||
if (!hasDispatcherServlet(registrations)) {
|
||||
return requestMatchers(RequestMatchers.antMatchersAsArray(method, patterns));
|
||||
}
|
||||
ServletRegistration dispatcherServlet = requireOneRootDispatcherServlet(registrations);
|
||||
if (dispatcherServlet != null) {
|
||||
if (registrations.size() == 1) {
|
||||
return requestMatchers(createMvcMatchers(method, patterns).toArray(RequestMatcher[]::new));
|
||||
}
|
||||
List<RequestMatcher> matchers = new ArrayList<>();
|
||||
for (String pattern : patterns) {
|
||||
AntPathRequestMatcher ant = new AntPathRequestMatcher(pattern, (method != null) ? method.name() : null);
|
||||
MvcRequestMatcher mvc = createMvcMatchers(method, pattern).get(0);
|
||||
matchers.add(new DispatcherServletDelegatingRequestMatcher(ant, mvc, servletContext));
|
||||
}
|
||||
return requestMatchers(matchers.toArray(new RequestMatcher[0]));
|
||||
if (registrations.size() > 1) {
|
||||
String errorMessage = computeErrorMessage(registrations.values());
|
||||
throw new IllegalArgumentException(errorMessage);
|
||||
}
|
||||
dispatcherServlet = requireOnlyPathMappedDispatcherServlet(registrations);
|
||||
if (dispatcherServlet != null) {
|
||||
String mapping = dispatcherServlet.getMappings().iterator().next();
|
||||
List<MvcRequestMatcher> matchers = createMvcMatchers(method, patterns);
|
||||
for (MvcRequestMatcher matcher : matchers) {
|
||||
matcher.setServletPath(mapping.substring(0, mapping.length() - 2));
|
||||
}
|
||||
return requestMatchers(matchers.toArray(new RequestMatcher[0]));
|
||||
}
|
||||
String errorMessage = computeErrorMessage(registrations.values());
|
||||
throw new IllegalArgumentException(errorMessage);
|
||||
return requestMatchers(createMvcMatchers(method, patterns).toArray(new RequestMatcher[0]));
|
||||
}
|
||||
|
||||
private Map<String, ? extends ServletRegistration> mappableServletRegistrations(ServletContext servletContext) {
|
||||
|
@ -245,66 +225,22 @@ public abstract class AbstractRequestMatcherRegistry<C> {
|
|||
if (registrations == null) {
|
||||
return false;
|
||||
}
|
||||
Class<?> dispatcherServlet = ClassUtils.resolveClassName("org.springframework.web.servlet.DispatcherServlet",
|
||||
null);
|
||||
for (ServletRegistration registration : registrations.values()) {
|
||||
if (isDispatcherServlet(registration)) {
|
||||
return true;
|
||||
try {
|
||||
Class<?> clazz = Class.forName(registration.getClassName());
|
||||
if (dispatcherServlet.isAssignableFrom(clazz)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private ServletRegistration requireOneRootDispatcherServlet(
|
||||
Map<String, ? extends ServletRegistration> registrations) {
|
||||
ServletRegistration rootDispatcherServlet = null;
|
||||
for (ServletRegistration registration : registrations.values()) {
|
||||
if (!isDispatcherServlet(registration)) {
|
||||
continue;
|
||||
}
|
||||
if (registration.getMappings().size() > 1) {
|
||||
return null;
|
||||
}
|
||||
if (!"/".equals(registration.getMappings().iterator().next())) {
|
||||
return null;
|
||||
}
|
||||
rootDispatcherServlet = registration;
|
||||
}
|
||||
return rootDispatcherServlet;
|
||||
}
|
||||
|
||||
private ServletRegistration requireOnlyPathMappedDispatcherServlet(
|
||||
Map<String, ? extends ServletRegistration> registrations) {
|
||||
ServletRegistration pathDispatcherServlet = null;
|
||||
for (ServletRegistration registration : registrations.values()) {
|
||||
if (!isDispatcherServlet(registration)) {
|
||||
return null;
|
||||
}
|
||||
if (registration.getMappings().size() > 1) {
|
||||
return null;
|
||||
}
|
||||
String mapping = registration.getMappings().iterator().next();
|
||||
if (!mapping.startsWith("/") || !mapping.endsWith("/*")) {
|
||||
return null;
|
||||
}
|
||||
if (pathDispatcherServlet != null) {
|
||||
return null;
|
||||
}
|
||||
pathDispatcherServlet = registration;
|
||||
}
|
||||
return pathDispatcherServlet;
|
||||
}
|
||||
|
||||
private boolean isDispatcherServlet(ServletRegistration registration) {
|
||||
Class<?> dispatcherServlet = ClassUtils.resolveClassName("org.springframework.web.servlet.DispatcherServlet",
|
||||
null);
|
||||
try {
|
||||
Class<?> clazz = Class.forName(registration.getClassName());
|
||||
return dispatcherServlet.isAssignableFrom(clazz);
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private String computeErrorMessage(Collection<? extends ServletRegistration> registrations) {
|
||||
String template = "This method cannot decide whether these patterns are Spring MVC patterns or not. "
|
||||
+ "If this endpoint is a Spring MVC endpoint, please use requestMatchers(MvcRequestMatcher); "
|
||||
|
@ -444,55 +380,4 @@ public abstract class AbstractRequestMatcherRegistry<C> {
|
|||
|
||||
}
|
||||
|
||||
static class DispatcherServletDelegatingRequestMatcher implements RequestMatcher {
|
||||
|
||||
private final AntPathRequestMatcher ant;
|
||||
|
||||
private final MvcRequestMatcher mvc;
|
||||
|
||||
private final ServletContext servletContext;
|
||||
|
||||
DispatcherServletDelegatingRequestMatcher(AntPathRequestMatcher ant, MvcRequestMatcher mvc,
|
||||
ServletContext servletContext) {
|
||||
this.ant = ant;
|
||||
this.mvc = mvc;
|
||||
this.servletContext = servletContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(HttpServletRequest request) {
|
||||
String name = request.getHttpServletMapping().getServletName();
|
||||
ServletRegistration registration = this.servletContext.getServletRegistration(name);
|
||||
Assert.notNull(registration, "Failed to find servlet [" + name + "] in the servlet context");
|
||||
if (isDispatcherServlet(registration)) {
|
||||
return this.mvc.matches(request);
|
||||
}
|
||||
return this.ant.matches(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MatchResult matcher(HttpServletRequest request) {
|
||||
String name = request.getHttpServletMapping().getServletName();
|
||||
ServletRegistration registration = this.servletContext.getServletRegistration(name);
|
||||
Assert.notNull(registration, "Failed to find servlet [" + name + "] in the servlet context");
|
||||
if (isDispatcherServlet(registration)) {
|
||||
return this.mvc.matcher(request);
|
||||
}
|
||||
return this.ant.matcher(request);
|
||||
}
|
||||
|
||||
private boolean isDispatcherServlet(ServletRegistration registration) {
|
||||
Class<?> dispatcherServlet = ClassUtils
|
||||
.resolveClassName("org.springframework.web.servlet.DispatcherServlet", null);
|
||||
try {
|
||||
Class<?> clazz = Class.forName(registration.getClassName());
|
||||
return dispatcherServlet.isAssignableFrom(clazz);
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -127,7 +127,11 @@ final class FilterOrderRegistration {
|
|||
* @param position the position to associate with the {@link Filter}
|
||||
*/
|
||||
void put(Class<? extends Filter> filter, int position) {
|
||||
this.filterToOrder.putIfAbsent(filter.getName(), position);
|
||||
String className = filter.getName();
|
||||
if (this.filterToOrder.containsKey(className)) {
|
||||
return;
|
||||
}
|
||||
this.filterToOrder.put(className, position);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -70,11 +70,9 @@ import org.springframework.security.config.annotation.web.configurers.SessionMan
|
|||
import org.springframework.security.config.annotation.web.configurers.X509Configurer;
|
||||
import org.springframework.security.config.annotation.web.configurers.oauth2.client.OAuth2ClientConfigurer;
|
||||
import org.springframework.security.config.annotation.web.configurers.oauth2.client.OAuth2LoginConfigurer;
|
||||
import org.springframework.security.config.annotation.web.configurers.oauth2.client.OidcLogoutConfigurer;
|
||||
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
|
||||
import org.springframework.security.config.annotation.web.configurers.saml2.Saml2LoginConfigurer;
|
||||
import org.springframework.security.config.annotation.web.configurers.saml2.Saml2LogoutConfigurer;
|
||||
import org.springframework.security.config.annotation.web.configurers.saml2.Saml2MetadataConfigurer;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
|
@ -286,13 +284,8 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
|
|||
* </pre>
|
||||
* @return the {@link HeadersConfigurer} for further customizations
|
||||
* @throws Exception
|
||||
* @deprecated For removal in 7.0. Use {@link #headers(Customizer)} or
|
||||
* {@code headers(Customizer.withDefaults())} to stick with defaults. See the <a href=
|
||||
* "https://docs.spring.io/spring-security/reference/migration-7/configuration.html#_use_the_lambda_dsl">documentation</a>
|
||||
* for more details.
|
||||
* @see HeadersConfigurer
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public HeadersConfigurer<HttpSecurity> headers() throws Exception {
|
||||
return getOrApply(new HeadersConfigurer<>());
|
||||
}
|
||||
|
@ -405,12 +398,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
|
|||
* on the classpath a {@link HandlerMappingIntrospector} is used.
|
||||
* @return the {@link CorsConfigurer} for customizations
|
||||
* @throws Exception
|
||||
* @deprecated For removal in 7.0. Use {@link #cors(Customizer)} or
|
||||
* {@code cors(Customizer.withDefaults())} to stick with defaults. See the <a href=
|
||||
* "https://docs.spring.io/spring-security/reference/migration-7/configuration.html#_use_the_lambda_dsl">documentation</a>
|
||||
* for more details.
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public CorsConfigurer<HttpSecurity> cors() throws Exception {
|
||||
return getOrApply(new CorsConfigurer<>());
|
||||
}
|
||||
|
@ -497,13 +485,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
|
|||
* could return true.
|
||||
* @return the {@link SessionManagementConfigurer} for further customizations
|
||||
* @throws Exception
|
||||
* @deprecated For removal in 7.0. Use {@link #sessionManagement(Customizer)} or
|
||||
* {@code sessionManagement(Customizer.withDefaults())} to stick with defaults. See
|
||||
* the <a href=
|
||||
* "https://docs.spring.io/spring-security/reference/migration-7/configuration.html#_use_the_lambda_dsl">documentation</a>
|
||||
* for more details.
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public SessionManagementConfigurer<HttpSecurity> sessionManagement() throws Exception {
|
||||
return getOrApply(new SessionManagementConfigurer<>());
|
||||
}
|
||||
|
@ -625,14 +607,8 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
|
|||
* </pre>
|
||||
* @return the {@link PortMapperConfigurer} for further customizations
|
||||
* @throws Exception
|
||||
* @deprecated For removal in 7.0. Use {@link #portMapper(Customizer)} or
|
||||
* {@code portMapper(Customizer.withDefaults())} to stick with defaults. See the
|
||||
* <a href=
|
||||
* "https://docs.spring.io/spring-security/reference/migration-7/configuration.html#_use_the_lambda_dsl">documentation</a>
|
||||
* for more details.
|
||||
* @see #requiresChannel()
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public PortMapperConfigurer<HttpSecurity> portMapper() throws Exception {
|
||||
return getOrApply(new PortMapperConfigurer<>());
|
||||
}
|
||||
|
@ -762,12 +738,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
|
|||
* Servlet Container's documentation.
|
||||
* @return the {@link JeeConfigurer} for further customizations
|
||||
* @throws Exception
|
||||
* @deprecated For removal in 7.0. Use {@link #jee(Customizer)} or
|
||||
* {@code jee(Customizer.withDefaults())} to stick with defaults. See the <a href=
|
||||
* "https://docs.spring.io/spring-security/reference/migration-7/configuration.html#_use_the_lambda_dsl">documentation</a>
|
||||
* for more details.
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public JeeConfigurer<HttpSecurity> jee() throws Exception {
|
||||
return getOrApply(new JeeConfigurer<>());
|
||||
}
|
||||
|
@ -878,12 +849,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
|
|||
* </pre>
|
||||
* @return the {@link X509Configurer} for further customizations
|
||||
* @throws Exception
|
||||
* @deprecated For removal in 7.0. Use {@link #x509(Customizer)} or
|
||||
* {@code x509(Customizer.withDefaults())} to stick with defaults. See the <a href=
|
||||
* "https://docs.spring.io/spring-security/reference/migration-7/configuration.html#_use_the_lambda_dsl">documentation</a>
|
||||
* for more details.
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public X509Configurer<HttpSecurity> x509() throws Exception {
|
||||
return getOrApply(new X509Configurer<>());
|
||||
}
|
||||
|
@ -961,13 +927,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
|
|||
* </pre>
|
||||
* @return the {@link RememberMeConfigurer} for further customizations
|
||||
* @throws Exception
|
||||
* @deprecated For removal in 7.0. Use {@link #rememberMe(Customizer)} or
|
||||
* {@code rememberMe(Customizer.withDefaults())} to stick with defaults. See the
|
||||
* <a href=
|
||||
* "https://docs.spring.io/spring-security/reference/migration-7/configuration.html#_use_the_lambda_dsl">documentation</a>
|
||||
* for more details.
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public RememberMeConfigurer<HttpSecurity> rememberMe() throws Exception {
|
||||
return getOrApply(new RememberMeConfigurer<>());
|
||||
}
|
||||
|
@ -1111,7 +1071,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
|
|||
* </pre>
|
||||
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further customizations
|
||||
* @throws Exception
|
||||
* @deprecated For removal in 7.0. Use {@link #authorizeHttpRequests()} instead
|
||||
* @deprecated Use {@link #authorizeHttpRequests()} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry authorizeRequests()
|
||||
|
@ -1226,7 +1186,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
|
|||
* for the {@link ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry}
|
||||
* @return the {@link HttpSecurity} for further customizations
|
||||
* @throws Exception
|
||||
* @deprecated For removal in 7.0. Use {@link #authorizeHttpRequests} instead
|
||||
* @deprecated Use {@link #authorizeHttpRequests} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public HttpSecurity authorizeRequests(
|
||||
|
@ -1341,10 +1301,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
|
|||
* @return the {@link HttpSecurity} for further customizations
|
||||
* @throws Exception
|
||||
* @since 5.6
|
||||
* @deprecated For removal in 7.0. Use {@link #authorizeHttpRequests(Customizer)}
|
||||
* instead
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry authorizeHttpRequests()
|
||||
throws Exception {
|
||||
ApplicationContext context = getContext();
|
||||
|
@ -1476,13 +1433,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
|
|||
* when using {@link EnableWebSecurity}.
|
||||
* @return the {@link RequestCacheConfigurer} for further customizations
|
||||
* @throws Exception
|
||||
* @deprecated For removal in 7.0. Use {@link #requestCache(Customizer)} or
|
||||
* {@code requestCache(Customizer.withDefaults())} to stick with defaults. See the
|
||||
* <a href=
|
||||
* "https://docs.spring.io/spring-security/reference/migration-7/configuration.html#_use_the_lambda_dsl">documentation</a>
|
||||
* for more details.
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public RequestCacheConfigurer<HttpSecurity> requestCache() throws Exception {
|
||||
return getOrApply(new RequestCacheConfigurer<>());
|
||||
}
|
||||
|
@ -1533,13 +1484,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
|
|||
* {@link EnableWebSecurity}.
|
||||
* @return the {@link ExceptionHandlingConfigurer} for further customizations
|
||||
* @throws Exception
|
||||
* @deprecated For removal in 7.0. Use {@link #exceptionHandling(Customizer)} or
|
||||
* {@code exceptionHandling(Customizer.withDefaults())} to stick with defaults. See
|
||||
* the <a href=
|
||||
* "https://docs.spring.io/spring-security/reference/migration-7/configuration.html#_use_the_lambda_dsl">documentation</a>
|
||||
* for more details.
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public ExceptionHandlingConfigurer<HttpSecurity> exceptionHandling() throws Exception {
|
||||
return getOrApply(new ExceptionHandlingConfigurer<>());
|
||||
}
|
||||
|
@ -1591,13 +1536,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
|
|||
* automatically applied when using {@link EnableWebSecurity}.
|
||||
* @return the {@link SecurityContextConfigurer} for further customizations
|
||||
* @throws Exception
|
||||
* @deprecated For removal in 7.0. Use {@link #securityContext(Customizer)} or
|
||||
* {@code securityContext(Customizer.withDefaults())} to stick with defaults. See the
|
||||
* <a href=
|
||||
* "https://docs.spring.io/spring-security/reference/migration-7/configuration.html#_use_the_lambda_dsl">documentation</a>
|
||||
* for more details.
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public SecurityContextConfigurer<HttpSecurity> securityContext() throws Exception {
|
||||
return getOrApply(new SecurityContextConfigurer<>());
|
||||
}
|
||||
|
@ -1642,13 +1581,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
|
|||
* {@link EnableWebSecurity}.
|
||||
* @return the {@link ServletApiConfigurer} for further customizations
|
||||
* @throws Exception
|
||||
* @deprecated For removal in 7.0. Use {@link #servletApi(Customizer)} or
|
||||
* {@code servletApi(Customizer.withDefaults())} to stick with defaults. See the
|
||||
* <a href=
|
||||
* "https://docs.spring.io/spring-security/reference/migration-7/configuration.html#_use_the_lambda_dsl">documentation</a>
|
||||
* for more details.
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public ServletApiConfigurer<HttpSecurity> servletApi() throws Exception {
|
||||
return getOrApply(new ServletApiConfigurer<>());
|
||||
}
|
||||
|
@ -1704,12 +1637,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
|
|||
* </pre>
|
||||
* @return the {@link CsrfConfigurer} for further customizations
|
||||
* @throws Exception
|
||||
* @deprecated For removal in 7.0. Use {@link #csrf(Customizer)} or
|
||||
* {@code csrf(Customizer.withDefaults())} to stick with defaults. See the <a href=
|
||||
* "https://docs.spring.io/spring-security/reference/migration-7/configuration.html#_use_the_lambda_dsl">documentation</a>
|
||||
* for more details.
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public CsrfConfigurer<HttpSecurity> csrf() throws Exception {
|
||||
ApplicationContext context = getContext();
|
||||
return getOrApply(new CsrfConfigurer<>(context));
|
||||
|
@ -1784,12 +1712,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
|
|||
* </pre>
|
||||
* @return the {@link LogoutConfigurer} for further customizations
|
||||
* @throws Exception
|
||||
* @deprecated For removal in 7.0. Use {@link #logout(Customizer)} or
|
||||
* {@code logout(Customizer.withDefaults())} to stick with defaults. See the <a href=
|
||||
* "https://docs.spring.io/spring-security/reference/migration-7/configuration.html#_use_the_lambda_dsl">documentation</a>
|
||||
* for more details.
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public LogoutConfigurer<HttpSecurity> logout() throws Exception {
|
||||
return getOrApply(new LogoutConfigurer<>());
|
||||
}
|
||||
|
@ -1928,13 +1851,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
|
|||
* </pre>
|
||||
* @return the {@link AnonymousConfigurer} for further customizations
|
||||
* @throws Exception
|
||||
* @deprecated For removal in 7.0. Use {@link #anonymous(Customizer)} or
|
||||
* {@code anonymous(Customizer.withDefaults())} to stick with defaults. See the
|
||||
* <a href=
|
||||
* "https://docs.spring.io/spring-security/reference/migration-7/configuration.html#_use_the_lambda_dsl">documentation</a>
|
||||
* for more details.
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public AnonymousConfigurer<HttpSecurity> anonymous() throws Exception {
|
||||
return getOrApply(new AnonymousConfigurer<>());
|
||||
}
|
||||
|
@ -2097,14 +2014,8 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
|
|||
* </pre>
|
||||
* @return the {@link FormLoginConfigurer} for further customizations
|
||||
* @throws Exception
|
||||
* @deprecated For removal in 7.0. Use {@link #formLogin(Customizer)} or
|
||||
* {@code formLogin(Customizer.withDefaults())} to stick with defaults. See the
|
||||
* <a href=
|
||||
* "https://docs.spring.io/spring-security/reference/migration-7/configuration.html#_use_the_lambda_dsl">documentation</a>
|
||||
* for more details.
|
||||
* @see FormLoginConfigurer#loginPage(String)
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public FormLoginConfigurer<HttpSecurity> formLogin() throws Exception {
|
||||
return getOrApply(new FormLoginConfigurer<>());
|
||||
}
|
||||
|
@ -2279,13 +2190,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
|
|||
* @return the {@link Saml2LoginConfigurer} for further customizations
|
||||
* @throws Exception
|
||||
* @since 5.2
|
||||
* @deprecated For removal in 7.0. Use {@link #saml2Login(Customizer)} or
|
||||
* {@code saml2Login(Customizer.withDefaults())} to stick with defaults. See the
|
||||
* <a href=
|
||||
* "https://docs.spring.io/spring-security/reference/migration-7/configuration.html#_use_the_lambda_dsl">documentation</a>
|
||||
* for more details.
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public Saml2LoginConfigurer<HttpSecurity> saml2Login() throws Exception {
|
||||
return getOrApply(new Saml2LoginConfigurer<>());
|
||||
}
|
||||
|
@ -2515,119 +2420,11 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
|
|||
* @return the {@link Saml2LoginConfigurer} for further customizations
|
||||
* @throws Exception
|
||||
* @since 5.6
|
||||
* @deprecated For removal in 7.0. Use {@link #saml2Logout(Customizer)} or
|
||||
* {@code saml2Logout(Customizer.withDefaults())} to stick with defaults. See the
|
||||
* <a href=
|
||||
* "https://docs.spring.io/spring-security/reference/migration-7/configuration.html#_use_the_lambda_dsl">documentation</a>
|
||||
* for more details.
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public Saml2LogoutConfigurer<HttpSecurity> saml2Logout() throws Exception {
|
||||
return getOrApply(new Saml2LogoutConfigurer<>(getContext()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures a SAML 2.0 metadata endpoint that presents relying party configurations
|
||||
* in an {@code <md:EntityDescriptor>} payload.
|
||||
*
|
||||
* <p>
|
||||
* By default, the endpoints are {@code /saml2/metadata} and
|
||||
* {@code /saml2/metadata/{registrationId}} though note that also
|
||||
* {@code /saml2/service-provider-metadata/{registrationId}} is recognized for
|
||||
* backward compatibility purposes.
|
||||
*
|
||||
* <p>
|
||||
* <h2>Example Configuration</h2>
|
||||
*
|
||||
* The following example shows the minimal configuration required, using a
|
||||
* hypothetical asserting party.
|
||||
*
|
||||
* <pre>
|
||||
* @EnableWebSecurity
|
||||
* @Configuration
|
||||
* public class Saml2LogoutSecurityConfig {
|
||||
* @Bean
|
||||
* public SecurityFilterChain web(HttpSecurity http) throws Exception {
|
||||
* http
|
||||
* .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated())
|
||||
* .saml2Metadata(Customizer.withDefaults());
|
||||
* return http.build();
|
||||
* }
|
||||
*
|
||||
* @Bean
|
||||
* public RelyingPartyRegistrationRepository relyingPartyRegistrationRepository() {
|
||||
* RelyingPartyRegistration registration = RelyingPartyRegistrations
|
||||
* .withMetadataLocation("https://ap.example.org/metadata")
|
||||
* .registrationId("simple")
|
||||
* .build();
|
||||
* return new InMemoryRelyingPartyRegistrationRepository(registration);
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
* @param saml2MetadataConfigurer the {@link Customizer} to provide more options for
|
||||
* the {@link Saml2MetadataConfigurer}
|
||||
* @return the {@link HttpSecurity} for further customizations
|
||||
* @throws Exception
|
||||
* @since 6.1
|
||||
*/
|
||||
public HttpSecurity saml2Metadata(Customizer<Saml2MetadataConfigurer<HttpSecurity>> saml2MetadataConfigurer)
|
||||
throws Exception {
|
||||
saml2MetadataConfigurer.customize(getOrApply(new Saml2MetadataConfigurer<>(getContext())));
|
||||
return HttpSecurity.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures a SAML 2.0 metadata endpoint that presents relying party configurations
|
||||
* in an {@code <md:EntityDescriptor>} payload.
|
||||
*
|
||||
* <p>
|
||||
* By default, the endpoints are {@code /saml2/metadata} and
|
||||
* {@code /saml2/metadata/{registrationId}} though note that also
|
||||
* {@code /saml2/service-provider-metadata/{registrationId}} is recognized for
|
||||
* backward compatibility purposes.
|
||||
*
|
||||
* <p>
|
||||
* <h2>Example Configuration</h2>
|
||||
*
|
||||
* The following example shows the minimal configuration required, using a
|
||||
* hypothetical asserting party.
|
||||
*
|
||||
* <pre>
|
||||
* @EnableWebSecurity
|
||||
* @Configuration
|
||||
* public class Saml2LogoutSecurityConfig {
|
||||
* @Bean
|
||||
* public SecurityFilterChain web(HttpSecurity http) throws Exception {
|
||||
* http
|
||||
* .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated())
|
||||
* .saml2Metadata(Customizer.withDefaults());
|
||||
* return http.build();
|
||||
* }
|
||||
*
|
||||
* @Bean
|
||||
* public RelyingPartyRegistrationRepository relyingPartyRegistrationRepository() {
|
||||
* RelyingPartyRegistration registration = RelyingPartyRegistrations
|
||||
* .withMetadataLocation("https://ap.example.org/metadata")
|
||||
* .registrationId("simple")
|
||||
* .build();
|
||||
* return new InMemoryRelyingPartyRegistrationRepository(registration);
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
* @return the {@link Saml2MetadataConfigurer} for further customizations
|
||||
* @throws Exception
|
||||
* @since 6.1
|
||||
* @deprecated For removal in 7.0. Use {@link #saml2Metadata(Customizer)} or
|
||||
* {@code saml2Metadata(Customizer.withDefaults())} to stick with defaults. See the
|
||||
* <a href=
|
||||
* "https://docs.spring.io/spring-security/reference/migration-7/configuration.html#_use_the_lambda_dsl">documentation</a>
|
||||
* for more details.
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public Saml2MetadataConfigurer<HttpSecurity> saml2Metadata() throws Exception {
|
||||
return getOrApply(new Saml2MetadataConfigurer<>(getContext()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures authentication support using an OAuth 2.0 and/or OpenID Connect 1.0
|
||||
* Provider. <br>
|
||||
|
@ -2714,11 +2511,6 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
|
|||
* @return the {@link OAuth2LoginConfigurer} for further customizations
|
||||
* @throws Exception
|
||||
* @since 5.0
|
||||
* @deprecated For removal in 7.0. Use {@link #oauth2Login(Customizer)} or
|
||||
* {@code oauth2Login(Customizer.withDefaults())} to stick with defaults. See the
|
||||
* <a href=
|
||||
* "https://docs.spring.io/spring-security/reference/migration-7/configuration.html#_use_the_lambda_dsl">documentation</a>
|
||||
* for more details.
|
||||
* @see <a target="_blank" href=
|
||||
* "https://tools.ietf.org/html/rfc6749#section-4.1">Section 4.1 Authorization Code
|
||||
* Grant</a>
|
||||
|
@ -2728,7 +2520,6 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
|
|||
* @see org.springframework.security.oauth2.client.registration.ClientRegistration
|
||||
* @see org.springframework.security.oauth2.client.registration.ClientRegistrationRepository
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public OAuth2LoginConfigurer<HttpSecurity> oauth2Login() throws Exception {
|
||||
return getOrApply(new OAuth2LoginConfigurer<>());
|
||||
}
|
||||
|
@ -2836,31 +2627,15 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
|
|||
return HttpSecurity.this;
|
||||
}
|
||||
|
||||
public OidcLogoutConfigurer<HttpSecurity> oidcLogout() throws Exception {
|
||||
return getOrApply(new OidcLogoutConfigurer<>());
|
||||
}
|
||||
|
||||
public HttpSecurity oidcLogout(Customizer<OidcLogoutConfigurer<HttpSecurity>> oidcLogoutCustomizer)
|
||||
throws Exception {
|
||||
oidcLogoutCustomizer.customize(getOrApply(new OidcLogoutConfigurer<>()));
|
||||
return HttpSecurity.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures OAuth 2.0 Client support.
|
||||
* @return the {@link OAuth2ClientConfigurer} for further customizations
|
||||
* @throws Exception
|
||||
* @since 5.1
|
||||
* @deprecated For removal in 7.0. Use {@link #oauth2Client(Customizer)} or
|
||||
* {@code oauth2Client(Customizer.withDefaults())} to stick with defaults. See the
|
||||
* <a href=
|
||||
* "https://docs.spring.io/spring-security/reference/migration-7/configuration.html#_use_the_lambda_dsl">documentation</a>
|
||||
* for more details.
|
||||
* @see <a target="_blank" href=
|
||||
* "https://tools.ietf.org/html/rfc6749#section-1.1">OAuth 2.0 Authorization
|
||||
* Framework</a>
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public OAuth2ClientConfigurer<HttpSecurity> oauth2Client() throws Exception {
|
||||
OAuth2ClientConfigurer<HttpSecurity> configurer = getOrApply(new OAuth2ClientConfigurer<>());
|
||||
this.postProcess(configurer);
|
||||
|
@ -2911,13 +2686,10 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
|
|||
* @return the {@link OAuth2ResourceServerConfigurer} for further customizations
|
||||
* @throws Exception
|
||||
* @since 5.1
|
||||
* @deprecated For removal in 7.0. Use {@link #oauth2ResourceServer(Customizer)}
|
||||
* instead
|
||||
* @see <a target="_blank" href=
|
||||
* "https://tools.ietf.org/html/rfc6749#section-1.1">OAuth 2.0 Authorization
|
||||
* Framework</a>
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public OAuth2ResourceServerConfigurer<HttpSecurity> oauth2ResourceServer() throws Exception {
|
||||
OAuth2ResourceServerConfigurer<HttpSecurity> configurer = getOrApply(
|
||||
new OAuth2ResourceServerConfigurer<>(getContext()));
|
||||
|
@ -3015,13 +2787,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
|
|||
* </pre>
|
||||
* @return the {@link ChannelSecurityConfigurer} for further customizations
|
||||
* @throws Exception
|
||||
* @deprecated For removal in 7.0. Use {@link #requiresChannel(Customizer)} or
|
||||
* {@code requiresChannel(Customizer.withDefaults())} to stick with defaults. See the
|
||||
* <a href=
|
||||
* "https://docs.spring.io/spring-security/reference/migration-7/configuration.html#_use_the_lambda_dsl">documentation</a>
|
||||
* for more details.
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public ChannelSecurityConfigurer<HttpSecurity>.ChannelRequestMatcherRegistry requiresChannel() throws Exception {
|
||||
ApplicationContext context = getContext();
|
||||
return getOrApply(new ChannelSecurityConfigurer<>(context)).getRegistry();
|
||||
|
@ -3116,13 +2882,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
|
|||
* </pre>
|
||||
* @return the {@link HttpBasicConfigurer} for further customizations
|
||||
* @throws Exception
|
||||
* @deprecated For removal in 7.0. Use {@link #httpBasic(Customizer)} or
|
||||
* {@code httpBasic(Customizer.withDefaults())} to stick with defaults. See the
|
||||
* <a href=
|
||||
* "https://docs.spring.io/spring-security/reference/migration-7/configuration.html#_use_the_lambda_dsl">documentation</a>
|
||||
* for more details.
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public HttpBasicConfigurer<HttpSecurity> httpBasic() throws Exception {
|
||||
return getOrApply(new HttpBasicConfigurer<>());
|
||||
}
|
||||
|
@ -3451,13 +3211,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
|
|||
* }
|
||||
* </pre>
|
||||
* @return the {@link RequestMatcherConfigurer} for further customizations
|
||||
* @deprecated For removal in 7.0. Use {@link #securityMatchers(Customizer)} or
|
||||
* {@code securityMatchers(Customizer.withDefaults())} to stick with defaults. See the
|
||||
* <a href=
|
||||
* "https://docs.spring.io/spring-security/reference/migration-7/configuration.html#_use_the_lambda_dsl">documentation</a>
|
||||
* for more details.
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public RequestMatcherConfigurer securityMatchers() {
|
||||
return this.requestMatcherConfigurer;
|
||||
}
|
||||
|
@ -3715,28 +3469,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
|
|||
/**
|
||||
* Return the {@link HttpSecurity} for further customizations
|
||||
* @return the {@link HttpSecurity} for further customizations
|
||||
* @deprecated Use the lambda based configuration instead. For example: <pre>
|
||||
* @Configuration
|
||||
* @EnableWebSecurity
|
||||
* public class SecurityConfig {
|
||||
*
|
||||
* @Bean
|
||||
* public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
* http
|
||||
* .securityMatchers((matchers) -> matchers
|
||||
* .requestMatchers("/api/**")
|
||||
* )
|
||||
* .authorizeHttpRequests((authorize) -> authorize
|
||||
* .anyRequest().hasRole("USER")
|
||||
* )
|
||||
* .httpBasic(Customizer.withDefaults());
|
||||
* return http.build();
|
||||
* }
|
||||
*
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public HttpSecurity and() {
|
||||
return HttpSecurity.this;
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ import org.springframework.security.web.SecurityFilterChain;
|
|||
*
|
||||
* @Bean
|
||||
* public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
* http.authorizeHttpRequests().requestMatchers("/public/**").permitAll().anyRequest()
|
||||
* http.authorizeRequests().requestMatchers("/public/**").permitAll().anyRequest()
|
||||
* .hasRole("USER").and()
|
||||
* // Possibly more configuration ...
|
||||
* .formLogin() // enable form based log in
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -47,7 +47,6 @@ import org.springframework.security.crypto.password.PasswordEncoder;
|
|||
import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;
|
||||
import org.springframework.web.accept.ContentNegotiationStrategy;
|
||||
import org.springframework.web.accept.HeaderContentNegotiationStrategy;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
|
||||
import static org.springframework.security.config.Customizer.withDefaults;
|
||||
|
||||
|
@ -125,18 +124,10 @@ class HttpSecurityConfiguration {
|
|||
.apply(new DefaultLoginPageConfigurer<>());
|
||||
http.logout(withDefaults());
|
||||
// @formatter:on
|
||||
applyCorsIfAvailable(http);
|
||||
applyDefaultConfigurers(http);
|
||||
return http;
|
||||
}
|
||||
|
||||
private void applyCorsIfAvailable(HttpSecurity http) throws Exception {
|
||||
String[] beanNames = this.context.getBeanNamesForType(CorsConfigurationSource.class);
|
||||
if (beanNames.length == 1) {
|
||||
http.cors(withDefaults());
|
||||
}
|
||||
}
|
||||
|
||||
private AuthenticationManager authenticationManager() throws Exception {
|
||||
return this.authenticationConfiguration.getAuthenticationManager();
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -16,46 +16,19 @@
|
|||
|
||||
package org.springframework.security.config.annotation.web.configuration;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.BeanFactoryUtils;
|
||||
import org.springframework.beans.factory.BeanInitializationException;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
|
||||
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.ImportSelector;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.security.core.context.SecurityContextHolderStrategy;
|
||||
import org.springframework.security.oauth2.client.AuthorizationCodeOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.ClientCredentialsOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.DelegatingOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.JwtBearerOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.PasswordOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.RefreshTokenOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.endpoint.JwtBearerGrantRequest;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
|
||||
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
|
||||
|
@ -75,8 +48,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
|||
* @since 5.1
|
||||
* @see OAuth2ImportSelector
|
||||
*/
|
||||
@Import({ OAuth2ClientConfiguration.OAuth2ClientWebMvcImportSelector.class,
|
||||
OAuth2ClientConfiguration.OAuth2AuthorizedClientManagerConfiguration.class })
|
||||
@Import(OAuth2ClientConfiguration.OAuth2ClientWebMvcImportSelector.class)
|
||||
final class OAuth2ClientConfiguration {
|
||||
|
||||
private static final boolean webMvcPresent;
|
||||
|
@ -93,22 +65,8 @@ final class OAuth2ClientConfiguration {
|
|||
if (!webMvcPresent) {
|
||||
return new String[0];
|
||||
}
|
||||
return new String[] {
|
||||
OAuth2ClientConfiguration.class.getName() + ".OAuth2ClientWebMvcSecurityConfiguration" };
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Joe Grandja
|
||||
* @since 6.2.0
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class OAuth2AuthorizedClientManagerConfiguration {
|
||||
|
||||
@Bean
|
||||
OAuth2AuthorizedClientManagerRegistrar authorizedClientManagerRegistrar() {
|
||||
return new OAuth2AuthorizedClientManagerRegistrar();
|
||||
return new String[] { "org.springframework.security.config.annotation.web.configuration."
|
||||
+ "OAuth2ClientConfiguration.OAuth2ClientWebMvcSecurityConfiguration" };
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -116,12 +74,16 @@ final class OAuth2ClientConfiguration {
|
|||
@Configuration(proxyBeanMethods = false)
|
||||
static class OAuth2ClientWebMvcSecurityConfiguration implements WebMvcConfigurer {
|
||||
|
||||
private ClientRegistrationRepository clientRegistrationRepository;
|
||||
|
||||
private OAuth2AuthorizedClientRepository authorizedClientRepository;
|
||||
|
||||
private OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> accessTokenResponseClient;
|
||||
|
||||
private OAuth2AuthorizedClientManager authorizedClientManager;
|
||||
|
||||
private SecurityContextHolderStrategy securityContextHolderStrategy;
|
||||
|
||||
private OAuth2AuthorizedClientManagerRegistrar authorizedClientManagerRegistrar;
|
||||
|
||||
@Override
|
||||
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
|
||||
OAuth2AuthorizedClientManager authorizedClientManager = getAuthorizedClientManager();
|
||||
|
@ -135,6 +97,26 @@ final class OAuth2ClientConfiguration {
|
|||
}
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
void setClientRegistrationRepository(List<ClientRegistrationRepository> clientRegistrationRepositories) {
|
||||
if (clientRegistrationRepositories.size() == 1) {
|
||||
this.clientRegistrationRepository = clientRegistrationRepositories.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
void setAuthorizedClientRepository(List<OAuth2AuthorizedClientRepository> authorizedClientRepositories) {
|
||||
if (authorizedClientRepositories.size() == 1) {
|
||||
this.authorizedClientRepository = authorizedClientRepositories.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
void setAccessTokenResponseClient(
|
||||
OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> accessTokenResponseClient) {
|
||||
this.accessTokenResponseClient = accessTokenResponseClient;
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
void setAuthorizedClientManager(List<OAuth2AuthorizedClientManager> authorizedClientManagers) {
|
||||
if (authorizedClientManagers.size() == 1) {
|
||||
|
@ -147,262 +129,35 @@ final class OAuth2ClientConfiguration {
|
|||
this.securityContextHolderStrategy = strategy;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
void setAuthorizedClientManagerRegistrar(
|
||||
OAuth2AuthorizedClientManagerRegistrar authorizedClientManagerRegistrar) {
|
||||
this.authorizedClientManagerRegistrar = authorizedClientManagerRegistrar;
|
||||
}
|
||||
|
||||
private OAuth2AuthorizedClientManager getAuthorizedClientManager() {
|
||||
if (this.authorizedClientManager != null) {
|
||||
return this.authorizedClientManager;
|
||||
}
|
||||
return this.authorizedClientManagerRegistrar.getAuthorizedClientManagerIfAvailable();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A registrar for registering the default {@link OAuth2AuthorizedClientManager} bean
|
||||
* definition, if not already present.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @author Steve Riesenberg
|
||||
* @since 6.2.0
|
||||
*/
|
||||
static final class OAuth2AuthorizedClientManagerRegistrar
|
||||
implements BeanDefinitionRegistryPostProcessor, BeanFactoryAware {
|
||||
|
||||
// @formatter:off
|
||||
private static final Set<Class<?>> KNOWN_AUTHORIZED_CLIENT_PROVIDERS = Set.of(
|
||||
AuthorizationCodeOAuth2AuthorizedClientProvider.class,
|
||||
RefreshTokenOAuth2AuthorizedClientProvider.class,
|
||||
ClientCredentialsOAuth2AuthorizedClientProvider.class,
|
||||
PasswordOAuth2AuthorizedClientProvider.class,
|
||||
JwtBearerOAuth2AuthorizedClientProvider.class
|
||||
);
|
||||
// @formatter:on
|
||||
|
||||
private final AnnotationBeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator();
|
||||
|
||||
private ListableBeanFactory beanFactory;
|
||||
|
||||
@Override
|
||||
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
|
||||
if (getBeanNamesForType(OAuth2AuthorizedClientManager.class).length != 0
|
||||
|| getBeanNamesForType(ClientRegistrationRepository.class).length != 1
|
||||
|| getBeanNamesForType(OAuth2AuthorizedClientRepository.class).length != 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
BeanDefinition beanDefinition = BeanDefinitionBuilder
|
||||
.genericBeanDefinition(OAuth2AuthorizedClientManager.class, this::getAuthorizedClientManager)
|
||||
.getBeanDefinition();
|
||||
|
||||
registry.registerBeanDefinition(this.beanNameGenerator.generateBeanName(beanDefinition, registry),
|
||||
beanDefinition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
this.beanFactory = (ListableBeanFactory) beanFactory;
|
||||
}
|
||||
|
||||
OAuth2AuthorizedClientManager getAuthorizedClientManagerIfAvailable() {
|
||||
if (getBeanNamesForType(ClientRegistrationRepository.class).length != 1
|
||||
|| getBeanNamesForType(OAuth2AuthorizedClientRepository.class).length != 1) {
|
||||
return null;
|
||||
}
|
||||
return getAuthorizedClientManager();
|
||||
}
|
||||
|
||||
private OAuth2AuthorizedClientManager getAuthorizedClientManager() {
|
||||
ClientRegistrationRepository clientRegistrationRepository = BeanFactoryUtils
|
||||
.beanOfTypeIncludingAncestors(this.beanFactory, ClientRegistrationRepository.class, true, true);
|
||||
|
||||
OAuth2AuthorizedClientRepository authorizedClientRepository = BeanFactoryUtils
|
||||
.beanOfTypeIncludingAncestors(this.beanFactory, OAuth2AuthorizedClientRepository.class, true, true);
|
||||
|
||||
Collection<OAuth2AuthorizedClientProvider> authorizedClientProviderBeans = BeanFactoryUtils
|
||||
.beansOfTypeIncludingAncestors(this.beanFactory, OAuth2AuthorizedClientProvider.class, true, true)
|
||||
.values();
|
||||
|
||||
OAuth2AuthorizedClientProvider authorizedClientProvider;
|
||||
if (hasDelegatingAuthorizedClientProvider(authorizedClientProviderBeans)) {
|
||||
authorizedClientProvider = authorizedClientProviderBeans.iterator().next();
|
||||
}
|
||||
else {
|
||||
List<OAuth2AuthorizedClientProvider> authorizedClientProviders = new ArrayList<>();
|
||||
authorizedClientProviders
|
||||
.add(getAuthorizationCodeAuthorizedClientProvider(authorizedClientProviderBeans));
|
||||
authorizedClientProviders.add(getRefreshTokenAuthorizedClientProvider(authorizedClientProviderBeans));
|
||||
authorizedClientProviders
|
||||
.add(getClientCredentialsAuthorizedClientProvider(authorizedClientProviderBeans));
|
||||
authorizedClientProviders.add(getPasswordAuthorizedClientProvider(authorizedClientProviderBeans));
|
||||
|
||||
OAuth2AuthorizedClientProvider jwtBearerAuthorizedClientProvider = getJwtBearerAuthorizedClientProvider(
|
||||
authorizedClientProviderBeans);
|
||||
if (jwtBearerAuthorizedClientProvider != null) {
|
||||
authorizedClientProviders.add(jwtBearerAuthorizedClientProvider);
|
||||
OAuth2AuthorizedClientManager authorizedClientManager = null;
|
||||
if (this.clientRegistrationRepository != null && this.authorizedClientRepository != null) {
|
||||
if (this.accessTokenResponseClient != null) {
|
||||
// @formatter:off
|
||||
OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder
|
||||
.builder()
|
||||
.authorizationCode()
|
||||
.refreshToken()
|
||||
.clientCredentials((configurer) -> configurer.accessTokenResponseClient(this.accessTokenResponseClient))
|
||||
.password()
|
||||
.build();
|
||||
// @formatter:on
|
||||
DefaultOAuth2AuthorizedClientManager defaultAuthorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
|
||||
this.clientRegistrationRepository, this.authorizedClientRepository);
|
||||
defaultAuthorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
|
||||
authorizedClientManager = defaultAuthorizedClientManager;
|
||||
}
|
||||
else {
|
||||
authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
|
||||
this.clientRegistrationRepository, this.authorizedClientRepository);
|
||||
}
|
||||
|
||||
authorizedClientProviders.addAll(getAdditionalAuthorizedClientProviders(authorizedClientProviderBeans));
|
||||
authorizedClientProvider = new DelegatingOAuth2AuthorizedClientProvider(authorizedClientProviders);
|
||||
}
|
||||
|
||||
DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
|
||||
clientRegistrationRepository, authorizedClientRepository);
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
|
||||
|
||||
Consumer<DefaultOAuth2AuthorizedClientManager> authorizedClientManagerConsumer = getBeanOfType(
|
||||
ResolvableType.forClassWithGenerics(Consumer.class, DefaultOAuth2AuthorizedClientManager.class));
|
||||
if (authorizedClientManagerConsumer != null) {
|
||||
authorizedClientManagerConsumer.accept(authorizedClientManager);
|
||||
}
|
||||
|
||||
return authorizedClientManager;
|
||||
}
|
||||
|
||||
private boolean hasDelegatingAuthorizedClientProvider(
|
||||
Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders) {
|
||||
if (authorizedClientProviders.size() != 1) {
|
||||
return false;
|
||||
}
|
||||
return authorizedClientProviders.iterator().next() instanceof DelegatingOAuth2AuthorizedClientProvider;
|
||||
}
|
||||
|
||||
private OAuth2AuthorizedClientProvider getAuthorizationCodeAuthorizedClientProvider(
|
||||
Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders) {
|
||||
AuthorizationCodeOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType(
|
||||
authorizedClientProviders, AuthorizationCodeOAuth2AuthorizedClientProvider.class);
|
||||
if (authorizedClientProvider == null) {
|
||||
authorizedClientProvider = new AuthorizationCodeOAuth2AuthorizedClientProvider();
|
||||
}
|
||||
|
||||
return authorizedClientProvider;
|
||||
}
|
||||
|
||||
private OAuth2AuthorizedClientProvider getRefreshTokenAuthorizedClientProvider(
|
||||
Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders) {
|
||||
RefreshTokenOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType(
|
||||
authorizedClientProviders, RefreshTokenOAuth2AuthorizedClientProvider.class);
|
||||
if (authorizedClientProvider == null) {
|
||||
authorizedClientProvider = new RefreshTokenOAuth2AuthorizedClientProvider();
|
||||
}
|
||||
|
||||
OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> accessTokenResponseClient = getBeanOfType(
|
||||
ResolvableType.forClassWithGenerics(OAuth2AccessTokenResponseClient.class,
|
||||
OAuth2RefreshTokenGrantRequest.class));
|
||||
if (accessTokenResponseClient != null) {
|
||||
authorizedClientProvider.setAccessTokenResponseClient(accessTokenResponseClient);
|
||||
}
|
||||
|
||||
return authorizedClientProvider;
|
||||
}
|
||||
|
||||
private OAuth2AuthorizedClientProvider getClientCredentialsAuthorizedClientProvider(
|
||||
Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders) {
|
||||
ClientCredentialsOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType(
|
||||
authorizedClientProviders, ClientCredentialsOAuth2AuthorizedClientProvider.class);
|
||||
if (authorizedClientProvider == null) {
|
||||
authorizedClientProvider = new ClientCredentialsOAuth2AuthorizedClientProvider();
|
||||
}
|
||||
|
||||
OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> accessTokenResponseClient = getBeanOfType(
|
||||
ResolvableType.forClassWithGenerics(OAuth2AccessTokenResponseClient.class,
|
||||
OAuth2ClientCredentialsGrantRequest.class));
|
||||
if (accessTokenResponseClient != null) {
|
||||
authorizedClientProvider.setAccessTokenResponseClient(accessTokenResponseClient);
|
||||
}
|
||||
|
||||
return authorizedClientProvider;
|
||||
}
|
||||
|
||||
private OAuth2AuthorizedClientProvider getPasswordAuthorizedClientProvider(
|
||||
Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders) {
|
||||
PasswordOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType(
|
||||
authorizedClientProviders, PasswordOAuth2AuthorizedClientProvider.class);
|
||||
if (authorizedClientProvider == null) {
|
||||
authorizedClientProvider = new PasswordOAuth2AuthorizedClientProvider();
|
||||
}
|
||||
|
||||
OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> accessTokenResponseClient = getBeanOfType(
|
||||
ResolvableType.forClassWithGenerics(OAuth2AccessTokenResponseClient.class,
|
||||
OAuth2PasswordGrantRequest.class));
|
||||
if (accessTokenResponseClient != null) {
|
||||
authorizedClientProvider.setAccessTokenResponseClient(accessTokenResponseClient);
|
||||
}
|
||||
|
||||
return authorizedClientProvider;
|
||||
}
|
||||
|
||||
private OAuth2AuthorizedClientProvider getJwtBearerAuthorizedClientProvider(
|
||||
Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders) {
|
||||
JwtBearerOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType(
|
||||
authorizedClientProviders, JwtBearerOAuth2AuthorizedClientProvider.class);
|
||||
|
||||
OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> accessTokenResponseClient = getBeanOfType(
|
||||
ResolvableType.forClassWithGenerics(OAuth2AccessTokenResponseClient.class,
|
||||
JwtBearerGrantRequest.class));
|
||||
if (accessTokenResponseClient != null) {
|
||||
if (authorizedClientProvider == null) {
|
||||
authorizedClientProvider = new JwtBearerOAuth2AuthorizedClientProvider();
|
||||
}
|
||||
|
||||
authorizedClientProvider.setAccessTokenResponseClient(accessTokenResponseClient);
|
||||
}
|
||||
|
||||
return authorizedClientProvider;
|
||||
}
|
||||
|
||||
private List<OAuth2AuthorizedClientProvider> getAdditionalAuthorizedClientProviders(
|
||||
Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders) {
|
||||
List<OAuth2AuthorizedClientProvider> additionalAuthorizedClientProviders = new ArrayList<>(
|
||||
authorizedClientProviders);
|
||||
additionalAuthorizedClientProviders
|
||||
.removeIf((provider) -> KNOWN_AUTHORIZED_CLIENT_PROVIDERS.contains(provider.getClass()));
|
||||
return additionalAuthorizedClientProviders;
|
||||
}
|
||||
|
||||
private <T extends OAuth2AuthorizedClientProvider> T getAuthorizedClientProviderByType(
|
||||
Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders, Class<T> providerClass) {
|
||||
T authorizedClientProvider = null;
|
||||
for (OAuth2AuthorizedClientProvider current : authorizedClientProviders) {
|
||||
if (providerClass.isInstance(current)) {
|
||||
assertAuthorizedClientProviderIsNull(authorizedClientProvider);
|
||||
authorizedClientProvider = providerClass.cast(current);
|
||||
}
|
||||
}
|
||||
return authorizedClientProvider;
|
||||
}
|
||||
|
||||
private static void assertAuthorizedClientProviderIsNull(
|
||||
OAuth2AuthorizedClientProvider authorizedClientProvider) {
|
||||
if (authorizedClientProvider != null) {
|
||||
// @formatter:off
|
||||
throw new BeanInitializationException(String.format(
|
||||
"Unable to create an %s bean. Expected one bean of type %s, but found multiple. " +
|
||||
"Please consider defining only a single bean of this type, or define an %s bean yourself.",
|
||||
OAuth2AuthorizedClientManager.class.getName(),
|
||||
authorizedClientProvider.getClass().getName(),
|
||||
OAuth2AuthorizedClientManager.class.getName()));
|
||||
// @formatter:on
|
||||
}
|
||||
}
|
||||
|
||||
private <T> String[] getBeanNamesForType(Class<T> beanClass) {
|
||||
return BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, beanClass, true, true);
|
||||
}
|
||||
|
||||
private <T> T getBeanOfType(ResolvableType resolvableType) {
|
||||
ObjectProvider<T> objectProvider = this.beanFactory.getBeanProvider(resolvableType, true);
|
||||
return objectProvider.getIfAvailable();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -51,8 +51,8 @@ import org.springframework.web.accept.HeaderContentNegotiationStrategy;
|
|||
* Base class for configuring {@link AbstractAuthenticationFilterConfigurer}. This is
|
||||
* intended for internal use only.
|
||||
*
|
||||
* @param <T> refers to "this" for returning the current configurer
|
||||
* @param <F> refers to the {@link AbstractAuthenticationProcessingFilter} that is being
|
||||
* @param T refers to "this" for returning the current configurer
|
||||
* @param F refers to the {@link AbstractAuthenticationProcessingFilter} that is being
|
||||
* built
|
||||
* @author Rob Winch
|
||||
* @since 3.2
|
||||
|
@ -122,7 +122,7 @@ public abstract class AbstractAuthenticationFilterConfigurer<B extends HttpSecur
|
|||
* true. This is a shortcut for calling
|
||||
* {@link #successHandler(AuthenticationSuccessHandler)}.
|
||||
* @param defaultSuccessUrl the default success url
|
||||
* @param alwaysUse true if the {@code defaultSuccessUrl} should be used after
|
||||
* @param alwaysUse true if the {@code defaultSuccesUrl} should be used after
|
||||
* authentication despite if a protected page had been previously visited
|
||||
* @return the {@link FormLoginConfigurer} for additional customization
|
||||
*/
|
||||
|
@ -380,6 +380,7 @@ public abstract class AbstractAuthenticationFilterConfigurer<B extends HttpSecur
|
|||
|
||||
/**
|
||||
* Updates the default values for authentication.
|
||||
* @throws Exception
|
||||
*/
|
||||
protected final void updateAuthenticationDefaults() {
|
||||
if (this.loginProcessingUrl == null) {
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.annotation.web.configurers;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
|
||||
abstract class AbstractRequestMatcherBuilderRegistry<C> extends AbstractRequestMatcherRegistry<C> {
|
||||
|
||||
private final RequestMatcherBuilder builder;
|
||||
|
||||
AbstractRequestMatcherBuilderRegistry(ApplicationContext context) {
|
||||
this(context, RequestMatcherBuilders.createDefault(context));
|
||||
}
|
||||
|
||||
AbstractRequestMatcherBuilderRegistry(ApplicationContext context, RequestMatcherBuilder builder) {
|
||||
setApplicationContext(context);
|
||||
this.builder = builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final C requestMatchers(String... patterns) {
|
||||
return requestMatchers(null, patterns);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final C requestMatchers(HttpMethod method, String... patterns) {
|
||||
return requestMatchers(this.builder.matchers(method, patterns).toArray(RequestMatcher[]::new));
|
||||
}
|
||||
|
||||
@Override
|
||||
public final C requestMatchers(HttpMethod method) {
|
||||
return requestMatchers(method, "/**");
|
||||
}
|
||||
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.annotation.web.configurers;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
|
||||
final class AntPathRequestMatcherBuilder implements RequestMatcherBuilder {
|
||||
|
||||
private final String servletPath;
|
||||
|
||||
private AntPathRequestMatcherBuilder(String servletPath) {
|
||||
this.servletPath = servletPath;
|
||||
}
|
||||
|
||||
static AntPathRequestMatcherBuilder absolute() {
|
||||
return new AntPathRequestMatcherBuilder(null);
|
||||
}
|
||||
|
||||
static AntPathRequestMatcherBuilder relativeTo(String path) {
|
||||
return new AntPathRequestMatcherBuilder(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AntPathRequestMatcher matcher(String pattern) {
|
||||
return matcher((String) null, pattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AntPathRequestMatcher matcher(HttpMethod method, String pattern) {
|
||||
return matcher((method != null) ? method.name() : null, pattern);
|
||||
}
|
||||
|
||||
private AntPathRequestMatcher matcher(String method, String pattern) {
|
||||
return new AntPathRequestMatcher(prependServletPath(pattern), method);
|
||||
}
|
||||
|
||||
private String prependServletPath(String pattern) {
|
||||
if (this.servletPath == null) {
|
||||
return pattern;
|
||||
}
|
||||
return this.servletPath + pattern;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -16,19 +16,12 @@
|
|||
|
||||
package org.springframework.security.config.annotation.web.configurers;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
import jakarta.servlet.http.HttpServletMapping;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy;
|
||||
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
|
||||
import org.springframework.security.authorization.AuthenticatedAuthorizationManager;
|
||||
import org.springframework.security.authorization.AuthorityAuthorizationManager;
|
||||
import org.springframework.security.authorization.AuthorizationDecision;
|
||||
|
@ -36,22 +29,15 @@ import org.springframework.security.authorization.AuthorizationEventPublisher;
|
|||
import org.springframework.security.authorization.AuthorizationManager;
|
||||
import org.springframework.security.authorization.ObservationAuthorizationManager;
|
||||
import org.springframework.security.authorization.SpringAuthorizationEventPublisher;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.ObjectPostProcessor;
|
||||
import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry;
|
||||
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.core.GrantedAuthorityDefaults;
|
||||
import org.springframework.security.web.access.intercept.AuthorizationFilter;
|
||||
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
|
||||
import org.springframework.security.web.access.intercept.RequestMatcherDelegatingAuthorizationManager;
|
||||
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcherEntry;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.function.SingletonSupplier;
|
||||
import org.springframework.web.servlet.DispatcherServlet;
|
||||
|
||||
/**
|
||||
* Adds a URL based authorization using {@link AuthorizationManager}.
|
||||
|
@ -70,10 +56,6 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
|
|||
|
||||
private final AuthorizationEventPublisher publisher;
|
||||
|
||||
private final Supplier<RoleHierarchy> roleHierarchy;
|
||||
|
||||
private String rolePrefix = "ROLE_";
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
* @param context the {@link ApplicationContext} to use
|
||||
|
@ -86,13 +68,6 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
|
|||
else {
|
||||
this.publisher = new SpringAuthorizationEventPublisher(context);
|
||||
}
|
||||
this.roleHierarchy = SingletonSupplier.of(() -> (context.getBeanNamesForType(RoleHierarchy.class).length > 0)
|
||||
? context.getBean(RoleHierarchy.class) : new NullRoleHierarchy());
|
||||
String[] grantedAuthorityDefaultsBeanNames = context.getBeanNamesForType(GrantedAuthorityDefaults.class);
|
||||
if (grantedAuthorityDefaultsBeanNames.length > 0) {
|
||||
GrantedAuthorityDefaults grantedAuthorityDefaults = context.getBean(GrantedAuthorityDefaults.class);
|
||||
this.rolePrefix = grantedAuthorityDefaults.getRolePrefix();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -146,62 +121,41 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
|
|||
* @author Evgeniy Cheban
|
||||
*/
|
||||
public final class AuthorizationManagerRequestMatcherRegistry
|
||||
extends AbstractRequestMatcherBuilderRegistry<AuthorizedUrl> {
|
||||
extends AbstractRequestMatcherRegistry<AuthorizedUrl> {
|
||||
|
||||
private final RequestMatcherDelegatingAuthorizationManager.Builder managerBuilder = RequestMatcherDelegatingAuthorizationManager
|
||||
.builder();
|
||||
|
||||
List<RequestMatcher> unmappedMatchers;
|
||||
private List<RequestMatcher> unmappedMatchers;
|
||||
|
||||
private int mappingCount;
|
||||
|
||||
private boolean shouldFilterAllDispatcherTypes = true;
|
||||
|
||||
private final Map<String, AuthorizationManagerServletRequestMatcherRegistry> servletPattern = new LinkedHashMap<>();
|
||||
|
||||
AuthorizationManagerRequestMatcherRegistry(ApplicationContext context) {
|
||||
super(context);
|
||||
private AuthorizationManagerRequestMatcherRegistry(ApplicationContext context) {
|
||||
setApplicationContext(context);
|
||||
}
|
||||
|
||||
private void addMapping(RequestMatcher matcher, AuthorizationManager<RequestAuthorizationContext> manager) {
|
||||
Assert.isTrue(this.servletPattern.isEmpty(),
|
||||
"Since you have used forServletPattern, all request matchers must be configured using forServletPattern; alternatively, you can use requestMatchers(RequestMatcher) for all requests.");
|
||||
this.unmappedMatchers = null;
|
||||
this.managerBuilder.add(matcher, manager);
|
||||
this.mappingCount++;
|
||||
}
|
||||
|
||||
private void addFirst(RequestMatcher matcher, AuthorizationManager<RequestAuthorizationContext> manager) {
|
||||
Assert.isTrue(this.servletPattern.isEmpty(),
|
||||
"Since you have used forServletPattern, all request matchers must be configured using forServletPattern; alternatively, you can use requestMatchers(RequestMatcher) for all requests.");
|
||||
this.unmappedMatchers = null;
|
||||
this.managerBuilder.mappings((m) -> m.add(0, new RequestMatcherEntry<>(matcher, manager)));
|
||||
this.mappingCount++;
|
||||
}
|
||||
|
||||
private AuthorizationManager<HttpServletRequest> servletAuthorizationManager() {
|
||||
for (Map.Entry<String, AuthorizationManagerServletRequestMatcherRegistry> entry : this.servletPattern
|
||||
.entrySet()) {
|
||||
AuthorizationManagerServletRequestMatcherRegistry registry = entry.getValue();
|
||||
this.managerBuilder.add(new ServletPatternRequestMatcher(entry.getKey()),
|
||||
registry.authorizationManager());
|
||||
}
|
||||
return postProcess(this.managerBuilder.build());
|
||||
}
|
||||
|
||||
private AuthorizationManager<HttpServletRequest> authorizationManager() {
|
||||
private AuthorizationManager<HttpServletRequest> createAuthorizationManager() {
|
||||
Assert.state(this.unmappedMatchers == null,
|
||||
() -> "An incomplete mapping was found for " + this.unmappedMatchers
|
||||
+ ". Try completing it with something like requestUrls().<something>.hasRole('USER')");
|
||||
Assert.state(this.mappingCount > 0,
|
||||
"At least one mapping is required (for example, authorizeHttpRequests().anyRequest().authenticated())");
|
||||
return postProcess(this.managerBuilder.build());
|
||||
}
|
||||
|
||||
private AuthorizationManager<HttpServletRequest> createAuthorizationManager() {
|
||||
AuthorizationManager<HttpServletRequest> manager = (this.servletPattern.isEmpty()) ? authorizationManager()
|
||||
: servletAuthorizationManager();
|
||||
ObservationRegistry registry = getObservationRegistry();
|
||||
RequestMatcherDelegatingAuthorizationManager manager = postProcess(this.managerBuilder.build());
|
||||
if (registry.isNoop()) {
|
||||
return manager;
|
||||
}
|
||||
|
@ -211,74 +165,7 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
|
|||
@Override
|
||||
protected AuthorizedUrl chainRequestMatchers(List<RequestMatcher> requestMatchers) {
|
||||
this.unmappedMatchers = requestMatchers;
|
||||
return new AuthorizedUrl(
|
||||
(manager) -> AuthorizeHttpRequestsConfigurer.this.addMapping(requestMatchers, manager));
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin registering {@link RequestMatcher}s based on the type of the servlet
|
||||
* mapped to {@code pattern}. Each registered request matcher will additionally
|
||||
* check {@link HttpServletMapping#getPattern} against the provided
|
||||
* {@code pattern}.
|
||||
*
|
||||
* <p>
|
||||
* If the corresponding servlet is of type {@link DispatcherServlet}, then use a
|
||||
* {@link AuthorizationManagerServletRequestMatcherRegistry} that registers
|
||||
* {@link MvcRequestMatcher}s.
|
||||
*
|
||||
* <p>
|
||||
* Otherwise, use a configurer that registers {@link AntPathRequestMatcher}s.
|
||||
*
|
||||
* <p>
|
||||
* When doing a path-based pattern, like `/path/*`, registered URIs should leave
|
||||
* out the matching path. For example, if the target URI is `/path/resource/3`,
|
||||
* then the configuration should look like this: <code>
|
||||
* .forServletPattern("/path/*", (path) -> path
|
||||
* .requestMatchers("/resource/3").hasAuthority(...)
|
||||
* )
|
||||
* </code>
|
||||
*
|
||||
* <p>
|
||||
* Or, if the pattern is `/path/subpath/*`, and the URI is
|
||||
* `/path/subpath/resource/3`, then the configuration should look like this:
|
||||
* <code>
|
||||
* .forServletPattern("/path/subpath/*", (path) -> path
|
||||
* .requestMatchers("/resource/3").hasAuthority(...)
|
||||
* )
|
||||
* </code>
|
||||
*
|
||||
* <p>
|
||||
* For all other patterns, please supply the URI in absolute terms. For example,
|
||||
* if the target URI is `/js/**` and it matches to the default servlet, then the
|
||||
* configuration should look like this: <code>
|
||||
* .forServletPattern("/", (root) -> root
|
||||
* .requestMatchers("/js/**").hasAuthority(...)
|
||||
* )
|
||||
* </code>
|
||||
*
|
||||
* <p>
|
||||
* Or, if the target URI is `/views/**`, and it matches to a `*.jsp` extension
|
||||
* servlet, then the configuration should look like this: <code>
|
||||
* .forServletPattern("*.jsp", (jsp) -> jsp
|
||||
* .requestMatchers("/views/**").hasAuthority(...)
|
||||
* )
|
||||
* </code>
|
||||
* @param customizer a customizer that uses a
|
||||
* {@link AuthorizationManagerServletRequestMatcherRegistry} for URIs mapped to
|
||||
* the provided servlet
|
||||
* @return an {@link AuthorizationManagerServletRequestMatcherRegistry} for
|
||||
* further configurations
|
||||
* @since 6.2
|
||||
*/
|
||||
public AuthorizationManagerRequestMatcherRegistry forServletPattern(String pattern,
|
||||
Customizer<AuthorizationManagerServletRequestMatcherRegistry> customizer) {
|
||||
ApplicationContext context = getApplicationContext();
|
||||
RequestMatcherBuilder builder = RequestMatcherBuilders.createForServletPattern(context, pattern);
|
||||
AuthorizationManagerServletRequestMatcherRegistry registry = new AuthorizationManagerServletRequestMatcherRegistry(
|
||||
builder);
|
||||
customizer.customize(registry);
|
||||
this.servletPattern.put(pattern, registry);
|
||||
return this;
|
||||
return new AuthorizedUrl(requestMatchers);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -299,25 +186,7 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
|
|||
* @return the {@link AuthorizationManagerRequestMatcherRegistry} for further
|
||||
* customizations
|
||||
* @since 5.7
|
||||
* @deprecated Permit access to the {@link jakarta.servlet.DispatcherType}
|
||||
* instead. <pre>
|
||||
* @Configuration
|
||||
* @EnableWebSecurity
|
||||
* public class SecurityConfig {
|
||||
*
|
||||
* @Bean
|
||||
* public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
* http
|
||||
* .authorizeHttpRequests((authorize) -> authorize
|
||||
* .dispatcherTypeMatchers(DispatcherType.ERROR).permitAll()
|
||||
* // ...
|
||||
* );
|
||||
* return http.build();
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public AuthorizationManagerRequestMatcherRegistry shouldFilterAllDispatcherTypes(boolean shouldFilter) {
|
||||
this.shouldFilterAllDispatcherTypes = shouldFilter;
|
||||
return this;
|
||||
|
@ -327,272 +196,11 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
|
|||
* Return the {@link HttpSecurityBuilder} when done using the
|
||||
* {@link AuthorizeHttpRequestsConfigurer}. This is useful for method chaining.
|
||||
* @return the {@link HttpSecurityBuilder} for further customizations
|
||||
* @deprecated For removal in 7.0. Use the lambda based configuration instead.
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public H and() {
|
||||
return AuthorizeHttpRequestsConfigurer.this.and();
|
||||
}
|
||||
|
||||
/**
|
||||
* A decorator class for registering {@link RequestMatcher} instances based on the
|
||||
* type of servlet. If the servlet is {@link DispatcherServlet}, then it will use
|
||||
* a {@link MvcRequestMatcher}; otherwise, it will use a
|
||||
* {@link AntPathRequestMatcher}.
|
||||
*
|
||||
* <p>
|
||||
* This class is designed primarily for use with the {@link HttpSecurity} DSL. For
|
||||
* that reason, please use {@link HttpSecurity#authorizeHttpRequests} instead as
|
||||
* it exposes this class fluently alongside related DSL configurations.
|
||||
*
|
||||
* <p>
|
||||
* NOTE: In many cases, which kind of request matcher is needed is apparent by the
|
||||
* servlet configuration, and so you should generally use the methods found in
|
||||
* {@link AbstractRequestMatcherRegistry} instead of this these. Use this class
|
||||
* when you want or need to indicate which request matcher URIs belong to which
|
||||
* servlet.
|
||||
*
|
||||
* <p>
|
||||
* In all cases, though, you may arrange your request matchers by servlet pattern
|
||||
* with the {@link AuthorizationManagerRequestMatcherRegistry#forServletPattern}
|
||||
* method in the {@link HttpSecurity#authorizeHttpRequests} DSL.
|
||||
*
|
||||
* <p>
|
||||
* Consider, for example, the circumstance where you have Spring MVC configured
|
||||
* and also Spring Boot H2 Console. Spring MVC registers a servlet of type
|
||||
* {@link DispatcherServlet} as the default servlet and Spring Boot registers a
|
||||
* servlet of its own as well at `/h2-console/*`.
|
||||
*
|
||||
* <p>
|
||||
* Such might have a configuration like this in Spring Security: <code>
|
||||
* http
|
||||
* .authorizeHttpRequests((authorize) -> authorize
|
||||
* .requestMatchers("/js/**", "/css/**").permitAll()
|
||||
* .requestMatchers("/my/controller/**").hasAuthority("CONTROLLER")
|
||||
* .requestMatchers("/h2-console/**").hasAuthority("H2")
|
||||
* )
|
||||
* // ...
|
||||
* </code>
|
||||
*
|
||||
* <p>
|
||||
* Spring Security by default addresses the above configuration on its own.
|
||||
*
|
||||
* <p>
|
||||
* However, consider the same situation, but where {@link DispatcherServlet} is
|
||||
* mapped to a path like `/mvc/*`. In this case, the above configuration is
|
||||
* ambiguous, and you should use this class to clarify the rest of each MVC URI
|
||||
* like so: <code>
|
||||
* http
|
||||
* .authorizeHttpRequests((authorize) -> authorize
|
||||
* .forServletPattern("/", (root) -> root
|
||||
* .requestMatchers("/js/**", "/css/**").permitAll()
|
||||
* )
|
||||
* .forServletPattern("/mvc/*", (mvc) -> mvc
|
||||
* .requestMatchers("/my/controller/**").hasAuthority("CONTROLLER")
|
||||
* )
|
||||
* .forServletPattern("/h2-console/*", (h2) -> h2
|
||||
* .anyRequest().hasAuthority("OTHER")
|
||||
* )
|
||||
* )
|
||||
* // ...
|
||||
* </code>
|
||||
*
|
||||
* <p>
|
||||
* In the above configuration, it's now clear to Spring Security that the
|
||||
* following matchers map to these corresponding URIs:
|
||||
*
|
||||
* <ul>
|
||||
* <li><default> + <strong>`/js/**`</strong> ==> `/js/**`</li>
|
||||
* <li><default> + <strong>`/css/**`</strong> ==> `/css/**`</li>
|
||||
* <li>`/mvc` + <strong>`/my/controller/**`</strong> ==>
|
||||
* `/mvc/my/controller/**`</li>
|
||||
* <li>`/h2-console` + <strong><any request></strong> ==>
|
||||
* `/h2-console/**`</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 6.2
|
||||
* @see AbstractRequestMatcherRegistry
|
||||
* @see AuthorizeHttpRequestsConfigurer
|
||||
*/
|
||||
public final class AuthorizationManagerServletRequestMatcherRegistry
|
||||
extends AbstractRequestMatcherBuilderRegistry<ServletAuthorizedUrl> {
|
||||
|
||||
private final RequestMatcherDelegatingAuthorizationManager.Builder managerBuilder = RequestMatcherDelegatingAuthorizationManager
|
||||
.builder();
|
||||
|
||||
private List<RequestMatcher> unmappedMatchers;
|
||||
|
||||
AuthorizationManagerServletRequestMatcherRegistry(RequestMatcherBuilder builder) {
|
||||
super(AuthorizationManagerRequestMatcherRegistry.this.getApplicationContext(), builder);
|
||||
}
|
||||
|
||||
AuthorizationManager<RequestAuthorizationContext> authorizationManager() {
|
||||
Assert.state(this.unmappedMatchers == null,
|
||||
() -> "An incomplete mapping was found for " + this.unmappedMatchers
|
||||
+ ". Try completing it with something like requestUrls().<something>.hasRole('USER')");
|
||||
AuthorizationManager<HttpServletRequest> request = this.managerBuilder.build();
|
||||
return (authentication, context) -> request.check(authentication, context.getRequest());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ServletAuthorizedUrl chainRequestMatchers(List<RequestMatcher> requestMatchers) {
|
||||
this.unmappedMatchers = requestMatchers;
|
||||
return new ServletAuthorizedUrl((manager) -> addMapping(requestMatchers, manager));
|
||||
}
|
||||
|
||||
private AuthorizationManagerServletRequestMatcherRegistry addMapping(List<RequestMatcher> matchers,
|
||||
AuthorizationManager<RequestAuthorizationContext> manager) {
|
||||
this.unmappedMatchers = null;
|
||||
for (RequestMatcher matcher : matchers) {
|
||||
this.managerBuilder.add(matcher, manager);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* An object that allows configuring the {@link AuthorizationManager} for
|
||||
* {@link RequestMatcher}s.
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 6.2
|
||||
*/
|
||||
public final class ServletAuthorizedUrl {
|
||||
|
||||
private final Function<AuthorizationManager<RequestAuthorizationContext>, AuthorizationManagerServletRequestMatcherRegistry> registrar;
|
||||
|
||||
ServletAuthorizedUrl(
|
||||
Function<AuthorizationManager<RequestAuthorizationContext>, AuthorizationManagerServletRequestMatcherRegistry> registrar) {
|
||||
this.registrar = registrar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify that URLs are allowed by anyone.
|
||||
* @return the {@link AuthorizationManagerRequestMatcherRegistry} for further
|
||||
* customizations
|
||||
*/
|
||||
public AuthorizationManagerServletRequestMatcherRegistry permitAll() {
|
||||
return access(permitAllAuthorizationManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify that URLs are not allowed by anyone.
|
||||
* @return the {@link AuthorizationManagerRequestMatcherRegistry} for further
|
||||
* customizations
|
||||
*/
|
||||
public AuthorizationManagerServletRequestMatcherRegistry denyAll() {
|
||||
return access((a, o) -> new AuthorizationDecision(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies a user requires a role.
|
||||
* @param role the role that should be required which is prepended with ROLE_
|
||||
* automatically (i.e. USER, ADMIN, etc). It should not start with ROLE_
|
||||
* @return {@link AuthorizationManagerRequestMatcherRegistry} for further
|
||||
* customizations
|
||||
*/
|
||||
public AuthorizationManagerServletRequestMatcherRegistry hasRole(String role) {
|
||||
return access(withRoleHierarchy(AuthorityAuthorizationManager
|
||||
.hasAnyRole(AuthorizeHttpRequestsConfigurer.this.rolePrefix, new String[] { role })));
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies that a user requires one of many roles.
|
||||
* @param roles the roles that the user should have at least one of (i.e.
|
||||
* ADMIN, USER, etc). Each role should not start with ROLE_ since it is
|
||||
* automatically prepended already
|
||||
* @return the {@link AuthorizationManagerRequestMatcherRegistry} for further
|
||||
* customizations
|
||||
*/
|
||||
public AuthorizationManagerServletRequestMatcherRegistry hasAnyRole(String... roles) {
|
||||
return access(withRoleHierarchy(AuthorityAuthorizationManager
|
||||
.hasAnyRole(AuthorizeHttpRequestsConfigurer.this.rolePrefix, roles)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies a user requires an authority.
|
||||
* @param authority the authority that should be required
|
||||
* @return the {@link AuthorizationManagerRequestMatcherRegistry} for further
|
||||
* customizations
|
||||
*/
|
||||
public AuthorizationManagerServletRequestMatcherRegistry hasAuthority(String authority) {
|
||||
return access(withRoleHierarchy(AuthorityAuthorizationManager.hasAuthority(authority)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies that a user requires one of many authorities.
|
||||
* @param authorities the authorities that the user should have at least one
|
||||
* of (i.e. ROLE_USER, ROLE_ADMIN, etc)
|
||||
* @return the {@link AuthorizationManagerRequestMatcherRegistry} for further
|
||||
* customizations
|
||||
*/
|
||||
public AuthorizationManagerServletRequestMatcherRegistry hasAnyAuthority(String... authorities) {
|
||||
return access(withRoleHierarchy(AuthorityAuthorizationManager.hasAnyAuthority(authorities)));
|
||||
}
|
||||
|
||||
private AuthorityAuthorizationManager<RequestAuthorizationContext> withRoleHierarchy(
|
||||
AuthorityAuthorizationManager<RequestAuthorizationContext> manager) {
|
||||
manager.setRoleHierarchy(AuthorizeHttpRequestsConfigurer.this.roleHierarchy.get());
|
||||
return manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify that URLs are allowed by any authenticated user.
|
||||
* @return the {@link AuthorizationManagerRequestMatcherRegistry} for further
|
||||
* customizations
|
||||
*/
|
||||
public AuthorizationManagerServletRequestMatcherRegistry authenticated() {
|
||||
return access(AuthenticatedAuthorizationManager.authenticated());
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify that URLs are allowed by users who have authenticated and were not
|
||||
* "remembered".
|
||||
* @return the {@link AuthorizationManagerRequestMatcherRegistry} for further
|
||||
* customization
|
||||
* @see RememberMeConfigurer
|
||||
*/
|
||||
public AuthorizationManagerServletRequestMatcherRegistry fullyAuthenticated() {
|
||||
return access(AuthenticatedAuthorizationManager.fullyAuthenticated());
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify that URLs are allowed by users that have been remembered.
|
||||
* @return the {@link AuthorizationManagerRequestMatcherRegistry} for further
|
||||
* customization
|
||||
* @since 5.8
|
||||
* @see RememberMeConfigurer
|
||||
*/
|
||||
public AuthorizationManagerServletRequestMatcherRegistry rememberMe() {
|
||||
return access(AuthenticatedAuthorizationManager.rememberMe());
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify that URLs are allowed by anonymous users.
|
||||
* @return the {@link AuthorizationManagerRequestMatcherRegistry} for further
|
||||
* customization
|
||||
* @since 5.8
|
||||
*/
|
||||
public AuthorizationManagerServletRequestMatcherRegistry anonymous() {
|
||||
return access(AuthenticatedAuthorizationManager.anonymous());
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows specifying a custom {@link AuthorizationManager}.
|
||||
* @param manager the {@link AuthorizationManager} to use
|
||||
* @return the {@link AuthorizationManagerRequestMatcherRegistry} for further
|
||||
* customizations
|
||||
*/
|
||||
public AuthorizationManagerServletRequestMatcherRegistry access(
|
||||
AuthorizationManager<RequestAuthorizationContext> manager) {
|
||||
Assert.notNull(manager, "manager cannot be null");
|
||||
return this.registrar.apply(manager);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -603,11 +211,18 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
|
|||
*/
|
||||
public class AuthorizedUrl {
|
||||
|
||||
private final Function<AuthorizationManager<RequestAuthorizationContext>, AuthorizationManagerRequestMatcherRegistry> registrar;
|
||||
private final List<? extends RequestMatcher> matchers;
|
||||
|
||||
AuthorizedUrl(
|
||||
Function<AuthorizationManager<RequestAuthorizationContext>, AuthorizationManagerRequestMatcherRegistry> registrar) {
|
||||
this.registrar = registrar;
|
||||
/**
|
||||
* Creates an instance.
|
||||
* @param matchers the {@link RequestMatcher} instances to map
|
||||
*/
|
||||
AuthorizedUrl(List<? extends RequestMatcher> matchers) {
|
||||
this.matchers = matchers;
|
||||
}
|
||||
|
||||
protected List<? extends RequestMatcher> getMatchers() {
|
||||
return this.matchers;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -636,8 +251,7 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
|
|||
* customizations
|
||||
*/
|
||||
public AuthorizationManagerRequestMatcherRegistry hasRole(String role) {
|
||||
return access(withRoleHierarchy(AuthorityAuthorizationManager
|
||||
.hasAnyRole(AuthorizeHttpRequestsConfigurer.this.rolePrefix, new String[] { role })));
|
||||
return access(AuthorityAuthorizationManager.hasRole(role));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -649,8 +263,7 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
|
|||
* customizations
|
||||
*/
|
||||
public AuthorizationManagerRequestMatcherRegistry hasAnyRole(String... roles) {
|
||||
return access(withRoleHierarchy(
|
||||
AuthorityAuthorizationManager.hasAnyRole(AuthorizeHttpRequestsConfigurer.this.rolePrefix, roles)));
|
||||
return access(AuthorityAuthorizationManager.hasAnyRole(roles));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -660,7 +273,7 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
|
|||
* customizations
|
||||
*/
|
||||
public AuthorizationManagerRequestMatcherRegistry hasAuthority(String authority) {
|
||||
return access(withRoleHierarchy(AuthorityAuthorizationManager.hasAuthority(authority)));
|
||||
return access(AuthorityAuthorizationManager.hasAuthority(authority));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -671,13 +284,7 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
|
|||
* customizations
|
||||
*/
|
||||
public AuthorizationManagerRequestMatcherRegistry hasAnyAuthority(String... authorities) {
|
||||
return access(withRoleHierarchy(AuthorityAuthorizationManager.hasAnyAuthority(authorities)));
|
||||
}
|
||||
|
||||
private AuthorityAuthorizationManager<RequestAuthorizationContext> withRoleHierarchy(
|
||||
AuthorityAuthorizationManager<RequestAuthorizationContext> manager) {
|
||||
manager.setRoleHierarchy(AuthorizeHttpRequestsConfigurer.this.roleHierarchy.get());
|
||||
return manager;
|
||||
return access(AuthorityAuthorizationManager.hasAnyAuthority(authorities));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -731,7 +338,7 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
|
|||
public AuthorizationManagerRequestMatcherRegistry access(
|
||||
AuthorizationManager<RequestAuthorizationContext> manager) {
|
||||
Assert.notNull(manager, "manager cannot be null");
|
||||
return this.registrar.apply(manager);
|
||||
return AuthorizeHttpRequestsConfigurer.this.addMapping(this.matchers, manager);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -24,7 +24,6 @@ import java.util.List;
|
|||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.security.access.ConfigAttribute;
|
||||
import org.springframework.security.access.SecurityConfig;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.ObjectPostProcessor;
|
||||
import org.springframework.security.config.annotation.SecurityBuilder;
|
||||
import org.springframework.security.config.annotation.SecurityConfigurer;
|
||||
|
@ -195,10 +194,7 @@ public final class ChannelSecurityConfigurer<H extends HttpSecurityBuilder<H>>
|
|||
* Return the {@link SecurityBuilder} when done using the
|
||||
* {@link SecurityConfigurer}. This is useful for method chaining.
|
||||
* @return the type of {@link HttpSecurityBuilder} that is being configured
|
||||
* @deprecated For removal in 7.0. Use
|
||||
* {@link HttpSecurity#requiresChannel(Customizer)} instead
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public H and() {
|
||||
return ChannelSecurityConfigurer.this.and();
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -40,6 +40,7 @@ import org.springframework.security.web.csrf.CsrfLogoutHandler;
|
|||
import org.springframework.security.web.csrf.CsrfTokenRepository;
|
||||
import org.springframework.security.web.csrf.CsrfTokenRequestHandler;
|
||||
import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
|
||||
import org.springframework.security.web.csrf.LazyCsrfTokenRepository;
|
||||
import org.springframework.security.web.csrf.MissingCsrfTokenException;
|
||||
import org.springframework.security.web.session.InvalidSessionAccessDeniedHandler;
|
||||
import org.springframework.security.web.session.InvalidSessionStrategy;
|
||||
|
@ -82,7 +83,7 @@ import org.springframework.util.Assert;
|
|||
public final class CsrfConfigurer<H extends HttpSecurityBuilder<H>>
|
||||
extends AbstractHttpConfigurer<CsrfConfigurer<H>, H> {
|
||||
|
||||
private CsrfTokenRepository csrfTokenRepository = new HttpSessionCsrfTokenRepository();
|
||||
private CsrfTokenRepository csrfTokenRepository = new LazyCsrfTokenRepository(new HttpSessionCsrfTokenRepository());
|
||||
|
||||
private RequestMatcher requireCsrfProtectionMatcher = CsrfFilter.DEFAULT_CSRF_MATCHER;
|
||||
|
||||
|
@ -104,7 +105,7 @@ public final class CsrfConfigurer<H extends HttpSecurityBuilder<H>>
|
|||
|
||||
/**
|
||||
* Specify the {@link CsrfTokenRepository} to use. The default is an
|
||||
* {@link HttpSessionCsrfTokenRepository}.
|
||||
* {@link HttpSessionCsrfTokenRepository} wrapped by {@link LazyCsrfTokenRepository}.
|
||||
* @param csrfTokenRepository the {@link CsrfTokenRepository} to use
|
||||
* @return the {@link CsrfConfigurer} for further customizations
|
||||
*/
|
||||
|
|
|
@ -1,103 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.annotation.web.configurers;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
final class DispatcherServletDelegatingRequestMatcherBuilder implements RequestMatcherBuilder {
|
||||
|
||||
final MvcRequestMatcherBuilder mvc;
|
||||
|
||||
final AntPathRequestMatcherBuilder ant;
|
||||
|
||||
final ServletRegistrationCollection registrations;
|
||||
|
||||
DispatcherServletDelegatingRequestMatcherBuilder(MvcRequestMatcherBuilder mvc, AntPathRequestMatcherBuilder ant,
|
||||
ServletRegistrationCollection registrations) {
|
||||
this.mvc = mvc;
|
||||
this.ant = ant;
|
||||
this.registrations = registrations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestMatcher matcher(String pattern) {
|
||||
MvcRequestMatcher mvc = this.mvc.matcher(pattern);
|
||||
AntPathRequestMatcher ant = this.ant.matcher(pattern);
|
||||
return new DispatcherServletDelegatingRequestMatcher(mvc, ant, this.registrations);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestMatcher matcher(HttpMethod method, String pattern) {
|
||||
MvcRequestMatcher mvc = this.mvc.matcher(method, pattern);
|
||||
AntPathRequestMatcher ant = this.ant.matcher(method, pattern);
|
||||
return new DispatcherServletDelegatingRequestMatcher(mvc, ant, this.registrations);
|
||||
}
|
||||
|
||||
static final class DispatcherServletDelegatingRequestMatcher implements RequestMatcher {
|
||||
|
||||
private final MvcRequestMatcher mvc;
|
||||
|
||||
private final AntPathRequestMatcher ant;
|
||||
|
||||
private final ServletRegistrationCollection registrations;
|
||||
|
||||
private DispatcherServletDelegatingRequestMatcher(MvcRequestMatcher mvc, AntPathRequestMatcher ant,
|
||||
ServletRegistrationCollection registrations) {
|
||||
this.mvc = mvc;
|
||||
this.ant = ant;
|
||||
this.registrations = registrations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(HttpServletRequest request) {
|
||||
String name = request.getHttpServletMapping().getServletName();
|
||||
ServletRegistrationCollection.Registration registration = this.registrations.registrationByName(name);
|
||||
Assert.notNull(registration,
|
||||
String.format("Could not find %s in servlet configuration %s", name, this.registrations));
|
||||
if (registration.isDispatcherServlet()) {
|
||||
return this.mvc.matches(request);
|
||||
}
|
||||
return this.ant.matches(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MatchResult matcher(HttpServletRequest request) {
|
||||
String name = request.getHttpServletMapping().getServletName();
|
||||
ServletRegistrationCollection.Registration registration = this.registrations.registrationByName(name);
|
||||
Assert.notNull(registration,
|
||||
String.format("Could not find %s in servlet configuration %s", name, this.registrations));
|
||||
if (registration.isDispatcherServlet()) {
|
||||
return this.mvc.matcher(request);
|
||||
}
|
||||
return this.ant.matcher(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("DispatcherServlet [mvc=[%s], ant=[%s], servlet=[%s]]", this.mvc, this.ant,
|
||||
this.registrations);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -24,7 +24,6 @@ import java.util.Map;
|
|||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.configuration.ObjectPostProcessorConfiguration;
|
||||
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
|
@ -136,13 +135,7 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
|
|||
* X-Content-Type-Options: nosniff
|
||||
* </pre>
|
||||
* @return the {@link ContentTypeOptionsConfig} for additional customizations
|
||||
* @deprecated For removal in 7.0. Use {@link #contentTypeOptions(Customizer)} or
|
||||
* {@code contentTypeOptions(Customizer.withDefaults())} to stick with defaults. See
|
||||
* the <a href=
|
||||
* "https://docs.spring.io/spring-security/reference/migration-7/configuration.html#_use_the_lambda_dsl">documentation</a>
|
||||
* for more details.
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public ContentTypeOptionsConfig contentTypeOptions() {
|
||||
return this.contentTypeOptions.enable();
|
||||
}
|
||||
|
@ -173,13 +166,7 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
|
|||
* >X-XSS-Protection header</a>
|
||||
* </p>
|
||||
* @return the {@link XXssConfig} for additional customizations
|
||||
* @deprecated For removal in 7.0. Use {@link #xssProtection(Customizer)} or
|
||||
* {@code xssProtection(Customizer.withDefaults())} to stick with defaults. See the
|
||||
* <a href=
|
||||
* "https://docs.spring.io/spring-security/reference/migration-7/configuration.html#_use_the_lambda_dsl">documentation</a>
|
||||
* for more details.
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public XXssConfig xssProtection() {
|
||||
return this.xssProtection.enable();
|
||||
}
|
||||
|
@ -210,13 +197,7 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
|
|||
* <li>Expires: 0</li>
|
||||
* </ul>
|
||||
* @return the {@link CacheControlConfig} for additional customizations
|
||||
* @deprecated For removal in 7.0. Use {@link #cacheControl(Customizer)} or
|
||||
* {@code cacheControl(Customizer.withDefaults())} to stick with defaults. See the
|
||||
* <a href=
|
||||
* "https://docs.spring.io/spring-security/reference/migration-7/configuration.html#_use_the_lambda_dsl">documentation</a>
|
||||
* for more details.
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public CacheControlConfig cacheControl() {
|
||||
return this.cacheControl.enable();
|
||||
}
|
||||
|
@ -243,10 +224,7 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
|
|||
* <a href="https://tools.ietf.org/html/rfc6797">HTTP Strict Transport Security
|
||||
* (HSTS)</a>.
|
||||
* @return the {@link HstsConfig} for additional customizations
|
||||
* @deprecated For removal in 7.0. Use
|
||||
* {@link #httpStrictTransportSecurity(Customizer)} instead
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public HstsConfig httpStrictTransportSecurity() {
|
||||
return this.hsts.enable();
|
||||
}
|
||||
|
@ -267,13 +245,7 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
|
|||
/**
|
||||
* Allows customizing the {@link XFrameOptionsHeaderWriter}.
|
||||
* @return the {@link FrameOptionsConfig} for additional customizations
|
||||
* @deprecated For removal in 7.0. Use {@link #frameOptions(Customizer)} or
|
||||
* {@code frameOptions(Customizer.withDefaults())} to stick with defaults. See the
|
||||
* <a href=
|
||||
* "https://docs.spring.io/spring-security/reference/migration-7/configuration.html#_use_the_lambda_dsl">documentation</a>
|
||||
* for more details.
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public FrameOptionsConfig frameOptions() {
|
||||
return this.frameOptions.enable();
|
||||
}
|
||||
|
@ -343,11 +315,8 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
|
|||
* @return the {@link ContentSecurityPolicyConfig} for additional configuration
|
||||
* @throws IllegalArgumentException if policyDirectives is null or empty
|
||||
* @since 4.1
|
||||
* @deprecated For removal in 7.0. Use {@link #contentSecurityPolicy(Customizer)}
|
||||
* instead
|
||||
* @see ContentSecurityPolicyHeaderWriter
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public ContentSecurityPolicyConfig contentSecurityPolicy(String policyDirectives) {
|
||||
this.contentSecurityPolicy.writer = new ContentSecurityPolicyHeaderWriter(policyDirectives);
|
||||
return this.contentSecurityPolicy;
|
||||
|
@ -477,14 +446,8 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
|
|||
* </pre>
|
||||
* @return the {@link ReferrerPolicyConfig} for additional configuration
|
||||
* @since 4.2
|
||||
* @deprecated For removal in 7.0. Use {@link #referrerPolicy(Customizer)} or
|
||||
* {@code referrerPolicy(Customizer.withDefaults())} to stick with defaults. See the
|
||||
* <a href=
|
||||
* "https://docs.spring.io/spring-security/reference/migration-7/configuration.html#_use_the_lambda_dsl">documentation</a>
|
||||
* for more details.
|
||||
* @see ReferrerPolicyHeaderWriter
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public ReferrerPolicyConfig referrerPolicy() {
|
||||
this.referrerPolicy.writer = new ReferrerPolicyHeaderWriter();
|
||||
return this.referrerPolicy;
|
||||
|
@ -506,14 +469,8 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
|
|||
* @return the {@link ReferrerPolicyConfig} for additional configuration
|
||||
* @throws IllegalArgumentException if policy is null or empty
|
||||
* @since 4.2
|
||||
* @deprecated For removal in 7.0. Use {@link #referrerPolicy(Customizer)} or
|
||||
* {@code referrerPolicy(Customizer.withDefaults())} to stick with defaults. See the
|
||||
* <a href=
|
||||
* "https://docs.spring.io/spring-security/reference/migration-7/configuration.html#_use_the_lambda_dsl">documentation</a>
|
||||
* for more details.
|
||||
* @see ReferrerPolicyHeaderWriter
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public ReferrerPolicyConfig referrerPolicy(ReferrerPolicy policy) {
|
||||
this.referrerPolicy.writer = new ReferrerPolicyHeaderWriter(policy);
|
||||
return this.referrerPolicy;
|
||||
|
@ -555,12 +512,8 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
|
|||
* @return the {@link FeaturePolicyConfig} for additional configuration
|
||||
* @throws IllegalArgumentException if policyDirectives is {@code null} or empty
|
||||
* @since 5.1
|
||||
* @deprecated For removal in 7.0. Use {@link #permissionsPolicy(Customizer)} or
|
||||
* {@code permissionsPolicy(Customizer.withDefaults())} to stick with defaults. See
|
||||
* the <a href=
|
||||
* "https://docs.spring.io/spring-security/reference/migration-7/configuration.html#_use_the_lambda_dsl">documentation</a>
|
||||
* for more details.
|
||||
* @see ObjectPostProcessorConfiguration FeaturePolicyHeaderWriter
|
||||
* @deprecated Use {@link #permissionsPolicy(Customizer)} instead.
|
||||
* @seeObjectPostProcessorConfiguration FeaturePolicyHeaderWriter
|
||||
*/
|
||||
@Deprecated
|
||||
public FeaturePolicyConfig featurePolicy(String policyDirectives) {
|
||||
|
@ -584,14 +537,8 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
|
|||
* </ul>
|
||||
* @return the {@link PermissionsPolicyConfig} for additional configuration
|
||||
* @since 5.5
|
||||
* @deprecated For removal in 7.0. Use {@link #permissionsPolicy(Customizer)} or
|
||||
* {@code permissionsPolicy(Customizer.withDefaults())} to stick with defaults. See
|
||||
* the <a href=
|
||||
* "https://docs.spring.io/spring-security/reference/migration-7/configuration.html#_use_the_lambda_dsl">documentation</a>
|
||||
* for more details.
|
||||
* @see PermissionsPolicyHeaderWriter
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public PermissionsPolicyConfig permissionsPolicy() {
|
||||
this.permissionsPolicy.writer = new PermissionsPolicyHeaderWriter();
|
||||
return this.permissionsPolicy;
|
||||
|
@ -628,11 +575,8 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
|
|||
* </p>
|
||||
* @return the {@link CrossOriginOpenerPolicyConfig} for additional confniguration
|
||||
* @since 5.7
|
||||
* @deprecated For removal in 7.0. Use {@link #crossOriginOpenerPolicy(Customizer)}
|
||||
* instead
|
||||
* @see CrossOriginOpenerPolicyHeaderWriter
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public CrossOriginOpenerPolicyConfig crossOriginOpenerPolicy() {
|
||||
this.crossOriginOpenerPolicy.writer = new CrossOriginOpenerPolicyHeaderWriter();
|
||||
return this.crossOriginOpenerPolicy;
|
||||
|
@ -672,11 +616,8 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
|
|||
* </p>
|
||||
* @return the {@link CrossOriginEmbedderPolicyConfig} for additional customizations
|
||||
* @since 5.7
|
||||
* @deprecated For removal in 7.0. Use {@link #crossOriginEmbedderPolicy(Customizer)}
|
||||
* instead
|
||||
* @see CrossOriginEmbedderPolicyHeaderWriter
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public CrossOriginEmbedderPolicyConfig crossOriginEmbedderPolicy() {
|
||||
this.crossOriginEmbedderPolicy.writer = new CrossOriginEmbedderPolicyHeaderWriter();
|
||||
return this.crossOriginEmbedderPolicy;
|
||||
|
@ -716,11 +657,8 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
|
|||
* </p>
|
||||
* @return the {@link HeadersConfigurer} for additional customizations
|
||||
* @since 5.7
|
||||
* @deprecated For removal in 7.0. Use {@link #crossOriginResourcePolicy(Customizer)}
|
||||
* instead
|
||||
* @see CrossOriginResourcePolicyHeaderWriter
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public CrossOriginResourcePolicyConfig crossOriginResourcePolicy() {
|
||||
this.crossOriginResourcePolicy.writer = new CrossOriginResourcePolicyHeaderWriter();
|
||||
return this.crossOriginResourcePolicy;
|
||||
|
@ -770,10 +708,7 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
|
|||
/**
|
||||
* Allows customizing the {@link HeadersConfigurer}
|
||||
* @return the {@link HeadersConfigurer} for additional customization
|
||||
* @deprecated For removal in 7.0. Use {@link #contentTypeOptions(Customizer)}
|
||||
* instead
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public HeadersConfigurer<H> and() {
|
||||
return HeadersConfigurer.this;
|
||||
}
|
||||
|
@ -846,13 +781,7 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
|
|||
* Allows completing configuration of X-XSS-Protection and continuing
|
||||
* configuration of headers.
|
||||
* @return the {@link HeadersConfigurer} for additional configuration
|
||||
* @deprecated For removal in 7.0. Use {@link #xssProtection(Customizer)} or
|
||||
* {@code xssProtection(Customizer.withDefaults())} to stick with defaults. See
|
||||
* the <a href=
|
||||
* "https://docs.spring.io/spring-security/reference/migration-7/configuration.html#_use_the_lambda_dsl">documentation</a>
|
||||
* for more details.
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public HeadersConfigurer<H> and() {
|
||||
return HeadersConfigurer.this;
|
||||
}
|
||||
|
@ -891,13 +820,7 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
|
|||
* Allows completing configuration of Cache Control and continuing configuration
|
||||
* of headers.
|
||||
* @return the {@link HeadersConfigurer} for additional configuration
|
||||
* @deprecated For removal in 7.0. Use {@link #cacheControl(Customizer)} or
|
||||
* {@code cacheControl(Customizer.withDefaults())} to stick with defaults. See the
|
||||
* <a href=
|
||||
* "https://docs.spring.io/spring-security/reference/migration-7/configuration.html#_use_the_lambda_dsl">documentation</a>
|
||||
* for more details.
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public HeadersConfigurer<H> and() {
|
||||
return HeadersConfigurer.this;
|
||||
}
|
||||
|
@ -1003,10 +926,7 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
|
|||
* Allows completing configuration of Strict Transport Security and continuing
|
||||
* configuration of headers.
|
||||
* @return the {@link HeadersConfigurer} for additional configuration
|
||||
* @deprecated For removal in 7.0. Use
|
||||
* {@link #httpStrictTransportSecurity(Customizer)} instead
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public HeadersConfigurer<H> and() {
|
||||
return HeadersConfigurer.this;
|
||||
}
|
||||
|
@ -1067,13 +987,7 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
|
|||
/**
|
||||
* Allows continuing customizing the headers configuration.
|
||||
* @return the {@link HeadersConfigurer} for additional configuration
|
||||
* @deprecated For removal in 7.0. Use {@link #frameOptions(Customizer)} or
|
||||
* {@code frameOptions(Customizer.withDefaults())} to stick with defaults. See the
|
||||
* <a href=
|
||||
* "https://docs.spring.io/spring-security/reference/migration-7/configuration.html#_use_the_lambda_dsl">documentation</a>
|
||||
* for more details.
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public HeadersConfigurer<H> and() {
|
||||
return HeadersConfigurer.this;
|
||||
}
|
||||
|
@ -1296,10 +1210,7 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
|
|||
* Allows completing configuration of Content Security Policy and continuing
|
||||
* configuration of headers.
|
||||
* @return the {@link HeadersConfigurer} for additional configuration
|
||||
* @deprecated For removal in 7.0. Use {@link #contentSecurityPolicy(Customizer)}
|
||||
* instead
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public HeadersConfigurer<H> and() {
|
||||
return HeadersConfigurer.this;
|
||||
}
|
||||
|
@ -1324,14 +1235,6 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated For removal in 7.0. Use {@link #referrerPolicy(Customizer)} or
|
||||
* {@code referrerPolicy(Customizer.withDefaults())} to stick with defaults. See
|
||||
* the <a href=
|
||||
* "https://docs.spring.io/spring-security/reference/migration-7/configuration.html#_use_the_lambda_dsl">documentation</a>
|
||||
* for more details.
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public HeadersConfigurer<H> and() {
|
||||
return HeadersConfigurer.this;
|
||||
}
|
||||
|
@ -1378,10 +1281,7 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
|
|||
* Allows completing configuration of Permissions Policy and continuing
|
||||
* configuration of headers.
|
||||
* @return the {@link HeadersConfigurer} for additional configuration
|
||||
* @deprecated For removal in 7.0. Use {@link #permissionsPolicy(Customizer)}
|
||||
* instead
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public HeadersConfigurer<H> and() {
|
||||
return HeadersConfigurer.this;
|
||||
}
|
||||
|
@ -1411,10 +1311,7 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
|
|||
* Allows completing configuration of Cross Origin Opener Policy and continuing
|
||||
* configuration of headers.
|
||||
* @return the {@link HeadersConfigurer} for additional configuration
|
||||
* @deprecated For removal in 7.0. Use
|
||||
* {@link #crossOriginOpenerPolicy(Customizer)} instead
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public HeadersConfigurer<H> and() {
|
||||
return HeadersConfigurer.this;
|
||||
}
|
||||
|
@ -1445,10 +1342,7 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
|
|||
* Allows completing configuration of Cross-Origin-Embedder-Policy and continuing
|
||||
* configuration of headers.
|
||||
* @return the {@link HeadersConfigurer} for additional configuration
|
||||
* @deprecated For removal in 7.0. Use
|
||||
* {@link #crossOriginEmbedderPolicy(Customizer)} instead
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public HeadersConfigurer<H> and() {
|
||||
return HeadersConfigurer.this;
|
||||
}
|
||||
|
@ -1479,10 +1373,7 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
|
|||
* Allows completing configuration of Cross-Origin-Resource-Policy and continuing
|
||||
* configuration of headers.
|
||||
* @return the {@link HeadersConfigurer} for additional configuration
|
||||
* @deprecated For removal in 7.0. Use
|
||||
* {@link #crossOriginResourcePolicy(Customizer)} instead
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public HeadersConfigurer<H> and() {
|
||||
return HeadersConfigurer.this;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -36,8 +36,6 @@ import org.springframework.security.web.authentication.WebAuthenticationDetailsS
|
|||
import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler;
|
||||
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
|
||||
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
|
||||
import org.springframework.security.web.context.RequestAttributeSecurityContextRepository;
|
||||
import org.springframework.security.web.context.SecurityContextRepository;
|
||||
import org.springframework.security.web.util.matcher.AndRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
|
||||
|
@ -77,7 +75,6 @@ import org.springframework.web.accept.HeaderContentNegotiationStrategy;
|
|||
* </ul>
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Evgeniy Cheban
|
||||
* @since 3.2
|
||||
*/
|
||||
public final class HttpBasicConfigurer<B extends HttpSecurityBuilder<B>>
|
||||
|
@ -94,8 +91,6 @@ public final class HttpBasicConfigurer<B extends HttpSecurityBuilder<B>>
|
|||
|
||||
private BasicAuthenticationEntryPoint basicAuthEntryPoint = new BasicAuthenticationEntryPoint();
|
||||
|
||||
private SecurityContextRepository securityContextRepository;
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
* @see HttpSecurity#httpBasic()
|
||||
|
@ -147,19 +142,6 @@ public final class HttpBasicConfigurer<B extends HttpSecurityBuilder<B>>
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies a custom {@link SecurityContextRepository} to use for basic
|
||||
* authentication. The default is {@link RequestAttributeSecurityContextRepository}.
|
||||
* @param securityContextRepository the custom {@link SecurityContextRepository} to
|
||||
* use
|
||||
* @return {@link HttpBasicConfigurer} for additional customization
|
||||
* @since 6.1
|
||||
*/
|
||||
public HttpBasicConfigurer<B> securityContextRepository(SecurityContextRepository securityContextRepository) {
|
||||
this.securityContextRepository = securityContextRepository;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(B http) {
|
||||
registerDefaults(http);
|
||||
|
@ -213,9 +195,6 @@ public final class HttpBasicConfigurer<B extends HttpSecurityBuilder<B>>
|
|||
if (this.authenticationDetailsSource != null) {
|
||||
basicAuthenticationFilter.setAuthenticationDetailsSource(this.authenticationDetailsSource);
|
||||
}
|
||||
if (this.securityContextRepository != null) {
|
||||
basicAuthenticationFilter.setSecurityContextRepository(this.securityContextRepository);
|
||||
}
|
||||
RememberMeServices rememberMeServices = http.getSharedObject(RememberMeServices.class);
|
||||
if (rememberMeServices != null) {
|
||||
basicAuthenticationFilter.setRememberMeServices(rememberMeServices);
|
||||
|
|
|
@ -1,76 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.annotation.web.configurers;
|
||||
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.config.annotation.ObjectPostProcessor;
|
||||
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
|
||||
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
|
||||
|
||||
final class MvcRequestMatcherBuilder implements RequestMatcherBuilder {
|
||||
|
||||
private static final String HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME = "mvcHandlerMappingIntrospector";
|
||||
|
||||
private final HandlerMappingIntrospector introspector;
|
||||
|
||||
private final ObjectPostProcessor<Object> objectPostProcessor;
|
||||
|
||||
private final String servletPath;
|
||||
|
||||
private MvcRequestMatcherBuilder(ApplicationContext context, String servletPath) {
|
||||
if (!context.containsBean(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME)) {
|
||||
throw new NoSuchBeanDefinitionException("A Bean named " + HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME
|
||||
+ " of type " + HandlerMappingIntrospector.class.getName()
|
||||
+ " is required to use MvcRequestMatcher. Please ensure Spring Security & Spring MVC are configured in a shared ApplicationContext.");
|
||||
}
|
||||
this.introspector = context.getBean(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME, HandlerMappingIntrospector.class);
|
||||
this.objectPostProcessor = context.getBean(ObjectPostProcessor.class);
|
||||
this.servletPath = servletPath;
|
||||
}
|
||||
|
||||
static MvcRequestMatcherBuilder absolute(ApplicationContext context) {
|
||||
return new MvcRequestMatcherBuilder(context, null);
|
||||
}
|
||||
|
||||
static MvcRequestMatcherBuilder relativeTo(ApplicationContext context, String path) {
|
||||
return new MvcRequestMatcherBuilder(context, path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MvcRequestMatcher matcher(String pattern) {
|
||||
MvcRequestMatcher matcher = new MvcRequestMatcher(this.introspector, pattern);
|
||||
this.objectPostProcessor.postProcess(matcher);
|
||||
if (this.servletPath != null) {
|
||||
matcher.setServletPath(this.servletPath);
|
||||
}
|
||||
return matcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MvcRequestMatcher matcher(HttpMethod method, String pattern) {
|
||||
MvcRequestMatcher matcher = new MvcRequestMatcher(this.introspector, pattern);
|
||||
this.objectPostProcessor.postProcess(matcher);
|
||||
matcher.setMethod(method);
|
||||
if (this.servletPath != null) {
|
||||
matcher.setServletPath(this.servletPath);
|
||||
}
|
||||
return matcher;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,106 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.annotation.web.configurers;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
|
||||
/**
|
||||
* An interface that abstracts how matchers are created
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 6.2
|
||||
*/
|
||||
interface RequestMatcherBuilder {
|
||||
|
||||
/**
|
||||
* Create a request matcher for the given pattern.
|
||||
*
|
||||
* <p>
|
||||
* For example, you might do something like the following: <code>
|
||||
* builder.matcher("/controller/**")
|
||||
* </code>
|
||||
* @param pattern the pattern to use, typically an Ant path
|
||||
* @return a {@link RequestMatcher} that matches on the given {@code pattern}
|
||||
*/
|
||||
RequestMatcher matcher(String pattern);
|
||||
|
||||
/**
|
||||
* Create a request matcher for the given pattern.
|
||||
*
|
||||
* <p>
|
||||
* For example, you might do something like the following: <code>
|
||||
* builder.matcher(HttpMethod.GET, "/controller/**")
|
||||
* </code>
|
||||
* @param method the HTTP method to use
|
||||
* @param pattern the pattern to use, typically an Ant path
|
||||
* @return a {@link RequestMatcher} that matches on the given HTTP {@code method} and
|
||||
* {@code pattern}
|
||||
*/
|
||||
RequestMatcher matcher(HttpMethod method, String pattern);
|
||||
|
||||
/**
|
||||
* Create a request matcher that matches any request
|
||||
* @return a {@link RequestMatcher} that matches any request
|
||||
*/
|
||||
default RequestMatcher any() {
|
||||
return AnyRequestMatcher.INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an array request matchers, one for each of the given patterns.
|
||||
*
|
||||
* <p>
|
||||
* For example, you might do something like the following: <code>
|
||||
* builder.matcher("/controller-one/**", "/controller-two/**")
|
||||
* </code>
|
||||
* @param patterns the patterns to use, typically Ant paths
|
||||
* @return a list of {@link RequestMatcher} that match on the given {@code pattern}
|
||||
*/
|
||||
default List<RequestMatcher> matchers(String... patterns) {
|
||||
List<RequestMatcher> matchers = new ArrayList<>();
|
||||
for (String pattern : patterns) {
|
||||
matchers.add(matcher(pattern));
|
||||
}
|
||||
return matchers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an array request matchers, one for each of the given patterns.
|
||||
*
|
||||
* <p>
|
||||
* For example, you might do something like the following: <code>
|
||||
* builder.matcher(HttpMethod.POST, "/controller-one/**", "/controller-two/**")
|
||||
* </code>
|
||||
* @param method the HTTP method to use
|
||||
* @param patterns the patterns to use, typically Ant paths
|
||||
* @return a list of {@link RequestMatcher} that match on the given HTTP
|
||||
* {@code method} and {@code pattern}
|
||||
*/
|
||||
default List<RequestMatcher> matchers(HttpMethod method, String... patterns) {
|
||||
List<RequestMatcher> matchers = new ArrayList<>();
|
||||
for (String pattern : patterns) {
|
||||
matchers.add(matcher(method, pattern));
|
||||
}
|
||||
return matchers;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,215 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.annotation.web.configurers;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.web.servlet.DispatcherServlet;
|
||||
|
||||
/**
|
||||
* A factory for constructing {@link RequestMatcherBuilder} instances
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 6.2
|
||||
*/
|
||||
final class RequestMatcherBuilders {
|
||||
|
||||
private static final String HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME = "mvcHandlerMappingIntrospector";
|
||||
|
||||
private static final String HANDLER_MAPPING_INTROSPECTOR = "org.springframework.web.servlet.handler.HandlerMappingIntrospector";
|
||||
|
||||
private static final boolean mvcPresent;
|
||||
|
||||
static {
|
||||
mvcPresent = ClassUtils.isPresent(HANDLER_MAPPING_INTROSPECTOR, RequestMatcherBuilders.class.getClassLoader());
|
||||
}
|
||||
|
||||
private static final Log logger = LogFactory.getLog(RequestMatcherBuilders.class);
|
||||
|
||||
private RequestMatcherBuilders() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the default {@link RequestMatcherBuilder} for use by Spring Security DSLs.
|
||||
*
|
||||
* <p>
|
||||
* If Spring MVC is not present on the classpath or if there is no
|
||||
* {@link DispatcherServlet}, this method will return an Ant-based builder.
|
||||
*
|
||||
* <p>
|
||||
* If the servlet configuration has only {@link DispatcherServlet} with a single
|
||||
* mapping (for example `/` or `/path/*`), then this method will return an MVC-based
|
||||
* builder.
|
||||
*
|
||||
* <p>
|
||||
* If the servlet configuration maps {@link DispatcherServlet} to a path and also has
|
||||
* other servlets, this will throw an exception. In that case, an application should
|
||||
* instead use the {@link RequestMatcherBuilders#createForServletPattern} ideally with
|
||||
* the associated
|
||||
* {@link org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer}
|
||||
* to create builders by servlet path.
|
||||
*
|
||||
* <p>
|
||||
* Otherwise, (namely if {@link DispatcherServlet} is root), this method will return a
|
||||
* builder that delegates to an Ant or Mvc builder at runtime.
|
||||
* @param context the application context
|
||||
* @return the appropriate {@link RequestMatcherBuilder} based on application
|
||||
* configuration
|
||||
*/
|
||||
static RequestMatcherBuilder createDefault(ApplicationContext context) {
|
||||
if (!mvcPresent) {
|
||||
logger.trace("Defaulting to Ant matching since Spring MVC is not on the classpath");
|
||||
return AntPathRequestMatcherBuilder.absolute();
|
||||
}
|
||||
if (!context.containsBean(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME)) {
|
||||
logger.trace("Defaulting to Ant matching since Spring MVC is not fully configured");
|
||||
return AntPathRequestMatcherBuilder.absolute();
|
||||
}
|
||||
ServletRegistrationCollection registrations = ServletRegistrationCollection.registrations(context);
|
||||
if (registrations.isEmpty()) {
|
||||
logger.trace("Defaulting to MVC matching since Spring MVC is on the class path and no servlet "
|
||||
+ "information is available");
|
||||
return AntPathRequestMatcherBuilder.absolute();
|
||||
}
|
||||
ServletRegistrationCollection dispatcherServlets = registrations.dispatcherServlets();
|
||||
if (dispatcherServlets.isEmpty()) {
|
||||
logger.trace("Defaulting to Ant matching since there is no DispatcherServlet configured");
|
||||
return AntPathRequestMatcherBuilder.absolute();
|
||||
}
|
||||
ServletRegistrationCollection.ServletPath servletPath = registrations.deduceOneServletPath();
|
||||
if (servletPath != null) {
|
||||
String message = "Defaulting to MVC matching since DispatcherServlet [%s] is the only servlet mapping";
|
||||
logger.trace(String.format(message, servletPath.path()));
|
||||
return MvcRequestMatcherBuilder.relativeTo(context, servletPath.path());
|
||||
}
|
||||
servletPath = dispatcherServlets.deduceOneServletPath();
|
||||
if (servletPath == null) {
|
||||
logger.trace("Did not choose a default since there is more than one DispatcherServlet mapping");
|
||||
String message = String.format("""
|
||||
This method cannot decide whether these patterns are Spring MVC patterns or not
|
||||
since your servlet configuration has multiple Spring MVC servlet mappings.
|
||||
|
||||
For your reference, here is your servlet configuration: %s
|
||||
|
||||
To address this, you need to specify the servlet path for each endpoint.
|
||||
You can use .forServletPattern in conjunction with requestMatchers do to this
|
||||
like so:
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain appSecurity(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeHttpRequests((authorize) -> authorize
|
||||
.forServletPattern("/mvc-one/*", (one) -> one
|
||||
.requestMatchers("/controller/**", "/endpoints/**"
|
||||
)...
|
||||
.forServletPattern("/mvc-two/*", (two) -> two
|
||||
.requestMatchers("/other/**", "/controllers/**")...
|
||||
)
|
||||
.forServletPattern("/h2-console/*", (h2) -> h2
|
||||
.requestMatchers("/**")...
|
||||
)
|
||||
)
|
||||
// ...
|
||||
return http.build();
|
||||
}
|
||||
""", registrations);
|
||||
return new ErrorRequestMatcherBuilder(message);
|
||||
}
|
||||
if (servletPath.path() != null) {
|
||||
logger.trace("Did not choose a default since there is a non-root DispatcherServlet mapping");
|
||||
String message = String.format("""
|
||||
This method cannot decide whether these patterns are Spring MVC patterns or not
|
||||
since your Spring MVC mapping is mapped to a path and you have other servlet mappings.
|
||||
|
||||
For your reference, here is your servlet configuration: %s
|
||||
|
||||
To address this, you need to specify the servlet path for each endpoint.
|
||||
You can use .forServletPattern in conjunction with requestMatchers do to this
|
||||
like so:
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain appSecurity(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeHttpRequests((authorize) -> authorize
|
||||
.forServletPattern("/mvc/*", (mvc) -> mvc
|
||||
.requestMatchers("/controller/**", "/endpoints/**")...
|
||||
)
|
||||
.forServletPattern("/h2-console/*", (h2) -> h2
|
||||
.requestMatchers("/**")...
|
||||
)
|
||||
)
|
||||
// ...
|
||||
return http.build();
|
||||
}
|
||||
""", registrations);
|
||||
return new ErrorRequestMatcherBuilder(message);
|
||||
}
|
||||
logger.trace("Defaulting to request-time checker since DispatcherServlet is mapped to root, but there are also "
|
||||
+ "other servlet mappings");
|
||||
return new DispatcherServletDelegatingRequestMatcherBuilder(MvcRequestMatcherBuilder.absolute(context),
|
||||
AntPathRequestMatcherBuilder.absolute(), registrations);
|
||||
}
|
||||
|
||||
static RequestMatcherBuilder createForServletPattern(ApplicationContext context, String pattern) {
|
||||
Assert.notNull(pattern, "pattern cannot be null");
|
||||
ServletRegistrationCollection registrations = ServletRegistrationCollection.registrations(context);
|
||||
ServletRegistrationCollection.Registration registration = registrations.registrationByMapping(pattern);
|
||||
Assert.notNull(registration, () -> String
|
||||
.format("The given pattern %s doesn't seem to match any configured servlets: %s", pattern, registrations));
|
||||
boolean isPathPattern = pattern.startsWith("/") && pattern.endsWith("/*");
|
||||
if (isPathPattern) {
|
||||
String path = pattern.substring(0, pattern.length() - 2);
|
||||
return (registration.isDispatcherServlet()) ? MvcRequestMatcherBuilder.relativeTo(context, path)
|
||||
: AntPathRequestMatcherBuilder.relativeTo(path);
|
||||
}
|
||||
return (registration.isDispatcherServlet()) ? MvcRequestMatcherBuilder.absolute(context)
|
||||
: AntPathRequestMatcherBuilder.absolute();
|
||||
}
|
||||
|
||||
private static class ErrorRequestMatcherBuilder implements RequestMatcherBuilder {
|
||||
|
||||
private final String errorMessage;
|
||||
|
||||
ErrorRequestMatcherBuilder(String errorMessage) {
|
||||
this.errorMessage = errorMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestMatcher matcher(String pattern) {
|
||||
throw new IllegalArgumentException(this.errorMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestMatcher matcher(HttpMethod method, String pattern) {
|
||||
throw new IllegalArgumentException(this.errorMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestMatcher any() {
|
||||
throw new IllegalArgumentException(this.errorMessage);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.annotation.web.configurers;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
final class ServletPatternRequestMatcher implements RequestMatcher {
|
||||
|
||||
final String pattern;
|
||||
|
||||
ServletPatternRequestMatcher(String pattern) {
|
||||
Assert.notNull(pattern, "pattern cannot be null");
|
||||
this.pattern = pattern;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(HttpServletRequest request) {
|
||||
return this.pattern.equals(request.getHttpServletMapping().getPattern());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("ServletPattern [pattern='%s']", this.pattern);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,152 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.annotation.web.configurers;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import jakarta.servlet.ServletContext;
|
||||
import jakarta.servlet.ServletRegistration;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
final class ServletRegistrationCollection {
|
||||
|
||||
private List<Registration> registrations;
|
||||
|
||||
private ServletRegistrationCollection() {
|
||||
this.registrations = Collections.emptyList();
|
||||
}
|
||||
|
||||
private ServletRegistrationCollection(List<Registration> registrations) {
|
||||
this.registrations = registrations;
|
||||
}
|
||||
|
||||
static ServletRegistrationCollection registrations(ApplicationContext context) {
|
||||
if (!(context instanceof WebApplicationContext web)) {
|
||||
return new ServletRegistrationCollection();
|
||||
}
|
||||
ServletContext servletContext = web.getServletContext();
|
||||
if (servletContext == null) {
|
||||
return new ServletRegistrationCollection();
|
||||
}
|
||||
Map<String, ? extends ServletRegistration> registrations = servletContext.getServletRegistrations();
|
||||
if (registrations == null) {
|
||||
return new ServletRegistrationCollection();
|
||||
}
|
||||
List<Registration> filtered = new ArrayList<>();
|
||||
for (ServletRegistration registration : registrations.values()) {
|
||||
Collection<String> mappings = registration.getMappings();
|
||||
if (!CollectionUtils.isEmpty(mappings)) {
|
||||
filtered.add(new Registration(registration));
|
||||
}
|
||||
}
|
||||
return new ServletRegistrationCollection(filtered);
|
||||
}
|
||||
|
||||
boolean isEmpty() {
|
||||
return this.registrations.isEmpty();
|
||||
}
|
||||
|
||||
Registration registrationByName(String name) {
|
||||
for (Registration registration : this.registrations) {
|
||||
if (registration.registration().getName().equals(name)) {
|
||||
return registration;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Registration registrationByMapping(String target) {
|
||||
for (Registration registration : this.registrations) {
|
||||
for (String mapping : registration.registration().getMappings()) {
|
||||
if (target.equals(mapping)) {
|
||||
return registration;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
ServletRegistrationCollection dispatcherServlets() {
|
||||
List<Registration> dispatcherServlets = new ArrayList<>();
|
||||
for (Registration registration : this.registrations) {
|
||||
if (registration.isDispatcherServlet()) {
|
||||
dispatcherServlets.add(registration);
|
||||
}
|
||||
}
|
||||
return new ServletRegistrationCollection(dispatcherServlets);
|
||||
}
|
||||
|
||||
ServletPath deduceOneServletPath() {
|
||||
if (this.registrations.size() > 1) {
|
||||
return null;
|
||||
}
|
||||
ServletRegistration registration = this.registrations.iterator().next().registration();
|
||||
if (registration.getMappings().size() > 1) {
|
||||
return null;
|
||||
}
|
||||
String mapping = registration.getMappings().iterator().next();
|
||||
if ("/".equals(mapping)) {
|
||||
return new ServletPath();
|
||||
}
|
||||
if (mapping.endsWith("/*")) {
|
||||
return new ServletPath(mapping.substring(0, mapping.length() - 2));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
Map<String, Collection<String>> mappings = new LinkedHashMap<>();
|
||||
for (Registration registration : this.registrations) {
|
||||
mappings.put(registration.registration().getClassName(), registration.registration().getMappings());
|
||||
}
|
||||
return mappings.toString();
|
||||
}
|
||||
|
||||
record Registration(ServletRegistration registration) {
|
||||
boolean isDispatcherServlet() {
|
||||
Class<?> dispatcherServlet = ClassUtils
|
||||
.resolveClassName("org.springframework.web.servlet.DispatcherServlet", null);
|
||||
try {
|
||||
Class<?> clazz = Class.forName(this.registration.getClassName());
|
||||
if (dispatcherServlet.isAssignableFrom(clazz)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
record ServletPath(String path) {
|
||||
ServletPath() {
|
||||
this(null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -296,7 +296,7 @@ public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
|
|||
* @param sessionAuthenticationStrategy
|
||||
* @return the {@link SessionManagementConfigurer} for further customizations
|
||||
*/
|
||||
public SessionManagementConfigurer<H> addSessionAuthenticationStrategy(
|
||||
SessionManagementConfigurer<H> addSessionAuthenticationStrategy(
|
||||
SessionAuthenticationStrategy sessionAuthenticationStrategy) {
|
||||
this.sessionAuthenticationStrategies.add(sessionAuthenticationStrategy);
|
||||
return this;
|
||||
|
@ -769,10 +769,7 @@ public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
|
|||
/**
|
||||
* Used to chain back to the {@link SessionManagementConfigurer}
|
||||
* @return the {@link SessionManagementConfigurer} for further customizations
|
||||
* @deprecated For removal in 7.0. Use {@link #sessionConcurrency(Customizer)}
|
||||
* instead
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public SessionManagementConfigurer<H> and() {
|
||||
return SessionManagementConfigurer.this;
|
||||
}
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.annotation.web.configurers.oauth2.client;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.security.oauth2.jwt.JwtTimestampValidator;
|
||||
|
||||
final class DefaultOidcLogoutTokenValidatorFactory implements Function<ClientRegistration, OAuth2TokenValidator<Jwt>> {
|
||||
|
||||
@Override
|
||||
public OAuth2TokenValidator<Jwt> apply(ClientRegistration clientRegistration) {
|
||||
return new DelegatingOAuth2TokenValidator<>(new JwtTimestampValidator(),
|
||||
new OidcBackChannelLogoutTokenValidator(clientRegistration));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -16,8 +16,6 @@
|
|||
|
||||
package org.springframework.security.config.annotation.web.configurers.oauth2.client;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
||||
|
@ -138,10 +136,7 @@ public final class OAuth2ClientConfigurer<B extends HttpSecurityBuilder<B>>
|
|||
* Returns the {@link AuthorizationCodeGrantConfigurer} for configuring the OAuth 2.0
|
||||
* Authorization Code Grant.
|
||||
* @return the {@link AuthorizationCodeGrantConfigurer}
|
||||
* @deprecated For removal in 7.0. Use {@link #authorizationCodeGrant(Customizer)}
|
||||
* instead
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public AuthorizationCodeGrantConfigurer authorizationCodeGrant() {
|
||||
return this.authorizationCodeGrantConfigurer;
|
||||
}
|
||||
|
@ -238,10 +233,7 @@ public final class OAuth2ClientConfigurer<B extends HttpSecurityBuilder<B>>
|
|||
/**
|
||||
* Returns the {@link OAuth2ClientConfigurer} for further configuration.
|
||||
* @return the {@link OAuth2ClientConfigurer}
|
||||
* @deprecated For removal in 7.0. Use {@link #authorizationCodeGrant(Customizer)}
|
||||
* instead
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public OAuth2ClientConfigurer<B> and() {
|
||||
return OAuth2ClientConfigurer.this;
|
||||
}
|
||||
|
@ -309,22 +301,7 @@ public final class OAuth2ClientConfigurer<B extends HttpSecurityBuilder<B>>
|
|||
if (this.accessTokenResponseClient != null) {
|
||||
return this.accessTokenResponseClient;
|
||||
}
|
||||
ResolvableType resolvableType = ResolvableType.forClassWithGenerics(OAuth2AccessTokenResponseClient.class,
|
||||
OAuth2AuthorizationCodeGrantRequest.class);
|
||||
OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> bean = getBeanOrNull(resolvableType);
|
||||
return (bean != null) ? bean : new DefaultAuthorizationCodeTokenResponseClient();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T getBeanOrNull(ResolvableType type) {
|
||||
ApplicationContext context = getBuilder().getSharedObject(ApplicationContext.class);
|
||||
if (context != null) {
|
||||
String[] names = context.getBeanNamesForType(type);
|
||||
if (names.length == 1) {
|
||||
return (T) context.getBean(names[0]);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return new DefaultAuthorizationCodeTokenResponseClient();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -25,8 +25,6 @@ import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
|||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||
import org.springframework.security.oauth2.client.InMemoryOAuth2AuthorizedClientService;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
|
||||
import org.springframework.security.oauth2.client.oidc.session.InMemoryOidcSessionRegistry;
|
||||
import org.springframework.security.oauth2.client.oidc.session.OidcSessionRegistry;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.web.AuthenticatedPrincipalOAuth2AuthorizedClientRepository;
|
||||
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
|
||||
|
@ -114,13 +112,4 @@ final class OAuth2ClientConfigurerUtils {
|
|||
return (!authorizedClientServiceMap.isEmpty() ? authorizedClientServiceMap.values().iterator().next() : null);
|
||||
}
|
||||
|
||||
static <B extends HttpSecurityBuilder<B>> OidcSessionRegistry getOidcSessionRegistry(B builder) {
|
||||
OidcSessionRegistry sessionRegistry = builder.getSharedObject(OidcSessionRegistry.class);
|
||||
if (sessionRegistry == null) {
|
||||
sessionRegistry = new InMemoryOidcSessionRegistry();
|
||||
builder.setSharedObject(OidcSessionRegistry.class, sessionRegistry);
|
||||
}
|
||||
return sessionRegistry;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -22,18 +22,9 @@ import java.util.HashMap;
|
|||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactoryUtils;
|
||||
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.event.GenericApplicationListenerAdapter;
|
||||
import org.springframework.context.event.SmartApplicationListener;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.config.Customizer;
|
||||
|
@ -41,14 +32,9 @@ import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
|||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer;
|
||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||
import org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer;
|
||||
import org.springframework.security.context.DelegatingApplicationListener;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
|
||||
import org.springframework.security.core.session.AbstractSessionEvent;
|
||||
import org.springframework.security.core.session.SessionDestroyedEvent;
|
||||
import org.springframework.security.core.session.SessionIdChangedEvent;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
|
||||
import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationProvider;
|
||||
import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken;
|
||||
|
@ -56,9 +42,6 @@ import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationC
|
|||
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
|
||||
import org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizationCodeAuthenticationProvider;
|
||||
import org.springframework.security.oauth2.client.oidc.session.InMemoryOidcSessionRegistry;
|
||||
import org.springframework.security.oauth2.client.oidc.session.OidcSessionInformation;
|
||||
import org.springframework.security.oauth2.client.oidc.session.OidcSessionRegistry;
|
||||
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
|
||||
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
|
@ -84,10 +67,7 @@ import org.springframework.security.web.AuthenticationEntryPoint;
|
|||
import org.springframework.security.web.RedirectStrategy;
|
||||
import org.springframework.security.web.authentication.DelegatingAuthenticationEntryPoint;
|
||||
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
|
||||
import org.springframework.security.web.authentication.session.SessionAuthenticationException;
|
||||
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
|
||||
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
|
||||
import org.springframework.security.web.csrf.CsrfToken;
|
||||
import org.springframework.security.web.savedrequest.RequestCache;
|
||||
import org.springframework.security.web.util.matcher.AndRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
|
@ -144,7 +124,6 @@ import org.springframework.util.ReflectionUtils;
|
|||
* <li>{@link DefaultLoginPageGeneratingFilter} - if {@link #loginPage(String)} is not
|
||||
* configured and {@code DefaultLoginPageGeneratingFilter} is available, then a default
|
||||
* login page will be made available</li>
|
||||
* <li>{@link OidcSessionRegistry}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Joe Grandja
|
||||
|
@ -223,26 +202,11 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the registry for managing the OIDC client-provider session link
|
||||
* @param oidcSessionRegistry the {@link OidcSessionRegistry} to use
|
||||
* @return the {@link OAuth2LoginConfigurer} for further configuration
|
||||
* @since 6.2
|
||||
*/
|
||||
public OAuth2LoginConfigurer<B> oidcSessionRegistry(OidcSessionRegistry oidcSessionRegistry) {
|
||||
Assert.notNull(oidcSessionRegistry, "oidcSessionRegistry cannot be null");
|
||||
getBuilder().setSharedObject(OidcSessionRegistry.class, oidcSessionRegistry);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link AuthorizationEndpointConfig} for configuring the Authorization
|
||||
* Server's Authorization Endpoint.
|
||||
* @return the {@link AuthorizationEndpointConfig}
|
||||
* @deprecated For removal in 7.0. Use {@link #authorizationEndpoint(Customizer)}
|
||||
* instead
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public AuthorizationEndpointConfig authorizationEndpoint() {
|
||||
return this.authorizationEndpointConfig;
|
||||
}
|
||||
|
@ -263,13 +227,7 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
|||
* Returns the {@link TokenEndpointConfig} for configuring the Authorization Server's
|
||||
* Token Endpoint.
|
||||
* @return the {@link TokenEndpointConfig}
|
||||
* @deprecated For removal in 7.0. Use {@link #tokenEndpoint(Customizer)} or
|
||||
* {@code tokenEndpoint(Customizer.withDefaults())} to stick with defaults. See the
|
||||
* <a href=
|
||||
* "https://docs.spring.io/spring-security/reference/migration-7/configuration.html#_use_the_lambda_dsl">documentation</a>
|
||||
* for more details.
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public TokenEndpointConfig tokenEndpoint() {
|
||||
return this.tokenEndpointConfig;
|
||||
}
|
||||
|
@ -290,10 +248,7 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
|||
* Returns the {@link RedirectionEndpointConfig} for configuring the Client's
|
||||
* Redirection Endpoint.
|
||||
* @return the {@link RedirectionEndpointConfig}
|
||||
* @deprecated For removal in 7.0. Use {@link #redirectionEndpoint(Customizer)}
|
||||
* instead
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public RedirectionEndpointConfig redirectionEndpoint() {
|
||||
return this.redirectionEndpointConfig;
|
||||
}
|
||||
|
@ -314,13 +269,7 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
|||
* Returns the {@link UserInfoEndpointConfig} for configuring the Authorization
|
||||
* Server's UserInfo Endpoint.
|
||||
* @return the {@link UserInfoEndpointConfig}
|
||||
* @deprecated For removal in 7.0. Use {@link #userInfoEndpoint(Customizer)} or
|
||||
* {@code userInfoEndpoint(Customizer.withDefaults())} to stick with defaults. See the
|
||||
* <a href=
|
||||
* "https://docs.spring.io/spring-security/reference/migration-7/configuration.html#_use_the_lambda_dsl">documentation</a>
|
||||
* for more details.
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public UserInfoEndpointConfig userInfoEndpoint() {
|
||||
return this.userInfoEndpointConfig;
|
||||
}
|
||||
|
@ -363,7 +312,10 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
|||
super.init(http);
|
||||
}
|
||||
}
|
||||
OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient = getAccessTokenResponseClient();
|
||||
OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient = this.tokenEndpointConfig.accessTokenResponseClient;
|
||||
if (accessTokenResponseClient == null) {
|
||||
accessTokenResponseClient = new DefaultAuthorizationCodeTokenResponseClient();
|
||||
}
|
||||
OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService = getOAuth2UserService();
|
||||
OAuth2LoginAuthenticationProvider oauth2LoginAuthenticationProvider = new OAuth2LoginAuthenticationProvider(
|
||||
accessTokenResponseClient, oauth2UserService);
|
||||
|
@ -430,7 +382,6 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
|||
authenticationFilter
|
||||
.setAuthorizationRequestRepository(this.authorizationEndpointConfig.authorizationRequestRepository);
|
||||
}
|
||||
configureOidcSessionRegistry(http);
|
||||
super.configure(http);
|
||||
}
|
||||
|
||||
|
@ -473,16 +424,6 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
|||
return (!grantedAuthoritiesMapperMap.isEmpty() ? grantedAuthoritiesMapperMap.values().iterator().next() : null);
|
||||
}
|
||||
|
||||
private OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> getAccessTokenResponseClient() {
|
||||
if (this.tokenEndpointConfig.accessTokenResponseClient != null) {
|
||||
return this.tokenEndpointConfig.accessTokenResponseClient;
|
||||
}
|
||||
ResolvableType resolvableType = ResolvableType.forClassWithGenerics(OAuth2AccessTokenResponseClient.class,
|
||||
OAuth2AuthorizationCodeGrantRequest.class);
|
||||
OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> bean = getBeanOrNull(resolvableType);
|
||||
return (bean != null) ? bean : new DefaultAuthorizationCodeTokenResponseClient();
|
||||
}
|
||||
|
||||
private OAuth2UserService<OidcUserRequest, OidcUser> getOidcUserService() {
|
||||
if (this.userInfoEndpointConfig.oidcUserService != null) {
|
||||
return this.userInfoEndpointConfig.oidcUserService;
|
||||
|
@ -581,29 +522,6 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
|||
return AnyRequestMatcher.INSTANCE;
|
||||
}
|
||||
|
||||
private void configureOidcSessionRegistry(B http) {
|
||||
OidcSessionRegistry sessionRegistry = OAuth2ClientConfigurerUtils.getOidcSessionRegistry(http);
|
||||
SessionManagementConfigurer<B> sessionConfigurer = http.getConfigurer(SessionManagementConfigurer.class);
|
||||
if (sessionConfigurer != null) {
|
||||
OidcSessionRegistryAuthenticationStrategy sessionAuthenticationStrategy = new OidcSessionRegistryAuthenticationStrategy();
|
||||
sessionAuthenticationStrategy.setSessionRegistry(sessionRegistry);
|
||||
sessionConfigurer.addSessionAuthenticationStrategy(sessionAuthenticationStrategy);
|
||||
}
|
||||
OidcClientSessionEventListener listener = new OidcClientSessionEventListener();
|
||||
listener.setSessionRegistry(sessionRegistry);
|
||||
registerDelegateApplicationListener(listener);
|
||||
}
|
||||
|
||||
private void registerDelegateApplicationListener(ApplicationListener<?> delegate) {
|
||||
DelegatingApplicationListener delegating = getBeanOrNull(
|
||||
ResolvableType.forType(DelegatingApplicationListener.class));
|
||||
if (delegating == null) {
|
||||
return;
|
||||
}
|
||||
SmartApplicationListener smartListener = new GenericApplicationListenerAdapter(delegate);
|
||||
delegating.addListener(smartListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration options for the Authorization Server's Authorization Endpoint.
|
||||
*/
|
||||
|
@ -673,10 +591,7 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
|||
/**
|
||||
* Returns the {@link OAuth2LoginConfigurer} for further configuration.
|
||||
* @return the {@link OAuth2LoginConfigurer}
|
||||
* @deprecated For removal in 7.0. Use {@link #authorizationEndpoint(Customizer)}
|
||||
* instead
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public OAuth2LoginConfigurer<B> and() {
|
||||
return OAuth2LoginConfigurer.this;
|
||||
}
|
||||
|
@ -710,13 +625,7 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
|||
/**
|
||||
* Returns the {@link OAuth2LoginConfigurer} for further configuration.
|
||||
* @return the {@link OAuth2LoginConfigurer}
|
||||
* @deprecated For removal in 7.0. Use {@link #tokenEndpoint(Customizer)} or
|
||||
* {@code tokenEndpoint(Customizer.withDefaults())} to stick with defaults. See
|
||||
* the <a href=
|
||||
* "https://docs.spring.io/spring-security/reference/migration-7/configuration.html#_use_the_lambda_dsl">documentation</a>
|
||||
* for more details.
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public OAuth2LoginConfigurer<B> and() {
|
||||
return OAuth2LoginConfigurer.this;
|
||||
}
|
||||
|
@ -748,10 +657,7 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
|||
/**
|
||||
* Returns the {@link OAuth2LoginConfigurer} for further configuration.
|
||||
* @return the {@link OAuth2LoginConfigurer}
|
||||
* @deprecated For removal in 7.0. Use {@link #redirectionEndpoint(Customizer)}
|
||||
* instead
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public OAuth2LoginConfigurer<B> and() {
|
||||
return OAuth2LoginConfigurer.this;
|
||||
}
|
||||
|
@ -813,10 +719,7 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
|||
/**
|
||||
* Returns the {@link OAuth2LoginConfigurer} for further configuration.
|
||||
* @return the {@link OAuth2LoginConfigurer}
|
||||
* @deprecated For removal in 7.0. Use {@link #userInfoEndpoint(Customizer)}
|
||||
* instead
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public OAuth2LoginConfigurer<B> and() {
|
||||
return OAuth2LoginConfigurer.this;
|
||||
}
|
||||
|
@ -851,88 +754,4 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
|||
|
||||
}
|
||||
|
||||
private static final class OidcClientSessionEventListener implements ApplicationListener<AbstractSessionEvent> {
|
||||
|
||||
private final Log logger = LogFactory.getLog(OidcClientSessionEventListener.class);
|
||||
|
||||
private OidcSessionRegistry sessionRegistry = new InMemoryOidcSessionRegistry();
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onApplicationEvent(AbstractSessionEvent event) {
|
||||
if (event instanceof SessionDestroyedEvent destroyed) {
|
||||
this.logger.debug("Received SessionDestroyedEvent");
|
||||
this.sessionRegistry.removeSessionInformation(destroyed.getId());
|
||||
return;
|
||||
}
|
||||
if (event instanceof SessionIdChangedEvent changed) {
|
||||
this.logger.debug("Received SessionIdChangedEvent");
|
||||
OidcSessionInformation information = this.sessionRegistry
|
||||
.removeSessionInformation(changed.getOldSessionId());
|
||||
if (information == null) {
|
||||
this.logger
|
||||
.debug("Failed to register new session id since old session id was not found in registry");
|
||||
return;
|
||||
}
|
||||
this.sessionRegistry.saveSessionInformation(information.withSessionId(changed.getNewSessionId()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The registry where OIDC Provider sessions are linked to the Client session.
|
||||
* Defaults to in-memory storage.
|
||||
* @param sessionRegistry the {@link OidcSessionRegistry} to use
|
||||
*/
|
||||
void setSessionRegistry(OidcSessionRegistry sessionRegistry) {
|
||||
Assert.notNull(sessionRegistry, "sessionRegistry cannot be null");
|
||||
this.sessionRegistry = sessionRegistry;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class OidcSessionRegistryAuthenticationStrategy implements SessionAuthenticationStrategy {
|
||||
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private OidcSessionRegistry sessionRegistry = new InMemoryOidcSessionRegistry();
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onAuthentication(Authentication authentication, HttpServletRequest request,
|
||||
HttpServletResponse response) throws SessionAuthenticationException {
|
||||
HttpSession session = request.getSession(false);
|
||||
if (session == null) {
|
||||
return;
|
||||
}
|
||||
if (!(authentication.getPrincipal() instanceof OidcUser user)) {
|
||||
return;
|
||||
}
|
||||
String sessionId = session.getId();
|
||||
CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
|
||||
Map<String, String> headers = (csrfToken != null) ? Map.of(csrfToken.getHeaderName(), csrfToken.getToken())
|
||||
: Collections.emptyMap();
|
||||
OidcSessionInformation registration = new OidcSessionInformation(sessionId, headers, user);
|
||||
if (this.logger.isTraceEnabled()) {
|
||||
this.logger
|
||||
.trace(String.format("Linking a provider [%s] session to this client's session", user.getIssuer()));
|
||||
}
|
||||
this.sessionRegistry.saveSessionInformation(registration);
|
||||
}
|
||||
|
||||
/**
|
||||
* The registration for linking OIDC Provider Session information to the Client's
|
||||
* session. Defaults to in-memory storage.
|
||||
* @param sessionRegistry the {@link OidcSessionRegistry} to use
|
||||
*/
|
||||
void setSessionRegistry(OidcSessionRegistry sessionRegistry) {
|
||||
Assert.notNull(sessionRegistry, "sessionRegistry cannot be null");
|
||||
this.sessionRegistry = sessionRegistry;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,66 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.annotation.web.configurers.oauth2.client;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.oauth2.client.oidc.authentication.logout.OidcLogoutToken;
|
||||
|
||||
/**
|
||||
* An {@link org.springframework.security.core.Authentication} implementation that
|
||||
* represents the result of authenticating an OIDC Logout token for the purposes of
|
||||
* performing Back-Channel Logout.
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 6.2
|
||||
* @see OidcLogoutAuthenticationToken
|
||||
* @see <a target="_blank" href=
|
||||
* "https://openid.net/specs/openid-connect-backchannel-1_0.html">OIDC Back-Channel
|
||||
* Logout</a>
|
||||
*/
|
||||
class OidcBackChannelLogoutAuthentication extends AbstractAuthenticationToken {
|
||||
|
||||
private final OidcLogoutToken logoutToken;
|
||||
|
||||
/**
|
||||
* Construct an {@link OidcBackChannelLogoutAuthentication}
|
||||
* @param logoutToken a deserialized, verified OIDC Logout Token
|
||||
*/
|
||||
OidcBackChannelLogoutAuthentication(OidcLogoutToken logoutToken) {
|
||||
super(Collections.emptyList());
|
||||
this.logoutToken = logoutToken;
|
||||
setAuthenticated(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public OidcLogoutToken getPrincipal() {
|
||||
return this.logoutToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public OidcLogoutToken getCredentials() {
|
||||
return this.logoutToken;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,114 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.annotation.web.configurers.oauth2.client;
|
||||
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.oauth2.client.oidc.authentication.OidcIdTokenDecoderFactory;
|
||||
import org.springframework.security.oauth2.client.oidc.authentication.logout.OidcLogoutToken;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
import org.springframework.security.oauth2.jwt.BadJwtException;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoderFactory;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An {@link AuthenticationProvider} that authenticates an OIDC Logout Token; namely
|
||||
* deserializing it, verifying its signature, and validating its claims.
|
||||
*
|
||||
* <p>
|
||||
* Intended to be included in a
|
||||
* {@link org.springframework.security.authentication.ProviderManager}
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 6.2
|
||||
* @see OidcLogoutAuthenticationToken
|
||||
* @see org.springframework.security.authentication.ProviderManager
|
||||
* @see <a target="_blank" href=
|
||||
* "https://openid.net/specs/openid-connect-backchannel-1_0.html">OIDC Back-Channel
|
||||
* Logout</a>
|
||||
*/
|
||||
final class OidcBackChannelLogoutAuthenticationProvider implements AuthenticationProvider {
|
||||
|
||||
private JwtDecoderFactory<ClientRegistration> logoutTokenDecoderFactory;
|
||||
|
||||
/**
|
||||
* Construct an {@link OidcBackChannelLogoutAuthenticationProvider}
|
||||
*/
|
||||
OidcBackChannelLogoutAuthenticationProvider() {
|
||||
OidcIdTokenDecoderFactory logoutTokenDecoderFactory = new OidcIdTokenDecoderFactory();
|
||||
logoutTokenDecoderFactory.setJwtValidatorFactory(new DefaultOidcLogoutTokenValidatorFactory());
|
||||
this.logoutTokenDecoderFactory = logoutTokenDecoderFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||
if (!(authentication instanceof OidcLogoutAuthenticationToken token)) {
|
||||
return null;
|
||||
}
|
||||
String logoutToken = token.getLogoutToken();
|
||||
ClientRegistration registration = token.getClientRegistration();
|
||||
Jwt jwt = decode(registration, logoutToken);
|
||||
OidcLogoutToken oidcLogoutToken = OidcLogoutToken.withTokenValue(logoutToken)
|
||||
.claims((claims) -> claims.putAll(jwt.getClaims()))
|
||||
.build();
|
||||
return new OidcBackChannelLogoutAuthentication(oidcLogoutToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean supports(Class<?> authentication) {
|
||||
return OidcLogoutAuthenticationToken.class.isAssignableFrom(authentication);
|
||||
}
|
||||
|
||||
private Jwt decode(ClientRegistration registration, String token) {
|
||||
JwtDecoder logoutTokenDecoder = this.logoutTokenDecoderFactory.createDecoder(registration);
|
||||
try {
|
||||
return logoutTokenDecoder.decode(token);
|
||||
}
|
||||
catch (BadJwtException failed) {
|
||||
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST, failed.getMessage(),
|
||||
"https://openid.net/specs/openid-connect-backchannel-1_0.html#Validation");
|
||||
throw new OAuth2AuthenticationException(error, failed);
|
||||
}
|
||||
catch (Exception failed) {
|
||||
throw new AuthenticationServiceException(failed.getMessage(), failed);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this {@link JwtDecoderFactory} to generate {@link JwtDecoder}s that correspond
|
||||
* to the {@link ClientRegistration} associated with the OIDC logout token.
|
||||
* @param logoutTokenDecoderFactory the {@link JwtDecoderFactory} to use
|
||||
*/
|
||||
void setLogoutTokenDecoderFactory(JwtDecoderFactory<ClientRegistration> logoutTokenDecoderFactory) {
|
||||
Assert.notNull(logoutTokenDecoderFactory, "logoutTokenDecoderFactory cannot be null");
|
||||
this.logoutTokenDecoderFactory = logoutTokenDecoderFactory;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,139 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.annotation.web.configurers.oauth2.client;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.http.server.ServletServerHttpResponse;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter;
|
||||
import org.springframework.security.web.authentication.AuthenticationConverter;
|
||||
import org.springframework.security.web.authentication.logout.LogoutHandler;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
/**
|
||||
* A filter for the Client-side OIDC Back-Channel Logout endpoint
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 6.2
|
||||
* @see <a target="_blank" href=
|
||||
* "https://openid.net/specs/openid-connect-backchannel-1_0.html">OIDC Back-Channel Logout
|
||||
* Spec</a>
|
||||
*/
|
||||
class OidcBackChannelLogoutFilter extends OncePerRequestFilter {
|
||||
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private final AuthenticationConverter authenticationConverter;
|
||||
|
||||
private final AuthenticationManager authenticationManager;
|
||||
|
||||
private final OAuth2ErrorHttpMessageConverter errorHttpMessageConverter = new OAuth2ErrorHttpMessageConverter();
|
||||
|
||||
private LogoutHandler logoutHandler = new OidcBackChannelLogoutHandler();
|
||||
|
||||
/**
|
||||
* Construct an {@link OidcBackChannelLogoutFilter}
|
||||
* @param authenticationConverter the {@link AuthenticationConverter} for deriving
|
||||
* Logout Token authentication
|
||||
* @param authenticationManager the {@link AuthenticationManager} for authenticating
|
||||
* Logout Tokens
|
||||
*/
|
||||
OidcBackChannelLogoutFilter(AuthenticationConverter authenticationConverter,
|
||||
AuthenticationManager authenticationManager) {
|
||||
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
|
||||
Assert.notNull(authenticationManager, "authenticationManager cannot be null");
|
||||
this.authenticationConverter = authenticationConverter;
|
||||
this.authenticationManager = authenticationManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
|
||||
throws ServletException, IOException {
|
||||
Authentication token;
|
||||
try {
|
||||
token = this.authenticationConverter.convert(request);
|
||||
}
|
||||
catch (AuthenticationServiceException ex) {
|
||||
this.logger.debug("Failed to process OIDC Back-Channel Logout", ex);
|
||||
throw ex;
|
||||
}
|
||||
catch (AuthenticationException ex) {
|
||||
handleAuthenticationFailure(response, ex);
|
||||
return;
|
||||
}
|
||||
if (token == null) {
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
Authentication authentication;
|
||||
try {
|
||||
authentication = this.authenticationManager.authenticate(token);
|
||||
}
|
||||
catch (AuthenticationServiceException ex) {
|
||||
this.logger.debug("Failed to process OIDC Back-Channel Logout", ex);
|
||||
throw ex;
|
||||
}
|
||||
catch (AuthenticationException ex) {
|
||||
handleAuthenticationFailure(response, ex);
|
||||
return;
|
||||
}
|
||||
this.logoutHandler.logout(request, response, authentication);
|
||||
}
|
||||
|
||||
private void handleAuthenticationFailure(HttpServletResponse response, Exception ex) throws IOException {
|
||||
this.logger.debug("Failed to process OIDC Back-Channel Logout", ex);
|
||||
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
|
||||
this.errorHttpMessageConverter.write(oauth2Error(ex), null, new ServletServerHttpResponse(response));
|
||||
}
|
||||
|
||||
private OAuth2Error oauth2Error(Exception ex) {
|
||||
if (ex instanceof OAuth2AuthenticationException oauth2) {
|
||||
return oauth2.getError();
|
||||
}
|
||||
return new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST, ex.getMessage(),
|
||||
"https://openid.net/specs/openid-connect-backchannel-1_0.html#Validation");
|
||||
}
|
||||
|
||||
/**
|
||||
* The strategy for expiring all Client sessions indicated by the logout request.
|
||||
* Defaults to {@link OidcBackChannelLogoutHandler}.
|
||||
* @param logoutHandler the {@link LogoutHandler} to use
|
||||
*/
|
||||
void setLogoutHandler(LogoutHandler logoutHandler) {
|
||||
Assert.notNull(logoutHandler, "logoutHandler cannot be null");
|
||||
this.logoutHandler = logoutHandler;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,177 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.annotation.web.configurers.oauth2.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.server.ServletServerHttpResponse;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.client.oidc.authentication.logout.OidcLogoutToken;
|
||||
import org.springframework.security.oauth2.client.oidc.session.InMemoryOidcSessionRegistry;
|
||||
import org.springframework.security.oauth2.client.oidc.session.OidcSessionInformation;
|
||||
import org.springframework.security.oauth2.client.oidc.session.OidcSessionRegistry;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter;
|
||||
import org.springframework.security.web.authentication.logout.LogoutHandler;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.client.RestClientException;
|
||||
import org.springframework.web.client.RestOperations;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* A {@link LogoutHandler} that locates the sessions associated with a given OIDC
|
||||
* Back-Channel Logout Token and invalidates each one.
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 6.2
|
||||
* @see <a target="_blank" href=
|
||||
* "https://openid.net/specs/openid-connect-backchannel-1_0.html">OIDC Back-Channel Logout
|
||||
* Spec</a>
|
||||
*/
|
||||
final class OidcBackChannelLogoutHandler implements LogoutHandler {
|
||||
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private OidcSessionRegistry sessionRegistry = new InMemoryOidcSessionRegistry();
|
||||
|
||||
private RestOperations restOperations = new RestTemplate();
|
||||
|
||||
private String logoutEndpointName = "/logout";
|
||||
|
||||
private String sessionCookieName = "JSESSIONID";
|
||||
|
||||
private final OAuth2ErrorHttpMessageConverter errorHttpMessageConverter = new OAuth2ErrorHttpMessageConverter();
|
||||
|
||||
@Override
|
||||
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
|
||||
if (!(authentication instanceof OidcBackChannelLogoutAuthentication token)) {
|
||||
if (this.logger.isDebugEnabled()) {
|
||||
String message = "Did not perform OIDC Back-Channel Logout since authentication [%s] was of the wrong type";
|
||||
this.logger.debug(String.format(message, authentication.getClass().getSimpleName()));
|
||||
}
|
||||
return;
|
||||
}
|
||||
Iterable<OidcSessionInformation> sessions = this.sessionRegistry.removeSessionInformation(token.getPrincipal());
|
||||
Collection<String> errors = new ArrayList<>();
|
||||
int totalCount = 0;
|
||||
int invalidatedCount = 0;
|
||||
for (OidcSessionInformation session : sessions) {
|
||||
totalCount++;
|
||||
try {
|
||||
eachLogout(request, session);
|
||||
invalidatedCount++;
|
||||
}
|
||||
catch (RestClientException ex) {
|
||||
this.logger.debug("Failed to invalidate session", ex);
|
||||
errors.add(ex.getMessage());
|
||||
this.sessionRegistry.saveSessionInformation(session);
|
||||
}
|
||||
}
|
||||
if (this.logger.isTraceEnabled()) {
|
||||
this.logger.trace(String.format("Invalidated %d out of %d sessions", invalidatedCount, totalCount));
|
||||
}
|
||||
if (!errors.isEmpty()) {
|
||||
handleLogoutFailure(response, oauth2Error(errors));
|
||||
}
|
||||
}
|
||||
|
||||
private void eachLogout(HttpServletRequest request, OidcSessionInformation session) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.add(HttpHeaders.COOKIE, this.sessionCookieName + "=" + session.getSessionId());
|
||||
for (Map.Entry<String, String> credential : session.getAuthorities().entrySet()) {
|
||||
headers.add(credential.getKey(), credential.getValue());
|
||||
}
|
||||
String url = request.getRequestURL().toString();
|
||||
String logout = UriComponentsBuilder.fromHttpUrl(url)
|
||||
.replacePath(this.logoutEndpointName)
|
||||
.build()
|
||||
.toUriString();
|
||||
HttpEntity<?> entity = new HttpEntity<>(null, headers);
|
||||
this.restOperations.postForEntity(logout, entity, Object.class);
|
||||
}
|
||||
|
||||
private OAuth2Error oauth2Error(Collection<String> errors) {
|
||||
return new OAuth2Error("partial_logout", "not all sessions were terminated: " + errors,
|
||||
"https://openid.net/specs/openid-connect-backchannel-1_0.html#Validation");
|
||||
}
|
||||
|
||||
private void handleLogoutFailure(HttpServletResponse response, OAuth2Error error) {
|
||||
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
|
||||
try {
|
||||
this.errorHttpMessageConverter.write(error, null, new ServletServerHttpResponse(response));
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this {@link OidcSessionRegistry} to identify sessions to invalidate. Note that
|
||||
* this class uses
|
||||
* {@link OidcSessionRegistry#removeSessionInformation(OidcLogoutToken)} to identify
|
||||
* sessions.
|
||||
* @param sessionRegistry the {@link OidcSessionRegistry} to use
|
||||
*/
|
||||
void setSessionRegistry(OidcSessionRegistry sessionRegistry) {
|
||||
Assert.notNull(sessionRegistry, "sessionRegistry cannot be null");
|
||||
this.sessionRegistry = sessionRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this {@link RestOperations} to perform the per-session back-channel logout
|
||||
* @param restOperations the {@link RestOperations} to use
|
||||
*/
|
||||
void setRestOperations(RestOperations restOperations) {
|
||||
Assert.notNull(restOperations, "restOperations cannot be null");
|
||||
this.restOperations = restOperations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this logout URI for performing per-session logout. Defaults to {@code /logout}
|
||||
* since that is the default URI for
|
||||
* {@link org.springframework.security.web.authentication.logout.LogoutFilter}.
|
||||
* @param logoutUri the URI to use
|
||||
*/
|
||||
void setLogoutUri(String logoutUri) {
|
||||
Assert.hasText(logoutUri, "logoutUri cannot be empty");
|
||||
this.logoutEndpointName = logoutUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this cookie name for the session identifier. Defaults to {@code JSESSIONID}.
|
||||
*
|
||||
* <p>
|
||||
* Note that if you are using Spring Session, this likely needs to change to SESSION.
|
||||
* @param sessionCookieName the cookie name to use
|
||||
*/
|
||||
void setSessionCookieName(String sessionCookieName) {
|
||||
Assert.hasText(sessionCookieName, "clientSessionCookieName cannot be empty");
|
||||
this.sessionCookieName = sessionCookieName;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,118 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.annotation.web.configurers.oauth2.client;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.security.oauth2.client.oidc.authentication.logout.LogoutTokenClaimAccessor;
|
||||
import org.springframework.security.oauth2.client.oidc.authentication.logout.OidcLogoutToken;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
|
||||
/**
|
||||
* A {@link OAuth2TokenValidator} that validates OIDC Logout Token claims in conformance
|
||||
* with the OIDC Back-Channel Logout Spec.
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 6.2
|
||||
* @see OidcLogoutToken
|
||||
* @see <a target="_blank" href=
|
||||
* "https://openid.net/specs/openid-connect-backchannel-1_0.html#LogoutToken">Logout
|
||||
* Token</a>
|
||||
* @see <a target="blank" href=
|
||||
* "https://openid.net/specs/openid-connect-backchannel-1_0.html#Validation">the OIDC
|
||||
* Back-Channel Logout spec</a>
|
||||
*/
|
||||
final class OidcBackChannelLogoutTokenValidator implements OAuth2TokenValidator<Jwt> {
|
||||
|
||||
private static final String LOGOUT_VALIDATION_URL = "https://openid.net/specs/openid-connect-backchannel-1_0.html#Validation";
|
||||
|
||||
private static final String BACK_CHANNEL_LOGOUT_EVENT = "http://schemas.openid.net/event/backchannel-logout";
|
||||
|
||||
private final String audience;
|
||||
|
||||
private final String issuer;
|
||||
|
||||
OidcBackChannelLogoutTokenValidator(ClientRegistration clientRegistration) {
|
||||
this.audience = clientRegistration.getClientId();
|
||||
this.issuer = clientRegistration.getProviderDetails().getIssuerUri();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2TokenValidatorResult validate(Jwt jwt) {
|
||||
Collection<OAuth2Error> errors = new ArrayList<>();
|
||||
|
||||
LogoutTokenClaimAccessor logoutClaims = jwt::getClaims;
|
||||
Map<String, Object> events = logoutClaims.getEvents();
|
||||
if (events == null) {
|
||||
errors.add(invalidLogoutToken("events claim must not be null"));
|
||||
}
|
||||
else if (events.get(BACK_CHANNEL_LOGOUT_EVENT) == null) {
|
||||
errors.add(invalidLogoutToken("events claim map must contain \"" + BACK_CHANNEL_LOGOUT_EVENT + "\" key"));
|
||||
}
|
||||
|
||||
String issuer = logoutClaims.getIssuer().toExternalForm();
|
||||
if (issuer == null) {
|
||||
errors.add(invalidLogoutToken("iss claim must not be null"));
|
||||
}
|
||||
else if (!this.issuer.equals(issuer)) {
|
||||
errors.add(invalidLogoutToken(
|
||||
"iss claim value must match `ClientRegistration#getProviderDetails#getIssuerUri`"));
|
||||
}
|
||||
|
||||
List<String> audience = logoutClaims.getAudience();
|
||||
if (audience == null) {
|
||||
errors.add(invalidLogoutToken("aud claim must not be null"));
|
||||
}
|
||||
else if (!audience.contains(this.audience)) {
|
||||
errors.add(invalidLogoutToken("aud claim value must include `ClientRegistration#getClientId`"));
|
||||
}
|
||||
|
||||
Instant issuedAt = logoutClaims.getIssuedAt();
|
||||
if (issuedAt == null) {
|
||||
errors.add(invalidLogoutToken("iat claim must not be null"));
|
||||
}
|
||||
|
||||
String jwtId = logoutClaims.getId();
|
||||
if (jwtId == null) {
|
||||
errors.add(invalidLogoutToken("jti claim must not be null"));
|
||||
}
|
||||
|
||||
if (logoutClaims.getSubject() == null && logoutClaims.getSessionId() == null) {
|
||||
errors.add(invalidLogoutToken("sub and sid claims must not both be null"));
|
||||
}
|
||||
|
||||
if (logoutClaims.getClaim("nonce") != null) {
|
||||
errors.add(invalidLogoutToken("nonce claim must not be present"));
|
||||
}
|
||||
|
||||
return OAuth2TokenValidatorResult.failure(errors);
|
||||
}
|
||||
|
||||
private static OAuth2Error invalidLogoutToken(String description) {
|
||||
return new OAuth2Error(OAuth2ErrorCodes.INVALID_TOKEN, description, LOGOUT_VALIDATION_URL);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,85 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.annotation.web.configurers.oauth2.client;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
import org.springframework.security.web.authentication.AuthenticationConverter;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An {@link AuthenticationConverter} that extracts the OIDC Logout Token authentication
|
||||
* request
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 6.2
|
||||
*/
|
||||
final class OidcLogoutAuthenticationConverter implements AuthenticationConverter {
|
||||
|
||||
private static final String DEFAULT_LOGOUT_URI = "/logout/connect/back-channel/{registrationId}";
|
||||
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private final ClientRegistrationRepository clientRegistrationRepository;
|
||||
|
||||
private RequestMatcher requestMatcher = new AntPathRequestMatcher(DEFAULT_LOGOUT_URI, "POST");
|
||||
|
||||
OidcLogoutAuthenticationConverter(ClientRegistrationRepository clientRegistrationRepository) {
|
||||
Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null");
|
||||
this.clientRegistrationRepository = clientRegistrationRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication convert(HttpServletRequest request) {
|
||||
RequestMatcher.MatchResult result = this.requestMatcher.matcher(request);
|
||||
if (!result.isMatch()) {
|
||||
return null;
|
||||
}
|
||||
String registrationId = result.getVariables().get("registrationId");
|
||||
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId);
|
||||
if (clientRegistration == null) {
|
||||
this.logger.debug("Did not process OIDC Back-Channel Logout since no ClientRegistration was found");
|
||||
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST);
|
||||
}
|
||||
String logoutToken = request.getParameter("logout_token");
|
||||
if (logoutToken == null) {
|
||||
this.logger.debug("Failed to process OIDC Back-Channel Logout since no logout token was found");
|
||||
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST);
|
||||
}
|
||||
return new OidcLogoutAuthenticationToken(logoutToken, clientRegistration);
|
||||
}
|
||||
|
||||
/**
|
||||
* The logout endpoint. Defaults to
|
||||
* {@code /logout/connect/back-channel/{registrationId}}.
|
||||
* @param requestMatcher the {@link RequestMatcher} to use
|
||||
*/
|
||||
void setRequestMatcher(RequestMatcher requestMatcher) {
|
||||
Assert.notNull(requestMatcher, "requestMatcher cannot be null");
|
||||
this.requestMatcher = requestMatcher;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.annotation.web.configurers.oauth2.client;
|
||||
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
|
||||
/**
|
||||
* An {@link org.springframework.security.core.Authentication} instance that represents a
|
||||
* request to authenticate an OIDC Logout Token.
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 6.2
|
||||
*/
|
||||
class OidcLogoutAuthenticationToken extends AbstractAuthenticationToken {
|
||||
|
||||
private final String logoutToken;
|
||||
|
||||
private final ClientRegistration clientRegistration;
|
||||
|
||||
/**
|
||||
* Construct an {@link OidcLogoutAuthenticationToken}
|
||||
* @param logoutToken a signed, serialized OIDC Logout token
|
||||
* @param clientRegistration the {@link ClientRegistration client} associated with
|
||||
* this token; this is usually derived from material in the logout HTTP request
|
||||
*/
|
||||
OidcLogoutAuthenticationToken(String logoutToken, ClientRegistration clientRegistration) {
|
||||
super(AuthorityUtils.NO_AUTHORITIES);
|
||||
this.logoutToken = logoutToken;
|
||||
this.clientRegistration = clientRegistration;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public String getCredentials() {
|
||||
return this.logoutToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public String getPrincipal() {
|
||||
return this.logoutToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the signed, serialized OIDC Logout token
|
||||
* @return the logout token
|
||||
*/
|
||||
String getLogoutToken() {
|
||||
return this.logoutToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link ClientRegistration} associated with this logout token
|
||||
* @return the {@link ClientRegistration}
|
||||
*/
|
||||
ClientRegistration getClientRegistration() {
|
||||
return this.clientRegistration;
|
||||
}
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue