1
0
mirror of synced 2026-07-08 12:20:02 +00:00

Compare commits

...

143 Commits

Author SHA1 Message Date
Joe Grandja fa4c5449e7 Release 5.6.6 2022-06-20 14:50:24 -04:00
Joe Grandja 2a3845a7ed Update org.opensaml:opensaml-core4 to 4.1.1
Closes gh-11420
2022-06-20 14:50:24 -04:00
Joe Grandja ff96a7b5ad Update spring-ldap-core to 2.3.8.RELEASE
Closes gh-11419
2022-06-20 14:41:13 -04:00
Joe Grandja c37ff42234 Update org.springframework.data to 2021.1.5
Closes gh-11418
2022-06-20 14:41:10 -04:00
Joe Grandja 0a00d84838 Update org.springframework to 5.3.21
Closes gh-11417
2022-06-20 14:41:08 -04:00
Joe Grandja 96c6751a1d Update hibernate-entitymanager to 5.6.9.Final
Closes gh-11416
2022-06-20 14:41:05 -04:00
Joe Grandja 8ee9c32788 Update io.projectreactor to 2020.0.20
Closes gh-11414
2022-06-20 14:41:00 -04:00
Joe Grandja 7a5fb9eaf7 Update jackson-bom to 2.13.3
Closes gh-11411
2022-06-20 14:40:53 -04:00
Joe Grandja 8cbb972cef Add dependency update exclusion for spring-javaformat-checkstyle 2022-06-20 14:27:40 -04:00
Josh Cummings 539a11d0a4 Encode postLogoutRedirectUri query params
Closes gh-11379
2022-06-16 16:13:42 -06:00
Zhivko Delchev e97c5a533b Reverse content type check
When MultipartFormData is enabled currently the CsrfWebFilter compares
the content-type header against MULTIPART_FORM_DATA MediaType which
leads to NullPointerExecption when there is no content-type header.
This commit reverse the check to compare the MULTIPART_FORM_DATA
MediaType against the content-type which contains null check and avoids
the exception.

closes gh-11204
Closes gh-11205
2022-06-06 15:47:35 -05:00
shirohoo e0fa644b08 Fix typo in BasicLookupStrategy Javadoc
Closes gh-11336
2022-06-06 14:16:28 -05:00
Rob Winch 592db9180d Enable BackportBot on 5.6.x 2022-06-06 14:16:28 -05:00
André Luis Gomes aca3fc2412 Update opaque-token.adoc
Fixing yaml sample in Servlet and Reactive pages
2022-06-01 08:51:44 -03:00
Claudio Consolmagno 07f9afe057 Use 'md:' prefix in EntityDescriptor XML
Create the EntityDescriptor object with
EntityDescriptor.DEFAULT_ELEMENT_NAME instead of
EntityDescriptor.ELEMENT_QNAME. That ensures the EntityDescriptor tag
is marshalled to xml with the 'md:' prefix, consistent with all other
metadata tags.

Closes #11283
2022-05-31 17:11:02 -06:00
Josh Cummings d7077b441a Correct access(String) reference
Closes gh-11280
2022-05-27 15:00:15 -06:00
Josh Cummings 101f11ba94 Improve ContextConfiguration Docs
Point to updated Spring Reference

Issue gh-10934
2022-05-27 13:12:56 -06:00
Josh Cummings 18b903f6e3 Polish ExtendWith Docs
Use spring-framework-reference-url placeholder

Issue gh-10934
2022-05-27 13:12:56 -06:00
nor-ek 038266a94f Update JUnit 5 annotations in documentation
- replace Before with BeforeEach
- replace RunWith with ExtendWith

Closes gh-10934
2022-05-27 13:12:55 -06:00
Evgeniy Cheban cf559ab224 Some Security Expressions cause NPE when used within Query annotation
Added trustResolver, roleHierarchy, permissionEvaluator, defaultRolePrefix
fields to SecurityEvaluationContextExtension.

Closes gh-11196
Closes gh-11290
2022-05-26 17:44:28 -05:00
Juny Tse 649428b49a Use Base64 encoder with no CRLF in output for SAML 2.0 messages
Closes gh-11262
2022-05-25 12:06:27 -06:00
Steve Riesenberg 0355e960d7 Next development version 2022-05-18 11:52:05 -05:00
Steve Riesenberg fdad14af63 Release 5.6.5 2022-05-18 11:03:25 -05:00
Rob Winch 077c9e0b3e StrictHttpFirewall allows CJKV characters
Closes gh-11264
2022-05-18 08:56:57 -05:00
Rob Winch dc648cf79f Next Developement Version 2022-05-16 11:51:06 -05:00
Rob Winch c2d2914a4f Release 5.6.4 2022-05-16 11:36:26 -05:00
Josh Cummings a40f73521c Improve Upgrading 2022-05-16 11:36:26 -05:00
Rob Winch 70863952ae AntRegexRequestMatcher Optimization
Closes gh-11234
2022-05-16 10:17:44 -05:00
Rob Winch af95be34c6 Extract rejectNonPrintableAsciiCharactersInFieldName
Closes gh-11234
2022-05-16 10:17:44 -05:00
Rob Winch 71478c2534 Update org.springframework.data to 2021.1.4
Closes gh-11210
2022-05-13 09:28:59 -05:00
Rob Winch b289d12aad Update org.springframework to 5.3.20
Closes gh-11209
2022-05-13 09:28:56 -05:00
Rob Winch e2361c5c79 Update io.projectreactor to 2020.0.19
Closes gh-11207
2022-05-13 09:28:52 -05:00
Rob Winch 8b9c8ca96c Update mockk to 1.12.4
Closes gh-11206
2022-05-13 09:28:49 -05:00
Marcus Da Coregio 44e672d969 Use properties in the checkSamples job
Issue gh-10344
2022-05-11 16:13:34 -03:00
Marcus Da Coregio 330f0f050d Add initScripts and projectProperties to IncludeCheckRemotePlugin
Issue gh-10344
2022-05-11 16:13:34 -03:00
Marcus Da Coregio e45dcb3ab2 Update copyright headers
Issue gh-10956
2022-05-06 14:18:42 -03:00
Marcus Da Coregio d3a451fffb Fix mvcMatchers overriding previous paths
Closes gh-10956
2022-05-06 14:18:36 -03:00
Marcus Da Coregio 9d378103b0 Fix setServletContext not being called for AuthorizationManagerWebInvocationPrivilegeEvaluator
Issue gh-10908
2022-04-25 09:43:50 -03:00
Marcus Da Coregio 2031f101dc Exclude duplicate issues from changelog
Closes gh-11154
2022-04-20 09:07:16 -03:00
Marcus Da Coregio 2a2c2dd209 Next development version 2022-04-18 13:27:01 -03:00
Marcus Da Coregio fa0e06ebdc Release 5.6.3 2022-04-18 11:59:42 -03:00
Marcus Da Coregio 6724627b50 Update spring-ldap-core to 2.3.7.RELEASE
Closes gh-11127
2022-04-18 11:45:27 -03:00
Marcus Da Coregio 4aab7b06a9 Update org.springframework.data to 2021.1.3
Closes gh-11126
2022-04-18 11:45:24 -03:00
Marcus Da Coregio d0d4d58c48 Update org.springframework to 5.3.19
Closes gh-11125
2022-04-18 11:45:22 -03:00
Marcus Da Coregio 29be60d035 Update hibernate-entitymanager to 5.6.8.Final
Closes gh-11124
2022-04-18 11:45:19 -03:00
Marcus Da Coregio 5024103b07 Update org.eclipse.jetty to 9.4.46.v20220331
Closes gh-11123
2022-04-18 11:45:17 -03:00
Marcus Da Coregio d0327807a7 Update org.aspectj to 1.9.9.1
Closes gh-11122
2022-04-18 11:45:14 -03:00
Marcus Da Coregio f880f1ba26 Update io.rsocket to 1.1.2
Closes gh-11121
2022-04-18 11:45:12 -03:00
Marcus Da Coregio 71dafc0c06 Update io.projectreactor to 2020.0.18
Closes gh-11119
2022-04-18 11:45:07 -03:00
Marcus Da Coregio 7d9357b871 Update mockk to 1.12.3
Closes gh-11118
2022-04-18 11:45:05 -03:00
Marcus Da Coregio 0b02bbecd1 Update jackson-bom to 2.13.2.20220328
Closes gh-11115
2022-04-18 11:44:57 -03:00
Marcus Da Coregio 932a5cc8b4 Update logback-classic to 1.2.11
Closes gh-11114
2022-04-18 11:44:55 -03:00
Simone Giannino e4a321511a Update saganCreateRelease property referenceDocUrl
- Updated saganCreateRelease task with the new referenceDocUrl for reference documentation

Closes gh-11016
2022-04-01 13:08:18 -03:00
Johannes Graf 4ee5800ec9 use okta as registration id
looks like `ping` is some registration id used in the past.

Closes gh-11034
2022-03-30 14:41:03 -06:00
Marcus Da Coregio c67632225d Use ServletContext in AuthorizationManagerWebInvocationPrivilegeEvaluator
Closes gh-10908
2022-03-28 10:13:40 -03:00
Rob Winch 6e45a376cd Remove "Hi ... there" From Docs
Close gh-10963
2022-03-11 13:42:38 -06:00
Marcus Da Coregio 70b67cd2f1 AuthorizationManagerWebInvocationPrivilegeEvaluator grant access when AuthorizationManager abstains
Closes gh-10950
2022-03-09 15:22:21 -03:00
Josh Cummings 304e89041c Polish Formatting
Issue gh-10799
2022-03-02 16:40:13 -07:00
Sander van Schouwenburg f1a76efc2d Preserve order of RelyingPartRegistration credentials
Issue gh-10799
2022-03-02 16:40:13 -07:00
Josh Cummings 963251314b Replace Apache Commons Base64 Decoding
Issue gh-10923
2022-03-02 16:40:11 -07:00
Josh Cummings ee061f3659 Use RFC2045 Encoding for SAML 2.0 Logout
Closes gh-10923
2022-03-02 16:39:31 -07:00
Talerngpong Virojwutikul 440ffce2eb Update PasswordEncoder declaration
Closes gh-10910
2022-03-01 07:50:55 -07:00
Filip Hanik 47871562ca Change HashSet to LinkedHashSet
For various RelyingPartyRegistration.credentials to preserve order of insertion.

Issue gh-10799
2022-02-28 15:02:03 -07:00
Josh Cummings f0c548cee7 Invert Log Messages
Closes gh-10909
2022-02-28 13:17:01 -07:00
Marcus Da Coregio 1a32b399e6 Next development version 2022-02-21 10:49:06 -03:00
Marcus Da Coregio e9f3da94fc Release 5.6.2 2022-02-21 10:04:41 -03:00
Marcus Da Coregio 4fb6f936a7 Update spring-ldap-core to 2.3.6.RELEASE
Closes gh-10877
2022-02-21 09:20:57 -03:00
Marcus Da Coregio c431b5479e Update org.springframework.data to 2021.1.2
Closes gh-10876
2022-02-21 09:20:54 -03:00
Marcus Da Coregio fc03950afd Update org.springframework to 5.3.16
Closes gh-10875
2022-02-21 09:20:52 -03:00
Marcus Da Coregio 516b5fed4a Update org.slf4j to 1.7.36
Closes gh-10874
2022-02-21 09:20:49 -03:00
Marcus Da Coregio 165e29e585 Update hibernate-entitymanager to 5.6.5.Final
Closes gh-10873
2022-02-21 09:20:47 -03:00
Marcus Da Coregio 0be3e86b21 Update org.eclipse.jetty to 9.4.45.v20220203
Closes gh-10872
2022-02-21 09:20:44 -03:00
Marcus Da Coregio 96d229a6d1 Update org.aspectj to 1.9.8
Closes gh-10871
2022-02-21 09:20:42 -03:00
Marcus Da Coregio 5098cad9cf Update io.spring.javaformat to 0.0.31
Closes gh-10870
2022-02-21 09:20:40 -03:00
Marcus Da Coregio 883c480af0 Update r2dbc-h2 to 0.8.5.RELEASE
Closes gh-10869
2022-02-21 09:20:37 -03:00
Marcus Da Coregio 0f83e40427 Update io.projectreactor to 2020.0.16
Closes gh-10867
2022-02-21 09:20:32 -03:00
Marcus Da Coregio ad6ef6e4c7 Update mockk to 1.12.2
Closes gh-10866
2022-02-21 09:20:30 -03:00
Marcus Da Coregio 94201e46f1 Update logback-classic to 1.2.10
Closes gh-10865
2022-02-21 09:20:27 -03:00
Rob Winch 910e198cee Add .sdkmanrc 2022-02-18 13:48:26 -06:00
Talerngpong Virojwutikul 45a88fc391 add Kotlin examples for Spring Data Integration of servlet application 2022-02-18 08:49:27 -03:00
giger85 8f01efb9e3 Replace StringUtils class completely
Issue gh-9925
Closes gh-10805
2022-02-10 17:50:07 +01:00
Josh Cummings f53c65b3a0 Polish ignoring() log messaging
- Public API remains unchanged

Issue gh-9334
2022-02-07 15:07:29 -07:00
Manuel Jordan 0be772ff5b Print ignore message DefaultSecurityFilterChain
When either `web.ignoring().mvcMatchers(...)` or
`web.ignoring().antMatchers(...)` methods are used, for all their
variations, the DefaultSecurityFilterChain class now indicates
correctly through its ouput what paths are ignored according the
`ignoring()` settings.

Closes gh-9334
2022-02-07 15:07:29 -07:00
Rob Winch ac990afa5d Document Authorize HTTP Requests for Reactive Security
Closes gh-10801
2022-02-03 13:47:35 -06:00
Ken Dombeck 55cccbf727 Fix broken link to SAML2 login example 2022-02-03 10:20:45 -06:00
Eleftheria Stein a095ea75a2 Fix typo in getting started docs
Closes gh-10736
2022-02-02 10:52:54 +01:00
Marcus Da Coregio a041e7c943 RequestMatcherDelegatingWebInvocationPrivilegeEvaluator doesn't provided access to the ServletContext
Closes gh-10779
2022-01-31 09:50:17 -03:00
Rob Winch 4f3072b3d9 Exclude javax from hibernate dependency
Issue gh-10501
2022-01-19 15:32:12 -06:00
Rob Winch 13c467734a Remove javax.transaction
Issue gh-10501
2022-01-19 15:32:12 -06:00
Rob Winch c01b2b946b Additional removal of javax.inject
Issue gh-10501
2022-01-19 15:32:12 -06:00
Rob Winch 549f9dab64 dependencies jakarta.transaction-api
Issue gh-10501
2022-01-19 15:32:12 -06:00
Rob Winch 58090c37ea jsr250-api -> jakarta.annotation-api
Issue gh-10501
2022-01-19 15:32:12 -06:00
Rob Winch 38344e8de6 javax.servlet.jsp-api -> jakarta.servlet.jsp-api
Issue gh-10501
2022-01-19 15:32:12 -06:00
Rob Winch 6f319f0b13 javax.servlet.jsp.jstl-api -> jakarta.servlet.jsp.jstl-api
Issue gh-10501
2022-01-19 15:32:12 -06:00
Rob Winch 4e71b26ea4 Exclude javax from cas-client-core
Issue gh-10501
2022-01-19 15:32:12 -06:00
Rob Winch 04f3bbcefa javax.xml.bind:jaxb-api -> jakarta.xml.bind:jakarta.xml.bind-api
Issue gh-10501
2022-01-19 15:32:12 -06:00
Rob Winch c67ee6f2a8 javax.servlet:javax.servlet-api -> jakarta.servlet:jakarta.servlet-api
Issue gh-10501
2022-01-19 15:32:12 -06:00
Rob Winch 4b590ce31f Remove javax.inject
Issue gh-10501
2022-01-19 15:32:12 -06:00
Rob Winch 5902b46e9b Remove jcl-over-slf4j
Issue gh-10499

# Conflicts:
#	dependencies/spring-security-dependencies.gradle
2022-01-19 15:32:01 -06:00
Rob Winch 62449d6fa2 Remove commons-logging
Closes gh-10499
2022-01-19 15:31:22 -06:00
Rob Winch aa06088077 Add CheckProhibitedDependenciesLifecyclePlugin
Issue gh-10501
2022-01-19 15:30:34 -06:00
Rob Winch 2a60776c8a Add CheckClasspathForProhibitedDependencies
Issues gh-10499 gh-10501
2022-01-19 15:30:34 -06:00
Rob Winch f411ecada2 CheckAntoraVersionTask has optional properties 2022-01-18 14:29:39 -06:00
Rob Winch 8eeacf4a36 Fix Antora for Milestone & RC
- Verify Antora display_version
- Run workflow for tags
- Allow run workflow manually

Issue gh-10765
2022-01-18 14:20:47 -06:00
Josh Cummings 20c252982e Remove SAML 2.0 Logout Default
Closes gh-10607
2022-01-14 15:29:50 -07:00
Josh Cummings 6c5ac0d8ec Use noNullElements
Collection#contains(null) does not work for all collection types

Closes gh-10703
2022-01-14 15:09:21 -07:00
Marcus Da Coregio e1cb375fbf Make source code compatible with JDK 8
Closes gh-10695
2022-01-12 16:39:50 -03:00
Marcus Da Coregio e7e3f06044 Fix @since tag
Issue gh-10590, gh-10554
2022-01-06 13:22:13 -03:00
Marcus Da Coregio 994e93741b Configure WebInvocationPrivilegeEvaluator bean for multiple filter chains
Closes gh-10554
2022-01-05 14:06:47 -03:00
Marcus Da Coregio 04e1a11e35 Add RequestMatcherEntry 2022-01-05 14:06:47 -03:00
Marcus Da Coregio 547056d5cc Introduce AuthorizationManagerWebInvocationPrivilegeEvaluator
Closes gh-10590
2022-01-05 14:06:47 -03:00
Rob Winch 1fa35fb9a8 Antora prerelease: true for milestone and rc 2022-01-05 10:00:56 -06:00
Rob Winch a49914e631 Update RELEASE.adoc for antora.yml 2022-01-04 15:52:05 -06:00
Rob Winch 783a43ea35 Add CheckAntoraVersionPlugin 2022-01-04 15:52:05 -06:00
Rob Winch 07a176d11e Update antora to 5.6.2 2022-01-04 15:52:05 -06:00
Josh Cummings c664fbc1a3 Support No SingleLogoutServiceLocation
Closes gh-10674
2022-01-03 13:38:47 -07:00
Steve Riesenberg af412c4d65 Next Development Version 2021-12-20 15:24:46 -06:00
Steve Riesenberg e38bf6e554 Release 5.6.1 2021-12-20 14:14:55 -06:00
Steve Riesenberg 624e0da669 Update spring-ldap-core to 2.3.5.RELEASE
Closes gh-10659
2021-12-20 12:28:54 -06:00
Steve Riesenberg b28aa6c647 Update org.springframework to 5.3.14
Closes gh-10658
2021-12-20 12:28:52 -06:00
Steve Riesenberg 9e83b4be25 Update junit-bom to 5.8.2
Closes gh-10656
2021-12-20 12:28:47 -06:00
Steve Riesenberg e9854c91ef Update org.jetbrains.kotlin to 1.5.32
Closes gh-10655
2021-12-20 12:28:45 -06:00
Steve Riesenberg 0345e29afb Update cas-client-core to 3.6.4
Closes gh-10654
2021-12-20 12:28:43 -06:00
Steve Riesenberg 8bd5795f8e Update hibernate-entitymanager to 5.6.3.Final
Closes gh-10653
2021-12-20 12:28:41 -06:00
Steve Riesenberg 4fbc98dcd2 Update io.projectreactor to 2020.0.14
Closes gh-10651
2021-12-20 12:28:36 -06:00
Steve Riesenberg 44cdbd6cf3 Update mockk to 1.12.1
Closes gh-10650
2021-12-20 12:28:34 -06:00
Steve Riesenberg a9af8c4128 Update jackson-bom to 2.13.1
Closes gh-10647
2021-12-20 12:28:28 -06:00
Steve Riesenberg 28c81af788 Update logback-classic to 1.2.9
Closes gh-10646
2021-12-20 12:28:26 -06:00
Steve Riesenberg 3a956daf0c Exclude minor version bump 2021-12-20 12:16:04 -06:00
Marcus Da Coregio 9c245865d7 Point to samples on 5.6.x branch 2021-12-10 14:38:24 -03:00
Guirong Hu 86ed937a47 Fix the bug that the custom GrantedAuthority comparison fails
Closes gh-10566
2021-12-08 08:51:54 -03:00
Steve Riesenberg 32ec8c3ae4 Fix Reactive OAuth2 Kotlin DSL examples
Closes gh-10580
2021-12-07 13:58:24 +01:00
Steve Riesenberg c7ffd2513a Update copyright year
Issue gh-10557
2021-12-01 17:36:19 -06:00
Steve Riesenberg b3e0f167ff Fix case sensitive headers comparison
Closes gh-10557
2021-12-01 15:01:06 -06:00
Jonas Erbe 8c063f8ccb Fix JwtClaimValidator wrong error code
Previously JwtClaimValidator returned the invalid_request error on claim validation failure.
But validators have to return invalid_token errors on failure according to:
https://datatracker.ietf.org/doc/html/rfc6750#section-3.1.
Also see gh-10337

Closes gh-10337
2021-11-29 12:13:24 -07:00
Henning Poettker f1ca42e501 Fix return type for NoOpPasswordEncoder bean in documentation 2021-11-23 14:14:05 -03:00
Lars Grefer 719149f01d Remove usages of Gradle's jcenter() repository
Closes gh-10253
2021-11-22 09:38:49 -03:00
Lars Grefer ccd30607a9 Fix Gradle Deprecation Warnings 2021-11-22 09:38:44 -03:00
Dávid Kováč aa1ef46d84 Update clockSkew javadoc according to implementation
Closes gh-10174
2021-11-19 13:33:05 +01:00
Josh Cummings 16a21264d0 Polish AuthRequestConverter Sample Doc
Issue gh-10364
2021-11-18 13:36:31 -07:00
Norbert Nowak 1c0f092176 Fix AuthnRequestConverter Sample Typos
Closes gh-10364
2021-11-18 13:26:53 -07:00
Jeff Maxwell 32d79f3f4e Fix setJWTClaimSetJWSKeySelector Typo
Closes gh-10504
2021-11-16 15:33:42 -07:00
Jeff Maxwell 088a24cf59 Fix jwtDecoder Documentation Usage
Closes gh-10505
2021-11-16 15:18:42 -07:00
144 changed files with 3187 additions and 464 deletions
+2
View File
@@ -1,9 +1,11 @@
name: Generate Antora Files and Request Build
on:
workflow_dispatch:
push:
branches-ignore:
- 'gh-pages'
tags: '**'
env:
GH_ACTIONS_REPO_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
+26
View File
@@ -0,0 +1,26 @@
name: Backport Bot
on:
issues:
types: [labeled]
pull_request:
types: [labeled]
push:
branches:
- '*.x'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '17'
- name: Download BackportBot
run: wget https://github.com/spring-io/backport-bot/releases/download/latest/backport-bot-0.0.1-SNAPSHOT.jar
- name: Backport
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_EVENT: ${{ toJSON(github.event) }}
run: java -jar backport-bot-0.0.1-SNAPSHOT.jar --github.accessToken="$GITHUB_TOKEN" --github.event_name "$GITHUB_EVENT_NAME" --github.event "$GITHUB_EVENT"
@@ -95,11 +95,15 @@ jobs:
mkdir -p ~/.gradle
echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties
- name: Check samples project
env:
LOCAL_REPOSITORY_PATH: ${{ github.workspace }}/build/publications/repos
SAMPLES_INIT_SCRIPT: ${{ github.workspace }}/build/includeRepo/spring-security-ci.gradle
run: |
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD"
export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY"
./gradlew checkSamples --stacktrace
./gradlew publishMavenJavaPublicationToLocalRepository
./gradlew checkSamples -PsamplesInitScript="$SAMPLES_INIT_SCRIPT" -PlocalRepositoryPath="$LOCAL_REPOSITORY_PATH" --stacktrace
check_tangles:
name: Check for Package Tangles
needs: [ prerequisites ]
+6
View File
@@ -0,0 +1,6 @@
# Use sdkman to run "sdk env" to initialize with correct JDK version
# Enable auto-env through the sdkman_auto_env config
# See https://sdkman.io/usage#config
# A summary is to add the following to ~/.sdkman/etc/config
# sdkman_auto_env=true
java=11.0.14-tem
+11 -1
View File
@@ -78,6 +78,16 @@ Alternatively, you can manually check using https://github.com/spring-projects/s
Update the version number in `gradle.properties` for the release, for example `5.5.0-M1`, `5.5.0-RC1`, `5.5.0`
= Update Antora Version
You will need to update the antora.yml version.
If you are unsure of what the values should be, the following task will instruct you what the expected values are:
[source,bash]
----
./gradlew :spring-security-docs:antoraCheckVersion
----
= Build Locally
Run the build using
@@ -119,7 +129,7 @@ git push origin 5.4.0-RC1
== 7. Update to Next Development Version
* Update `gradle.properties` version to next `+SNAPSHOT+` version and then push
* Update `gradle.properties` version to next `+SNAPSHOT+` version, update antora.yml, and then push
== 8. Update version on project page
@@ -73,8 +73,8 @@ import org.springframework.util.Assert;
* one in <tt>lookupObjectIdentities</tt>. These are built from the same select and "order
* by" clause, using a different where clause in each case. In order to use custom schema
* or column names, each of these SQL clauses can be customized, but they must be
* consistent with each other and with the expected result set generated by the the
* default values.
* consistent with each other and with the expected result set generated by the default
* values.
*
* @author Ben Alex
*/
+19 -1
View File
@@ -35,7 +35,7 @@ repositories {
}
tasks.named("saganCreateRelease") {
referenceDocUrl = "https://docs.spring.io/spring-security/site/docs/{version}/reference/html5/"
referenceDocUrl = "https://docs.spring.io/spring-security/reference/{version}/index.html"
apiDocUrl = "https://docs.spring.io/spring-security/site/docs/{version}/api/"
}
@@ -63,6 +63,7 @@ updateDependenciesSettings {
})
dependencyExcludes {
majorVersionBump()
minorVersionBump()
alphaBetaVersions()
releaseCandidatesVersions()
milestoneVersions()
@@ -94,6 +95,18 @@ updateDependenciesSettings {
selection.reject("org.opensaml maintains two different versions, so it must be updated manually");
}
}
components.withModule("io.spring.javaformat:spring-javaformat-gradle-plugin") { selection ->
ModuleComponentIdentifier candidate = selection.getCandidate();
if (!candidate.getVersion().equals(selection.getCurrentVersion())) {
selection.reject("spring-javaformat-gradle-plugin updates break checkstyle");
}
}
components.withModule("io.spring.javaformat:spring-javaformat-checkstyle") { selection ->
ModuleComponentIdentifier candidate = selection.getCandidate();
if (!candidate.getVersion().equals(selection.getCurrentVersion())) {
selection.reject("spring-javaformat-checkstyle updates break checkstyle");
}
}
}
}
}
@@ -105,6 +118,7 @@ subprojects {
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
options.compilerArgs.add("-parameters")
options.release = 8
}
}
@@ -152,6 +166,10 @@ tasks.register('checkSamples') {
includeCheckRemote {
repository = 'spring-projects/spring-security-samples'
ref = samplesBranch
if (project.hasProperty("samplesInitScript")) {
initScripts = [samplesInitScript]
projectProperties = ["localRepositoryPath": localRepositoryPath, "springSecurityVersion": project.version]
}
}
dependsOn checkRemote
}
+12 -4
View File
@@ -9,7 +9,6 @@ plugins {
sourceCompatibility = 1.8
repositories {
jcenter()
gradlePluginPortal()
mavenCentral()
maven { url 'https://repo.spring.io/plugins-release/' }
@@ -28,6 +27,10 @@ sourceSets {
gradlePlugin {
plugins {
checkAntoraVersion {
id = "org.springframework.antora.check-version"
implementationClass = "org.springframework.gradle.antora.CheckAntoraVersionPlugin"
}
trang {
id = "trang"
implementationClass = "trang.TrangPlugin"
@@ -73,10 +76,11 @@ dependencies {
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.thaiopensource:trang:20091111'
implementation 'net.sourceforge.saxon:saxon:9.1.0.8'
implementation 'org.yaml:snakeyaml:1.30'
implementation localGroovy()
implementation 'io.github.gradle-nexus:publish-plugin:1.1.0'
implementation 'io.projectreactor:reactor-core:3.4.12'
implementation 'io.projectreactor:reactor-core:3.4.19'
implementation 'gradle.plugin.org.gretty:gretty:3.0.1'
implementation 'com.apollographql.apollo:apollo-runtime:2.4.5'
implementation 'com.github.ben-manes:gradle-versions-plugin:0.38.0'
@@ -88,7 +92,7 @@ dependencies {
implementation 'org.jfrog.buildinfo:build-info-extractor-gradle:4.24.20'
implementation 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.7.1'
testImplementation platform('org.junit:junit-bom:5.8.1')
testImplementation platform('org.junit:junit-bom:5.8.2')
testImplementation "org.junit.jupiter:junit-jupiter-api"
testImplementation "org.junit.jupiter:junit-jupiter-params"
testImplementation "org.junit.jupiter:junit-jupiter-engine"
@@ -100,7 +104,11 @@ dependencies {
}
test {
tasks.named('test', Test).configure {
onlyIf { !project.hasProperty("buildSrc.skipTests") }
useJUnitPlatform()
jvmArgs(
'--add-opens', 'java.base/java.lang=ALL-UNNAMED',
'--add-opens', 'java.base/java.util=ALL-UNNAMED'
)
}
@@ -25,7 +25,7 @@ public class DocsPlugin implements Plugin<Project> {
group = 'Distribution'
archiveBaseName = project.rootProject.name
archiveClassifier = 'docs'
description = "Builds -${classifier} archive containing all " +
description = "Builds -${archiveClassifier.get()} archive containing all " +
"Docs for deployment at docs.spring.io"
from(project.tasks.api.outputs) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
@@ -19,7 +19,6 @@ package io.spring.gradle.convention
import io.spring.gradle.IncludeRepoTask
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.provider.Property
import org.gradle.api.tasks.GradleBuild
import org.gradle.api.tasks.TaskProvider
@@ -40,6 +39,12 @@ class IncludeCheckRemotePlugin implements Plugin<Project> {
it.dependsOn 'includeRepo'
it.dir = includeRepoTask.get().outputDirectory
it.tasks = extension.getTasks()
extension.getInitScripts().forEach {script ->
it.startParameter.addInitScript(new File(script))
}
extension.getProjectProperties().entrySet().forEach { entry ->
it.startParameter.projectProperties.put(entry.getKey(), entry.getValue())
}
}
}
@@ -60,6 +65,16 @@ class IncludeCheckRemotePlugin implements Plugin<Project> {
*/
List<String> tasks = ['check']
/**
* Init scripts for the build
*/
List<String> initScripts = []
/**
* Map of properties for the build
*/
Map<String, String> projectProperties = [:]
}
}
@@ -35,11 +35,6 @@ class RepositoryConventionPlugin implements Plugin<Project> {
mavenLocal()
}
mavenCentral()
jcenter() {
content {
includeGroup "org.gretty"
}
}
if (isSnapshot) {
maven {
name = 'artifactory-snapshot'
@@ -21,6 +21,7 @@ import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.plugins.BasePlugin
import org.gradle.api.plugins.PluginManager
import org.springframework.gradle.classpath.CheckProhibitedDependenciesLifecyclePlugin
import org.springframework.gradle.maven.SpringNexusPublishPlugin
class RootProjectPlugin implements Plugin<Project> {
@@ -32,6 +33,7 @@ class RootProjectPlugin implements Plugin<Project> {
pluginManager.apply(SchemaPlugin)
pluginManager.apply(NoHttpPlugin)
pluginManager.apply(SpringNexusPublishPlugin)
pluginManager.apply(CheckProhibitedDependenciesLifecyclePlugin)
pluginManager.apply("org.sonarqube")
project.repositories.mavenCentral()
@@ -20,6 +20,7 @@ import org.gradle.api.Project
import org.gradle.api.plugins.JavaLibraryPlugin;
import org.gradle.api.plugins.MavenPlugin;
import org.gradle.api.plugins.PluginManager
import org.springframework.gradle.classpath.CheckClasspathForProhibitedDependenciesPlugin;
import org.springframework.gradle.maven.SpringMavenPlugin;
/**
@@ -32,6 +33,7 @@ class SpringModulePlugin extends AbstractSpringJavaPlugin {
PluginManager pluginManager = project.getPluginManager();
pluginManager.apply(JavaLibraryPlugin.class)
pluginManager.apply(SpringMavenPlugin.class);
pluginManager.apply(CheckClasspathForProhibitedDependenciesPlugin.class);
pluginManager.apply("io.spring.convention.jacoco");
def deployArtifacts = project.task("deployArtifacts")
@@ -0,0 +1,85 @@
package org.springframework.gradle.antora;
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;
public class CheckAntoraVersionPlugin implements Plugin<Project> {
public static final String ANTORA_CHECK_VERSION_TASK_NAME = "antoraCheckVersion";
@Override
public void apply(Project project) {
TaskProvider<CheckAntoraVersionTask> antoraCheckVersion = project.getTasks().register(ANTORA_CHECK_VERSION_TASK_NAME, CheckAntoraVersionTask.class, new Action<CheckAntoraVersionTask>() {
@Override
public void execute(CheckAntoraVersionTask antoraCheckVersion) {
antoraCheckVersion.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP);
antoraCheckVersion.setDescription("Checks the antora.yml version properties match the Gradle version");
antoraCheckVersion.getAntoraVersion().convention(project.provider(() -> getDefaultAntoraVersion(project)));
antoraCheckVersion.getAntoraPrerelease().convention(project.provider(() -> getDefaultAntoraPrerelease(project)));
antoraCheckVersion.getAntoraDisplayVersion().convention(project.provider(() -> getDefaultAntoraDisplayVersion(project)));
antoraCheckVersion.getAntoraYmlFile().fileProvider(project.provider(() -> project.file("antora.yml")));
}
});
project.getPlugins().withType(LifecycleBasePlugin.class, new Action<LifecycleBasePlugin>() {
@Override
public void execute(LifecycleBasePlugin lifecycleBasePlugin) {
project.getTasks().named(LifecycleBasePlugin.CHECK_TASK_NAME).configure(new Action<Task>() {
@Override
public void execute(Task check) {
check.dependsOn(antoraCheckVersion);
}
});
}
});
}
private static String getDefaultAntoraVersion(Project project) {
String projectVersion = getProjectVersion(project);
int preReleaseIndex = getSnapshotIndex(projectVersion);
return isSnapshot(projectVersion) ? projectVersion.substring(0, preReleaseIndex) : projectVersion;
}
private static String getDefaultAntoraPrerelease(Project project) {
String projectVersion = getProjectVersion(project);
if (isSnapshot(projectVersion)) {
int preReleaseIndex = getSnapshotIndex(projectVersion);
return projectVersion.substring(preReleaseIndex);
}
if (isPreRelease(projectVersion)) {
return Boolean.TRUE.toString();
}
return null;
}
private static String getDefaultAntoraDisplayVersion(Project project) {
String projectVersion = getProjectVersion(project);
if (!isSnapshot(projectVersion) && isPreRelease(projectVersion)) {
return getDefaultAntoraVersion(project);
}
return null;
}
private static String getProjectVersion(Project project) {
Object projectVersion = project.getVersion();
if (projectVersion == null) {
throw new GradleException("Please define antoraVersion and antoraPrerelease on " + ANTORA_CHECK_VERSION_TASK_NAME + " or provide a Project version so they can be defaulted");
}
return String.valueOf(projectVersion);
}
private static boolean isSnapshot(String projectVersion) {
return getSnapshotIndex(projectVersion) >= 0;
}
private static int getSnapshotIndex(String projectVersion) {
return projectVersion.lastIndexOf("-SNAPSHOT");
}
private static boolean isPreRelease(String projectVersion) {
return projectVersion.lastIndexOf("-") >= 0;
}
}
@@ -0,0 +1,96 @@
package org.springframework.gradle.antora;
import org.gradle.api.DefaultTask;
import org.gradle.api.GradleException;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.TaskAction;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;
import org.yaml.snakeyaml.representer.Representer;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public abstract class CheckAntoraVersionTask extends DefaultTask {
@TaskAction
public void check() throws FileNotFoundException {
File antoraYmlFile = getAntoraYmlFile().getAsFile().get();
String expectedAntoraVersion = getAntoraVersion().get();
String expectedAntoraPrerelease = getAntoraPrerelease().getOrElse(null);
String expectedAntoraDisplayVersion = getAntoraDisplayVersion().getOrElse(null);
Representer representer = new Representer();
representer.getPropertyUtils().setSkipMissingProperties(true);
Yaml yaml = new Yaml(new Constructor(AntoraYml.class), representer);
AntoraYml antoraYml = yaml.load(new FileInputStream(antoraYmlFile));
String actualAntoraPrerelease = antoraYml.getPrerelease();
boolean preReleaseMatches = antoraYml.getPrerelease() == null && expectedAntoraPrerelease == null ||
(actualAntoraPrerelease != null && actualAntoraPrerelease.equals(expectedAntoraPrerelease));
String actualAntoraDisplayVersion = antoraYml.getDisplay_version();
boolean displayVersionMatches = antoraYml.getDisplay_version() == null && expectedAntoraDisplayVersion == null ||
(actualAntoraDisplayVersion != null && actualAntoraDisplayVersion.equals(expectedAntoraDisplayVersion));
String actualAntoraVersion = antoraYml.getVersion();
if (!preReleaseMatches ||
!displayVersionMatches ||
!expectedAntoraVersion.equals(actualAntoraVersion)) {
throw new GradleException("The Gradle version of '" + getProject().getVersion() + "' should have version: '"
+ expectedAntoraVersion + "' prerelease: '" + expectedAntoraPrerelease + "' display_version: '"
+ expectedAntoraDisplayVersion + "' defined in " + antoraYmlFile + " but got version: '"
+ actualAntoraVersion + "' prerelease: '" + actualAntoraPrerelease + "' display_version: '" + actualAntoraDisplayVersion + "'");
}
}
@InputFile
public abstract RegularFileProperty getAntoraYmlFile();
@Input
public abstract Property<String> getAntoraVersion();
@Input
@Optional
public abstract Property<String> getAntoraPrerelease();
@Input
@Optional
public abstract Property<String> getAntoraDisplayVersion();
public static class AntoraYml {
private String version;
private String prerelease;
private String display_version;
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public String getPrerelease() {
return prerelease;
}
public void setPrerelease(String prerelease) {
this.prerelease = prerelease;
}
public String getDisplay_version() {
return display_version;
}
public void setDisplay_version(String display_version) {
this.display_version = display_version;
}
}
}
@@ -0,0 +1,99 @@
/*
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.gradle.classpath;
import org.gradle.api.DefaultTask;
import org.gradle.api.GradleException;
import org.gradle.api.Task;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ModuleVersionIdentifier;
import org.gradle.api.artifacts.ResolvedConfiguration;
import org.gradle.api.file.FileCollection;
import org.gradle.api.tasks.Classpath;
import org.gradle.api.tasks.TaskAction;
import java.io.IOException;
import java.util.TreeSet;
import java.util.stream.Collectors;
/**
* A {@link Task} for checking the classpath for prohibited dependencies.
*
* @author Andy Wilkinson
*/
public class CheckClasspathForProhibitedDependencies extends DefaultTask {
private Configuration classpath;
public CheckClasspathForProhibitedDependencies() {
getOutputs().upToDateWhen((task) -> true);
}
public void setClasspath(Configuration classpath) {
this.classpath = classpath;
}
@Classpath
public FileCollection getClasspath() {
return this.classpath;
}
@TaskAction
public void checkForProhibitedDependencies() throws IOException {
ResolvedConfiguration resolvedConfiguration = this.classpath.getResolvedConfiguration();
TreeSet<String> prohibited = resolvedConfiguration.getResolvedArtifacts().stream()
.map((artifact) -> artifact.getModuleVersion().getId()).filter(this::prohibited)
.map((id) -> id.getGroup() + ":" + id.getName()).collect(Collectors.toCollection(TreeSet::new));
if (!prohibited.isEmpty()) {
StringBuilder message = new StringBuilder(String.format("Found prohibited dependencies in '%s':%n", this.classpath.getName()));
for (String dependency : prohibited) {
message.append(String.format(" %s%n", dependency));
}
throw new GradleException(message.toString());
}
}
private boolean prohibited(ModuleVersionIdentifier id) {
String group = id.getGroup();
if (group.equals("javax.batch")) {
return false;
}
if (group.equals("javax.cache")) {
return false;
}
if (group.equals("javax.money")) {
return false;
}
if (group.startsWith("javax")) {
return true;
}
if (group.equals("commons-logging")) {
return true;
}
if (group.equals("org.slf4j") && id.getName().equals("jcl-over-slf4j")) {
return true;
}
if (group.startsWith("org.jboss.spec")) {
return true;
}
if (group.equals("org.apache.geronimo.specs")) {
return true;
}
return false;
}
}
@@ -0,0 +1,67 @@
/*
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.gradle.classpath;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.plugins.JavaBasePlugin;
import org.gradle.api.tasks.SourceSetContainer;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.language.base.plugins.LifecycleBasePlugin;
import org.springframework.util.StringUtils;
/**
* @author Andy Wilkinson
* @author Rob Winch
*/
public class CheckClasspathForProhibitedDependenciesPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
project.getPlugins().apply(CheckProhibitedDependenciesLifecyclePlugin.class);
project.getPlugins().withType(JavaBasePlugin.class, javaBasePlugin -> {
configureProhibitedDependencyChecks(project);
});
}
private void configureProhibitedDependencyChecks(Project project) {
SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class);
sourceSets.all((sourceSet) -> createProhibitedDependenciesChecks(project,
sourceSet.getCompileClasspathConfigurationName(), sourceSet.getRuntimeClasspathConfigurationName()));
}
private void createProhibitedDependenciesChecks(Project project, String... configurationNames) {
ConfigurationContainer configurations = project.getConfigurations();
for (String configurationName : configurationNames) {
Configuration configuration = configurations.getByName(configurationName);
createProhibitedDependenciesCheck(configuration, project);
}
}
private void createProhibitedDependenciesCheck(Configuration classpath, Project project) {
String taskName = "check" + StringUtils.capitalize(classpath.getName() + "ForProhibitedDependencies");
TaskProvider<CheckClasspathForProhibitedDependencies> checkClasspathTask = project.getTasks().register(taskName,
CheckClasspathForProhibitedDependencies.class, checkClasspath -> {
checkClasspath.setGroup(LifecycleBasePlugin.CHECK_TASK_NAME);
checkClasspath.setDescription("Checks " + classpath.getName() + " for prohibited dependencies");
checkClasspath.setClasspath(classpath);
});
project.getTasks().named(CheckProhibitedDependenciesLifecyclePlugin.CHECK_PROHIBITED_DEPENDENCIES_TASK_NAME, checkProhibitedTask -> checkProhibitedTask.dependsOn(checkClasspathTask));
}
}
@@ -0,0 +1,41 @@
/*
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.gradle.classpath;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.plugins.JavaBasePlugin;
import org.gradle.api.tasks.TaskProvider;
/**
* @author Rob Winch
*/
public class CheckProhibitedDependenciesLifecyclePlugin implements Plugin<Project> {
public static final String CHECK_PROHIBITED_DEPENDENCIES_TASK_NAME = "checkForProhibitedDependencies";
@Override
public void apply(Project project) {
TaskProvider<Task> checkProhibitedDependencies = project.getTasks().register(CheckProhibitedDependenciesLifecyclePlugin.CHECK_PROHIBITED_DEPENDENCIES_TASK_NAME, task -> {
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);
});
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
@@ -16,6 +16,11 @@
package io.spring.gradle.convention;
import java.io.File;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import io.spring.gradle.IncludeRepoTask;
import org.apache.commons.io.FileUtils;
import org.gradle.api.Project;
@@ -24,8 +29,6 @@ import org.gradle.testfixtures.ProjectBuilder;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import static org.assertj.core.api.Assertions.assertThat;
class IncludeCheckRemotePluginTest {
@@ -68,6 +71,40 @@ class IncludeCheckRemotePluginTest {
assertThat(checkRemote.getTasks()).containsExactly("clean", "build", "test");
}
@Test
void applyWhenExtensionPropertiesInitScriptsThenCreateCheckRemoteWithProvidedTasks() {
this.rootProject = ProjectBuilder.builder().build();
this.rootProject.getPluginManager().apply(IncludeCheckRemotePlugin.class);
this.rootProject.getExtensions().configure(IncludeCheckRemotePlugin.IncludeCheckRemoteExtension.class,
(includeCheckRemoteExtension) -> {
includeCheckRemoteExtension.setProperty("repository", "my-project/my-repository");
includeCheckRemoteExtension.setProperty("ref", "main");
includeCheckRemoteExtension.setProperty("initScripts", Arrays.asList("spring-security-ci.gradle"));
});
GradleBuild checkRemote = (GradleBuild) this.rootProject.getTasks().named("checkRemote").get();
assertThat(checkRemote.getStartParameter().getAllInitScripts()).extracting(File::getName).containsExactly("spring-security-ci.gradle");
}
@Test
void applyWhenExtensionPropertiesBuildPropertiesThenCreateCheckRemoteWithProvidedTasks() {
Map<String, String> projectProperties = new HashMap<>();
projectProperties.put("localRepositoryPath", "~/local/repository");
projectProperties.put("anotherProperty", "some_value");
this.rootProject = ProjectBuilder.builder().build();
this.rootProject.getPluginManager().apply(IncludeCheckRemotePlugin.class);
this.rootProject.getExtensions().configure(IncludeCheckRemotePlugin.IncludeCheckRemoteExtension.class,
(includeCheckRemoteExtension) -> {
includeCheckRemoteExtension.setProperty("repository", "my-project/my-repository");
includeCheckRemoteExtension.setProperty("ref", "main");
includeCheckRemoteExtension.setProperty("projectProperties", projectProperties);
});
GradleBuild checkRemote = (GradleBuild) this.rootProject.getTasks().named("checkRemote").get();
assertThat(checkRemote.getStartParameter().getProjectProperties()).containsEntry("localRepositoryPath", "~/local/repository")
.containsEntry("anotherProperty", "some_value");
}
@Test
void applyWhenExtensionPropertiesThenRegisterIncludeRepoTaskWithExtensionProperties() {
this.rootProject = ProjectBuilder.builder().build();
@@ -107,7 +107,7 @@ public class RepositoryConventionPluginTests {
this.project.getPluginManager().apply(RepositoryConventionPlugin.class);
RepositoryHandler repositories = this.project.getRepositories();
assertThat(repositories).hasSize(5);
assertThat(repositories).hasSize(4);
assertThat((repositories.get(0)).getName()).isEqualTo("MavenLocal");
}
@@ -119,39 +119,33 @@ public class RepositoryConventionPluginTests {
this.project.getPluginManager().apply(RepositoryConventionPlugin.class);
RepositoryHandler repositories = this.project.getRepositories();
assertThat(repositories).hasSize(6);
assertThat(repositories).hasSize(5);
assertThat((repositories.get(0)).getName()).isEqualTo("MavenLocal");
}
private void assertSnapshotRepository(RepositoryHandler repositories) {
assertThat(repositories).extracting(ArtifactRepository::getName).hasSize(6);
assertThat(((MavenArtifactRepository) repositories.get(0)).getUrl().toString())
.isEqualTo("https://repo.maven.apache.org/maven2/");
assertThat(((MavenArtifactRepository) repositories.get(1)).getUrl().toString())
.isEqualTo("https://jcenter.bintray.com/");
assertThat(((MavenArtifactRepository) repositories.get(2)).getUrl().toString())
.isEqualTo("https://repo.spring.io/snapshot/");
assertThat(((MavenArtifactRepository) repositories.get(3)).getUrl().toString())
.isEqualTo("https://repo.spring.io/milestone/");
}
private void assertMilestoneRepository(RepositoryHandler repositories) {
assertThat(repositories).extracting(ArtifactRepository::getName).hasSize(5);
assertThat(((MavenArtifactRepository) repositories.get(0)).getUrl().toString())
.isEqualTo("https://repo.maven.apache.org/maven2/");
assertThat(((MavenArtifactRepository) repositories.get(1)).getUrl().toString())
.isEqualTo("https://jcenter.bintray.com/");
.isEqualTo("https://repo.spring.io/snapshot/");
assertThat(((MavenArtifactRepository) repositories.get(2)).getUrl().toString())
.isEqualTo("https://repo.spring.io/milestone/");
}
private void assertReleaseRepository(RepositoryHandler repositories) {
private void assertMilestoneRepository(RepositoryHandler repositories) {
assertThat(repositories).extracting(ArtifactRepository::getName).hasSize(4);
assertThat(((MavenArtifactRepository) repositories.get(0)).getUrl().toString())
.isEqualTo("https://repo.maven.apache.org/maven2/");
assertThat(((MavenArtifactRepository) repositories.get(1)).getUrl().toString())
.isEqualTo("https://jcenter.bintray.com/");
assertThat(((MavenArtifactRepository) repositories.get(2)).getUrl().toString())
.isEqualTo("https://repo.spring.io/milestone/");
}
private void assertReleaseRepository(RepositoryHandler repositories) {
assertThat(repositories).extracting(ArtifactRepository::getName).hasSize(3);
assertThat(((MavenArtifactRepository) repositories.get(0)).getUrl().toString())
.isEqualTo("https://repo.maven.apache.org/maven2/");
assertThat(((MavenArtifactRepository) repositories.get(1)).getUrl().toString())
.isEqualTo("https://repo.spring.io/release/");
}
@@ -58,7 +58,7 @@ public class SaganApiTests {
Release release = new Release();
release.setVersion("5.6.0-SNAPSHOT");
release.setApiDocUrl("https://docs.spring.io/spring-security/site/docs/{version}/api/");
release.setReferenceDocUrl("https://docs.spring.io/spring-security/site/docs/{version}/reference/html5/");
release.setReferenceDocUrl("https://docs.spring.io/spring-security/reference/{version}/index.html");
this.sagan.createReleaseForProject(release, "spring-security");
RecordedRequest request = this.server.takeRequest(1, TimeUnit.SECONDS);
assertThat(request.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/projects/spring-security/releases");
@@ -67,7 +67,7 @@ public class SaganApiTests {
assertThat(request.getBody().readString(Charset.defaultCharset())).isEqualToIgnoringWhitespace("{\n" +
" \"version\":\"5.6.0-SNAPSHOT\",\n" +
" \"current\":false,\n" +
" \"referenceDocUrl\":\"https://docs.spring.io/spring-security/site/docs/{version}/reference/html5/\",\n" +
" \"referenceDocUrl\":\"https://docs.spring.io/spring-security/reference/{version}/index.html\",\n" +
" \"apiDocUrl\":\"https://docs.spring.io/spring-security/site/docs/{version}/api/\"\n" +
"}");
}
@@ -0,0 +1,270 @@
package org.springframework.gradle.antora;
import org.apache.commons.io.IOUtils;
import org.gradle.api.GradleException;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.testfixtures.ProjectBuilder;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.io.FileOutputStream;
import java.nio.charset.StandardCharsets;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIOException;
class CheckAntoraVersionPluginTests {
@Test
void defaultsPropertiesWhenSnapshot() {
String expectedVersion = "1.0.0-SNAPSHOT";
Project project = ProjectBuilder.builder().build();
project.setVersion(expectedVersion);
project.getPluginManager().apply(CheckAntoraVersionPlugin.class);
Task task = project.getTasks().findByName(CheckAntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
assertThat(checkAntoraVersionTask.getAntoraVersion().get()).isEqualTo("1.0.0");
assertThat(checkAntoraVersionTask.getAntoraPrerelease().get()).isEqualTo("-SNAPSHOT");
assertThat(checkAntoraVersionTask.getAntoraDisplayVersion().isPresent()).isFalse();
assertThat(checkAntoraVersionTask.getAntoraYmlFile().getAsFile().get()).isEqualTo(project.file("antora.yml"));
}
@Test
void defaultsPropertiesWhenMilestone() {
String expectedVersion = "1.0.0-M1";
Project project = ProjectBuilder.builder().build();
project.setVersion(expectedVersion);
project.getPluginManager().apply(CheckAntoraVersionPlugin.class);
Task task = project.getTasks().findByName(CheckAntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
assertThat(checkAntoraVersionTask.getAntoraVersion().get()).isEqualTo("1.0.0-M1");
assertThat(checkAntoraVersionTask.getAntoraPrerelease().get()).isEqualTo("true");
assertThat(checkAntoraVersionTask.getAntoraDisplayVersion().get()).isEqualTo(checkAntoraVersionTask.getAntoraVersion().get());
assertThat(checkAntoraVersionTask.getAntoraYmlFile().getAsFile().get()).isEqualTo(project.file("antora.yml"));
}
@Test
void defaultsPropertiesWhenRc() {
String expectedVersion = "1.0.0-RC1";
Project project = ProjectBuilder.builder().build();
project.setVersion(expectedVersion);
project.getPluginManager().apply(CheckAntoraVersionPlugin.class);
Task task = project.getTasks().findByName(CheckAntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
assertThat(checkAntoraVersionTask.getAntoraVersion().get()).isEqualTo("1.0.0-RC1");
assertThat(checkAntoraVersionTask.getAntoraPrerelease().get()).isEqualTo("true");
assertThat(checkAntoraVersionTask.getAntoraDisplayVersion().get()).isEqualTo(checkAntoraVersionTask.getAntoraVersion().get());
assertThat(checkAntoraVersionTask.getAntoraYmlFile().getAsFile().get()).isEqualTo(project.file("antora.yml"));
}
@Test
void defaultsPropertiesWhenRelease() {
String expectedVersion = "1.0.0";
Project project = ProjectBuilder.builder().build();
project.setVersion(expectedVersion);
project.getPluginManager().apply(CheckAntoraVersionPlugin.class);
Task task = project.getTasks().findByName(CheckAntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
assertThat(checkAntoraVersionTask.getAntoraVersion().get()).isEqualTo("1.0.0");
assertThat(checkAntoraVersionTask.getAntoraPrerelease().isPresent()).isFalse();
assertThat(checkAntoraVersionTask.getAntoraDisplayVersion().isPresent()).isFalse();
assertThat(checkAntoraVersionTask.getAntoraYmlFile().getAsFile().get()).isEqualTo(project.file("antora.yml"));
}
@Test
void explicitProperties() {
Project project = ProjectBuilder.builder().build();
project.getPluginManager().apply(CheckAntoraVersionPlugin.class);
Task task = project.getTasks().findByName(CheckAntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
checkAntoraVersionTask.getAntoraVersion().set("1.0.0");
checkAntoraVersionTask.getAntoraPrerelease().set("-SNAPSHOT");
assertThat(checkAntoraVersionTask.getAntoraVersion().get()).isEqualTo("1.0.0");
assertThat(checkAntoraVersionTask.getAntoraPrerelease().get()).isEqualTo("-SNAPSHOT");
assertThat(checkAntoraVersionTask.getAntoraDisplayVersion().isPresent()).isFalse();
assertThat(checkAntoraVersionTask.getAntoraYmlFile().getAsFile().get()).isEqualTo(project.file("antora.yml"));
}
@Test
void versionNotDefined() throws Exception {
Project project = ProjectBuilder.builder().build();
File rootDir = project.getRootDir();
IOUtils.write("version: '1.0.0'", new FileOutputStream(new File(rootDir, "antora.yml")), StandardCharsets.UTF_8);
project.getPluginManager().apply(CheckAntoraVersionPlugin.class);
Task task = project.getTasks().findByName(CheckAntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
assertThatExceptionOfType(GradleException.class).isThrownBy(() -> checkAntoraVersionTask.check());
}
@Test
void antoraFileNotFound() throws Exception {
String expectedVersion = "1.0.0-SNAPSHOT";
Project project = ProjectBuilder.builder().build();
project.setVersion(expectedVersion);
project.getPluginManager().apply(CheckAntoraVersionPlugin.class);
Task task = project.getTasks().findByName(CheckAntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
assertThatIOException().isThrownBy(() -> checkAntoraVersionTask.check());
}
@Test
void actualAntoraPrereleaseNull() throws Exception {
String expectedVersion = "1.0.0-SNAPSHOT";
Project project = ProjectBuilder.builder().build();
File rootDir = project.getRootDir();
IOUtils.write("version: '1.0.0'", new FileOutputStream(new File(rootDir, "antora.yml")), StandardCharsets.UTF_8);
project.setVersion(expectedVersion);
project.getPluginManager().apply(CheckAntoraVersionPlugin.class);
Task task = project.getTasks().findByName(CheckAntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
assertThatExceptionOfType(GradleException.class).isThrownBy(() -> checkAntoraVersionTask.check());
}
@Test
void matchesWhenSnapshot() throws Exception {
String expectedVersion = "1.0.0-SNAPSHOT";
Project project = ProjectBuilder.builder().build();
File rootDir = project.getRootDir();
IOUtils.write("version: '1.0.0'\nprerelease: '-SNAPSHOT'", new FileOutputStream(new File(rootDir, "antora.yml")), StandardCharsets.UTF_8);
project.setVersion(expectedVersion);
project.getPluginManager().apply(CheckAntoraVersionPlugin.class);
Task task = project.getTasks().findByName(CheckAntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
checkAntoraVersionTask.check();
}
@Test
void matchesWhenMilestone() throws Exception {
String expectedVersion = "1.0.0-M1";
Project project = ProjectBuilder.builder().build();
File rootDir = project.getRootDir();
IOUtils.write("version: '1.0.0-M1'\nprerelease: 'true'\ndisplay_version: '1.0.0-M1'", new FileOutputStream(new File(rootDir, "antora.yml")), StandardCharsets.UTF_8);
project.setVersion(expectedVersion);
project.getPluginManager().apply(CheckAntoraVersionPlugin.class);
Task task = project.getTasks().findByName(CheckAntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
checkAntoraVersionTask.check();
}
@Test
void matchesWhenRc() throws Exception {
String expectedVersion = "1.0.0-RC1";
Project project = ProjectBuilder.builder().build();
File rootDir = project.getRootDir();
IOUtils.write("version: '1.0.0-RC1'\nprerelease: 'true'\ndisplay_version: '1.0.0-RC1'", new FileOutputStream(new File(rootDir, "antora.yml")), StandardCharsets.UTF_8);
project.setVersion(expectedVersion);
project.getPluginManager().apply(CheckAntoraVersionPlugin.class);
Task task = project.getTasks().findByName(CheckAntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
checkAntoraVersionTask.check();
}
@Test
void matchesWhenReleaseAndPrereleaseUndefined() throws Exception {
String expectedVersion = "1.0.0";
Project project = ProjectBuilder.builder().build();
File rootDir = project.getRootDir();
IOUtils.write("version: '1.0.0'", new FileOutputStream(new File(rootDir, "antora.yml")), StandardCharsets.UTF_8);
project.setVersion(expectedVersion);
project.getPluginManager().apply(CheckAntoraVersionPlugin.class);
Task task = project.getTasks().findByName(CheckAntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
checkAntoraVersionTask.check();
}
@Test
void matchesWhenExplicitRelease() throws Exception {
Project project = ProjectBuilder.builder().build();
File rootDir = project.getRootDir();
IOUtils.write("version: '1.0.0'", new FileOutputStream(new File(rootDir, "antora.yml")), StandardCharsets.UTF_8);
project.getPluginManager().apply(CheckAntoraVersionPlugin.class);
Task task = project.getTasks().findByName(CheckAntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
((CheckAntoraVersionTask) task).getAntoraVersion().set("1.0.0");
checkAntoraVersionTask.check();
}
@Test
void matchesWhenExplicitPrerelease() throws Exception {
Project project = ProjectBuilder.builder().build();
File rootDir = project.getRootDir();
IOUtils.write("version: '1.0.0'\nprerelease: '-SNAPSHOT'", new FileOutputStream(new File(rootDir, "antora.yml")), StandardCharsets.UTF_8);
project.getPluginManager().apply(CheckAntoraVersionPlugin.class);
Task task = project.getTasks().findByName(CheckAntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
((CheckAntoraVersionTask) task).getAntoraVersion().set("1.0.0");
((CheckAntoraVersionTask) task).getAntoraPrerelease().set("-SNAPSHOT");
checkAntoraVersionTask.check();
}
@Test
void matchesWhenMissingPropertyDefined() throws Exception {
Project project = ProjectBuilder.builder().build();
File rootDir = project.getRootDir();
IOUtils.write("name: 'ROOT'\nversion: '1.0.0'", new FileOutputStream(new File(rootDir, "antora.yml")), StandardCharsets.UTF_8);
project.getPluginManager().apply(CheckAntoraVersionPlugin.class);
Task task = project.getTasks().findByName(CheckAntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
((CheckAntoraVersionTask) task).getAntoraVersion().set("1.0.0");
checkAntoraVersionTask.check();
}
}
@@ -9,6 +9,6 @@ repositories {
}
dependencies {
optional 'javax.servlet:javax.servlet-api:3.1.0'
optional 'jakarta.servlet:jakarta.servlet-api:3.1.0'
testCompile 'junit:junit:4.12'
}
+5 -2
View File
@@ -4,7 +4,10 @@ dependencies {
management platform(project(":spring-security-dependencies"))
api project(':spring-security-core')
api project(':spring-security-web')
api 'org.jasig.cas.client:cas-client-core'
api('org.jasig.cas.client:cas-client-core') {
exclude group: 'org.glassfish.jaxb', module: 'jaxb-core'
exclude group: 'javax.xml.bind', module: 'jaxb-api'
}
api 'org.springframework:spring-beans'
api 'org.springframework:spring-context'
api 'org.springframework:spring-core'
@@ -13,7 +16,7 @@ dependencies {
optional 'com.fasterxml.jackson.core:jackson-databind'
optional 'net.sf.ehcache:ehcache'
provided 'javax.servlet:javax.servlet-api'
provided 'jakarta.servlet:jakarta.servlet-api'
testImplementation "org.assertj:assertj-core"
testImplementation "org.junit.jupiter:junit-jupiter-api"
+13 -6
View File
@@ -37,9 +37,9 @@ dependencies {
optional'org.springframework:spring-websocket'
optional 'org.jetbrains.kotlin:kotlin-reflect'
optional 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
optional 'javax.annotation:jsr250-api'
optional 'jakarta.annotation:jakarta.annotation-api'
provided 'javax.servlet:javax.servlet-api'
provided 'jakarta.servlet:jakarta.servlet-api'
testImplementation project(':spring-security-aspects')
testImplementation project(':spring-security-cas')
@@ -62,8 +62,10 @@ dependencies {
testImplementation 'ch.qos.logback:logback-classic'
testImplementation 'io.projectreactor.netty:reactor-netty'
testImplementation 'io.rsocket:rsocket-transport-netty'
testImplementation 'javax.annotation:jsr250-api:1.0'
testImplementation 'javax.xml.bind:jaxb-api'
testImplementation 'jakarta.annotation:jakarta.annotation-api:1.0'
testImplementation "jakarta.inject:jakarta.inject-api"
testImplementation "jakarta.transaction:jakarta.transaction-api"
testImplementation 'jakarta.xml.bind:jakarta.xml.bind-api'
testImplementation 'ldapsdk:ldapsdk:4.1'
testImplementation('net.sourceforge.htmlunit:htmlunit') {
exclude group: 'commons-logging', module: 'commons-logging'
@@ -75,12 +77,18 @@ dependencies {
testImplementation "org.apache.directory.server:apacheds-server-jndi"
testImplementation 'org.apache.directory.shared:shared-ldap'
testImplementation 'org.eclipse.persistence:javax.persistence'
testImplementation 'org.hibernate:hibernate-entitymanager'
testImplementation('org.hibernate:hibernate-entitymanager') {
exclude group: 'javax.activation', module: 'javax.activation-api'
exclude group: 'javax.persistence', module: 'javax.persistence-api'
exclude group: 'javax.xml.bind', module: 'jaxb-api'
exclude group: 'org.jboss.spec.javax.transaction', module: 'jboss-transaction-api_1.2_spec'
}
testImplementation 'org.hsqldb:hsqldb'
testImplementation 'org.mockito:mockito-core'
testImplementation "org.mockito:mockito-inline"
testImplementation ('org.openid4java:openid4java-nodeps') {
exclude group: 'com.google.code.guice', module: 'guice'
exclude group: 'commons-logging', module: 'commons-logging'
}
testImplementation('org.seleniumhq.selenium:htmlunit-driver') {
exclude group: 'commons-logging', module: 'commons-logging'
@@ -89,7 +97,6 @@ dependencies {
exclude group: 'commons-logging', module: 'commons-logging'
exclude group: 'io.netty', module: 'netty'
}
testImplementation 'org.slf4j:jcl-over-slf4j'
testImplementation 'org.springframework.ldap:spring-ldap-core'
testImplementation 'org.springframework:spring-expression'
testImplementation 'org.springframework:spring-jdbc'
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2019 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -3283,20 +3283,26 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
*/
public final class MvcMatchersRequestMatcherConfigurer extends RequestMatcherConfigurer {
private final List<MvcRequestMatcher> mvcMatchers;
/**
* Creates a new instance
* @param context the {@link ApplicationContext} to use
* @param matchers the {@link MvcRequestMatcher} instances to set the servlet path
* on if {@link #servletPath(String)} is set.
* @param mvcMatchers the {@link MvcRequestMatcher} instances to set the servlet
* path on if {@link #servletPath(String)} is set.
* @param allMatchers the {@link RequestMatcher} instances to continue the
* configuration
*/
private MvcMatchersRequestMatcherConfigurer(ApplicationContext context, List<MvcRequestMatcher> matchers) {
private MvcMatchersRequestMatcherConfigurer(ApplicationContext context, List<MvcRequestMatcher> mvcMatchers,
List<RequestMatcher> allMatchers) {
super(context);
this.matchers = new ArrayList<>(matchers);
this.mvcMatchers = new ArrayList<>(mvcMatchers);
this.matchers = allMatchers;
}
public RequestMatcherConfigurer servletPath(String servletPath) {
for (RequestMatcher matcher : this.matchers) {
((MvcRequestMatcher) matcher).setServletPath(servletPath);
for (MvcRequestMatcher matcher : this.mvcMatchers) {
matcher.setServletPath(servletPath);
}
return this;
}
@@ -3321,7 +3327,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
public MvcMatchersRequestMatcherConfigurer mvcMatchers(HttpMethod method, String... mvcPatterns) {
List<MvcRequestMatcher> mvcMatchers = createMvcMatchers(method, mvcPatterns);
setMatchers(mvcMatchers);
return new MvcMatchersRequestMatcherConfigurer(getContext(), mvcMatchers);
return new MvcMatchersRequestMatcherConfigurer(getContext(), mvcMatchers, this.matchers);
}
@Override
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@ import java.util.ArrayList;
import java.util.List;
import javax.servlet.Filter;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
@@ -33,6 +34,7 @@ import org.springframework.http.HttpMethod;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.access.expression.SecurityExpressionHandler;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.SecurityBuilder;
@@ -47,9 +49,12 @@ import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AuthorizationManagerWebInvocationPrivilegeEvaluator;
import org.springframework.security.web.access.DefaultWebInvocationPrivilegeEvaluator;
import org.springframework.security.web.access.RequestMatcherDelegatingWebInvocationPrivilegeEvaluator;
import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
import org.springframework.security.web.access.intercept.AuthorizationFilter;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.debug.DebugFilter;
import org.springframework.security.web.firewall.HttpFirewall;
@@ -57,7 +62,9 @@ import org.springframework.security.web.firewall.RequestRejectedHandler;
import org.springframework.security.web.firewall.StrictHttpFirewall;
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcherEntry;
import org.springframework.util.Assert;
import org.springframework.web.context.ServletContextAware;
import org.springframework.web.filter.DelegatingFilterProxy;
/**
@@ -81,7 +88,7 @@ import org.springframework.web.filter.DelegatingFilterProxy;
* @see WebSecurityConfiguration
*/
public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter, WebSecurity>
implements SecurityBuilder<Filter>, ApplicationContextAware {
implements SecurityBuilder<Filter>, ApplicationContextAware, ServletContextAware {
private final Log logger = LogFactory.getLog(getClass());
@@ -108,6 +115,8 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
private Runnable postBuildAction = () -> {
};
private ServletContext servletContext;
/**
* Creates a new instance
* @param objectPostProcessor the {@link ObjectPostProcessor} to use
@@ -252,6 +261,8 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
* {@link WebSecurityConfigurerAdapter}.
* @param securityInterceptor the {@link FilterSecurityInterceptor} to use
* @return the {@link WebSecurity} for further customizations
* @deprecated Use {@link #privilegeEvaluator(WebInvocationPrivilegeEvaluator)}
* instead
*/
public WebSecurity securityInterceptor(FilterSecurityInterceptor securityInterceptor) {
this.filterSecurityInterceptor = securityInterceptor;
@@ -278,11 +289,24 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
+ ".addSecurityFilterChainBuilder directly");
int chainSize = this.ignoredRequests.size() + this.securityFilterChainBuilders.size();
List<SecurityFilterChain> securityFilterChains = new ArrayList<>(chainSize);
List<RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>>> requestMatcherPrivilegeEvaluatorsEntries = new ArrayList<>();
for (RequestMatcher ignoredRequest : this.ignoredRequests) {
securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
WebSecurity.this.logger.warn("You are asking Spring Security to ignore " + ignoredRequest
+ ". This is not recommended -- please use permitAll via HttpSecurity#authorizeHttpRequests instead.");
SecurityFilterChain securityFilterChain = new DefaultSecurityFilterChain(ignoredRequest);
securityFilterChains.add(securityFilterChain);
requestMatcherPrivilegeEvaluatorsEntries
.add(getRequestMatcherPrivilegeEvaluatorsEntry(securityFilterChain));
}
for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : this.securityFilterChainBuilders) {
securityFilterChains.add(securityFilterChainBuilder.build());
SecurityFilterChain securityFilterChain = securityFilterChainBuilder.build();
securityFilterChains.add(securityFilterChain);
requestMatcherPrivilegeEvaluatorsEntries
.add(getRequestMatcherPrivilegeEvaluatorsEntry(securityFilterChain));
}
if (this.privilegeEvaluator == null) {
this.privilegeEvaluator = new RequestMatcherDelegatingWebInvocationPrivilegeEvaluator(
requestMatcherPrivilegeEvaluatorsEntries);
}
FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
if (this.httpFirewall != null) {
@@ -306,6 +330,29 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
return result;
}
private RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>> getRequestMatcherPrivilegeEvaluatorsEntry(
SecurityFilterChain securityFilterChain) {
List<WebInvocationPrivilegeEvaluator> privilegeEvaluators = new ArrayList<>();
for (Filter filter : securityFilterChain.getFilters()) {
if (filter instanceof FilterSecurityInterceptor) {
DefaultWebInvocationPrivilegeEvaluator defaultWebInvocationPrivilegeEvaluator = new DefaultWebInvocationPrivilegeEvaluator(
(FilterSecurityInterceptor) filter);
defaultWebInvocationPrivilegeEvaluator.setServletContext(this.servletContext);
privilegeEvaluators.add(defaultWebInvocationPrivilegeEvaluator);
continue;
}
if (filter instanceof AuthorizationFilter) {
AuthorizationManager<HttpServletRequest> authorizationManager = ((AuthorizationFilter) filter)
.getAuthorizationManager();
AuthorizationManagerWebInvocationPrivilegeEvaluator evaluator = new AuthorizationManagerWebInvocationPrivilegeEvaluator(
authorizationManager);
evaluator.setServletContext(this.servletContext);
privilegeEvaluators.add(evaluator);
}
}
return new RequestMatcherEntry<>(securityFilterChain::matches, privilegeEvaluators);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.defaultWebSecurityExpressionHandler.setApplicationContext(applicationContext);
@@ -333,6 +380,11 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
}
}
@Override
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
/**
* An {@link IgnoredRequestConfigurer} that allows optionally configuring the
* {@link MvcRequestMatcher#setMethod(HttpMethod)}
@@ -128,8 +128,8 @@ public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAwa
}
/**
* Creates the {@link WebInvocationPrivilegeEvaluator} that is necessary for the JSP
* tag support.
* Creates the {@link WebInvocationPrivilegeEvaluator} that is necessary to evaluate
* privileges for a given web URI
* @return the {@link WebInvocationPrivilegeEvaluator}
*/
@Bean
@@ -297,8 +297,7 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
if (version != null) {
return version;
}
return Version.class.getModule().getDescriptor().version().map(Object::toString)
.orElseThrow(() -> new IllegalStateException("cannot determine OpenSAML version"));
return Version.getVersion();
}
private void registerDefaultAuthenticationProvider(B http) {
@@ -319,8 +319,7 @@ public final class Saml2LogoutConfigurer<H extends HttpSecurityBuilder<H>>
if (version != null) {
return version;
}
return Version.class.getModule().getDescriptor().version().map(Object::toString)
.orElseThrow(() -> new IllegalStateException("cannot determine OpenSAML version"));
return Version.getVersion();
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -33,6 +33,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
@@ -62,7 +63,7 @@ import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.DefaultWebInvocationPrivilegeEvaluator;
import org.springframework.security.web.access.RequestMatcherDelegatingWebInvocationPrivilegeEvaluator;
import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
import org.springframework.test.web.servlet.MockMvc;
@@ -84,6 +85,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
* @author Rob Winch
* @author Joe Grandja
* @author Evgeniy Cheban
* @author Marcus Da Coregio
*/
@ExtendWith(SpringTestContextExtension.class)
public class WebSecurityConfigurationTests {
@@ -218,10 +220,10 @@ public class WebSecurityConfigurationTests {
}
@Test
public void loadConfigWhenDefaultWebInvocationPrivilegeEvaluatorThenDefaultIsRegistered() {
public void loadConfigWhenDefaultWebInvocationPrivilegeEvaluatorThenRequestMatcherIsRegistered() {
this.spring.register(WebInvocationPrivilegeEvaluatorDefaultsConfig.class).autowire();
assertThat(this.spring.getContext().getBean(WebInvocationPrivilegeEvaluator.class))
.isInstanceOf(DefaultWebInvocationPrivilegeEvaluator.class);
.isInstanceOf(RequestMatcherDelegatingWebInvocationPrivilegeEvaluator.class);
}
@Test
@@ -229,7 +231,7 @@ public class WebSecurityConfigurationTests {
this.spring.register(AuthorizeRequestsFilterChainConfig.class).autowire();
assertThat(this.spring.getContext().getBean(WebInvocationPrivilegeEvaluator.class))
.isInstanceOf(DefaultWebInvocationPrivilegeEvaluator.class);
.isInstanceOf(RequestMatcherDelegatingWebInvocationPrivilegeEvaluator.class);
}
// SEC-2303
@@ -375,6 +377,69 @@ public class WebSecurityConfigurationTests {
assertThat(filterChains.get(1).matches(request)).isTrue();
}
@Test
public void loadConfigWhenTwoSecurityFilterChainsThenRequestMatcherDelegatingWebInvocationPrivilegeEvaluator() {
this.spring.register(TwoSecurityFilterChainConfig.class).autowire();
assertThat(this.spring.getContext().getBean(WebInvocationPrivilegeEvaluator.class))
.isInstanceOf(RequestMatcherDelegatingWebInvocationPrivilegeEvaluator.class);
}
@Test
public void loadConfigWhenTwoSecurityFilterChainDebugThenRequestMatcherDelegatingWebInvocationPrivilegeEvaluator() {
this.spring.register(TwoSecurityFilterChainConfig.class).autowire();
assertThat(this.spring.getContext().getBean(WebInvocationPrivilegeEvaluator.class))
.isInstanceOf(RequestMatcherDelegatingWebInvocationPrivilegeEvaluator.class);
}
// gh-10554
@Test
public void loadConfigWhenMultipleSecurityFilterChainsThenWebInvocationPrivilegeEvaluatorApplySecurity() {
this.spring.register(MultipleSecurityFilterChainConfig.class).autowire();
WebInvocationPrivilegeEvaluator privilegeEvaluator = this.spring.getContext()
.getBean(WebInvocationPrivilegeEvaluator.class);
assertUserPermissions(privilegeEvaluator);
assertAdminPermissions(privilegeEvaluator);
assertAnotherUserPermission(privilegeEvaluator);
}
// gh-10554
@Test
public void loadConfigWhenMultipleSecurityFilterChainAndIgnoringThenWebInvocationPrivilegeEvaluatorAcceptsNullAuthenticationOnIgnored() {
this.spring.register(MultipleSecurityFilterChainIgnoringConfig.class).autowire();
WebInvocationPrivilegeEvaluator privilegeEvaluator = this.spring.getContext()
.getBean(WebInvocationPrivilegeEvaluator.class);
assertUserPermissions(privilegeEvaluator);
assertAdminPermissions(privilegeEvaluator);
assertAnotherUserPermission(privilegeEvaluator);
// null authentication
assertThat(privilegeEvaluator.isAllowed("/user", null)).isFalse();
assertThat(privilegeEvaluator.isAllowed("/admin", null)).isFalse();
assertThat(privilegeEvaluator.isAllowed("/another", null)).isFalse();
assertThat(privilegeEvaluator.isAllowed("/ignoring1", null)).isTrue();
assertThat(privilegeEvaluator.isAllowed("/ignoring1/child", null)).isTrue();
}
private void assertAnotherUserPermission(WebInvocationPrivilegeEvaluator privilegeEvaluator) {
Authentication anotherUser = new TestingAuthenticationToken("anotherUser", "password", "ROLE_ANOTHER");
assertThat(privilegeEvaluator.isAllowed("/user", anotherUser)).isFalse();
assertThat(privilegeEvaluator.isAllowed("/admin", anotherUser)).isFalse();
assertThat(privilegeEvaluator.isAllowed("/another", anotherUser)).isTrue();
}
private void assertAdminPermissions(WebInvocationPrivilegeEvaluator privilegeEvaluator) {
Authentication admin = new TestingAuthenticationToken("admin", "password", "ROLE_ADMIN");
assertThat(privilegeEvaluator.isAllowed("/user", admin)).isFalse();
assertThat(privilegeEvaluator.isAllowed("/admin", admin)).isTrue();
assertThat(privilegeEvaluator.isAllowed("/another", admin)).isTrue();
}
private void assertUserPermissions(WebInvocationPrivilegeEvaluator privilegeEvaluator) {
Authentication user = new TestingAuthenticationToken("user", "password", "ROLE_USER");
assertThat(privilegeEvaluator.isAllowed("/user", user)).isTrue();
assertThat(privilegeEvaluator.isAllowed("/admin", user)).isFalse();
assertThat(privilegeEvaluator.isAllowed("/another", user)).isTrue();
}
@EnableWebSecurity
@Import(AuthenticationTestConfiguration.class)
static class SortedWebSecurityConfigurerAdaptersConfig {
@@ -1008,4 +1073,125 @@ public class WebSecurityConfigurationTests {
}
@EnableWebSecurity
static class TwoSecurityFilterChainConfig {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain path1(HttpSecurity http) throws Exception {
// @formatter:off
http
.requestMatchers((requests) -> requests.antMatchers("/path1/**"))
.authorizeRequests((requests) -> requests.anyRequest().authenticated());
// @formatter:on
return http.build();
}
@Bean
@Order(Ordered.LOWEST_PRECEDENCE)
public SecurityFilterChain permitAll(HttpSecurity http) throws Exception {
http.authorizeRequests((requests) -> requests.anyRequest().permitAll());
return http.build();
}
}
@EnableWebSecurity(debug = true)
static class TwoSecurityFilterChainDebugConfig {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain path1(HttpSecurity http) throws Exception {
// @formatter:off
http
.requestMatchers((requests) -> requests.antMatchers("/path1/**"))
.authorizeRequests((requests) -> requests.anyRequest().authenticated());
// @formatter:on
return http.build();
}
@Bean
@Order(Ordered.LOWEST_PRECEDENCE)
public SecurityFilterChain permitAll(HttpSecurity http) throws Exception {
http.authorizeRequests((requests) -> requests.anyRequest().permitAll());
return http.build();
}
}
@EnableWebSecurity
@Import(AuthenticationTestConfiguration.class)
static class MultipleSecurityFilterChainConfig {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain notAuthorized(HttpSecurity http) throws Exception {
// @formatter:off
http
.requestMatchers((requests) -> requests.antMatchers("/user"))
.authorizeRequests((requests) -> requests.anyRequest().hasRole("USER"));
// @formatter:on
return http.build();
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE + 1)
public SecurityFilterChain path1(HttpSecurity http) throws Exception {
// @formatter:off
http
.requestMatchers((requests) -> requests.antMatchers("/admin"))
.authorizeRequests((requests) -> requests.anyRequest().hasRole("ADMIN"));
// @formatter:on
return http.build();
}
@Bean
@Order(Ordered.LOWEST_PRECEDENCE)
public SecurityFilterChain permitAll(HttpSecurity http) throws Exception {
http.authorizeRequests((requests) -> requests.anyRequest().permitAll());
return http.build();
}
}
@EnableWebSecurity
@Import(AuthenticationTestConfiguration.class)
static class MultipleSecurityFilterChainIgnoringConfig {
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().antMatchers("/ignoring1/**");
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain notAuthorized(HttpSecurity http) throws Exception {
// @formatter:off
http
.requestMatchers((requests) -> requests.antMatchers("/user"))
.authorizeRequests((requests) -> requests.anyRequest().hasRole("USER"));
// @formatter:on
return http.build();
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE + 1)
public SecurityFilterChain admin(HttpSecurity http) throws Exception {
// @formatter:off
http
.requestMatchers((requests) -> requests.antMatchers("/admin"))
.authorizeRequests((requests) -> requests.anyRequest().hasRole("ADMIN"));
// @formatter:on
return http.build();
}
@Bean
@Order(Ordered.LOWEST_PRECEDENCE)
public SecurityFilterChain permitAll(HttpSecurity http) throws Exception {
http.authorizeRequests((requests) -> requests.anyRequest().permitAll());
return http.build();
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,17 +21,22 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.web.PortMapperImpl;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.channel.ChannelDecisionManagerImpl;
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
import org.springframework.security.web.access.channel.InsecureChannelProcessor;
import org.springframework.security.web.access.channel.SecureChannelProcessor;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.spy;
@@ -44,6 +49,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
*
* @author Rob Winch
* @author Eleftheria Stein
* @author Onur Kagan Ozcan
*/
@ExtendWith(SpringTestContextExtension.class)
public class ChannelSecurityConfigurerTests {
@@ -93,6 +99,24 @@ public class ChannelSecurityConfigurerTests {
this.mvc.perform(get("/")).andExpect(redirectedUrl("https://localhost/"));
}
// gh-10956
@Test
public void requestWhenRequiresChannelWithMultiMvcMatchersThenRedirectsToHttps() throws Exception {
this.spring.register(RequiresChannelMultiMvcMatchersConfig.class).autowire();
this.mvc.perform(get("/test-1")).andExpect(redirectedUrl("https://localhost/test-1"));
this.mvc.perform(get("/test-2")).andExpect(redirectedUrl("https://localhost/test-2"));
this.mvc.perform(get("/test-3")).andExpect(redirectedUrl("https://localhost/test-3"));
}
// gh-10956
@Test
public void requestWhenRequiresChannelWithMultiMvcMatchersInLambdaThenRedirectsToHttps() throws Exception {
this.spring.register(RequiresChannelMultiMvcMatchersInLambdaConfig.class).autowire();
this.mvc.perform(get("/test-1")).andExpect(redirectedUrl("https://localhost/test-1"));
this.mvc.perform(get("/test-2")).andExpect(redirectedUrl("https://localhost/test-2"));
this.mvc.perform(get("/test-3")).andExpect(redirectedUrl("https://localhost/test-3"));
}
@EnableWebSecurity
static class ObjectPostProcessorConfig extends WebSecurityConfigurerAdapter {
@@ -155,4 +179,59 @@ public class ChannelSecurityConfigurerTests {
}
@EnableWebSecurity
@EnableWebMvc
static class RequiresChannelMultiMvcMatchersConfig {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.portMapper()
.portMapper(new PortMapperImpl())
.and()
.requiresChannel()
.mvcMatchers("/test-1")
.requiresSecure()
.mvcMatchers("/test-2")
.requiresSecure()
.mvcMatchers("/test-3")
.requiresSecure()
.anyRequest()
.requiresInsecure();
// @formatter:on
return http.build();
}
}
@EnableWebSecurity
@EnableWebMvc
static class RequiresChannelMultiMvcMatchersInLambdaConfig {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.portMapper((port) -> port
.portMapper(new PortMapperImpl())
)
.requiresChannel((channel) -> channel
.mvcMatchers("/test-1")
.requiresSecure()
.mvcMatchers("/test-2")
.requiresSecure()
.mvcMatchers("/test-3")
.requiresSecure()
.anyRequest()
.requiresInsecure()
);
// @formatter:on
return http.build();
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,7 +23,10 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
@@ -33,6 +36,7 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
@@ -167,6 +171,38 @@ public class HttpSecurityRequestMatchersTests {
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
}
@Test
public void requestMatcherWhenMultiMvcMatcherInLambdaThenAllPathsAreDenied() throws Exception {
loadConfig(MultiMvcMatcherInLambdaConfig.class);
this.request.setRequestURI("/test-1");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setRequestURI("/test-2");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setRequestURI("/test-3");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
}
@Test
public void requestMatcherWhenMultiMvcMatcherThenAllPathsAreDenied() throws Exception {
loadConfig(MultiMvcMatcherConfig.class);
this.request.setRequestURI("/test-1");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setRequestURI("/test-2");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setRequestURI("/test-3");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
}
public void loadConfig(Class<?>... configs) {
this.context = new AnnotationConfigWebApplicationContext();
this.context.register(configs);
@@ -175,6 +211,101 @@ public class HttpSecurityRequestMatchersTests {
this.context.getAutowireCapableBeanFactory().autowireBean(this);
}
@EnableWebSecurity
@Configuration
@EnableWebMvc
static class MultiMvcMatcherInLambdaConfig {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
SecurityFilterChain first(HttpSecurity http) throws Exception {
// @formatter:off
http
.requestMatchers((requests) -> requests
.mvcMatchers("/test-1")
.mvcMatchers("/test-2")
.mvcMatchers("/test-3")
)
.authorizeRequests((authorize) -> authorize.anyRequest().denyAll())
.httpBasic(withDefaults());
// @formatter:on
return http.build();
}
@Bean
SecurityFilterChain second(HttpSecurity http) throws Exception {
// @formatter:off
http
.requestMatchers((requests) -> requests
.mvcMatchers("/test-1")
)
.authorizeRequests((authorize) -> authorize
.anyRequest().permitAll()
);
// @formatter:on
return http.build();
}
@RestController
static class PathController {
@RequestMapping({ "/test-1", "/test-2", "/test-3" })
String path() {
return "path";
}
}
}
@EnableWebSecurity
@Configuration
@EnableWebMvc
static class MultiMvcMatcherConfig {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
SecurityFilterChain first(HttpSecurity http) throws Exception {
// @formatter:off
http
.requestMatchers()
.mvcMatchers("/test-1")
.mvcMatchers("/test-2")
.mvcMatchers("/test-3")
.and()
.authorizeRequests()
.anyRequest().denyAll()
.and()
.httpBasic(withDefaults());
// @formatter:on
return http.build();
}
@Bean
SecurityFilterChain second(HttpSecurity http) throws Exception {
// @formatter:off
http
.requestMatchers()
.mvcMatchers("/test-1")
.and()
.authorizeRequests()
.anyRequest().permitAll();
// @formatter:on
return http.build();
}
@RestController
static class PathController {
@RequestMapping({ "/test-1", "/test-2", "/test-3" })
String path() {
return "path";
}
}
}
@EnableWebSecurity
@Configuration
@EnableWebMvc
@@ -21,11 +21,13 @@ import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import javax.security.auth.x500.X500Principal;
import javax.servlet.http.HttpServletRequest;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import sun.security.x509.X500Name;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
@@ -240,12 +242,8 @@ public class NamespaceHttpX509Tests {
}
private String extractCommonName(X509Certificate certificate) {
try {
return ((X500Name) certificate.getSubjectDN()).getCommonName();
}
catch (Exception ex) {
throw new IllegalArgumentException(ex);
}
X500Principal principal = certificate.getSubjectX500Principal();
return new X500Name(principal.getName()).getRDNs(BCStyle.CN)[0].getFirst().getValue().toString();
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,8 @@
package org.springframework.security.config.annotation.web.configurers;
import java.util.Base64;
import javax.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.AfterEach;
@@ -23,16 +25,24 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockServletContext;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
@@ -125,6 +135,35 @@ public class UrlAuthorizationConfigurerTests {
loadConfig(AnonymousUrlAuthorizationConfig.class);
}
// gh-10956
@Test
public void multiMvcMatchersConfig() throws Exception {
loadConfig(MultiMvcMatcherConfig.class);
this.request.addHeader("Authorization",
"Basic " + new String(Base64.getEncoder().encode("user:password".getBytes())));
this.request.setRequestURI("/test-1");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN);
setup();
this.request.addHeader("Authorization",
"Basic " + new String(Base64.getEncoder().encode("user:password".getBytes())));
this.request.setRequestURI("/test-2");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN);
setup();
this.request.addHeader("Authorization",
"Basic " + new String(Base64.getEncoder().encode("user:password".getBytes())));
this.request.setRequestURI("/test-3");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN);
setup();
this.request.addHeader("Authorization",
"Basic " + new String(Base64.getEncoder().encode("user:password".getBytes())));
this.request.setRequestURI("/test-x");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
}
public void loadConfig(Class<?>... configs) {
this.context = new AnnotationConfigWebApplicationContext();
this.context.register(configs);
@@ -228,4 +267,41 @@ public class UrlAuthorizationConfigurerTests {
}
@EnableWebSecurity
@EnableWebMvc
static class MultiMvcMatcherConfig {
@Bean
SecurityFilterChain security(HttpSecurity http, ApplicationContext context) throws Exception {
// @formatter:off
http
.httpBasic(Customizer.withDefaults())
.apply(new UrlAuthorizationConfigurer<>(context)).getRegistry()
.mvcMatchers("/test-1").hasRole("ADMIN")
.mvcMatchers("/test-2").hasRole("ADMIN")
.mvcMatchers("/test-3").hasRole("ADMIN")
.anyRequest().hasRole("USER");
// @formatter:on
return http.build();
}
@Bean
UserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder().username("user").password("password").roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
@RestController
static class PathController {
@RequestMapping({ "/test-1", "/test-2", "/test-3", "/test-x" })
String path() {
return "path";
}
}
}
}
+1 -2
View File
@@ -13,7 +13,7 @@ dependencies {
optional 'com.fasterxml.jackson.core:jackson-databind'
optional 'io.projectreactor:reactor-core'
optional 'javax.annotation:jsr250-api'
optional 'jakarta.annotation:jakarta.annotation-api'
optional 'net.sf.ehcache:ehcache'
optional 'org.aspectj:aspectjrt'
optional 'org.springframework:spring-jdbc'
@@ -31,7 +31,6 @@ dependencies {
testImplementation "org.mockito:mockito-junit-jupiter"
testImplementation "org.springframework:spring-test"
testImplementation 'org.skyscreamer:jsonassert'
testImplementation 'org.slf4j:jcl-over-slf4j'
testImplementation 'org.springframework:spring-test'
testRuntimeOnly 'org.hsqldb:hsqldb'
@@ -133,8 +133,10 @@ public final class AuthorityAuthorizationManager<T> implements AuthorizationMana
private boolean isAuthorized(Authentication authentication) {
for (GrantedAuthority grantedAuthority : authentication.getAuthorities()) {
if (this.authorities.contains(grantedAuthority)) {
return true;
for (GrantedAuthority authority : this.authorities) {
if (authority.getAuthority().equals(grantedAuthority.getAuthority())) {
return true;
}
}
}
return false;
@@ -45,9 +45,10 @@ public class AuthorityReactiveAuthorizationManager<T> implements ReactiveAuthori
@Override
public Mono<AuthorizationDecision> check(Mono<Authentication> authentication, T object) {
// @formatter:off
return authentication.filter((a) -> a.isAuthenticated())
return authentication.filter(Authentication::isAuthenticated)
.flatMapIterable(Authentication::getAuthorities)
.any(this.authorities::contains)
.map(GrantedAuthority::getAuthority)
.any((grantedAuthority) -> this.authorities.stream().anyMatch((authority) -> authority.getAuthority().equals(grantedAuthority)))
.map((granted) -> ((AuthorizationDecision) new AuthorityAuthorizationDecision(granted, this.authorities)))
.defaultIfEmpty(new AuthorityAuthorizationDecision(false, this.authorities));
// @formatter:on
@@ -16,12 +16,14 @@
package org.springframework.security.authorization;
import java.util.Collections;
import java.util.function.Supplier;
import org.junit.jupiter.api.Test;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
@@ -133,6 +135,30 @@ public class AuthorityAuthorizationManagerTests {
assertThat(manager.check(authentication, object).isGranted()).isFalse();
}
@Test
public void hasAuthorityWhenUserHasCustomAuthorityThenGrantedDecision() {
AuthorityAuthorizationManager<Object> manager = AuthorityAuthorizationManager.hasAuthority("ADMIN");
GrantedAuthority customGrantedAuthority = () -> "ADMIN";
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password",
Collections.singletonList(customGrantedAuthority));
Object object = new Object();
assertThat(manager.check(authentication, object).isGranted()).isTrue();
}
@Test
public void hasAuthorityWhenUserHasNotCustomAuthorityThenDeniedDecision() {
AuthorityAuthorizationManager<Object> manager = AuthorityAuthorizationManager.hasAuthority("ADMIN");
GrantedAuthority customGrantedAuthority = () -> "USER";
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password",
Collections.singletonList(customGrantedAuthority));
Object object = new Object();
assertThat(manager.check(authentication, object).isGranted()).isFalse();
}
@Test
public void hasAnyRoleWhenUserHasAnyRoleThenGrantedDecision() {
AuthorityAuthorizationManager<Object> manager = AuthorityAuthorizationManager.hasAnyRole("ADMIN", "USER");
@@ -27,6 +27,7 @@ import reactor.test.StepVerifier;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
@@ -88,6 +89,24 @@ public class AuthorityReactiveAuthorizationManagerTests {
assertThat(granted).isTrue();
}
@Test
public void checkWhenHasCustomAuthorityAndAuthorizedThenReturnTrue() {
GrantedAuthority customGrantedAuthority = () -> "ADMIN";
this.authentication = new TestingAuthenticationToken("rob", "secret",
Collections.singletonList(customGrantedAuthority));
boolean granted = this.manager.check(Mono.just(this.authentication), null).block().isGranted();
assertThat(granted).isTrue();
}
@Test
public void checkWhenHasCustomAuthorityAndAuthenticatedAndWrongAuthoritiesThenReturnFalse() {
GrantedAuthority customGrantedAuthority = () -> "USER";
this.authentication = new TestingAuthenticationToken("rob", "secret",
Collections.singletonList(customGrantedAuthority));
boolean granted = this.manager.check(Mono.just(this.authentication), null).block().isGranted();
assertThat(granted).isFalse();
}
@Test
public void checkWhenHasRoleAndAuthorizedThenReturnTrue() {
this.manager = AuthorityReactiveAuthorizationManager.hasRole("ADMIN");
@@ -18,6 +18,7 @@ package org.springframework.security.core;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -60,15 +61,24 @@ public class SpringSecurityCoreVersionTests {
@BeforeEach
public void setup() throws Exception {
Field logger = ReflectionUtils.findField(SpringSecurityCoreVersion.class, "logger");
StaticFinalReflectionUtils.setField(logger, this.logger);
setFinalStaticField(SpringSecurityCoreVersion.class, "logger", this.logger);
}
@AfterEach
public void cleanup() throws Exception {
System.clearProperty(getDisableChecksProperty());
Field logger = ReflectionUtils.findField(SpringSecurityCoreVersion.class, "logger");
StaticFinalReflectionUtils.setField(logger, LogFactory.getLog(SpringSecurityCoreVersion.class));
setFinalStaticField(SpringSecurityCoreVersion.class, "logger",
LogFactory.getLog(SpringSecurityCoreVersion.class));
}
private static void setFinalStaticField(Class<?> clazz, String fieldName, Object value)
throws ReflectiveOperationException {
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
Field modifiers = Field.class.getDeclaredField("modifiers");
modifiers.setAccessible(true);
modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(null, value);
}
@Test
@@ -1,115 +0,0 @@
/*
* Copyright 2008 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.core;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.security.PrivilegedAction;
import sun.misc.Unsafe;
import org.springframework.objenesis.instantiator.util.UnsafeUtils;
/**
* Used for setting static variables even if they are private static final.
*
* The code in this class has been adopted from Powermock's <a href=
* "https://github.com/noushadali/powermock/blob/powermock-1.5.4/reflect/src/main/java/org/powermock/reflect/internal/WhiteboxImpl.java#L326">WhiteboxImpl</a>.
*
* @author Rob Winch
*/
final class StaticFinalReflectionUtils {
/**
* Used to support setting static fields that are final using Java's Unsafe. If the
* field is not static final, use
* {@link org.springframework.test.util.ReflectionTestUtils}.
* @param field the field to set
* @param newValue the new value
*/
static void setField(final Field field, final Object newValue) {
try {
field.setAccessible(true);
int fieldModifiersMask = field.getModifiers();
boolean isFinalModifierPresent = (fieldModifiersMask & Modifier.FINAL) == Modifier.FINAL;
if (isFinalModifierPresent) {
AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
try {
Unsafe unsafe = UnsafeUtils.getUnsafe();
long offset = unsafe.staticFieldOffset(field);
Object base = unsafe.staticFieldBase(field);
setFieldUsingUnsafe(base, field.getType(), offset, newValue, unsafe);
return null;
}
catch (Throwable thrown) {
throw new RuntimeException(thrown);
}
}
});
}
else {
field.set(null, newValue);
}
}
catch (SecurityException ex) {
throw new RuntimeException(ex);
}
catch (IllegalAccessException ex) {
throw new RuntimeException(ex);
}
catch (IllegalArgumentException ex) {
throw new RuntimeException(ex);
}
}
private static void setFieldUsingUnsafe(Object base, Class type, long offset, Object newValue, Unsafe unsafe) {
if (type == Integer.TYPE) {
unsafe.putInt(base, offset, ((Integer) newValue));
}
else if (type == Short.TYPE) {
unsafe.putShort(base, offset, ((Short) newValue));
}
else if (type == Long.TYPE) {
unsafe.putLong(base, offset, ((Long) newValue));
}
else if (type == Byte.TYPE) {
unsafe.putByte(base, offset, ((Byte) newValue));
}
else if (type == Boolean.TYPE) {
unsafe.putBoolean(base, offset, ((Boolean) newValue));
}
else if (type == Float.TYPE) {
unsafe.putFloat(base, offset, ((Float) newValue));
}
else if (type == Double.TYPE) {
unsafe.putDouble(base, offset, ((Double) newValue));
}
else if (type == Character.TYPE) {
unsafe.putChar(base, offset, ((Character) newValue));
}
else {
unsafe.putObject(base, offset, newValue);
}
}
private StaticFinalReflectionUtils() {
}
}
@@ -526,35 +526,47 @@ public class BCrypt {
* @param safety bit 16 is set when the safety measure is requested
* @return an array containing the binary hashed password
*/
private byte[] crypt_raw(byte password[], byte salt[], int log_rounds, boolean sign_ext_bug, int safety) {
int rounds, i, j;
private byte[] crypt_raw(byte password[], byte salt[], int log_rounds, boolean sign_ext_bug, int safety,
boolean for_check) {
int cdata[] = bf_crypt_ciphertext.clone();
int clen = cdata.length;
byte ret[];
long rounds;
if (log_rounds < 4 || log_rounds > 31) {
throw new IllegalArgumentException("Bad number of rounds");
if (!for_check) {
throw new IllegalArgumentException("Bad number of rounds");
}
if (log_rounds != 0) {
throw new IllegalArgumentException("Bad number of rounds");
}
rounds = 0;
}
rounds = 1 << log_rounds;
else {
rounds = roundsForLogRounds(log_rounds);
if (rounds < 16 || rounds > Integer.MAX_VALUE) {
throw new IllegalArgumentException("Bad number of rounds");
}
}
if (salt.length != BCRYPT_SALT_LEN) {
throw new IllegalArgumentException("Bad salt length");
}
init_key();
ekskey(salt, password, sign_ext_bug, safety);
for (i = 0; i < rounds; i++) {
for (int i = 0; i < rounds; i++) {
key(password, sign_ext_bug, safety);
key(salt, false, safety);
}
for (i = 0; i < 64; i++) {
for (j = 0; j < (clen >> 1); j++) {
for (int i = 0; i < 64; i++) {
for (int j = 0; j < (clen >> 1); j++) {
encipher(cdata, j << 1);
}
}
ret = new byte[clen * 4];
for (i = 0, j = 0; i < clen; i++) {
byte[] ret = new byte[clen * 4];
for (int i = 0, j = 0; i < clen; i++) {
ret[j++] = (byte) ((cdata[i] >> 24) & 0xff);
ret[j++] = (byte) ((cdata[i] >> 16) & 0xff);
ret[j++] = (byte) ((cdata[i] >> 8) & 0xff);
@@ -563,6 +575,10 @@ public class BCrypt {
return ret;
}
private static String hashpwforcheck(byte[] passwordb, String salt) {
return hashpw(passwordb, salt, true);
}
/**
* Hash a password using the OpenBSD bcrypt scheme
* @param password the password to hash
@@ -584,6 +600,10 @@ public class BCrypt {
* @return the hashed password
*/
public static String hashpw(byte passwordb[], String salt) {
return hashpw(passwordb, salt, false);
}
private static String hashpw(byte passwordb[], String salt, boolean for_check) {
BCrypt B;
String real_salt;
byte saltb[], hashed[];
@@ -633,7 +653,7 @@ public class BCrypt {
}
B = new BCrypt();
hashed = B.crypt_raw(passwordb, saltb, rounds, minor == 'x', minor == 'a' ? 0x10000 : 0);
hashed = B.crypt_raw(passwordb, saltb, rounds, minor == 'x', minor == 'a' ? 0x10000 : 0, for_check);
rs.append("$2");
if (minor >= 'a') {
@@ -740,7 +760,8 @@ public class BCrypt {
* @return true if the passwords match, false otherwise
*/
public static boolean checkpw(String plaintext, String hashed) {
return equalsNoEarlyReturn(hashed, hashpw(plaintext, hashed));
byte[] passwordb = plaintext.getBytes(StandardCharsets.UTF_8);
return equalsNoEarlyReturn(hashed, hashpwforcheck(passwordb, hashed));
}
/**
@@ -751,7 +772,7 @@ public class BCrypt {
* @since 5.3
*/
public static boolean checkpw(byte[] passwordb, String hashed) {
return equalsNoEarlyReturn(hashed, hashpw(passwordb, hashed));
return equalsNoEarlyReturn(hashed, hashpwforcheck(passwordb, hashed));
}
static boolean equalsNoEarlyReturn(String a, String b) {
@@ -208,4 +208,18 @@ public class BCryptPasswordEncoderTests {
assertThatIllegalArgumentException().isThrownBy(() -> encoder.matches(null, "does-not-matter"));
}
@Test
public void upgradeWhenNoRoundsThenTrue() {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
assertThat(encoder.upgradeEncoding("$2a$00$9N8N35BVs5TLqGL3pspAte5OWWA2a2aZIs.EGp7At7txYakFERMue")).isTrue();
}
@Test
public void checkWhenNoRoundsThenTrue() {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
assertThat(encoder.matches("password", "$2a$00$9N8N35BVs5TLqGL3pspAte5OWWA2a2aZIs.EGp7At7txYakFERMue"))
.isTrue();
assertThat(encoder.matches("wrong", "$2a$00$9N8N35BVs5TLqGL3pspAte5OWWA2a2aZIs.EGp7At7txYakFERMue")).isFalse();
}
}
@@ -456,4 +456,11 @@ public class BCryptTests {
assertThat(BCrypt.equalsNoEarlyReturn("test", "pass")).isFalse();
}
@Test
public void checkpwWhenZeroRoundsThenMatches() {
String password = "$2a$00$9N8N35BVs5TLqGL3pspAte5OWWA2a2aZIs.EGp7At7txYakFERMue";
assertThat(BCrypt.checkpw("password", password)).isTrue();
assertThat(BCrypt.checkpw("wrong", password)).isFalse();
}
}
+1 -1
View File
@@ -3,7 +3,7 @@ apply plugin: 'io.spring.convention.spring-module'
dependencies {
management platform(project(":spring-security-dependencies"))
api project(':spring-security-core')
api 'javax.xml.bind:jaxb-api'
api 'jakarta.xml.bind:jakarta.xml.bind-api'
api 'org.springframework.data:spring-data-commons'
api 'org.springframework:spring-core'
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,13 @@
package org.springframework.security.data.repository.query;
import org.springframework.data.spel.spi.EvaluationContextExtension;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.access.expression.DenyAllPermissionEvaluator;
import org.springframework.security.access.expression.SecurityExpressionRoot;
import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
@@ -77,12 +83,21 @@ import org.springframework.security.core.context.SecurityContextHolder;
* it.
*
* @author Rob Winch
* @author Evgeniy Cheban
* @since 4.0
*/
public class SecurityEvaluationContextExtension implements EvaluationContextExtension {
private Authentication authentication;
private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
private RoleHierarchy roleHierarchy = new NullRoleHierarchy();
private PermissionEvaluator permissionEvaluator = new DenyAllPermissionEvaluator();
private String defaultRolePrefix = "ROLE_";
/**
* Creates a new instance that uses the current {@link Authentication} found on the
* {@link org.springframework.security.core.context.SecurityContextHolder}.
@@ -106,8 +121,13 @@ public class SecurityEvaluationContextExtension implements EvaluationContextExte
@Override
public SecurityExpressionRoot getRootObject() {
Authentication authentication = getAuthentication();
return new SecurityExpressionRoot(authentication) {
SecurityExpressionRoot root = new SecurityExpressionRoot(authentication) {
};
root.setTrustResolver(this.trustResolver);
root.setRoleHierarchy(this.roleHierarchy);
root.setPermissionEvaluator(this.permissionEvaluator);
root.setDefaultRolePrefix(this.defaultRolePrefix);
return root;
}
private Authentication getAuthentication() {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 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.
@@ -20,7 +20,10 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.security.access.expression.DenyAllPermissionEvaluator;
import org.springframework.security.access.expression.SecurityExpressionRoot;
import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
@@ -69,6 +72,17 @@ public class SecurityEvaluationContextExtensionTests {
assertThat(getRoot().getAuthentication()).isSameAs(explicit);
}
@Test
public void getRootObjectWhenAdditionalFieldsNotSetThenVerifyDefaults() {
TestingAuthenticationToken explicit = new TestingAuthenticationToken("explicit", "password", "ROLE_EXPLICIT");
this.securityExtension = new SecurityEvaluationContextExtension(explicit);
SecurityExpressionRoot root = getRoot();
assertThat(root).extracting("trustResolver").isInstanceOf(AuthenticationTrustResolverImpl.class);
assertThat(root).extracting("roleHierarchy").isInstanceOf(NullRoleHierarchy.class);
assertThat(root).extracting("permissionEvaluator").isInstanceOf(DenyAllPermissionEvaluator.class);
assertThat(root).extracting("defaultRolePrefix").isEqualTo("ROLE_");
}
private SecurityExpressionRoot getRoot() {
return this.securityExtension.getRootObject();
}
+21 -21
View File
@@ -8,15 +8,15 @@ javaPlatform {
dependencies {
api platform("org.springframework:spring-framework-bom:$springFrameworkVersion")
api platform("io.projectreactor:reactor-bom:2020.0.13")
api platform("io.rsocket:rsocket-bom:1.1.1")
api platform("org.junit:junit-bom:5.8.1")
api platform("org.springframework.data:spring-data-bom:2021.1.0")
api platform("io.projectreactor:reactor-bom:2020.0.20")
api platform("io.rsocket:rsocket-bom:1.1.2")
api platform("org.junit:junit-bom:5.8.2")
api platform("org.springframework.data:spring-data-bom:2021.1.5")
api platform("org.jetbrains.kotlin:kotlin-bom:$kotlinVersion")
api platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.5.2")
api platform("com.fasterxml.jackson:jackson-bom:2.13.0")
api platform("com.fasterxml.jackson:jackson-bom:2.13.3")
constraints {
api "ch.qos.logback:logback-classic:1.2.7"
api "ch.qos.logback:logback-classic:1.2.11"
api "com.google.inject:guice:3.0"
api "com.nimbusds:nimbus-jose-jwt:9.14"
api "com.nimbusds:oauth2-oidc-sdk:9.19"
@@ -25,14 +25,15 @@ dependencies {
api "com.unboundid:unboundid-ldapsdk:4.0.14"
api "commons-codec:commons-codec:1.15"
api "commons-collections:commons-collections:3.2.2"
api "commons-logging:commons-logging:1.2"
api "io.mockk:mockk:1.12.0"
api "io.mockk:mockk:1.12.4"
api "io.projectreactor.tools:blockhound:1.0.6.RELEASE"
api "javax.annotation:jsr250-api:1.0"
api "javax.servlet.jsp.jstl:javax.servlet.jsp.jstl-api:1.2.2"
api "javax.servlet.jsp:javax.servlet.jsp-api:2.3.3"
api "javax.servlet:javax.servlet-api:4.0.1"
api "javax.xml.bind:jaxb-api:2.3.1"
api "jakarta.inject:jakarta.inject-api:1.0.5"
api "jakarta.annotation:jakarta.annotation-api:1.3.5"
api "jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api:1.2.7"
api "jakarta.servlet.jsp:jakarta.servlet.jsp-api:2.3.6"
api "jakarta.servlet:jakarta.servlet-api:4.0.4"
api "jakarta.transaction:jakarta.transaction-api:1.3.3"
api "jakarta.xml.bind:jakarta.xml.bind-api:2.3.3"
api "ldapsdk:ldapsdk:4.1"
api "net.sf.ehcache:ehcache:2.10.9.2"
api "net.sourceforge.htmlunit:htmlunit:2.54.0"
@@ -49,13 +50,13 @@ dependencies {
api "org.assertj:assertj-core:3.21.0"
api "org.bouncycastle:bcpkix-jdk15on:1.69"
api "org.bouncycastle:bcprov-jdk15on:1.69"
api "org.eclipse.jetty:jetty-server:9.4.44.v20210927"
api "org.eclipse.jetty:jetty-servlet:9.4.44.v20210927"
api "org.eclipse.jetty:jetty-server:9.4.46.v20220331"
api "org.eclipse.jetty:jetty-servlet:9.4.46.v20220331"
api "org.eclipse.persistence:javax.persistence:2.2.1"
api "org.hamcrest:hamcrest:2.2"
api "org.hibernate:hibernate-entitymanager:5.6.1.Final"
api "org.hibernate:hibernate-entitymanager:5.6.9.Final"
api "org.hsqldb:hsqldb:2.6.1"
api "org.jasig.cas.client:cas-client-core:3.6.2"
api "org.jasig.cas.client:cas-client-core:3.6.4"
api "org.mockito:mockito-core:3.12.4"
api "org.mockito:mockito-inline:3.12.4"
api "org.mockito:mockito-junit-jupiter:3.12.4"
@@ -68,10 +69,9 @@ dependencies {
api "org.seleniumhq.selenium:selenium-java:3.141.59"
api "org.seleniumhq.selenium:selenium-support:3.141.59"
api "org.skyscreamer:jsonassert:1.5.0"
api "org.slf4j:jcl-over-slf4j:1.7.32"
api "org.slf4j:log4j-over-slf4j:1.7.32"
api "org.slf4j:slf4j-api:1.7.32"
api "org.springframework.ldap:spring-ldap-core:2.3.4.RELEASE"
api "org.slf4j:log4j-over-slf4j:1.7.36"
api "org.slf4j:slf4j-api:1.7.36"
api "org.springframework.ldap:spring-ldap-core:2.3.8.RELEASE"
api "org.synchronoss.cloud:nio-multipart-parser:1.1.0"
}
}
+1 -2
View File
@@ -1,3 +1,2 @@
name: ROOT
version: '5.6.1'
prerelease: '-SNAPSHOT'
version: '5.6.6'
+1
View File
@@ -124,6 +124,7 @@
*** xref:reactive/authentication/x509.adoc[X.509 Authentication]
*** xref:reactive/authentication/logout.adoc[Logout]
** Authorization
*** xref:reactive/authorization/authorize-http-requests.adoc[Authorize HTTP Requests]
*** xref:reactive/authorization/method.adoc[EnableReactiveMethodSecurity]
** xref:reactive/oauth2/index.adoc[OAuth2]
*** xref:reactive/oauth2/login/index.adoc[OAuth2 Log In]
@@ -463,7 +463,7 @@ You should instead migrate to using `DelegatingPasswordEncoder` to support secur
[source,java,role="primary"]
----
@Bean
public static NoOpPasswordEncoder passwordEncoder() {
public static PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
----
@@ -198,10 +198,13 @@ The password package of the spring-security-crypto module provides support for e
[source,java]
----
public interface PasswordEncoder {
String encode(CharSequence rawPassword);
String encode(String rawPassword);
boolean matches(CharSequence rawPassword, String encodedPassword);
boolean matches(String rawPassword, String encodedPassword);
default boolean upgradeEncoding(String encodedPassword) {
return false;
}
}
----
@@ -53,7 +53,7 @@ If you wish to override the Spring Security version, you may do so by providing
<properties>
<!-- ... -->
<spring-security.version>{spring-security-version}</spring-security.version>
</dependencies>
</properties>
----
====
@@ -68,7 +68,7 @@ You can do so by adding a Maven property, as the following example shows:
<properties>
<!-- ... -->
<spring.version>{spring-core-version}</spring.version>
</dependencies>
</properties>
----
====
@@ -0,0 +1,104 @@
[[servlet-authorization-authorizationfilter]]
= Authorize ServerHttpRequest
Spring Security provides support for authorizing the incoming HTTP requests.
By default, Spring Securitys authorization will require all requests to be authenticated.
The explicit configuration looks like:
.All Requests Require Authenticated User
====
.Java
[source,java,role="primary"]
----
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange(exchanges -> exchanges
.anyExchange().authenticated()
)
.httpBasic(withDefaults())
.formLogin(withDefaults());
return http.build();
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Bean
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
authorizeExchange {
authorize(anyExchange, authenticated)
}
formLogin { }
httpBasic { }
}
}
----
====
We can configure Spring Security to have different rules by adding more rules in order of precedence.
.Multiple Authorize Requests Rules
====
.Java
[source,java,role="primary"]
----
import static org.springframework.security.authorization.AuthorityReactiveAuthorizationManager.hasRole;
// ...
@Bean
SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) {
// @formatter:off
http
// ...
.authorizeExchange((authorize) -> authorize // <1>
.pathMatchers("/resources/**", "/signup", "/about").permitAll() // <2>
.pathMatchers("/admin/**").hasRole("ADMIN") // <3>
.pathMatchers("/db/**").access((authentication, context) -> // <4>
hasRole("ADMIN").check(authentication, context)
.filter(decision -> !decision.isGranted())
.switchIfEmpty(hasRole("DBA").check(authentication, context))
)
.anyExchange().denyAll() // <5>
);
// @formatter:on
return http.build();
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Bean
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
authorizeExchange { // <1>
authorize(pathMatchers("/resources/**", "/signup", "/about"), permitAll) // <2>
authorize("/admin/**", hasRole("ADMIN")) // <3>
authorize("/db/**", { authentication, context -> // <4>
hasRole("ADMIN").check(authentication, context)
.filter({ decision -> !decision.isGranted() })
.switchIfEmpty(hasRole("DBA").check(authentication, context))
})
authorize(anyExchange, denyAll) // <5>
}
// ...
}
}
----
====
<1> There are multiple authorization rules specified.
Each rule is considered in the order they were declared.
<2> We specified multiple URL patterns that any user can access.
Specifically, any user can access a request if the URL starts with "/resources/", equals "/signup", or equals "/about".
<3> Any URL that starts with "/admin/" will be restricted to users who have the authority "ROLE_ADMIN".
You will notice that since we are invoking the `hasRole` method we do not need to specify the "ROLE_" prefix.
<4> Any URL that starts with "/db/" requires the user to have both "ROLE_ADMIN" and "ROLE_DBA".
This demonstrates the flexibility of providing a custom `ReactiveAuthorizationManager` allowing us to implement arbitrary authorization logic.
For simplicity, the sample uses a lambda and delegate to the existing `AuthorityReactiveAuthorizationManager.hasRole` implementation.
However, in a real world situation applications would likely implement the logic in a proper class implementing `ReactiveAuthorizationManager`.
<5> Any URL that has not already been matched on is denied access.
This is a good strategy if you do not want to accidentally forget to update your authorization rules.
@@ -162,7 +162,7 @@ class SecurityConfig {
@Bean
fun securityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
http {
return http {
authorizeExchange {
authorize(anyExchange, authenticated)
}
@@ -170,8 +170,6 @@ class SecurityConfig {
authorizationRequestResolver = authorizationRequestResolver(customClientRegistrationRepository)
}
}
return http.build()
}
private fun authorizationRequestResolver(
@@ -282,13 +280,11 @@ class OAuth2ClientSecurityConfig {
@Bean
fun securityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
http {
return http {
oauth2Client {
authorizationRequestRepository = authorizationRequestRepository()
}
}
return http.build()
}
}
----
@@ -363,13 +359,11 @@ class OAuth2ClientSecurityConfig {
@Bean
fun securityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
http {
return http {
oauth2Client {
authenticationManager = authorizationCodeAuthenticationManager()
}
}
return http.build()
}
private fun authorizationCodeAuthenticationManager(): ReactiveAuthenticationManager {
@@ -55,7 +55,7 @@ class OAuth2ClientSecurityConfig {
@Bean
fun securityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
http {
return http {
oauth2Client {
clientRegistrationRepository = clientRegistrationRepository()
authorizedClientRepository = authorizedClientRepository()
@@ -64,8 +64,6 @@ class OAuth2ClientSecurityConfig {
authenticationManager = authenticationManager()
}
}
return http.build()
}
}
----
@@ -60,7 +60,7 @@ class OAuth2LoginSecurityConfig {
@Bean
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
http {
return http {
oauth2Login {
authenticationConverter = authenticationConverter()
authenticationMatcher = authenticationMatcher()
@@ -75,8 +75,6 @@ class OAuth2LoginSecurityConfig {
securityContextRepository = securityContextRepository()
}
}
return http.build()
}
}
----
@@ -158,7 +156,7 @@ class OAuth2LoginSecurityConfig {
@Bean
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
http {
return http {
exceptionHandling {
authenticationEntryPoint = RedirectServerAuthenticationEntryPoint("/login/oauth2")
}
@@ -166,8 +164,6 @@ class OAuth2LoginSecurityConfig {
authorizationRequestResolver = authorizationRequestResolver()
}
}
return http.build()
}
private fun authorizationRequestResolver(): ServerOAuth2AuthorizationRequestResolver {
@@ -243,13 +239,11 @@ class OAuth2LoginSecurityConfig {
@Bean
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
http {
return http {
oauth2Login {
authenticationMatcher = PathPatternParserServerWebExchangeMatcher("/login/oauth2/callback/{registrationId}")
}
}
return http.build()
}
}
----
@@ -369,11 +363,9 @@ class OAuth2LoginSecurityConfig {
@Bean
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
http {
return http {
oauth2Login { }
}
return http.build()
}
@Bean
@@ -458,11 +450,9 @@ class OAuth2LoginSecurityConfig {
@Bean
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
http {
return http {
oauth2Login { }
}
return http.build()
}
@Bean
@@ -536,11 +526,9 @@ class OAuth2LoginSecurityConfig {
@Bean
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
http {
return http {
oauth2Login { }
}
return http.build()
}
@Bean
@@ -594,11 +582,9 @@ class OAuth2LoginSecurityConfig {
@Bean
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
http {
return http {
oauth2Login { }
}
return http.build()
}
@Bean
@@ -730,7 +716,7 @@ class OAuth2LoginSecurityConfig {
@Bean
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
http {
return http {
authorizeExchange {
authorize(anyExchange, authenticated)
}
@@ -739,8 +725,6 @@ class OAuth2LoginSecurityConfig {
logoutSuccessHandler = oidcLogoutSuccessHandler()
}
}
return http.build()
}
private fun oidcLogoutSuccessHandler(): ServerLogoutSuccessHandler {
@@ -341,14 +341,12 @@ class OAuth2LoginSecurityConfig {
@Bean
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
http {
return http {
authorizeExchange {
authorize(anyExchange, authenticated)
}
oauth2Login { }
}
return http.build()
}
}
----
@@ -411,14 +409,12 @@ class OAuth2LoginConfig {
@Bean
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
http {
return http {
authorizeExchange {
authorize(anyExchange, authenticated)
}
oauth2Login { }
}
return http.build()
}
@Bean
@@ -505,14 +501,12 @@ class OAuth2LoginConfig {
@Bean
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
http {
return http {
authorizeExchange {
authorize(anyExchange, authenticated)
}
oauth2Login { }
}
return http.build()
}
@Bean
@@ -23,13 +23,14 @@ To specify where the introspection endpoint is, simply do:
[source,yaml]
----
security:
oauth2:
resourceserver:
opaque-token:
introspection-uri: https://idp.example.com/introspect
client-id: client
client-secret: secret
spring:
security:
oauth2:
resourceserver:
opaque-token:
introspection-uri: https://idp.example.com/introspect
client-id: client
client-secret: secret
----
Where `https://idp.example.com/introspect` is the introspection endpoint hosted by your authorization server and `client-id` and `client-secret` are the credentials needed to hit that endpoint.
@@ -8,7 +8,7 @@ Here is a minimal sample of what we can do:
.Java
[source,java,role="primary"]
----
@RunWith(SpringRunner.class)
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = HelloWebfluxMethodApplication.class)
public class HelloWorldMessageServiceTests {
@Autowired
@@ -42,7 +42,7 @@ public class HelloWorldMessageServiceTests {
.Kotlin
[source,kotlin,role="secondary"]
----
@RunWith(SpringRunner::class)
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = [HelloWebfluxMethodApplication::class])
class HelloWorldMessageServiceTests {
@Autowired
@@ -4,7 +4,7 @@ The basic setup looks like this:
[source,java]
----
@RunWith(SpringRunner.class)
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = HelloWebfluxMethodApplication.class)
public class HelloWebfluxMethodApplicationTests {
@Autowired
@@ -12,7 +12,7 @@ public class HelloWebfluxMethodApplicationTests {
WebTestClient rest;
@Before
@BeforeEach
public void setup() {
this.rest = WebTestClient
.bindToApplicationContext(this.context)
@@ -19,8 +19,6 @@ This also gives a good idea of the high level flow of authentication and how pie
[[servlet-authentication-securitycontextholder]]
== SecurityContextHolder
Hi {figures} there
At the heart of Spring Security's authentication model is the `SecurityContextHolder`.
It contains the <<servlet-authentication-securitycontext>>.
@@ -69,7 +69,11 @@ SecurityFilterChain web(HttpSecurity http) throws Exception {
.authorizeHttpRequests(authorize -> authorize // <1>
.mvcMatchers("/resources/**", "/signup", "/about").permitAll() // <2>
.mvcMatchers("/admin/**").hasRole("ADMIN") // <3>
.mvcMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')") // <4>
.mvcMatchers("/db/**").access((authentication, request) ->
Optional.of(hasRole("ADMIN").check(authentication, request))
.filter((decision) -> !decision.isGranted())
.orElseGet(() -> hasRole("DBA").check(authentication, request));
) // <4>
.anyRequest().denyAll() // <5>
);
@@ -7,10 +7,11 @@ It is not only useful but necessary to include the user in the queries to suppor
[[data-configuration]]
== Spring Data & Spring Security Configuration
To use this support, add `org.springframework.security:spring-security-data` dependency and provide a bean of type `SecurityEvaluationContextExtension`.
In Java Configuration, this would look like:
To use this support, add `org.springframework.security:spring-security-data` dependency and provide a bean of type `SecurityEvaluationContextExtension`:
[source,java]
====
.Java
[source,java,role="primary"]
----
@Bean
public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
@@ -18,6 +19,16 @@ public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Bean
fun securityEvaluationContextExtension(): SecurityEvaluationContextExtension {
return SecurityEvaluationContextExtension()
}
----
====
In XML Configuration, this would look like:
[source,xml]
@@ -31,7 +42,9 @@ In XML Configuration, this would look like:
Now Spring Security can be used within your queries.
For example:
[source,java]
====
.Java
[source,java,role="primary"]
----
@Repository
public interface MessageRepository extends PagingAndSortingRepository<Message,Long> {
@@ -40,6 +53,17 @@ public interface MessageRepository extends PagingAndSortingRepository<Message,Lo
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Repository
interface MessageRepository : PagingAndSortingRepository<Message,Long> {
@Query("select m from Message m where m.to.id = ?#{ principal?.id }")
fun findInbox(pageable: Pageable): Page<Message>
}
----
====
This checks to see if the `Authentication.getPrincipal().getId()` is equal to the recipient of the `Message`.
Note that this example assumes you have customized the principal to be an Object that has an id property.
By exposing the `SecurityEvaluationContextExtension` bean, all of the xref:servlet/authorization/expression-based.adoc#common-expressions[Common Security Expressions] are available within the Query.
@@ -323,7 +323,7 @@ Next, we can construct a `JWTProcessor`:
JWTProcessor jwtProcessor(JWTClaimSetJWSKeySelector keySelector) {
ConfigurableJWTProcessor<SecurityContext> jwtProcessor =
new DefaultJWTProcessor();
jwtProcessor.setJWTClaimSetJWSKeySelector(keySelector);
jwtProcessor.setJWTClaimsSetAwareJWSKeySelector(keySelector);
return jwtProcessor;
}
----
@@ -416,9 +416,9 @@ Now that we have a tenant-aware processor and a tenant-aware validator, we can p
----
@Bean
JwtDecoder jwtDecoder(JWTProcessor jwtProcessor, OAuth2TokenValidator<Jwt> jwtValidator) {
NimbusJwtDecoder decoder = new NimbusJwtDecoder(processor);
NimbusJwtDecoder decoder = new NimbusJwtDecoder(jwtProcessor);
OAuth2TokenValidator<Jwt> validator = new DelegatingOAuth2TokenValidator<>
(JwtValidators.createDefault(), this.jwtValidator);
(JwtValidators.createDefault(), jwtValidator);
decoder.setJwtValidator(validator);
return decoder;
}
@@ -24,13 +24,14 @@ To specify where the introspection endpoint is, simply do:
[source,yaml]
----
security:
oauth2:
resourceserver:
opaque-token:
introspection-uri: https://idp.example.com/introspect
client-id: client
client-secret: secret
spring:
security:
oauth2:
resourceserver:
opaque-token:
introspection-uri: https://idp.example.com/introspect
client-id: client
client-secret: secret
----
Where `https://idp.example.com/introspect` is the introspection endpoint hosted by your authorization server and `client-id` and `client-secret` are the credentials needed to hit that endpoint.
@@ -8,7 +8,7 @@ This filter by default responds to endpoint `+/saml2/authenticate/{registrationI
For example, if you were deployed to `https://rp.example.com` and you gave your registration an ID of `okta`, you could navigate to:
`https://rp.example.org/saml2/authenticate/ping`
`https://rp.example.org/saml2/authenticate/okta`
and the result would be a redirect that included a `SAMLRequest` parameter containing the signed, deflated, and encoded `<saml2:AuthnRequest>`.
@@ -187,7 +187,7 @@ But, if you do need something from the request, then you can use create a custom
----
@Component
public class AuthnRequestConverter implements
Converter<MySaml2AuthenticationRequestContext, AuthnRequest> {
Converter<Saml2AuthenticationRequestContext, AuthnRequest> {
private final AuthnRequestBuilder authnRequestBuilder;
private final IssuerBuilder issuerBuilder;
@@ -216,12 +216,12 @@ public class AuthnRequestConverter implements
[source,kotlin,role="secondary"]
----
@Component
class AuthnRequestConverter : Converter<MySaml2AuthenticationRequestContext, AuthnRequest> {
class AuthnRequestConverter : Converter<Saml2AuthenticationRequestContext, AuthnRequest> {
private val authnRequestBuilder: AuthnRequestBuilder? = null
private val issuerBuilder: IssuerBuilder? = null
// ... constructor
override fun convert(context: MySaml2AuthenticationRequestContext): AuthnRequest {
override fun convert(context: Saml2AuthenticationRequestContext): AuthnRequest {
val myContext: MySaml2AuthenticationRequestContext = context
val issuer: Issuer = issuerBuilder.buildObject()
issuer.value = myContext.getIssuer()
@@ -14,5 +14,5 @@ This process is similar to the one started in 2017 for xref:servlet/oauth2/index
[NOTE]
====
A working sample for {gh-samples-url}/servlet/spring-boot/java/saml2-login[SAML 2.0 Login] is available in the {gh-samples-url}[Spring Security Samples repository].
A working sample for {gh-samples-url}/servlet/spring-boot/java/saml2/login[SAML 2.0 Login] is available in the {gh-samples-url}[Spring Security Samples repository].
====
@@ -35,6 +35,7 @@ RelyingPartyRegistrationRepository registrations() {
RelyingPartyRegistration registration = RelyingPartyRegistrations
.fromMetadataLocation("https://ap.example.org/metadata")
.registrationId("id")
.singleLogoutServiceLocation("{baseUrl}/logout/saml2/slo")
.signingX509Credentials((signing) -> signing.add(credential)) <1>
.build();
return new InMemoryRelyingPartyRegistrationRepository(registration);
@@ -73,6 +74,10 @@ Also, your application can participate in an AP-initiated logout when the assert
3. Create, sign, and serialize a `<saml2:LogoutResponse>` based on the xref:servlet/saml2/login/overview.adoc#servlet-saml2login-relyingpartyregistration[`RelyingPartyRegistration`] associated with the just logged-out user
4. Send a redirect or post to the asserting party based on the xref:servlet/saml2/login/overview.adoc#servlet-saml2login-relyingpartyregistration[`RelyingPartyRegistration`]
NOTE: Adding `saml2Logout` adds the capability for logout to the service provider.
Because it is an optional capability, you need to enable it for each individual `RelyingPartyRegistration`.
You can do this by setting the `RelyingPartyRegistration.Builder#singleLogoutServiceLocation` property.
== Configuring Logout Endpoints
There are three behaviors that can be triggered by different endpoints:
@@ -49,7 +49,7 @@ Before we can use Spring Security Test support, we must perform some setup. An e
.Java
[source,java,role="primary"]
----
@RunWith(SpringJUnit4ClassRunner.class) // <1>
@ExtendWith(SpringExtension.class) // <1>
@ContextConfiguration // <2>
public class WithMockUserTests {
----
@@ -57,16 +57,15 @@ public class WithMockUserTests {
.Kotlin
[source,kotlin,role="secondary"]
----
@RunWith(SpringJUnit4ClassRunner::class)
@ExtendWith(SpringExtension.class)
@ContextConfiguration
class WithMockUserTests {
----
====
This is a basic example of how to setup Spring Security Test. The highlights are:
<1> `@RunWith` instructs the spring-test module that it should create an `ApplicationContext`. This is no different than using the existing Spring Test support. For additional information, refer to the https://docs.spring.io/spring-framework/docs/4.0.x/spring-framework-reference/htmlsingle/#integration-testing-annotations-standard[Spring Reference]
<2> `@ContextConfiguration` instructs the spring-test the configuration to use to create the `ApplicationContext`. Since no configuration is specified, the default configuration locations will be tried. This is no different than using the existing Spring Test support. For additional information, refer to the https://docs.spring.io/spring-framework/docs/4.0.x/spring-framework-reference/htmlsingle/#testcontext-ctx-management[Spring Reference]
<1> `@ExtendWith` instructs the spring-test module that it should create an `ApplicationContext`. For additional information, refer to the {spring-framework-reference-url}testing.html#testcontext-junit-jupiter-extension[Spring reference].
<2> `@ContextConfiguration` instructs the spring-test the configuration to use to create the `ApplicationContext`. Since no configuration is specified, the default configuration locations will be tried. This is no different than using the existing Spring Test support. For additional information, refer to the {spring-framework-reference-url}testing.html#spring-testing-annotation-contextconfiguration[Spring Reference]
NOTE: Spring Security hooks into Spring Test support using the `WithSecurityContextTestExecutionListener` which will ensure our tests are ran with the correct user.
It does this by populating the `SecurityContextHolder` prior to running our tests.
@@ -225,7 +224,7 @@ For example, the following would run every test with a user with the username "a
.Java
[source,java,role="primary"]
----
@RunWith(SpringJUnit4ClassRunner.class)
@ExtendWith(SpringExtension.class)
@ContextConfiguration
@WithMockUser(username="admin",roles={"USER","ADMIN"})
public class WithMockUserTests {
@@ -234,7 +233,7 @@ public class WithMockUserTests {
.Kotlin
[source,kotlin,role="secondary"]
----
@RunWith(SpringJUnit4ClassRunner::class)
@ExtendWith(SpringExtension.class)
@ContextConfiguration
@WithMockUser(username="admin",roles=["USER","ADMIN"])
class WithMockUserTests {
@@ -304,7 +303,7 @@ For example, the following will run withMockUser1 and withMockUser2 using <<test
.Java
[source,java,role="primary"]
----
@RunWith(SpringJUnit4ClassRunner.class)
@ExtendWith(SpringExtension.class)
@WithMockUser
public class WithUserClassLevelAuthenticationTests {
@@ -327,7 +326,7 @@ public class WithUserClassLevelAuthenticationTests {
.Kotlin
[source,kotlin,role="secondary"]
----
@RunWith(SpringJUnit4ClassRunner::class)
@ExtendWith(SpringExtension.class)
@WithMockUser
class WithUserClassLevelAuthenticationTests {
@Test
@@ -15,7 +15,7 @@ NOTE: Spring Security's testing support requires spring-test-4.1.3.RELEASE or gr
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*;
@RunWith(SpringJUnit4ClassRunner.class)
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = SecurityConfig.class)
@WebAppConfiguration
public class CsrfShowcaseTests {
@@ -25,7 +25,7 @@ public class CsrfShowcaseTests {
private MockMvc mvc;
@Before
@BeforeEach
public void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
@@ -39,7 +39,7 @@ public class CsrfShowcaseTests {
.Kotlin
[source,kotlin,role="secondary"]
----
@RunWith(SpringJUnit4ClassRunner::class)
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = [SecurityConfig::class])
@WebAppConfiguration
class CsrfShowcaseTests {
@@ -49,7 +49,7 @@ class CsrfShowcaseTests {
private var mvc: MockMvc? = null
@Before
@BeforeEach
fun setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
+1
View File
@@ -1,5 +1,6 @@
plugins {
id "io.github.rwinch.antora" version "0.0.2"
id "org.springframework.antora.check-version"
}
apply plugin: 'io.spring.convention.docs'
+6 -6
View File
@@ -1,11 +1,11 @@
aspectjVersion=1.9.7
springJavaformatVersion=0.0.29
aspectjVersion=1.9.9.1
springJavaformatVersion=0.0.31
springBootVersion=2.4.2
springFrameworkVersion=5.3.13
springFrameworkVersion=5.3.21
openSamlVersion=3.4.6
version=5.6.1-SNAPSHOT
kotlinVersion=1.5.31
samplesBranch=main
version=5.6.6
kotlinVersion=1.5.32
samplesBranch=5.6.x
org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError
org.gradle.parallel=true
org.gradle.caching=true
@@ -10,7 +10,7 @@ dependencies {
implementation 'org.springframework:spring-tx'
testImplementation project(':spring-security-web')
testImplementation 'javax.servlet:javax.servlet-api'
testImplementation 'jakarta.servlet:jakarta.servlet-api'
testImplementation 'org.springframework:spring-web'
testImplementation "org.assertj:assertj-core"
testImplementation "org.junit.jupiter:junit-jupiter-api"
+2 -2
View File
@@ -5,7 +5,7 @@ dependencies {
implementation 'org.springframework:spring-context'
implementation 'org.springframework:spring-web'
compileOnly 'javax.servlet:javax.servlet-api'
compileOnly 'jakarta.servlet:jakarta.servlet-api'
testImplementation project(':spring-security-core')
testImplementation project(':spring-security-test')
@@ -21,7 +21,7 @@ dependencies {
testImplementation "org.mockito:mockito-core"
testImplementation "org.mockito:mockito-junit-jupiter"
testImplementation "org.springframework:spring-test"
testImplementation 'javax.servlet:javax.servlet-api'
testImplementation 'jakarta.servlet:jakarta.servlet-api'
testRuntimeOnly project(':spring-security-config')
testRuntimeOnly project(':spring-security-ldap')
-1
View File
@@ -25,7 +25,6 @@ dependencies {
}
testImplementation project(':spring-security-test')
testImplementation 'org.slf4j:jcl-over-slf4j'
testImplementation 'org.slf4j:slf4j-api'
testImplementation "org.assertj:assertj-core"
testImplementation "org.junit.jupiter:junit-jupiter-api"
+1 -2
View File
@@ -12,7 +12,7 @@ dependencies {
optional project(':spring-security-web')
optional 'org.springframework:spring-websocket'
optional 'io.projectreactor:reactor-core'
optional 'javax.servlet:javax.servlet-api'
optional 'jakarta.servlet:jakarta.servlet-api'
testImplementation project(path: ':spring-security-core', configuration: 'tests')
testImplementation 'commons-codec:commons-codec'
@@ -24,7 +24,6 @@ dependencies {
testImplementation "org.mockito:mockito-junit-jupiter"
testImplementation "org.springframework:spring-test"
testImplementation "org.slf4j:slf4j-api"
testImplementation "org.slf4j:jcl-over-slf4j"
testImplementation "org.slf4j:log4j-over-slf4j"
testImplementation "ch.qos.logback:logback-classic"
@@ -23,7 +23,7 @@ dependencies {
testImplementation 'io.projectreactor:reactor-test'
testImplementation 'io.projectreactor.tools:blockhound'
testImplementation 'org.skyscreamer:jsonassert'
testImplementation 'io.r2dbc:r2dbc-h2:0.8.4.RELEASE'
testImplementation 'io.r2dbc:r2dbc-h2:0.8.5.RELEASE'
testImplementation 'io.r2dbc:r2dbc-spi-test:0.8.6.RELEASE'
testImplementation "org.assertj:assertj-core"
testImplementation "org.junit.jupiter:junit-jupiter-api"
@@ -35,5 +35,5 @@ dependencies {
testRuntimeOnly 'org.hsqldb:hsqldb'
provided 'javax.servlet:javax.servlet-api'
provided 'jakarta.servlet:jakarta.servlet-api'
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -204,10 +204,12 @@ public final class OAuth2AuthorizedClientProviderBuilder {
/**
* Sets the maximum acceptable clock skew, which is used when checking the access
* token expiry. An access token is considered expired if it's before
* {@code Instant.now(this.clock) - clockSkew}.
* token expiry. An access token is considered expired if
* {@code OAuth2Token#getExpiresAt() - clockSkew} is before the current time
* {@code clock#instant()}.
* @param clockSkew the maximum acceptable clock skew
* @return the {@link PasswordGrantBuilder}
* @see PasswordOAuth2AuthorizedClientProvider#setClockSkew(Duration)
*/
public PasswordGrantBuilder clockSkew(Duration clockSkew) {
this.clockSkew = clockSkew;
@@ -275,10 +277,12 @@ public final class OAuth2AuthorizedClientProviderBuilder {
/**
* Sets the maximum acceptable clock skew, which is used when checking the access
* token expiry. An access token is considered expired if it's before
* {@code Instant.now(this.clock) - clockSkew}.
* token expiry. An access token is considered expired if
* {@code OAuth2Token#getExpiresAt() - clockSkew} is before the current time
* {@code clock#instant()}.
* @param clockSkew the maximum acceptable clock skew
* @return the {@link ClientCredentialsGrantBuilder}
* @see ClientCredentialsOAuth2AuthorizedClientProvider#setClockSkew(Duration)
*/
public ClientCredentialsGrantBuilder clockSkew(Duration clockSkew) {
this.clockSkew = clockSkew;
@@ -365,10 +369,12 @@ public final class OAuth2AuthorizedClientProviderBuilder {
/**
* Sets the maximum acceptable clock skew, which is used when checking the access
* token expiry. An access token is considered expired if it's before
* {@code Instant.now(this.clock) - clockSkew}.
* token expiry. An access token is considered expired if
* {@code OAuth2Token#getExpiresAt() - clockSkew} is before the current time
* {@code clock#instant()}.
* @param clockSkew the maximum acceptable clock skew
* @return the {@link RefreshTokenGrantBuilder}
* @see RefreshTokenOAuth2AuthorizedClientProvider#setClockSkew(Duration)
*/
public RefreshTokenGrantBuilder clockSkew(Duration clockSkew) {
this.clockSkew = clockSkew;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -225,10 +225,12 @@ public final class ReactiveOAuth2AuthorizedClientProviderBuilder {
/**
* Sets the maximum acceptable clock skew, which is used when checking the access
* token expiry. An access token is considered expired if it's before
* {@code Instant.now(this.clock) - clockSkew}.
* token expiry. An access token is considered expired if
* {@code OAuth2Token#getExpiresAt() - clockSkew} is before the current time
* {@code clock#instant()}.
* @param clockSkew the maximum acceptable clock skew
* @return the {@link ClientCredentialsGrantBuilder}
* @see ClientCredentialsReactiveOAuth2AuthorizedClientProvider#setClockSkew(Duration)
*/
public ClientCredentialsGrantBuilder clockSkew(Duration clockSkew) {
this.clockSkew = clockSkew;
@@ -297,10 +299,12 @@ public final class ReactiveOAuth2AuthorizedClientProviderBuilder {
/**
* Sets the maximum acceptable clock skew, which is used when checking the access
* token expiry. An access token is considered expired if it's before
* {@code Instant.now(this.clock) - clockSkew}.
* token expiry. An access token is considered expired if
* {@code OAuth2Token#getExpiresAt() - clockSkew} is before the current time
* {@code clock#instant()}.
* @param clockSkew the maximum acceptable clock skew
* @return the {@link PasswordGrantBuilder}
* @see PasswordReactiveOAuth2AuthorizedClientProvider#setClockSkew(Duration)
*/
public PasswordGrantBuilder clockSkew(Duration clockSkew) {
this.clockSkew = clockSkew;
@@ -368,10 +372,12 @@ public final class ReactiveOAuth2AuthorizedClientProviderBuilder {
/**
* Sets the maximum acceptable clock skew, which is used when checking the access
* token expiry. An access token is considered expired if it's before
* {@code Instant.now(this.clock) - clockSkew}.
* token expiry. An access token is considered expired if
* {@code OAuth2Token#getExpiresAt() - clockSkew} is before the current time
* {@code clock#instant()}.
* @param clockSkew the maximum acceptable clock skew
* @return the {@link RefreshTokenGrantBuilder}
* @see RefreshTokenReactiveOAuth2AuthorizedClientProvider#setClockSkew(Duration)
*/
public RefreshTokenGrantBuilder clockSkew(Duration clockSkew) {
this.clockSkew = clockSkew;
@@ -85,13 +85,13 @@ public class OidcClientInitiatedServerLogoutSuccessHandler implements ServerLogo
return Mono.empty();
}
String idToken = idToken(authentication);
URI postLogoutRedirectUri = postLogoutRedirectUri(exchange.getExchange().getRequest());
String postLogoutRedirectUri = postLogoutRedirectUri(exchange.getExchange().getRequest());
return Mono.just(endpointUri(endSessionEndpoint, idToken, postLogoutRedirectUri));
})
.switchIfEmpty(
this.serverLogoutSuccessHandler.onLogoutSuccess(exchange, authentication).then(Mono.empty())
)
.flatMap((endpointUri) -> this.redirectStrategy.sendRedirect(exchange.getExchange(), endpointUri));
.flatMap((endpointUri) -> this.redirectStrategy.sendRedirect(exchange.getExchange(), URI.create(endpointUri)));
// @formatter:on
}
@@ -106,20 +106,20 @@ public class OidcClientInitiatedServerLogoutSuccessHandler implements ServerLogo
return null;
}
private URI endpointUri(URI endSessionEndpoint, String idToken, URI postLogoutRedirectUri) {
private String endpointUri(URI endSessionEndpoint, String idToken, String postLogoutRedirectUri) {
UriComponentsBuilder builder = UriComponentsBuilder.fromUri(endSessionEndpoint);
builder.queryParam("id_token_hint", idToken);
if (postLogoutRedirectUri != null) {
builder.queryParam("post_logout_redirect_uri", postLogoutRedirectUri);
}
return builder.encode(StandardCharsets.UTF_8).build().toUri();
return builder.encode(StandardCharsets.UTF_8).build().toUriString();
}
private String idToken(Authentication authentication) {
return ((OidcUser) authentication.getPrincipal()).getIdToken().getTokenValue();
}
private URI postLogoutRedirectUri(ServerHttpRequest request) {
private String postLogoutRedirectUri(ServerHttpRequest request) {
if (this.postLogoutRedirectUri == null) {
return null;
}
@@ -131,7 +131,7 @@ public class OidcClientInitiatedServerLogoutSuccessHandler implements ServerLogo
.build();
return UriComponentsBuilder.fromUriString(this.postLogoutRedirectUri)
.buildAndExpand(Collections.singletonMap("baseUrl", uriComponents.toUriString()))
.toUri();
.toUriString();
// @formatter:on
}
@@ -150,6 +150,19 @@ public class OidcClientInitiatedServerLogoutSuccessHandlerTests {
"https://endpoint?" + "id_token_hint=id-token&" + "post_logout_redirect_uri=https://rp.example.org");
}
// gh-11379
@Test
public void logoutWhenUsingPostLogoutRedirectUriWithQueryParametersThenBuildsItForRedirect() {
OAuth2AuthenticationToken token = new OAuth2AuthenticationToken(TestOidcUsers.create(),
AuthorityUtils.NO_AUTHORITIES, this.registration.getRegistrationId());
given(this.exchange.getPrincipal()).willReturn(Mono.just(token));
this.handler.setPostLogoutRedirectUri("https://rp.example.org/context?forwardUrl=secured%3Fparam%3Dtrue");
WebFilterExchange f = new WebFilterExchange(this.exchange, this.chain);
this.handler.onLogoutSuccess(f, token).block();
assertThat(redirectedUrl(this.exchange)).isEqualTo("https://endpoint?id_token_hint=id-token&"
+ "post_logout_redirect_uri=https://rp.example.org/context?forwardUrl%3Dsecured%253Fparam%253Dtrue");
}
@Test
public void setPostLogoutRedirectUriWhenGivenNullThenThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.handler.setPostLogoutRedirectUri((URI) null));
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -54,7 +54,7 @@ public final class JwtClaimValidator<T> implements OAuth2TokenValidator<Jwt> {
Assert.notNull(test, "test can not be null");
this.claim = claim;
this.test = test;
this.error = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST, "The " + this.claim + " claim is not valid",
this.error = new OAuth2Error(OAuth2ErrorCodes.INVALID_TOKEN, "The " + this.claim + " claim is not valid",
"https://tools.ietf.org/html/rfc6750#section-3.1");
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,10 +16,14 @@
package org.springframework.security.oauth2.jwt;
import java.util.Collection;
import java.util.Objects;
import java.util.function.Predicate;
import org.junit.jupiter.api.Test;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
import static org.assertj.core.api.Assertions.assertThat;
@@ -45,7 +49,9 @@ public class JwtClaimValidatorTests {
@Test
public void validateWhenClaimFailsTheTestThenReturnsFailure() {
Jwt jwt = TestJwts.jwt().claim(JwtClaimNames.ISS, "http://abc").build();
Collection<OAuth2Error> details = this.validator.validate(jwt).getErrors();
assertThat(this.validator.validate(jwt).getErrors().isEmpty()).isFalse();
assertThat(details).allMatch((error) -> Objects.equals(error.getErrorCode(), OAuth2ErrorCodes.INVALID_TOKEN));
}
@Test
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@ import java.time.ZoneId;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
@@ -64,6 +65,7 @@ public class JwtTimestampValidatorTests {
.collect(Collectors.toList());
// @formatter:on
assertThat(messages).contains("Jwt expired at " + oneHourAgo);
assertThat(details).allMatch((error) -> Objects.equals(error.getErrorCode(), OAuth2ErrorCodes.INVALID_TOKEN));
}
@Test
@@ -78,6 +80,7 @@ public class JwtTimestampValidatorTests {
.collect(Collectors.toList());
// @formatter:on
assertThat(messages).contains("Jwt used before " + oneHourFromNow);
assertThat(details).allMatch((error) -> Objects.equals(error.getErrorCode(), OAuth2ErrorCodes.INVALID_TOKEN));
}
@Test
@@ -12,7 +12,7 @@ dependencies {
optional 'io.projectreactor:reactor-core'
optional 'org.springframework:spring-webflux'
provided 'javax.servlet:javax.servlet-api'
provided 'jakarta.servlet:jakarta.servlet-api'
testImplementation project(path: ':spring-security-oauth2-jose', configuration: 'tests')
testImplementation 'com.squareup.okhttp3:mockwebserver'
+7 -2
View File
@@ -10,12 +10,14 @@ dependencies {
api project(':spring-security-web')
api('com.google.inject:guice') {
exclude group: 'aopalliance', module: 'aopalliance'
exclude group: 'javax.inject', module: 'javax.inject'
}
// openid4java has a compile time dep on guice with a group
// name which is different from the maven central one.
// We use the maven central version here instead.
api('org.openid4java:openid4java-nodeps') {
exclude group: 'com.google.code.guice', module: 'guice'
exclude group: 'commons-logging', module: 'commons-logging'
}
api 'org.springframework:spring-aop'
api 'org.springframework:spring-beans'
@@ -23,11 +25,14 @@ dependencies {
api 'org.springframework:spring-core'
api 'org.springframework:spring-web'
provided 'javax.servlet:javax.servlet-api'
provided 'jakarta.servlet:jakarta.servlet-api'
runtimeOnly 'net.sourceforge.nekohtml:nekohtml'
runtimeOnly 'org.apache.httpcomponents:httpclient'
runtimeOnly('org.apache.httpcomponents:httpclient') {
exclude group: 'commons-logging', module: 'commons-logging'
}
testImplementation "jakarta.inject:jakarta.inject-api"
testImplementation "org.assertj:assertj-core"
testImplementation "org.junit.jupiter:junit-jupiter-api"
testImplementation "org.junit.jupiter:junit-jupiter-params"
@@ -46,11 +46,11 @@ dependencies {
api "org.opensaml:opensaml-core"
api "org.opensaml:opensaml-saml-api"
api "org.opensaml:opensaml-saml-impl"
opensaml4MainImplementation "org.opensaml:opensaml-core:4.1.0"
opensaml4MainImplementation "org.opensaml:opensaml-saml-api:4.1.0"
opensaml4MainImplementation "org.opensaml:opensaml-saml-impl:4.1.0"
opensaml4MainImplementation "org.opensaml:opensaml-core:4.1.1"
opensaml4MainImplementation "org.opensaml:opensaml-saml-api:4.1.1"
opensaml4MainImplementation "org.opensaml:opensaml-saml-impl:4.1.1"
provided 'javax.servlet:javax.servlet-api'
provided 'jakarta.servlet:jakarta.servlet-api'
testImplementation 'com.squareup.okhttp3:mockwebserver'
testImplementation "org.assertj:assertj-core"
@@ -19,13 +19,12 @@ package org.springframework.security.saml2.provider.service.authentication;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterOutputStream;
import org.apache.commons.codec.binary.Base64;
import org.springframework.security.saml2.Saml2Exception;
/**
@@ -33,17 +32,15 @@ import org.springframework.security.saml2.Saml2Exception;
*/
final class Saml2Utils {
private static Base64 BASE64 = new Base64(0, new byte[] { '\n' });
private Saml2Utils() {
}
static String samlEncode(byte[] b) {
return BASE64.encodeAsString(b);
return Base64.getEncoder().encodeToString(b);
}
static byte[] samlDecode(String s) {
return BASE64.decode(s);
return Base64.getMimeDecoder().decode(s);
}
static byte[] samlDeflate(String s) {
@@ -44,7 +44,7 @@ final class Saml2Utils {
}
static byte[] samlDecode(String s) {
return Base64.getDecoder().decode(s);
return Base64.getMimeDecoder().decode(s);
}
static byte[] samlDeflate(String s) {
@@ -70,7 +70,7 @@ public final class OpenSamlMetadataResolver implements Saml2MetadataResolver {
@Override
public String resolve(RelyingPartyRegistration relyingPartyRegistration) {
EntityDescriptor entityDescriptor = build(EntityDescriptor.ELEMENT_QNAME);
EntityDescriptor entityDescriptor = build(EntityDescriptor.DEFAULT_ELEMENT_NAME);
entityDescriptor.setEntityID(relyingPartyRegistration.getEntityId());
SPSSODescriptor spSsoDescriptor = buildSpSsoDescriptor(relyingPartyRegistration);
entityDescriptor.getRoleDescriptors(SPSSODescriptor.DEFAULT_ELEMENT_NAME).add(spSsoDescriptor);
@@ -86,7 +86,9 @@ public final class OpenSamlMetadataResolver implements Saml2MetadataResolver {
spSsoDescriptor.getKeyDescriptors()
.addAll(buildKeys(registration.getDecryptionX509Credentials(), UsageType.ENCRYPTION));
spSsoDescriptor.getAssertionConsumerServices().add(buildAssertionConsumerService(registration));
spSsoDescriptor.getSingleLogoutServices().add(buildSingleLogoutService(registration));
if (registration.getSingleLogoutServiceLocation() != null) {
spSsoDescriptor.getSingleLogoutServices().add(buildSingleLogoutService(registration));
}
return spSsoDescriptor;
}
@@ -21,7 +21,7 @@ import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
@@ -106,6 +106,8 @@ public final class RelyingPartyRegistration {
Assert.hasText(entityId, "entityId cannot be empty");
Assert.hasText(assertionConsumerServiceLocation, "assertionConsumerServiceLocation cannot be empty");
Assert.notNull(assertionConsumerServiceBinding, "assertionConsumerServiceBinding cannot be null");
Assert.isTrue(singleLogoutServiceLocation == null || singleLogoutServiceBinding != null,
"singleLogoutServiceBinding cannot be null when singleLogoutServiceLocation is set");
Assert.notNull(providerDetails, "providerDetails cannot be null");
Assert.notEmpty(credentials, "credentials cannot be empty");
for (org.springframework.security.saml2.credentials.Saml2X509Credential c : credentials) {
@@ -449,7 +451,7 @@ public final class RelyingPartyRegistration {
org.springframework.security.saml2.credentials.Saml2X509Credential credential) {
PrivateKey privateKey = credential.getPrivateKey();
X509Certificate certificate = credential.getCertificate();
Set<Saml2X509Credential.Saml2X509CredentialType> credentialTypes = new HashSet<>();
Set<Saml2X509Credential.Saml2X509CredentialType> credentialTypes = new LinkedHashSet<>();
if (credential.isSigningCredential()) {
credentialTypes.add(Saml2X509Credential.Saml2X509CredentialType.SIGNING);
}
@@ -469,7 +471,7 @@ public final class RelyingPartyRegistration {
Saml2X509Credential credential) {
PrivateKey privateKey = credential.getPrivateKey();
X509Certificate certificate = credential.getCertificate();
Set<org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType> credentialTypes = new HashSet<>();
Set<org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType> credentialTypes = new LinkedHashSet<>();
if (credential.isSigningCredential()) {
credentialTypes.add(
org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType.SIGNING);
@@ -694,9 +696,9 @@ public final class RelyingPartyRegistration {
private List<String> signingAlgorithms = new ArrayList<>();
private Collection<Saml2X509Credential> verificationX509Credentials = new HashSet<>();
private Collection<Saml2X509Credential> verificationX509Credentials = new LinkedHashSet<>();
private Collection<Saml2X509Credential> encryptionX509Credentials = new HashSet<>();
private Collection<Saml2X509Credential> encryptionX509Credentials = new LinkedHashSet<>();
private String singleSignOnServiceLocation;
@@ -1004,15 +1006,15 @@ public final class RelyingPartyRegistration {
private String entityId = "{baseUrl}/saml2/service-provider-metadata/{registrationId}";
private Collection<Saml2X509Credential> signingX509Credentials = new HashSet<>();
private Collection<Saml2X509Credential> signingX509Credentials = new LinkedHashSet<>();
private Collection<Saml2X509Credential> decryptionX509Credentials = new HashSet<>();
private Collection<Saml2X509Credential> decryptionX509Credentials = new LinkedHashSet<>();
private String assertionConsumerServiceLocation = "{baseUrl}/login/saml2/sso/{registrationId}";
private Saml2MessageBinding assertionConsumerServiceBinding = Saml2MessageBinding.POST;
private String singleLogoutServiceLocation = "{baseUrl}/logout/saml2/slo";
private String singleLogoutServiceLocation;
private String singleLogoutServiceResponseLocation;
@@ -1020,7 +1022,7 @@ public final class RelyingPartyRegistration {
private ProviderDetails.Builder providerDetails = new ProviderDetails.Builder();
private Collection<org.springframework.security.saml2.credentials.Saml2X509Credential> credentials = new HashSet<>();
private Collection<org.springframework.security.saml2.credentials.Saml2X509Credential> credentials = new LinkedHashSet<>();
private Builder(String registrationId) {
this.registrationId = registrationId;
@@ -111,6 +111,9 @@ final class OpenSamlLogoutRequestResolver {
if (registration == null) {
return null;
}
if (registration.getAssertingPartyDetails().getSingleLogoutServiceLocation() == null) {
return null;
}
LogoutRequest logoutRequest = this.logoutRequestBuilder.buildObject();
logoutRequest.setDestination(registration.getAssertingPartyDetails().getSingleLogoutServiceLocation());
Issuer issuer = this.issuerBuilder.buildObject();
@@ -132,6 +132,9 @@ final class OpenSamlLogoutResponseResolver {
if (registration == null) {
return null;
}
if (registration.getAssertingPartyDetails().getSingleLogoutServiceResponseLocation() == null) {
return null;
}
String serialized = request.getParameter(Saml2ParameterNames.SAML_REQUEST);
byte[] b = Saml2Utils.samlDecode(serialized);
LogoutRequest logoutRequest = parse(inflateIfRequired(registration, b));
@@ -120,6 +120,12 @@ public final class Saml2LogoutRequestFilter extends OncePerRequestFilter {
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return;
}
if (registration.getSingleLogoutServiceLocation() == null) {
this.logger.trace(
"Did not process logout request since RelyingPartyRegistration has not been configured with a logout request endpoint");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
if (!isCorrectBinding(request, registration)) {
this.logger.trace("Did not process logout request since used incorrect binding");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
@@ -120,6 +120,12 @@ public final class Saml2LogoutResponseFilter extends OncePerRequestFilter {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, error.toString());
return;
}
if (registration.getSingleLogoutServiceResponseLocation() == null) {
this.logger.trace(
"Did not process logout response since RelyingPartyRegistration has not been configured with a logout response endpoint");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
if (!isCorrectBinding(request, registration)) {
this.logger.trace("Did not process logout request since used incorrect binding");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
@@ -44,7 +44,7 @@ final class Saml2Utils {
}
static byte[] samlDecode(String s) {
return Base64.getDecoder().decode(s);
return Base64.getMimeDecoder().decode(s);
}
static byte[] samlDeflate(String s) {
@@ -47,7 +47,9 @@ public class OpenSaml3LogoutRequestResolverTests {
this.relyingPartyRegistrationResolver);
logoutRequestResolver.setParametersConsumer((parameters) -> parameters.getLogoutRequest().setID("myid"));
HttpServletRequest request = new MockHttpServletRequest();
RelyingPartyRegistration registration = TestRelyingPartyRegistrations.relyingPartyRegistration().build();
RelyingPartyRegistration registration = TestRelyingPartyRegistrations.relyingPartyRegistration()
.assertingPartyDetails((party) -> party.singleLogoutServiceLocation("https://ap.example.com/logout"))
.build();
Authentication authentication = new TestingAuthenticationToken("user", "password");
given(this.relyingPartyRegistrationResolver.resolve(any(), any())).willReturn(registration);
Saml2LogoutRequest logoutRequest = logoutRequestResolver.resolve(request, authentication);

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