1
0
mirror of synced 2026-07-13 14:45:10 +00:00

Compare commits

..

160 Commits

Author SHA1 Message Date
Marcus Da Coregio 2426989441 Release 5.7.0-M2 2022-02-21 11:00:07 -03:00
Marcus Da Coregio 31d7d29282 Update org.springframework to 5.3.16
Closes gh-10892
2022-02-21 10:35:42 -03:00
Marcus Da Coregio efbd48c458 Update org.slf4j to 1.7.36
Closes gh-10891
2022-02-21 10:35:40 -03:00
Marcus Da Coregio 970beaa743 Update htmlunit-driver to 2.58.0
Closes gh-10890
2022-02-21 10:35:37 -03:00
Marcus Da Coregio 6e386e5f14 Update org.jetbrains.kotlin to 1.6.20-M1
Closes gh-10889
2022-02-21 10:35:35 -03:00
Marcus Da Coregio 44bcfd8468 Update hibernate-entitymanager to 5.6.5.Final
Closes gh-10888
2022-02-21 10:35:32 -03:00
Marcus Da Coregio 2e32a63a75 Update org.eclipse.jetty to 9.4.45.v20220203
Closes gh-10887
2022-02-21 10:35:29 -03:00
Marcus Da Coregio d30a4c197d Update org.aspectj to 1.9.8
Closes gh-10886
2022-02-21 10:35:27 -03:00
Marcus Da Coregio 2d328fa42f Update htmlunit to 2.58.0
Closes gh-10885
2022-02-21 10:35:25 -03:00
Marcus Da Coregio 5d1d9c765c Update io.spring.javaformat to 0.0.31
Closes gh-10884
2022-02-21 10:35:22 -03:00
Marcus Da Coregio bebd615507 Update io.r2dbc to 0.9.1.RELEASE
Closes gh-10883
2022-02-21 10:35:20 -03:00
Marcus Da Coregio c4f3cd294f Update io.projectreactor to 2020.0.16
Closes gh-10881
2022-02-21 10:35:15 -03:00
Marcus Da Coregio 1f028c0ec3 Update aspectj-plugin to 6.4.1
Closes gh-10880
2022-02-21 10:35:13 -03:00
Marcus Da Coregio 876a3e9296 Update com.nimbusds to 9.27
Closes gh-10879
2022-02-21 10:35:10 -03:00
Rob Winch 225e4fdc95 Add .sdkmanrc 2022-02-18 13:48:05 -06:00
Josh Cummings 3d878549f4 Remove WantAssertionsSigned
WantAssertionsSigned requires that asserting parties sign the
assertions. This does not reflect how Spring Security actually
behaves, creating behavior mismatches.

Closes gh-10844
2022-02-18 11:43:25 -07:00
Talerngpong Virojwutikul 8a56e4f35e add Kotlin examples for Spring Data Integration of servlet application 2022-02-18 08:46:20 -03:00
Eleftheria Stein e97c643870 Deprecate WebSecurityConfigurerAdapter
Closes gh-10822
2022-02-17 12:13:50 +01:00
Josh Cummings 97c18478e5 Add Skipping Decryption Error Message
Closes gh-10220
2022-02-16 16:10:36 -07:00
Josh Cummings 399562b2a8 Correct Test
Issue gh-10220
2022-02-16 16:10:36 -07:00
Josh Cummings 836335dc89 Collect All Validation Errors
- OpenSaml4AuthenticationProvider now collects all validation errors
instead of treating some as their own exception

Issue gh-10220
2022-02-16 16:10:19 -07:00
Josh Cummings 371389580b Update JavaDoc
Issue gh-10564
2022-02-15 12:57:32 -07:00
Yuriy Savchenko 0fb6840db3 Make WebAuthenticationDetails constructor public
Closes gh-10564
2022-02-15 12:57:32 -07:00
Eleftheria Stein c6b185465d Add DEFAULT_USER_SCHEMA_DDL_LOCATION constant
Closes gh-10837
2022-02-15 11:24:23 +01:00
Yuriy Savchenko 77ba94e1db Update docs to use multi-tenancy
Closes gh-10572
2022-02-14 11:07:42 +01:00
Talerngpong Virojwutikul 015036741b Add Kotlin example for logout configuration of reactive authentication
Closes gh-10819
2022-02-11 13:16:47 +01:00
Eleftheria Stein c2635ba6bf Apply configurers from spring.factories to HttpSecurity bean
Closes gh-10814
2022-02-09 14:40:57 +01:00
Eleftheria Stein 4492e5b667 Replace WebSecurityConfigurerAdapter with SecurityFilterChain in docs
Closes gh-10003
2022-02-08 16:12:10 +01:00
Josh Cummings cbd87fac89 Polish ignoring() log messaging
- Public API remains unchanged

Issue gh-9334
2022-02-07 14:50:28 -07:00
Manuel Jordan 01ed617d5f 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 14:50:19 -07:00
Josh Cummings 541a1e48b3 Add OpenSamlAssertingPartyDetails
Closes gh-10781
2022-02-07 14:42:17 -07:00
giger85 4a3654a95b Replace StringUtils class completely
Issue gh-9925
Closes gh-10805
2022-02-07 13:47:52 +01:00
Josh Cummings 5c4178beb7 Fix Checkstyle Error
Issue gh-9696
2022-02-04 20:07:17 -07:00
Josh Cummings 70bb588a25 Polish Testing for Custom Attributes Values
- Moved construction and management of custom objects
into TestCustomOpenSamlObjects

Issue gh-9696
2022-02-04 19:57:54 -07:00
pelesic 3cc7f384e6 Add OpenSaml custom types to Saml2AuthenticatedPrincipal
OpenSaml custom types are added to Saml2AutehnticatedPrincipal as
attributes.

Closes gh-9696
2022-02-04 13:41:41 -07:00
giger85 27ecae2545 Fix typo in role hierarchy docs
Closes gh-10804
2022-02-04 16:31:19 +01:00
Eleftheria Stein 7c9de7d0ff Use lambda DSL in logout header docs 2022-02-04 16:26:18 +01:00
Rob Winch c1dfe407bc Document Authorize HTTP Requests for Reactive Security
Closes gh-10801
2022-02-03 13:46:27 -06:00
Ken Dombeck e5def290a1 Fix broken link to SAML2 login example 2022-02-03 10:10:04 -06:00
Rob Winch 70fa8b1fdb Add Support for @Transient SecurityContext
Closes gh-9995
2022-02-03 09:45:51 -06:00
Eleftheria Stein f465403a99 Fix typo in getting started docs
Closes gh-10736
2022-02-02 10:30:43 +01:00
Josh Cummings 4095d89bb3 Add EntitiesDescriptor Support
Closes gh-10782
2022-01-31 13:13:21 -07:00
Marcus Da Coregio 1c10c10f73 RequestMatcherDelegatingWebInvocationPrivilegeEvaluator doesn't provided access to the ServletContext
Closes gh-10779
2022-01-31 09:43:18 -03:00
Josh Cummings b1a905befe Add Session Index Support
Closes gh-10613
2022-01-28 12:14:06 -07:00
Eleftheria Stein e280061538 Polish LDAP reference docs
Issue gh-10789
2022-01-28 17:04:39 +01:00
Eleftheria Stein d5824521e8 Use LDAP AuthenticationManager factory in reference docs
Closes gh-10789
2022-01-28 17:03:56 +01:00
Josh Cummings 9baf1134c7 Add Request-based AuthenticationManagerResolvers
Closes gh-6762
2022-01-26 09:09:02 -07:00
Josh Cummings 620081ea9a Deprecate Saml2 AuthnRequest Classes
Issue gh-10355
2022-01-24 15:16:15 -07:00
Josh Cummings d538423f98 Add Saml2AuthenticationRequestResolver
Closes gh-10355
2022-01-24 15:09:45 -07:00
Marcus Da Coregio cca35bdd93 Make Saml2AuthenticationRequests serializable
Closes gh-10550
2022-01-24 08:55:26 -03:00
Rob Winch ba922dcdf0 Exclude javax from hibernate dependency
Issue gh-10501
2022-01-19 14:35:25 -06:00
Rob Winch 27e1a2ca69 Remove javax.transaction
Issue gh-10501
2022-01-19 14:35:05 -06:00
Rob Winch 9d4ecc9c37 Additional removal of javax.inject
Issue gh-10501
2022-01-19 14:34:45 -06:00
Rob Winch 29e6241869 dependencies jakarta.transaction-api
Issue gh-10501
2022-01-19 14:34:37 -06:00
Rob Winch 678c386834 jsr250-api -> jakarta.annotation-api
Issue gh-10501
2022-01-19 14:34:32 -06:00
Rob Winch 6e145b459f javax.servlet.jsp-api -> jakarta.servlet.jsp-api
Issue gh-10501
2022-01-19 14:34:28 -06:00
Rob Winch 3d80419727 javax.servlet.jsp.jstl-api -> jakarta.servlet.jsp.jstl-api
Issue gh-10501
2022-01-19 14:34:24 -06:00
Rob Winch a64b72b837 Exclude javax from cas-client-core
Issue gh-10501
2022-01-19 14:34:20 -06:00
Rob Winch 0e8c03401b javax.xml.bind:jaxb-api -> jakarta.xml.bind:jakarta.xml.bind-api
Issue gh-10501
2022-01-19 14:34:16 -06:00
Rob Winch 8f64bb6c8c javax.servlet:javax.servlet-api -> jakarta.servlet:jakarta.servlet-api
Issue gh-10501
2022-01-19 14:33:53 -06:00
Rob Winch d8b406ab71 Remove javax.inject
Issue gh-10501
2022-01-19 14:33:49 -06:00
Rob Winch f8e14683f6 Remove jcl-over-slf4j
Issue gh-10499
2022-01-19 14:33:46 -06:00
Rob Winch 3c641dee75 Remove commons-logging
Closes gh-10499
2022-01-19 14:33:44 -06:00
Rob Winch 72dd4e738b Add CheckProhibitedDependenciesLifecyclePlugin
Issue gh-10501
2022-01-19 14:32:55 -06:00
Rob Winch d146bcb7fc Add CheckClasspathForProhibitedDependencies
Issues gh-10499 gh-10501
2022-01-19 14:32:34 -06:00
Rob Winch c8713b1d91 CheckAntoraVersionTask has optional properties 2022-01-18 14:29:29 -06:00
Rob Winch 73dda2e192 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:03 -06:00
Eleftheria Stein a537b636c1 Add LDAP factory beans
Issue gh-10138
2022-01-18 15:11:30 +01:00
Eleftheria Stein 096a3403cb Add embedded LDAP container interface
Issue gh-10138
2022-01-18 15:11:30 +01:00
Jerome Prinet d341328085 Bump up Gradle plugin dependencies 2022-01-18 13:40:58 +01:00
Juan Carlos 7435da6bbf Add serialVersionUID to DefaultSavedRequest and SavedCookie
Closes gh-10594
2022-01-18 09:26:56 -03:00
Josh Cummings 75f25bff82 Polish multiple RequestRejectedHandlers support
Issue gh-10603
2022-01-14 16:49:38 -07:00
Adam Ostrožlík 4ea57f3e3f Support multiple RequestRejectedHandler beans
Closes gh-10603
2022-01-14 16:46:15 -07:00
Josh Cummings aaaf7d3523 Use noNullElements
Collection#contains(null) does not work for all collection types

Closes gh-10703
2022-01-14 15:08:38 -07:00
Josh Cummings 3c45d46bd7 Polish LogoutRequest#EncryptedID Support
Issue gh-10663
2022-01-14 14:44:24 -07:00
Robert Stoiber 700cae8d3b Enabled SAML LogoutRequests with EncryptedID
The OpenSamlLogoutRequestValidator validates the subject to be logged out.
Formerly this was done only using the NameID from the OpenSamlLogoutRequest.
Now the EncryptedID is also supported, Since the SAML2 Standard also allows
the EncryptedID as subject identifiers,

- added EncryptedID as valid subject in OpenSamlLogoutRequestValidator
- added test

Closes gh-10663
2022-01-14 14:44:11 -07:00
Steve Riesenberg 7efbc9d5f7 Next Development Version 2022-01-14 12:51:23 -06:00
Steve Riesenberg a8457b518f Release 5.7.0-M1 2022-01-14 12:16:08 -06:00
Steve Riesenberg 4fd5c7ffa3 Update spring-ldap-core to 2.4.0-M1
Closes gh-10732
2022-01-14 11:58:45 -06:00
Steve Riesenberg ce35bee1bd Update org.springframework.data to 2021.2.0-M1
Closes gh-10731
2022-01-14 11:58:45 -06:00
Steve Riesenberg 28ea347b5a Update org.springframework to 5.3.15
Closes gh-10730
2022-01-14 11:58:45 -06:00
Steve Riesenberg 8aa7b5897a Update org.slf4j to 1.7.33
Closes gh-10729
2022-01-14 11:58:45 -06:00
Steve Riesenberg 29bc6745e8 Update htmlunit-driver to 2.56.0
Closes gh-10728
2022-01-14 11:58:45 -06:00
Steve Riesenberg 08bc7a53d6 Update junit-bom to 5.8.2
Closes gh-10726
2022-01-14 11:58:45 -06:00
Steve Riesenberg e5212926a1 Update org.jetbrains.kotlinx to 1.6.0
Closes gh-10725
2022-01-14 11:58:45 -06:00
Marcus Da Coregio 2ed93ec0cf Rename integrationTestCompile/Runtime configurations
The kotlin-gradle-plugin is changing some configuration's properties from configurations that has the same prefix as the sourceSet.
It is enforcing the canBeResolved property to false. See https://youtrack.jetbrains.com/issue/KT-50748.
This commits changes the suffix to compile -> compileClasspath, runtime -> runtimeClasspath to workaround this issue.

Issue gh-10350
2022-01-14 11:58:45 -06:00
Steve Riesenberg bb9d9d7f9d Update org.jetbrains.kotlin to 1.6.10
Closes gh-10724
2022-01-14 11:58:45 -06:00
Steve Riesenberg 815891d6f0 Update cas-client-core to 3.6.4
Closes gh-10723
2022-01-14 11:58:45 -06:00
Steve Riesenberg 5187bea5ea Update hibernate-entitymanager to 5.6.3.Final
Closes gh-10722
2022-01-14 11:58:45 -06:00
Steve Riesenberg c6c27d795b Update org.bouncycastle to 1.70
Closes gh-10721
2022-01-14 11:58:45 -06:00
Steve Riesenberg f8c8d049c3 Update assertj-core to 3.22.0
Closes gh-10720
2022-01-14 11:58:45 -06:00
Steve Riesenberg bb92dd3cc5 Update org.aspectj to 1.9.8.RC3
Closes gh-10719
2022-01-14 11:58:45 -06:00
Steve Riesenberg 60653ddf19 Update htmlunit to 2.56.0
Closes gh-10718
2022-01-14 11:58:45 -06:00
Steve Riesenberg 7c54f98944 Update io.r2dbc to 0.9.0.RELEASE
Closes gh-10717
2022-01-14 11:58:45 -06:00
Steve Riesenberg 5cd7c71923 Update io.projectreactor to 2020.0.15
Closes gh-10715
2022-01-14 11:58:45 -06:00
Steve Riesenberg 7d0185f051 Update mockk to 1.12.2
Closes gh-10714
2022-01-14 11:58:45 -06:00
Steve Riesenberg b91d38752a Update com.nimbusds to 9.22
Closes gh-10713
2022-01-14 11:58:45 -06:00
Steve Riesenberg 9b12616913 Update jackson-bom to 2.13.1
Closes gh-10710
2022-01-14 11:58:45 -06:00
Steve Riesenberg 08139cf9f4 Update logback-classic to 1.2.10
Closes gh-10709
2022-01-14 11:58:45 -06:00
Steve Riesenberg 801dcfdcb4 Allow milestones and release candidates in version upgrades 2022-01-14 11:58:45 -06:00
Steve Riesenberg 4939331501 Fix inconsistency in hasProperty check 2022-01-13 17:39:37 -06:00
Steve Riesenberg 8abd4e999f Add GitHubReleasePlugin with createGitHubRelease task
Closes gh-10456
Closes gh-10457
2022-01-13 17:39:36 -06:00
Josh Cummings 194eaf8491 Pull most recent Structure101 version
Closes gh-10696
2022-01-11 10:03:13 -07:00
Marcus Da Coregio 60ed3602f6 Make source code compatible with JDK 8
Closes gh-10695
2022-01-11 09:19:41 -03:00
Joe Grandja 214cfe807e Allow Jwt assertion to be resolved
Closes gh-9812
2022-01-10 10:42:10 -05:00
heowc 1ab0705b47 Fix typo 2022-01-10 16:17:42 +01:00
Marcus Da Coregio f04cd641b0 Fix @since tag
Issue gh-10590, gh-10554
2022-01-06 13:18:25 -03:00
Rob Winch 3bb82c4449 Antora prerelease: true for milestone and rc 2022-01-05 09:59:48 -06:00
Rob Winch 89366d0874 Update RELEASE.adoc for antora.yml 2022-01-04 15:51:03 -06:00
Rob Winch 6884a16726 Add CheckAntoraVersionPlugin 2022-01-04 15:29:14 -06:00
Josh Cummings 6b54afe9a3 Remove SAML 2.0 Logout Default
Closes gh-10607
2022-01-03 12:54:22 -07:00
Josh Cummings b9453da343 Support No SingleLogoutServiceLocation
Closes gh-10674
2022-01-03 12:54:18 -07:00
Marcus Da Coregio 18427b6411 Configure WebInvocationPrivilegeEvaluator bean for multiple filter chains
Closes gh-10554
2021-12-13 08:57:30 -03:00
Marcus Da Coregio 7e17a00197 Add RequestMatcherEntry 2021-12-13 08:57:30 -03:00
Marcus Da Coregio 53b8cff26f Introduce AuthorizationManagerWebInvocationPrivilegeEvaluator
Closes gh-10590
2021-12-13 08:57:30 -03:00
Josh Cummings cd8983d4e5 Polish enableSessionUrlRewriting Clarification
Closes gh-7644
2021-12-09 12:14:40 -07:00
James Howe 5598688fa6 Clarify behaviour of enableSessionUrlRewriting
See #3087
2021-12-09 12:06:30 -07:00
Guirong Hu 22379e79e7 Fix the bug that the custom GrantedAuthority comparison fails
Closes gh-10566
2021-12-08 08:50:36 -03:00
Marcus Da Coregio 65426a40ec Add Cross Origin Policies headers
Add DSL support for Cross-Origin-Opener-Policy, Cross-Origin-Embedder-Policy and Cross-Origin-Resource-Policy headers

Closes gh-9385, gh-10118
2021-12-07 17:23:06 +01:00
Steve Riesenberg 7ec3b55ab3 Fix Reactive OAuth2 Kotlin DSL examples
Closes gh-10580
2021-12-07 13:48:11 +01:00
Marcus Da Coregio ed3b0fbaad Prevent using both authorizeRequests and authorizeHttpRequests
Closes gh-10573
2021-12-06 15:47:49 -03:00
Steve Riesenberg 62e8799a8d Use BDD in tests 2021-12-02 17:44:47 -06:00
Steve Riesenberg df0f6f83af Polish gh-9597 2021-12-02 17:44:47 -06:00
Karl Tinawi 925d531cbe Set details on authentication token created by HttpServlet3RequestFactory
Currently the login mechanism when triggered by executing HttpServlet3RequestFactory#login does not set any details on the underlying authentication token that is authenticated.

This change adds an AuthenticationDetailsSource on the HttpServlet3RequestFactory, which defaults to a WebAuthenticationDetailsSource.

Closes gh-9579
2021-12-02 17:44:46 -06:00
Steve Riesenberg 074e38d565 Add missing since
Issue gh-7765
2021-12-02 12:09:57 -06:00
Steve Riesenberg 3af619d565 Add hasIpAddress to Reactive Kotlin DSL
Closes gh-10571
2021-12-02 12:01:11 -06:00
Steve Riesenberg bb2d80fea3 Update copyright year
Issue gh-10557
2021-12-01 17:35:43 -06:00
Steve Riesenberg f49c286050 Fix case sensitive headers comparison
Closes gh-10557
2021-12-01 15:05:13 -06:00
Arnaud Mergey dbe4d704f8 Add SP NameIDFormat Support
closes gh-9115
2021-12-01 13:23:30 -07:00
Josh Cummings a68411566e Polish Memory Leak Mitigation
Issue gh-9841
2021-11-30 15:33:47 -07:00
Hiroshi Shirosaki 2bc643d6c8 Address SecurityContextHolder memory leak
To get current context without creating a new context.
Creating a new context may cause ThreadLocal leak.

Closes gh-9841
2021-11-30 15:33:39 -07:00
Josh Cummings 1251cde04c Add Missing Since
Issue gh-10482
2021-11-30 15:17:48 -07:00
Igor Pelesic a3a9de1b9b PermitAllSupport supports AuthorizeHttpRequestsConfigurer
PermitAllSupport supports either an ExpressionUrlAuthorizationConfigurer or an AuthorizeHttpRequestsConfigurer. If none or both are configured an error message is thrown.

Closes gh-10482
2021-11-30 15:17:22 -07:00
Steve Riesenberg f838b7cb1d Polish gh-10081 2021-11-30 15:44:41 -06:00
Jonas Dittrich 23e895f0b1 Add ObjectIdentityGenerator customization to JdbcAclService
Providing the possibility to change, how ObjectIdentitys are created inside the BasicLookupStrategy,JdbcAclService

There was a problem with hard coded object identity creation inside the BasicLookupStrategy and the JdbcAclService. It was overkill to overwrite
these classes only for changing this, so introducing an ObjectIdentityGenerator seems the be the better solution here. At default, the standard
ObjectIdentityRetrievalStrategyImpl is used, but can be customized due to setters.

Closes gh-10079
2021-11-30 15:43:11 -06:00
Steve Riesenberg 204f0b4599 Polish gh-10007 2021-11-30 15:27:58 -06:00
Guirong Hu 43317c5a61 Support IP whitelist for Spring Security Webflux
Closes gh-7765
2021-11-30 15:27:58 -06:00
Jonas Erbe dec858a5b7 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:04:30 -07:00
Eleftheria Stein bbeca7cd65 Polish LDAP serialization
Closes gh-9263
2021-11-29 18:03:15 +01:00
Markus Heiden 3c18278123 Start with LDAP Jackson2 mixins
Issue gh-9263
2021-11-29 18:03:03 +01:00
Henning Poettker bb99d7d95a Fix return type for NoOpPasswordEncoder bean in documentation 2021-11-23 14:13:12 -03:00
Lars Grefer d736a2b358 Remove usages of Gradle's jcenter() repository
Closes gh-10253
2021-11-22 09:28:47 -03:00
Lars Grefer cf95d3f91e Fix Gradle Deprecation Warnings 2021-11-22 09:28:24 -03:00
Dávid Kováč 17e28fa7aa Update clockSkew javadoc according to implementation
Closes gh-10174
2021-11-19 13:48:32 +01:00
Josh Cummings 739cdc1a4c Polish AuthRequestConverter Sample Doc
Issue gh-10364
2021-11-18 13:36:10 -07:00
Norbert Nowak 02cd1dd3c4 Fix AuthnRequestConverter Sample Typos
Closes gh-10364
2021-11-18 13:26:25 -07:00
Jeff Maxwell b7cc667d21 Fix setJWTClaimSetJWSKeySelector Typo
Closes gh-10504
2021-11-16 15:33:27 -07:00
Jeff Maxwell 3fb1565cc0 Fix jwtDecoder Documentation Usage
Closes gh-10505
2021-11-16 15:17:37 -07:00
Khaled Hamlaoui 00fafd878c Allow custom OAuth2ErrorHttpMessageConverter with OAuth2ErrorResponseErrorHandler
Closes gh-10425
2021-11-16 15:27:48 -06:00
Rob Winch 96a6fef820 Prevent Save @Transient Authentication with existing HttpSession
Previously, @Transient Authentication would get saved if an existing
HttpSession existed but it shouldn't.

This commit always prevents @Transient Authentication from being saved.

Closes gh-9992
2021-11-16 14:44:49 -06:00
« Christophe 4318a51971 Fix CsrfConfigurer default AccessDeniedHandler consistency
Fix when AccessDeniedHandler is specified per RequestMatcher on
ExceptionHandlingConfigurer.

This introduces evolutions on :
- CsrfConfigurer#getDefaultAccessDeniedHandler,
to retrieve an AccessDeniedHandler similar to the one used by
ExceptionHandlingConfigurer.
- OAuth2ResourceServerConfigurer#accessDeniedHandler, to continue to
handle CsrfException with the default AccessDeniedHandler implementation

Fixes: gh-6511
2021-11-16 14:22:35 -06:00
Stephane Nicoll 61ee4e5a76 Avoid using SpEL to change the meaning of the injection point
This commit removes the use of SpEL expression and replaces it with an
explicit call to the underlying method.
2021-11-16 13:53:00 -06:00
Onur Kagan Ozcan aa0f788f59 Add RedirectStrategy customization to ChannelSecurityConfigurer for RetryWith classes 2021-11-16 13:44:18 -06:00
Rob Winch 0c201565fc Fix format DelegatingPasswordEncoder 2021-11-16 13:32:15 -06:00
Rob Winch 582629c087 Rename prefix/suffix in DelegatingPasswordEncoder
Issue gh-10273
2021-11-16 13:16:37 -06:00
heowc 399cf2e59d Support for changing prefix and suffix in DelegatingPasswordEncoder
Closes gh-10273
2021-11-16 13:16:37 -06:00
Lars Grefer ec8912aa47 Update aspectj-plugin to 6.3.0
Version 6.3.0 aligns with the used Gradle 7.3
2021-11-16 12:52:42 -06:00
Josh Cummings 7b15098570 Update Spring Security to 5.7
Closes gh-10509
2021-11-15 17:10:00 -07:00
Josh Cummings 6b6f473a1b Added authorizeHttpRequests Docs
Closes gh-10442
2021-11-15 16:23:18 -07:00
Rob Winch 29a4b2bc9b Next Development Version 2021-11-15 16:13:45 -06:00
307 changed files with 17310 additions and 1452 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 }}
+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
@@ -42,7 +42,7 @@ import org.springframework.security.acls.domain.AuditLogger;
import org.springframework.security.acls.domain.DefaultPermissionFactory;
import org.springframework.security.acls.domain.DefaultPermissionGrantingStrategy;
import org.springframework.security.acls.domain.GrantedAuthoritySid;
import org.springframework.security.acls.domain.ObjectIdentityImpl;
import org.springframework.security.acls.domain.ObjectIdentityRetrievalStrategyImpl;
import org.springframework.security.acls.domain.PermissionFactory;
import org.springframework.security.acls.domain.PrincipalSid;
import org.springframework.security.acls.model.AccessControlEntry;
@@ -51,6 +51,7 @@ import org.springframework.security.acls.model.AclCache;
import org.springframework.security.acls.model.MutableAcl;
import org.springframework.security.acls.model.NotFoundException;
import org.springframework.security.acls.model.ObjectIdentity;
import org.springframework.security.acls.model.ObjectIdentityGenerator;
import org.springframework.security.acls.model.Permission;
import org.springframework.security.acls.model.PermissionGrantingStrategy;
import org.springframework.security.acls.model.Sid;
@@ -109,6 +110,8 @@ public class BasicLookupStrategy implements LookupStrategy {
private final AclAuthorizationStrategy aclAuthorizationStrategy;
private ObjectIdentityGenerator objectIdentityGenerator;
private PermissionFactory permissionFactory = new DefaultPermissionFactory();
private final AclCache aclCache;
@@ -162,6 +165,7 @@ public class BasicLookupStrategy implements LookupStrategy {
this.aclCache = aclCache;
this.aclAuthorizationStrategy = aclAuthorizationStrategy;
this.grantingStrategy = grantingStrategy;
this.objectIdentityGenerator = new ObjectIdentityRetrievalStrategyImpl();
this.aclClassIdUtils = new AclClassIdUtils();
this.fieldAces.setAccessible(true);
this.fieldAcl.setAccessible(true);
@@ -488,6 +492,11 @@ public class BasicLookupStrategy implements LookupStrategy {
}
}
public final void setObjectIdentityGenerator(ObjectIdentityGenerator objectIdentityGenerator) {
Assert.notNull(objectIdentityGenerator, "objectIdentityGenerator cannot be null");
this.objectIdentityGenerator = objectIdentityGenerator;
}
public final void setConversionService(ConversionService conversionService) {
this.aclClassIdUtils = new AclClassIdUtils(conversionService);
}
@@ -569,7 +578,8 @@ public class BasicLookupStrategy implements LookupStrategy {
// target id type, e.g. UUID.
Serializable identifier = (Serializable) rs.getObject("object_id_identity");
identifier = BasicLookupStrategy.this.aclClassIdUtils.identifierFrom(identifier, rs);
ObjectIdentity objectIdentity = new ObjectIdentityImpl(rs.getString("class"), identifier);
ObjectIdentity objectIdentity = BasicLookupStrategy.this.objectIdentityGenerator
.createObjectIdentity(identifier, rs.getString("class"));
Acl parentAcl = null;
long parentAclId = rs.getLong("parent_object");
@@ -31,11 +31,12 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.core.convert.ConversionService;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.acls.domain.ObjectIdentityImpl;
import org.springframework.security.acls.domain.ObjectIdentityRetrievalStrategyImpl;
import org.springframework.security.acls.model.Acl;
import org.springframework.security.acls.model.AclService;
import org.springframework.security.acls.model.NotFoundException;
import org.springframework.security.acls.model.ObjectIdentity;
import org.springframework.security.acls.model.ObjectIdentityGenerator;
import org.springframework.security.acls.model.Sid;
import org.springframework.util.Assert;
@@ -81,6 +82,8 @@ public class JdbcAclService implements AclService {
private AclClassIdUtils aclClassIdUtils;
private ObjectIdentityGenerator objectIdentityGenerator;
public JdbcAclService(DataSource dataSource, LookupStrategy lookupStrategy) {
this(new JdbcTemplate(dataSource), lookupStrategy);
}
@@ -91,6 +94,7 @@ public class JdbcAclService implements AclService {
this.jdbcOperations = jdbcOperations;
this.lookupStrategy = lookupStrategy;
this.aclClassIdUtils = new AclClassIdUtils();
this.objectIdentityGenerator = new ObjectIdentityRetrievalStrategyImpl();
}
@Override
@@ -105,7 +109,7 @@ public class JdbcAclService implements AclService {
String javaType = rs.getString("class");
Serializable identifier = (Serializable) rs.getObject("obj_id");
identifier = this.aclClassIdUtils.identifierFrom(identifier, rs);
return new ObjectIdentityImpl(javaType, identifier);
return this.objectIdentityGenerator.createObjectIdentity(identifier, javaType);
}
@Override
@@ -165,6 +169,11 @@ public class JdbcAclService implements AclService {
this.aclClassIdUtils = new AclClassIdUtils(conversionService);
}
public void setObjectIdentityGenerator(ObjectIdentityGenerator objectIdentityGenerator) {
Assert.notNull(objectIdentityGenerator, "objectIdentityGenerator cannot be null");
this.objectIdentityGenerator = objectIdentityGenerator;
}
protected boolean isAclClassIdSupported() {
return this.aclClassIdSupported;
}
@@ -318,4 +318,13 @@ public abstract class AbstractBasicLookupStrategyTests {
assertThat(((GrantedAuthoritySid) result).getGrantedAuthority()).isEqualTo("sid");
}
@Test
public void setObjectIdentityGeneratorWhenNullThenThrowsIllegalArgumentException() {
// @formatter:off
assertThatIllegalArgumentException()
.isThrownBy(() -> this.strategy.setObjectIdentityGenerator(null))
.withMessage("objectIdentityGenerator cannot be null");
// @formatter:on
}
}
@@ -45,6 +45,7 @@ import org.springframework.security.acls.model.Sid;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.anyString;
@@ -170,6 +171,26 @@ public class JdbcAclServiceTests {
.isEqualTo(UUID.fromString("25d93b3f-c3aa-4814-9d5e-c7c96ced7762"));
}
@Test
public void setObjectIdentityGeneratorWhenNullThenThrowsIllegalArgumentException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> this.aclServiceIntegration.setObjectIdentityGenerator(null))
.withMessage("objectIdentityGenerator cannot be null");
}
@Test
public void findChildrenWhenObjectIdentityGeneratorSetThenUsed() {
this.aclServiceIntegration
.setObjectIdentityGenerator((id, type) -> new ObjectIdentityImpl(type, "prefix:" + id));
ObjectIdentity objectIdentity = new ObjectIdentityImpl("location", "US");
this.aclServiceIntegration.setAclClassIdSupported(true);
List<ObjectIdentity> objectIdentities = this.aclServiceIntegration.findChildren(objectIdentity);
assertThat(objectIdentities.size()).isEqualTo(1);
assertThat(objectIdentities.get(0).getType()).isEqualTo("location");
assertThat(objectIdentities.get(0).getIdentifier()).isEqualTo("prefix:US-PAL");
}
class MockLongIdDomainObject {
private Object id;
-4
View File
@@ -27,7 +27,3 @@ sourceSets.test.aspectj.srcDir "src/test/java"
sourceSets.test.java.srcDirs = files()
compileAspectj.ajcOptions.outxmlfile = "META-INF/aop.xml"
aspectj {
version = aspectjVersion
}
+10 -3
View File
@@ -2,7 +2,7 @@ buildscript {
dependencies {
classpath "io.spring.javaformat:spring-javaformat-gradle-plugin:$springJavaformatVersion"
classpath 'io.spring.nohttp:nohttp-gradle:0.0.10'
classpath "io.freefair.gradle:aspectj-plugin:6.2.0"
classpath "io.freefair.gradle:aspectj-plugin:6.4.1"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
classpath "com.netflix.nebula:nebula-project-plugin:8.2.0"
}
@@ -22,6 +22,7 @@ apply plugin: 'org.springframework.security.update-dependencies'
apply plugin: 'org.springframework.security.sagan'
apply plugin: 'org.springframework.github.milestone'
apply plugin: 'org.springframework.github.changelog'
apply plugin: 'org.springframework.github.release'
group = 'org.springframework.security'
description = 'Spring Security'
@@ -46,6 +47,13 @@ tasks.named("gitHubCheckMilestoneHasNoOpenIssues") {
}
}
tasks.named("createGitHubRelease") {
repository {
owner = "spring-projects"
name = "spring-security"
}
}
tasks.named("updateDependencies") {
// we aren't Gradle 7 compatible yet
checkForGradleUpdate = false
@@ -64,8 +72,6 @@ updateDependenciesSettings {
dependencyExcludes {
majorVersionBump()
alphaBetaVersions()
releaseCandidatesVersions()
milestoneVersions()
snapshotVersions()
addRule { components ->
components.withModule("commons-codec:commons-codec") { selection ->
@@ -105,6 +111,7 @@ subprojects {
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
options.compilerArgs.add("-parameters")
options.release = 8
}
}
+16 -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"
@@ -56,6 +59,10 @@ gradlePlugin {
id = "org.springframework.github.changelog"
implementationClass = "org.springframework.gradle.github.changelog.GitHubChangelogPlugin"
}
githubRelease {
id = "org.springframework.github.release"
implementationClass = "org.springframework.gradle.github.release.GitHubReleasePlugin"
}
s101 {
id = "s101"
implementationClass = "s101.S101Plugin"
@@ -73,10 +80,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.15'
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 +96,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 +108,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) {
@@ -59,14 +59,22 @@ public class IntegrationTestPlugin implements Plugin<Project> {
integrationTestRuntime {
extendsFrom integrationTestCompile, testRuntime, testRuntimeOnly
}
integrationTestCompileClasspath {
extendsFrom integrationTestCompile
canBeResolved = true
}
integrationTestRuntimeClasspath {
extendsFrom integrationTestRuntime
canBeResolved = true
}
}
project.sourceSets {
integrationTest {
java.srcDir project.file('src/integration-test/java')
resources.srcDir project.file('src/integration-test/resources')
compileClasspath = project.sourceSets.main.output + project.sourceSets.test.output + project.configurations.integrationTestCompile
runtimeClasspath = output + compileClasspath + project.configurations.integrationTestRuntime
compileClasspath = project.sourceSets.main.output + project.sourceSets.test.output + project.configurations.integrationTestCompileClasspath
runtimeClasspath = output + compileClasspath + project.configurations.integrationTestRuntimeClasspath
}
}
@@ -85,7 +93,7 @@ public class IntegrationTestPlugin implements Plugin<Project> {
project.idea {
module {
testSourceDirs += project.file('src/integration-test/java')
scopes.TEST.plus += [ project.configurations.integrationTestCompile ]
scopes.TEST.plus += [ project.configurations.integrationTestCompileClasspath ]
}
}
}
@@ -115,7 +123,7 @@ public class IntegrationTestPlugin implements Plugin<Project> {
project.plugins.withType(EclipsePlugin) {
project.eclipse.classpath {
plusConfigurations += [ project.configurations.integrationTestCompile ]
plusConfigurations += [ project.configurations.integrationTestCompileClasspath ]
}
}
}
@@ -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,10 +1,11 @@
package org.springframework.gradle.github.milestones;
package org.springframework.gradle.github;
public class RepositoryRef {
private String owner;
private String name;
RepositoryRef() {
public RepositoryRef() {
}
public RepositoryRef(String owner, String name) {
@@ -62,4 +63,3 @@ public class RepositoryRef {
}
}
}
@@ -16,6 +16,9 @@
package org.springframework.gradle.github.changelog;
import java.io.File;
import java.nio.file.Paths;
import org.gradle.api.Action;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
@@ -28,12 +31,10 @@ import org.gradle.api.artifacts.repositories.IvyArtifactRepository;
import org.gradle.api.artifacts.repositories.IvyPatternRepositoryLayout;
import org.gradle.api.tasks.JavaExec;
import java.io.File;
import java.nio.file.Paths;
public class GitHubChangelogPlugin implements Plugin<Project> {
public static final String CHANGELOG_GENERATOR_CONFIGURATION_NAME = "changelogGenerator";
public static final String RELEASE_NOTES_PATH = "changelog/release-notes.md";
@Override
public void apply(Project project) {
@@ -42,7 +43,7 @@ public class GitHubChangelogPlugin implements Plugin<Project> {
project.getTasks().register("generateChangelog", JavaExec.class, new Action<JavaExec>() {
@Override
public void execute(JavaExec generateChangelog) {
File outputFile = project.file(Paths.get(project.getBuildDir().getPath(), "changelog/release-notes.md"));
File outputFile = project.file(Paths.get(project.getBuildDir().getPath(), RELEASE_NOTES_PATH));
outputFile.getParentFile().mkdirs();
generateChangelog.setGroup("Release");
generateChangelog.setDescription("Generates the changelog");
@@ -16,6 +16,9 @@
package org.springframework.gradle.github.milestones;
import java.io.IOException;
import java.util.List;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import okhttp3.Interceptor;
@@ -23,8 +26,7 @@ import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import java.io.IOException;
import java.util.List;
import org.springframework.gradle.github.RepositoryRef;
public class GitHubMilestoneApi {
private String baseUrl = "https://api.github.com";
@@ -21,6 +21,8 @@ import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.TaskAction;
import org.springframework.gradle.github.RepositoryRef;
public class GitHubMilestoneHasNoOpenIssuesTask extends DefaultTask {
@Input
private RepositoryRef repository = new RepositoryRef();
@@ -29,7 +29,7 @@ public class GitHubMilestonePlugin implements Plugin<Project> {
githubCheckMilestoneHasNoOpenIssues.setGroup("Release");
githubCheckMilestoneHasNoOpenIssues.setDescription("Checks if there are any open issues for the specified repository and milestone");
githubCheckMilestoneHasNoOpenIssues.setMilestoneTitle((String) project.findProperty("nextVersion"));
if (project.hasProperty("githubAccessToken")) {
if (project.hasProperty("gitHubAccessToken")) {
githubCheckMilestoneHasNoOpenIssues.setGitHubAccessToken((String) project.findProperty("gitHubAccessToken"));
}
}
@@ -0,0 +1,130 @@
/*
* 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.
* 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.github.release;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import org.gradle.api.Action;
import org.gradle.api.DefaultTask;
import org.gradle.api.Project;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.TaskAction;
import org.springframework.gradle.github.RepositoryRef;
import org.springframework.gradle.github.changelog.GitHubChangelogPlugin;
/**
* @author Steve Riesenberg
*/
public class CreateGitHubReleaseTask extends DefaultTask {
@Input
private RepositoryRef repository = new RepositoryRef();
@Input @Optional
private String gitHubAccessToken;
@Input
private String version;
@Input @Optional
private String branch = "main";
@Input
private boolean createRelease = false;
@TaskAction
public void createGitHubRelease() {
String body = readReleaseNotes();
Release release = Release.tag(this.version)
.commit(this.branch)
.name(this.version)
.body(body)
.preRelease(this.version.contains("-"))
.build();
System.out.printf("%sCreating GitHub release for %s/%s@%s\n",
this.createRelease ? "" : "[DRY RUN] ",
this.repository.getOwner(),
this.repository.getName(),
this.version
);
System.out.printf(" Release Notes:\n\n----\n%s\n----\n\n", body.trim());
if (this.createRelease) {
GitHubReleaseApi github = new GitHubReleaseApi(this.gitHubAccessToken);
github.publishRelease(this.repository, release);
}
}
private String readReleaseNotes() {
Project project = getProject();
File inputFile = project.file(Paths.get(project.getBuildDir().getPath(), GitHubChangelogPlugin.RELEASE_NOTES_PATH));
try {
return Files.readString(inputFile.toPath());
} catch (IOException ex) {
throw new RuntimeException("Unable to read release notes from " + inputFile, ex);
}
}
public RepositoryRef getRepository() {
return repository;
}
public void repository(Action<RepositoryRef> repository) {
repository.execute(this.repository);
}
public void setRepository(RepositoryRef repository) {
this.repository = repository;
}
public String getGitHubAccessToken() {
return gitHubAccessToken;
}
public void setGitHubAccessToken(String gitHubAccessToken) {
this.gitHubAccessToken = gitHubAccessToken;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public String getBranch() {
return branch;
}
public void setBranch(String branch) {
this.branch = branch;
}
public boolean isCreateRelease() {
return createRelease;
}
public void setCreateRelease(boolean createRelease) {
this.createRelease = createRelease;
}
}
@@ -0,0 +1,91 @@
/*
* 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.
* 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.github.release;
import java.io.IOException;
import com.google.gson.Gson;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.springframework.gradle.github.RepositoryRef;
/**
* Manage GitHub releases.
*
* @author Steve Riesenberg
*/
public class GitHubReleaseApi {
private String baseUrl = "https://api.github.com";
private final OkHttpClient httpClient;
private Gson gson = new Gson();
public GitHubReleaseApi(String gitHubAccessToken) {
this.httpClient = new OkHttpClient.Builder()
.addInterceptor(new AuthorizationInterceptor(gitHubAccessToken))
.build();
}
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
/**
* Publish a release with no binary attachments.
*
* @param repository The repository owner/name
* @param release The contents of the release
*/
public void publishRelease(RepositoryRef repository, Release release) {
String url = this.baseUrl + "/repos/" + repository.getOwner() + "/" + repository.getName() + "/releases";
String json = this.gson.toJson(release);
RequestBody body = RequestBody.create(MediaType.parse("application/json"), json);
Request request = new Request.Builder().url(url).post(body).build();
try {
Response response = this.httpClient.newCall(request).execute();
if (!response.isSuccessful()) {
throw new RuntimeException(String.format("Could not create release %s for repository %s/%s. Got response %s",
release.getName(), repository.getOwner(), repository.getName(), response));
}
} catch (IOException ex) {
throw new RuntimeException(String.format("Could not create release %s for repository %s/%s",
release.getName(), repository.getOwner(), repository.getName()), ex);
}
}
private static class AuthorizationInterceptor implements Interceptor {
private final String token;
public AuthorizationInterceptor(String token) {
this.token = token;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request().newBuilder()
.addHeader("Authorization", "Bearer " + this.token)
.build();
return chain.proceed(request);
}
}
}
@@ -0,0 +1,49 @@
/*
* 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.
* 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.github.release;
import groovy.lang.MissingPropertyException;
import org.gradle.api.Action;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
/**
* @author Steve Riesenberg
*/
public class GitHubReleasePlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
project.getTasks().register("createGitHubRelease", CreateGitHubReleaseTask.class, new Action<CreateGitHubReleaseTask>() {
@Override
public void execute(CreateGitHubReleaseTask createGitHubRelease) {
createGitHubRelease.setGroup("Release");
createGitHubRelease.setDescription("Create a github release");
createGitHubRelease.dependsOn("generateChangelog");
createGitHubRelease.setCreateRelease("true".equals(project.findProperty("createRelease")));
createGitHubRelease.setVersion((String) project.findProperty("nextVersion"));
if (project.hasProperty("branch")) {
createGitHubRelease.setBranch((String) project.findProperty("branch"));
}
createGitHubRelease.setGitHubAccessToken((String) project.findProperty("gitHubAccessToken"));
if (createGitHubRelease.isCreateRelease() && createGitHubRelease.getGitHubAccessToken() == null) {
throw new MissingPropertyException("Please provide an access token with -PgitHubAccessToken=...");
}
}
});
}
}
@@ -0,0 +1,156 @@
/*
* 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.
* 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.github.release;
import com.google.gson.annotations.SerializedName;
/**
* @author Steve Riesenberg
*/
public class Release {
@SerializedName("tag_name")
private final String tag;
@SerializedName("target_commitish")
private final String commit;
@SerializedName("name")
private final String name;
@SerializedName("body")
private final String body;
@SerializedName("draft")
private final boolean draft;
@SerializedName("prerelease")
private final boolean preRelease;
@SerializedName("generate_release_notes")
private final boolean generateReleaseNotes;
private Release(String tag, String commit, String name, String body, boolean draft, boolean preRelease, boolean generateReleaseNotes) {
this.tag = tag;
this.commit = commit;
this.name = name;
this.body = body;
this.draft = draft;
this.preRelease = preRelease;
this.generateReleaseNotes = generateReleaseNotes;
}
public String getTag() {
return tag;
}
public String getCommit() {
return commit;
}
public String getName() {
return name;
}
public String getBody() {
return body;
}
public boolean isDraft() {
return draft;
}
public boolean isPreRelease() {
return preRelease;
}
public boolean isGenerateReleaseNotes() {
return generateReleaseNotes;
}
@Override
public String toString() {
return "Release{" +
"tag='" + tag + '\'' +
", commit='" + commit + '\'' +
", name='" + name + '\'' +
", body='" + body + '\'' +
", draft=" + draft +
", preRelease=" + preRelease +
", generateReleaseNotes=" + generateReleaseNotes +
'}';
}
public static Builder tag(String tag) {
return new Builder().tag(tag);
}
public static Builder commit(String commit) {
return new Builder().commit(commit);
}
public static final class Builder {
private String tag;
private String commit;
private String name;
private String body;
private boolean draft;
private boolean preRelease;
private boolean generateReleaseNotes;
private Builder() {
}
public Builder tag(String tag) {
this.tag = tag;
return this;
}
public Builder commit(String commit) {
this.commit = commit;
return this;
}
public Builder name(String name) {
this.name = name;
return this;
}
public Builder body(String body) {
this.body = body;
return this;
}
public Builder draft(boolean draft) {
this.draft = draft;
return this;
}
public Builder preRelease(boolean preRelease) {
this.preRelease = preRelease;
return this;
}
public Builder generateReleaseNotes(boolean generateReleaseNotes) {
this.generateReleaseNotes = generateReleaseNotes;
return this;
}
public Release build() {
return new Release(tag, commit, name, body, draft, preRelease, generateReleaseNotes);
}
}
}
@@ -164,14 +164,18 @@ public class S101Configurer {
String source = "https://structure101.com/binaries/v6";
try (final WebClient webClient = new WebClient()) {
HtmlPage page = webClient.getPage(source);
Matcher matcher = null;
for (HtmlAnchor anchor : page.getAnchors()) {
Matcher matcher = Pattern.compile("(structure101-build-java-all-)(.*).zip").matcher(anchor.getHrefAttribute());
if (matcher.find()) {
copyZipToFilesystem(source, installationDirectory, matcher.group(1) + matcher.group(2));
return matcher.group(2);
Matcher candidate = Pattern.compile("(structure101-build-java-all-)(.*).zip").matcher(anchor.getHrefAttribute());
if (candidate.find()) {
matcher = candidate;
}
}
return null;
if (matcher == null) {
return null;
}
copyZipToFilesystem(source, installationDirectory, matcher.group(1) + matcher.group(2));
return matcher.group(2);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
@@ -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/");
}
@@ -1,15 +1,16 @@
package io.spring.gradle.github.milestones;
import java.util.concurrent.TimeUnit;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.gradle.github.milestones.GitHubMilestoneApi;
import org.springframework.gradle.github.milestones.RepositoryRef;
import java.util.concurrent.TimeUnit;
import org.springframework.gradle.github.RepositoryRef;
import org.springframework.gradle.github.milestones.GitHubMilestoneApi;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -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();
}
}
@@ -1,5 +1,7 @@
package org.springframework.gradle.github.milestones;
import java.util.concurrent.TimeUnit;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
@@ -7,7 +9,7 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.concurrent.TimeUnit;
import org.springframework.gradle.github.RepositoryRef;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -0,0 +1,151 @@
/*
* 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.
* 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.github.release;
import java.util.concurrent.TimeUnit;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import org.junit.Test;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.gradle.github.RepositoryRef;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* @author Steve Riesenberg
*/
public class GitHubReleaseApiTests {
private GitHubReleaseApi github;
private RepositoryRef repository = new RepositoryRef("spring-projects", "spring-security");
private MockWebServer server;
private String baseUrl;
@BeforeEach
public void setup() throws Exception {
this.server = new MockWebServer();
this.server.start();
this.github = new GitHubReleaseApi("mock-oauth-token");
this.baseUrl = this.server.url("/api").toString();
this.github.setBaseUrl(this.baseUrl);
}
@AfterEach
public void cleanup() throws Exception {
this.server.shutdown();
}
@Test
public void publishReleaseWhenValidParametersThenSuccess() throws Exception {
String responseJson = "{\n" +
" \"url\": \"https://api.github.com/spring-projects/spring-security/releases/1\",\n" +
" \"html_url\": \"https://github.com/spring-projects/spring-security/releases/tags/v1.0.0\",\n" +
" \"assets_url\": \"https://api.github.com/spring-projects/spring-security/releases/1/assets\",\n" +
" \"upload_url\": \"https://uploads.github.com/spring-projects/spring-security/releases/1/assets{?name,label}\",\n" +
" \"tarball_url\": \"https://api.github.com/spring-projects/spring-security/tarball/v1.0.0\",\n" +
" \"zipball_url\": \"https://api.github.com/spring-projects/spring-security/zipball/v1.0.0\",\n" +
" \"discussion_url\": \"https://github.com/spring-projects/spring-security/discussions/90\",\n" +
" \"id\": 1,\n" +
" \"node_id\": \"MDc6UmVsZWFzZTE=\",\n" +
" \"tag_name\": \"v1.0.0\",\n" +
" \"target_commitish\": \"main\",\n" +
" \"name\": \"v1.0.0\",\n" +
" \"body\": \"Description of the release\",\n" +
" \"draft\": false,\n" +
" \"prerelease\": false,\n" +
" \"created_at\": \"2013-02-27T19:35:32Z\",\n" +
" \"published_at\": \"2013-02-27T19:35:32Z\",\n" +
" \"author\": {\n" +
" \"login\": \"sjohnr\",\n" +
" \"id\": 1,\n" +
" \"node_id\": \"MDQ6VXNlcjE=\",\n" +
" \"avatar_url\": \"https://github.com/images/avatar.gif\",\n" +
" \"gravatar_id\": \"\",\n" +
" \"url\": \"https://api.github.com/users/sjohnr\",\n" +
" \"html_url\": \"https://github.com/sjohnr\",\n" +
" \"followers_url\": \"https://api.github.com/users/sjohnr/followers\",\n" +
" \"following_url\": \"https://api.github.com/users/sjohnr/following{/other_user}\",\n" +
" \"gists_url\": \"https://api.github.com/users/sjohnr/gists{/gist_id}\",\n" +
" \"starred_url\": \"https://api.github.com/users/sjohnr/starred{/owner}{/repo}\",\n" +
" \"subscriptions_url\": \"https://api.github.com/users/sjohnr/subscriptions\",\n" +
" \"organizations_url\": \"https://api.github.com/users/sjohnr/orgs\",\n" +
" \"repos_url\": \"https://api.github.com/users/sjohnr/repos\",\n" +
" \"events_url\": \"https://api.github.com/users/sjohnr/events{/privacy}\",\n" +
" \"received_events_url\": \"https://api.github.com/users/sjohnr/received_events\",\n" +
" \"type\": \"User\",\n" +
" \"site_admin\": false\n" +
" },\n" +
" \"assets\": [\n" +
" {\n" +
" \"url\": \"https://api.github.com/spring-projects/spring-security/releases/assets/1\",\n" +
" \"browser_download_url\": \"https://github.com/spring-projects/spring-security/releases/download/v1.0.0/example.zip\",\n" +
" \"id\": 1,\n" +
" \"node_id\": \"MDEyOlJlbGVhc2VBc3NldDE=\",\n" +
" \"name\": \"example.zip\",\n" +
" \"label\": \"short description\",\n" +
" \"state\": \"uploaded\",\n" +
" \"content_type\": \"application/zip\",\n" +
" \"size\": 1024,\n" +
" \"download_count\": 42,\n" +
" \"created_at\": \"2013-02-27T19:35:32Z\",\n" +
" \"updated_at\": \"2013-02-27T19:35:32Z\",\n" +
" \"uploader\": {\n" +
" \"login\": \"sjohnr\",\n" +
" \"id\": 1,\n" +
" \"node_id\": \"MDQ6VXNlcjE=\",\n" +
" \"avatar_url\": \"https://github.com/images/avatar.gif\",\n" +
" \"gravatar_id\": \"\",\n" +
" \"url\": \"https://api.github.com/users/sjohnr\",\n" +
" \"html_url\": \"https://github.com/sjohnr\",\n" +
" \"followers_url\": \"https://api.github.com/users/sjohnr/followers\",\n" +
" \"following_url\": \"https://api.github.com/users/sjohnr/following{/other_user}\",\n" +
" \"gists_url\": \"https://api.github.com/users/sjohnr/gists{/gist_id}\",\n" +
" \"starred_url\": \"https://api.github.com/users/sjohnr/starred{/owner}{/repo}\",\n" +
" \"subscriptions_url\": \"https://api.github.com/users/sjohnr/subscriptions\",\n" +
" \"organizations_url\": \"https://api.github.com/users/sjohnr/orgs\",\n" +
" \"repos_url\": \"https://api.github.com/users/sjohnr/repos\",\n" +
" \"events_url\": \"https://api.github.com/users/sjohnr/events{/privacy}\",\n" +
" \"received_events_url\": \"https://api.github.com/users/sjohnr/received_events\",\n" +
" \"type\": \"User\",\n" +
" \"site_admin\": false\n" +
" }\n" +
" }\n" +
" ]\n" +
"}";
this.server.enqueue(new MockResponse().setBody(responseJson));
this.github.publishRelease(this.repository, Release.tag("1.0.0").build());
RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS);
assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("post");
assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/releases");
assertThat(recordedRequest.getBody().toString()).isEqualTo("{\"tag_name\":\"1.0.0\"}");
}
@Test
public void publishReleaseWhenErrorResponseThenException() throws Exception {
this.server.enqueue(new MockResponse().setResponseCode(400));
assertThatExceptionOfType(RuntimeException.class)
.isThrownBy(() -> this.github.publishRelease(this.repository, Release.tag("1.0.0").build()));
}
}
@@ -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"
+14 -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'
@@ -74,13 +76,20 @@ dependencies {
testImplementation "org.apache.directory.server:apacheds-protocol-ldap"
testImplementation "org.apache.directory.server:apacheds-server-jndi"
testImplementation 'org.apache.directory.shared:shared-ldap'
testImplementation "com.unboundid:unboundid-ldapsdk"
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 +98,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'
@@ -0,0 +1,185 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.ldap;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.test.web.servlet.MockMvc;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
@ExtendWith(SpringTestContextExtension.class)
public class EmbeddedLdapServerContextSourceFactoryBeanITests {
public final SpringTestContext spring = new SpringTestContext(this);
@Autowired
private MockMvc mockMvc;
@Test
public void contextSourceFactoryBeanWhenEmbeddedServerThenAuthenticates() throws Exception {
this.spring.register(FromEmbeddedLdapServerConfig.class).autowire();
this.mockMvc.perform(formLogin().user("bob").password("bobspassword"))
.andExpect(authenticated().withUsername("bob"));
}
@Test
public void contextSourceFactoryBeanWhenPortZeroThenAuthenticates() throws Exception {
this.spring.register(PortZeroConfig.class).autowire();
this.mockMvc.perform(formLogin().user("bob").password("bobspassword"))
.andExpect(authenticated().withUsername("bob"));
}
@Test
public void contextSourceFactoryBeanWhenCustomLdifAndRootThenAuthenticates() throws Exception {
this.spring.register(CustomLdifAndRootConfig.class).autowire();
this.mockMvc.perform(formLogin().user("pg").password("password")).andExpect(authenticated().withUsername("pg"));
}
@Test
public void contextSourceFactoryBeanWhenCustomManagerDnThenAuthenticates() throws Exception {
this.spring.register(CustomManagerDnConfig.class).autowire();
this.mockMvc.perform(formLogin().user("bob").password("bobspassword"))
.andExpect(authenticated().withUsername("bob"));
}
@Test
public void contextSourceFactoryBeanWhenManagerDnAndNoPasswordThenException() {
assertThatExceptionOfType(UnsatisfiedDependencyException.class)
.isThrownBy(() -> this.spring.register(CustomManagerDnNoPasswordConfig.class).autowire())
.withRootCauseInstanceOf(IllegalStateException.class)
.withMessageContaining("managerPassword is required if managerDn is supplied");
}
@EnableWebSecurity
static class FromEmbeddedLdapServerConfig {
@Bean
EmbeddedLdapServerContextSourceFactoryBean contextSourceFactoryBean() {
return EmbeddedLdapServerContextSourceFactoryBean.fromEmbeddedLdapServer();
}
@Bean
AuthenticationManager authenticationManager(LdapContextSource contextSource) {
LdapBindAuthenticationManagerFactory factory = new LdapBindAuthenticationManagerFactory(contextSource);
factory.setUserDnPatterns("uid={0},ou=people");
return factory.createAuthenticationManager();
}
}
@EnableWebSecurity
static class PortZeroConfig {
@Bean
EmbeddedLdapServerContextSourceFactoryBean contextSourceFactoryBean() {
EmbeddedLdapServerContextSourceFactoryBean factoryBean = EmbeddedLdapServerContextSourceFactoryBean
.fromEmbeddedLdapServer();
factoryBean.setPort(0);
return factoryBean;
}
@Bean
AuthenticationManager authenticationManager(LdapContextSource contextSource) {
LdapBindAuthenticationManagerFactory factory = new LdapBindAuthenticationManagerFactory(contextSource);
factory.setUserDnPatterns("uid={0},ou=people");
return factory.createAuthenticationManager();
}
}
@EnableWebSecurity
static class CustomLdifAndRootConfig {
@Bean
EmbeddedLdapServerContextSourceFactoryBean contextSourceFactoryBean() {
EmbeddedLdapServerContextSourceFactoryBean factoryBean = EmbeddedLdapServerContextSourceFactoryBean
.fromEmbeddedLdapServer();
factoryBean.setLdif("classpath*:test-server2.xldif");
factoryBean.setRoot("dc=monkeymachine,dc=co,dc=uk");
return factoryBean;
}
@Bean
AuthenticationManager authenticationManager(LdapContextSource contextSource) {
LdapBindAuthenticationManagerFactory factory = new LdapBindAuthenticationManagerFactory(contextSource);
factory.setUserDnPatterns("uid={0},ou=gorillas");
return factory.createAuthenticationManager();
}
}
@EnableWebSecurity
static class CustomManagerDnConfig {
@Bean
EmbeddedLdapServerContextSourceFactoryBean contextSourceFactoryBean() {
EmbeddedLdapServerContextSourceFactoryBean factoryBean = EmbeddedLdapServerContextSourceFactoryBean
.fromEmbeddedLdapServer();
factoryBean.setManagerDn("uid=admin,ou=system");
factoryBean.setManagerPassword("secret");
return factoryBean;
}
@Bean
AuthenticationManager authenticationManager(LdapContextSource contextSource) {
LdapPasswordComparisonAuthenticationManagerFactory factory = new LdapPasswordComparisonAuthenticationManagerFactory(
contextSource, NoOpPasswordEncoder.getInstance());
factory.setUserDnPatterns("uid={0},ou=people");
return factory.createAuthenticationManager();
}
}
@EnableWebSecurity
static class CustomManagerDnNoPasswordConfig {
@Bean
EmbeddedLdapServerContextSourceFactoryBean contextSourceFactoryBean() {
EmbeddedLdapServerContextSourceFactoryBean factoryBean = EmbeddedLdapServerContextSourceFactoryBean
.fromEmbeddedLdapServer();
factoryBean.setManagerDn("uid=admin,ou=system");
return factoryBean;
}
@Bean
AuthenticationManager authenticationManager(LdapContextSource contextSource) {
LdapPasswordComparisonAuthenticationManagerFactory factory = new LdapPasswordComparisonAuthenticationManagerFactory(
contextSource, NoOpPasswordEncoder.getInstance());
factory.setUserDnPatterns("uid={0},ou=people");
return factory.createAuthenticationManager();
}
}
}
@@ -0,0 +1,241 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.ldap;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
import org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
import org.springframework.security.ldap.server.ApacheDSContainer;
import org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator;
import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator;
import org.springframework.security.ldap.userdetails.UserDetailsContextMapper;
import org.springframework.test.web.servlet.MockMvc;
import static org.mockito.Mockito.mock;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
@ExtendWith(SpringTestContextExtension.class)
public class LdapBindAuthenticationManagerFactoryITests {
public final SpringTestContext spring = new SpringTestContext(this);
@Autowired
private MockMvc mockMvc;
@Test
public void authenticationManagerFactoryWhenFromContextSourceThenAuthenticates() throws Exception {
this.spring.register(FromContextSourceConfig.class).autowire();
this.mockMvc.perform(formLogin().user("bob").password("bobspassword"))
.andExpect(authenticated().withUsername("bob"));
}
@Test
public void ldapAuthenticationProviderCustomLdapAuthoritiesPopulator() throws Exception {
CustomAuthoritiesPopulatorConfig.LAP = new DefaultLdapAuthoritiesPopulator(mock(LdapContextSource.class),
null) {
@Override
protected Set<GrantedAuthority> getAdditionalRoles(DirContextOperations user, String username) {
return new HashSet<>(AuthorityUtils.createAuthorityList("ROLE_EXTRA"));
}
};
this.spring.register(CustomAuthoritiesPopulatorConfig.class).autowire();
this.mockMvc.perform(formLogin().user("bob").password("bobspassword")).andExpect(
authenticated().withAuthorities(Collections.singleton(new SimpleGrantedAuthority("ROLE_EXTRA"))));
}
@Test
public void authenticationManagerFactoryWhenCustomAuthoritiesMapperThenUsed() throws Exception {
CustomAuthoritiesMapperConfig.AUTHORITIES_MAPPER = ((authorities) -> AuthorityUtils
.createAuthorityList("ROLE_CUSTOM"));
this.spring.register(CustomAuthoritiesMapperConfig.class).autowire();
this.mockMvc.perform(formLogin().user("bob").password("bobspassword")).andExpect(
authenticated().withAuthorities(Collections.singleton(new SimpleGrantedAuthority("ROLE_CUSTOM"))));
}
@Test
public void authenticationManagerFactoryWhenCustomUserDetailsContextMapperThenUsed() throws Exception {
CustomUserDetailsContextMapperConfig.CONTEXT_MAPPER = new UserDetailsContextMapper() {
@Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username,
Collection<? extends GrantedAuthority> authorities) {
return User.withUsername("other").password("password").roles("USER").build();
}
@Override
public void mapUserToContext(UserDetails user, DirContextAdapter ctx) {
}
};
this.spring.register(CustomUserDetailsContextMapperConfig.class).autowire();
this.mockMvc.perform(formLogin().user("bob").password("bobspassword"))
.andExpect(authenticated().withUsername("other"));
}
@Test
public void authenticationManagerFactoryWhenCustomUserDnPatternsThenUsed() throws Exception {
this.spring.register(CustomUserDnPatternsConfig.class).autowire();
this.mockMvc.perform(formLogin().user("bob").password("bobspassword"))
.andExpect(authenticated().withUsername("bob"));
}
@Test
public void authenticationManagerFactoryWhenCustomUserSearchThenUsed() throws Exception {
this.spring.register(CustomUserSearchConfig.class).autowire();
this.mockMvc.perform(formLogin().user("bob").password("bobspassword"))
.andExpect(authenticated().withUsername("bob"));
}
@EnableWebSecurity
static class FromContextSourceConfig extends BaseLdapServerConfig {
@Bean
AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource) {
LdapBindAuthenticationManagerFactory factory = new LdapBindAuthenticationManagerFactory(contextSource);
factory.setUserDnPatterns("uid={0},ou=people");
return factory.createAuthenticationManager();
}
}
@EnableWebSecurity
static class CustomAuthoritiesMapperConfig extends BaseLdapServerConfig {
static GrantedAuthoritiesMapper AUTHORITIES_MAPPER;
@Bean
AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource) {
LdapBindAuthenticationManagerFactory factory = new LdapBindAuthenticationManagerFactory(contextSource);
factory.setUserDnPatterns("uid={0},ou=people");
factory.setAuthoritiesMapper(AUTHORITIES_MAPPER);
return factory.createAuthenticationManager();
}
}
@EnableWebSecurity
static class CustomAuthoritiesPopulatorConfig extends BaseLdapServerConfig {
static LdapAuthoritiesPopulator LAP;
@Bean
AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource) {
LdapBindAuthenticationManagerFactory factory = new LdapBindAuthenticationManagerFactory(contextSource);
factory.setUserDnPatterns("uid={0},ou=people");
factory.setLdapAuthoritiesPopulator(LAP);
return factory.createAuthenticationManager();
}
}
@EnableWebSecurity
static class CustomUserDetailsContextMapperConfig extends BaseLdapServerConfig {
static UserDetailsContextMapper CONTEXT_MAPPER;
@Bean
AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource) {
LdapBindAuthenticationManagerFactory factory = new LdapBindAuthenticationManagerFactory(contextSource);
factory.setUserDnPatterns("uid={0},ou=people");
factory.setUserDetailsContextMapper(CONTEXT_MAPPER);
return factory.createAuthenticationManager();
}
}
@EnableWebSecurity
static class CustomUserDnPatternsConfig extends BaseLdapServerConfig {
@Bean
AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource) {
LdapBindAuthenticationManagerFactory factory = new LdapBindAuthenticationManagerFactory(contextSource);
factory.setUserDnPatterns("uid={0},ou=people");
return factory.createAuthenticationManager();
}
}
@EnableWebSecurity
static class CustomUserSearchConfig extends BaseLdapServerConfig {
@Bean
AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource) {
LdapBindAuthenticationManagerFactory factory = new LdapBindAuthenticationManagerFactory(contextSource);
factory.setUserSearchFilter("uid={0}");
factory.setUserSearchBase("ou=people");
return factory.createAuthenticationManager();
}
}
@EnableWebSecurity
abstract static class BaseLdapServerConfig implements DisposableBean {
private ApacheDSContainer container;
@Bean
ApacheDSContainer ldapServer() throws Exception {
this.container = new ApacheDSContainer("dc=springframework,dc=org", "classpath:/test-server.ldif");
this.container.setPort(0);
return this.container;
}
@Bean
BaseLdapPathContextSource contextSource(ApacheDSContainer container) {
int port = container.getLocalPort();
return new DefaultSpringSecurityContextSource("ldap://localhost:" + port + "/dc=springframework,dc=org");
}
@Override
public void destroy() {
this.container.stop();
}
}
}
@@ -0,0 +1,114 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.ldap;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
import org.springframework.security.ldap.server.ApacheDSContainer;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
@ExtendWith(SpringTestContextExtension.class)
public class LdapPasswordComparisonAuthenticationManagerFactoryITests {
public final SpringTestContext spring = new SpringTestContext(this);
@Autowired
private MockMvc mockMvc;
@Test
public void authenticationManagerFactoryWhenCustomPasswordEncoderThenUsed() throws Exception {
this.spring.register(CustomPasswordEncoderConfig.class).autowire();
this.mockMvc.perform(formLogin().user("bcrypt").password("password"))
.andExpect(authenticated().withUsername("bcrypt"));
}
@Test
public void authenticationManagerFactoryWhenCustomPasswordAttributeThenUsed() throws Exception {
this.spring.register(CustomPasswordAttributeConfig.class).autowire();
this.mockMvc.perform(formLogin().user("bob").password("bob")).andExpect(authenticated().withUsername("bob"));
}
@EnableWebSecurity
static class CustomPasswordEncoderConfig extends BaseLdapServerConfig {
@Bean
AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource) {
LdapPasswordComparisonAuthenticationManagerFactory factory = new LdapPasswordComparisonAuthenticationManagerFactory(
contextSource, new BCryptPasswordEncoder());
factory.setUserDnPatterns("uid={0},ou=people");
return factory.createAuthenticationManager();
}
}
@EnableWebSecurity
static class CustomPasswordAttributeConfig extends BaseLdapServerConfig {
@Bean
AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource) {
LdapPasswordComparisonAuthenticationManagerFactory factory = new LdapPasswordComparisonAuthenticationManagerFactory(
contextSource, NoOpPasswordEncoder.getInstance());
factory.setPasswordAttribute("uid");
factory.setUserDnPatterns("uid={0},ou=people");
return factory.createAuthenticationManager();
}
}
@EnableWebSecurity
abstract static class BaseLdapServerConfig implements DisposableBean {
private ApacheDSContainer container;
@Bean
ApacheDSContainer ldapServer() throws Exception {
this.container = new ApacheDSContainer("dc=springframework,dc=org", "classpath:/test-server.ldif");
this.container.setPort(0);
return this.container;
}
@Bean
BaseLdapPathContextSource contextSource(ApacheDSContainer container) {
int port = container.getLocalPort();
return new DefaultSpringSecurityContextSource("ldap://localhost:" + port + "/dc=springframework,dc=org");
}
@Override
public void destroy() {
this.container.stop();
}
}
}
@@ -94,7 +94,7 @@ public final class SecurityNamespaceHandler implements NamespaceHandler {
if (!namespaceMatchesVersion(element)) {
pc.getReaderContext().fatal("You cannot use a spring-security-2.0.xsd or spring-security-3.0.xsd or "
+ "spring-security-3.1.xsd schema or spring-security-3.2.xsd schema or spring-security-4.0.xsd schema "
+ "with Spring Security 5.6. Please update your schema declarations to the 5.6 schema.", element);
+ "with Spring Security 5.7. Please update your schema declarations to the 5.7 schema.", element);
}
String name = pc.getDelegate().getLocalName(element);
BeanDefinitionParser parser = this.parsers.get(name);
@@ -215,7 +215,7 @@ public final class SecurityNamespaceHandler implements NamespaceHandler {
private boolean matchesVersionInternal(Element element) {
String schemaLocation = element.getAttributeNS("http://www.w3.org/2001/XMLSchema-instance", "schemaLocation");
return schemaLocation.matches("(?m).*spring-security-5\\.6.*.xsd.*")
return schemaLocation.matches("(?m).*spring-security-5\\.7.*.xsd.*")
|| schemaLocation.matches("(?m).*spring-security.xsd.*")
|| !schemaLocation.matches("(?m).*spring-security.*");
}
@@ -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.
@@ -2889,8 +2889,15 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
}
}
@SuppressWarnings("unchecked")
@Override
protected DefaultSecurityFilterChain performBuild() {
ExpressionUrlAuthorizationConfigurer<?> expressionConfigurer = getConfigurer(
ExpressionUrlAuthorizationConfigurer.class);
AuthorizeHttpRequestsConfigurer<?> httpConfigurer = getConfigurer(AuthorizeHttpRequestsConfigurer.class);
boolean oneConfigurerPresent = expressionConfigurer == null ^ httpConfigurer == null;
Assert.state((expressionConfigurer == null && httpConfigurer == null) || oneConfigurerPresent,
"authorizeHttpRequests cannot be used in conjunction with authorizeRequests. Please select just one.");
this.filters.sort(OrderComparator.INSTANCE);
List<Filter> sortedFilters = new ArrayList<>(this.filters.size());
for (Filter filter : this.filters) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -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;
@@ -268,6 +279,19 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
return this;
}
/**
* Sets the handler to handle
* {@link org.springframework.security.web.firewall.RequestRejectedException}
* @param requestRejectedHandler
* @return the {@link WebSecurity} for further customizations
* @since 5.7
*/
public WebSecurity requestRejectedHandler(RequestRejectedHandler requestRejectedHandler) {
Assert.notNull(requestRejectedHandler, "requestRejectedHandler cannot be null");
this.requestRejectedHandler = requestRejectedHandler;
return this;
}
@Override
protected Filter performBuild() throws Exception {
Assert.state(!this.securityFilterChainBuilders.isEmpty(),
@@ -278,11 +302,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 +343,26 @@ 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();
privilegeEvaluators.add(new AuthorizationManagerWebInvocationPrivilegeEvaluator(authorizationManager));
}
}
return new RequestMatcherEntry<>(securityFilterChain::matches, privilegeEvaluators);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.defaultWebSecurityExpressionHandler.setApplicationContext(applicationContext);
@@ -333,6 +390,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)}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
package org.springframework.security.config.annotation.web.configuration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
@@ -24,11 +25,13 @@ import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.DefaultLoginPageConfigurer;
import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;
@@ -97,6 +100,7 @@ class HttpSecurityConfiguration {
.apply(new DefaultLoginPageConfigurer<>());
http.logout(withDefaults());
// @formatter:on
applyDefaultConfigurers(http);
return http;
}
@@ -105,6 +109,15 @@ class HttpSecurityConfiguration {
: this.authenticationConfiguration.getAuthenticationManager();
}
private void applyDefaultConfigurers(HttpSecurity http) throws Exception {
ClassLoader classLoader = this.context.getClassLoader();
List<AbstractHttpConfigurer> defaultHttpConfigurers = SpringFactoriesLoader
.loadFactories(AbstractHttpConfigurer.class, classLoader);
for (AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
http.apply(configurer);
}
}
private Map<Class<?>, Object> createSharedObjects() {
Map<Class<?>, Object> sharedObjects = new HashMap<>();
sharedObjects.put(ApplicationContext.class, this.context);
@@ -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.config.annotation.web.configuration;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Supplier;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -67,17 +71,22 @@ class SecurityReactorContextConfiguration {
private static final String SECURITY_REACTOR_CONTEXT_OPERATOR_KEY = "org.springframework.security.SECURITY_REACTOR_CONTEXT_OPERATOR";
private static final Map<Object, Supplier<Object>> CONTEXT_ATTRIBUTE_VALUE_LOADERS = new HashMap<>();
static {
CONTEXT_ATTRIBUTE_VALUE_LOADERS.put(HttpServletRequest.class,
SecurityReactorContextSubscriberRegistrar::getRequest);
CONTEXT_ATTRIBUTE_VALUE_LOADERS.put(HttpServletResponse.class,
SecurityReactorContextSubscriberRegistrar::getResponse);
CONTEXT_ATTRIBUTE_VALUE_LOADERS.put(Authentication.class,
SecurityReactorContextSubscriberRegistrar::getAuthentication);
}
@Override
public void afterPropertiesSet() throws Exception {
Function<? super Publisher<Object>, ? extends Publisher<Object>> lifter = Operators
.liftPublisher((pub, sub) -> createSubscriberIfNecessary(sub));
Hooks.onLastOperator(SECURITY_REACTOR_CONTEXT_OPERATOR_KEY, (pub) -> {
if (!contextAttributesAvailable()) {
// No need to decorate so return original Publisher
return pub;
}
return lifter.apply(pub);
});
Hooks.onLastOperator(SECURITY_REACTOR_CONTEXT_OPERATOR_KEY, lifter::apply);
}
@Override
@@ -93,36 +102,30 @@ class SecurityReactorContextConfiguration {
return new SecurityReactorContextSubscriber<>(delegate, getContextAttributes());
}
private static boolean contextAttributesAvailable() {
return SecurityContextHolder.getContext().getAuthentication() != null
|| RequestContextHolder.getRequestAttributes() instanceof ServletRequestAttributes;
private static Map<Object, Object> getContextAttributes() {
return new LoadingMap<>(CONTEXT_ATTRIBUTE_VALUE_LOADERS);
}
private static Map<Object, Object> getContextAttributes() {
HttpServletRequest servletRequest = null;
HttpServletResponse servletResponse = null;
private static HttpServletRequest getRequest() {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes instanceof ServletRequestAttributes) {
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
servletRequest = servletRequestAttributes.getRequest();
servletResponse = servletRequestAttributes.getResponse(); // possible null
}
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null && servletRequest == null) {
return Collections.emptyMap();
}
Map<Object, Object> contextAttributes = new HashMap<>();
if (servletRequest != null) {
contextAttributes.put(HttpServletRequest.class, servletRequest);
}
if (servletResponse != null) {
contextAttributes.put(HttpServletResponse.class, servletResponse);
}
if (authentication != null) {
contextAttributes.put(Authentication.class, authentication);
return servletRequestAttributes.getRequest();
}
return null;
}
return contextAttributes;
private static HttpServletResponse getResponse() {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes instanceof ServletRequestAttributes) {
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
return servletRequestAttributes.getResponse(); // possible null
}
return null;
}
private static Authentication getAuthentication() {
return SecurityContextHolder.getContext().getAuthentication();
}
}
@@ -175,4 +178,112 @@ class SecurityReactorContextConfiguration {
}
/**
* A map that computes each value when {@link #get} is invoked
*/
static class LoadingMap<K, V> implements Map<K, V> {
private final Map<K, V> loaded = new ConcurrentHashMap<>();
private final Map<K, Supplier<V>> loaders;
LoadingMap(Map<K, Supplier<V>> loaders) {
this.loaders = Collections.unmodifiableMap(new HashMap<>(loaders));
}
@Override
public int size() {
return this.loaders.size();
}
@Override
public boolean isEmpty() {
return this.loaders.isEmpty();
}
@Override
public boolean containsKey(Object key) {
return this.loaders.containsKey(key);
}
@Override
public Set<K> keySet() {
return this.loaders.keySet();
}
@Override
public V get(Object key) {
if (!this.loaders.containsKey(key)) {
throw new IllegalArgumentException(
"This map only supports the following keys: " + this.loaders.keySet());
}
return this.loaded.computeIfAbsent((K) key, (k) -> this.loaders.get(k).get());
}
@Override
public V put(K key, V value) {
if (!this.loaders.containsKey(key)) {
throw new IllegalArgumentException(
"This map only supports the following keys: " + this.loaders.keySet());
}
return this.loaded.put(key, value);
}
@Override
public V remove(Object key) {
if (!this.loaders.containsKey(key)) {
throw new IllegalArgumentException(
"This map only supports the following keys: " + this.loaders.keySet());
}
return this.loaded.remove(key);
}
@Override
public void putAll(Map<? extends K, ? extends V> m) {
for (Map.Entry<? extends K, ? extends V> entry : m.entrySet()) {
put(entry.getKey(), entry.getValue());
}
}
@Override
public void clear() {
this.loaded.clear();
}
@Override
public boolean containsValue(Object value) {
return this.loaded.containsValue(value);
}
@Override
public Collection<V> values() {
return this.loaded.values();
}
@Override
public Set<Entry<K, V>> entrySet() {
return this.loaded.entrySet();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
LoadingMap<?, ?> that = (LoadingMap<?, ?>) o;
return this.loaded.equals(that.loaded);
}
@Override
public int hashCode() {
return this.loaded.hashCode();
}
}
}
@@ -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.
@@ -24,7 +24,6 @@ import javax.servlet.Filter;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Bean;
@@ -128,8 +127,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
@@ -143,19 +142,20 @@ public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAwa
* instances used to create the web configuration.
* @param objectPostProcessor the {@link ObjectPostProcessor} used to create a
* {@link WebSecurity} instance
* @param webSecurityConfigurers the
* @param beanFactory the bean factory to use to retrieve the relevant
* {@code <SecurityConfigurer<FilterChainProxy, WebSecurityBuilder>} instances used to
* create the web configuration
* @throws Exception
*/
@Autowired(required = false)
public void setFilterChainProxySecurityConfigurer(ObjectPostProcessor<Object> objectPostProcessor,
@Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
throws Exception {
ConfigurableListableBeanFactory beanFactory) throws Exception {
this.webSecurity = objectPostProcessor.postProcess(new WebSecurity(objectPostProcessor));
if (this.debugEnabled != null) {
this.webSecurity.debug(this.debugEnabled);
}
List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers = new AutowiredWebSecurityConfigurersIgnoreParents(
beanFactory).getWebSecurityConfigurers();
webSecurityConfigurers.sort(AnnotationAwareOrderComparator.INSTANCE);
Integer previousOrder = null;
Object previousConfig = null;
@@ -189,12 +189,6 @@ public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAwa
return new RsaKeyConversionServicePostProcessor();
}
@Bean
public static AutowiredWebSecurityConfigurersIgnoreParents autowiredWebSecurityConfigurersIgnoreParents(
ConfigurableListableBeanFactory beanFactory) {
return new AutowiredWebSecurityConfigurersIgnoreParents(beanFactory);
}
@Override
public void setImportMetadata(AnnotationMetadata importMetadata) {
Map<String, Object> enableWebSecurityAttrMap = importMetadata
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 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.
@@ -89,8 +89,12 @@ import org.springframework.web.accept.HeaderContentNegotiationStrategy;
*
* @author Rob Winch
* @see EnableWebSecurity
* @deprecated Use a {@link org.springframework.security.web.SecurityFilterChain} Bean to
* configure {@link HttpSecurity} or a {@link WebSecurityCustomizer} Bean to configure
* {@link WebSecurity}
*/
@Order(100)
@Deprecated
public abstract class WebSecurityConfigurerAdapter implements WebSecurityConfigurer<WebSecurity> {
private final Log logger = LogFactory.getLog(WebSecurityConfigurerAdapter.class);
@@ -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,6 +16,7 @@
package org.springframework.security.config.annotation.web.configurers;
import java.util.LinkedHashMap;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
@@ -46,6 +47,9 @@ import org.springframework.util.Assert;
public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder<H>>
extends AbstractHttpConfigurer<AuthorizeHttpRequestsConfigurer<H>, H> {
static final AuthorizationManager<RequestAuthorizationContext> permitAllAuthorizationManager = (a,
o) -> new AuthorizationDecision(true);
private final AuthorizationManagerRequestMatcherRegistry registry;
/**
@@ -81,6 +85,12 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
return this.registry;
}
AuthorizationManagerRequestMatcherRegistry addFirst(RequestMatcher matcher,
AuthorizationManager<RequestAuthorizationContext> manager) {
this.registry.addFirst(matcher, manager);
return this.registry;
}
/**
* Registry for mapping a {@link RequestMatcher} to an {@link AuthorizationManager}.
*
@@ -106,6 +116,19 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
this.mappingCount++;
}
private void addFirst(RequestMatcher matcher, AuthorizationManager<RequestAuthorizationContext> manager) {
this.unmappedMatchers = null;
this.managerBuilder.mappings((m) -> {
LinkedHashMap<RequestMatcher, AuthorizationManager<RequestAuthorizationContext>> reorderedMap = new LinkedHashMap<>(
m.size() + 1);
reorderedMap.put(matcher, manager);
reorderedMap.putAll(m);
m.clear();
m.putAll(reorderedMap);
});
this.mappingCount++;
}
private AuthorizationManager<HttpServletRequest> createAuthorizationManager() {
Assert.state(this.unmappedMatchers == null,
() -> "An incomplete mapping was found for " + this.unmappedMatchers
@@ -209,7 +232,7 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
* customizations
*/
public AuthorizationManagerRequestMatcherRegistry permitAll() {
return access((a, o) -> new AuthorizationDecision(true));
return access(permitAllAuthorizationManager);
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 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.
@@ -30,7 +30,9 @@ import org.springframework.security.config.annotation.SecurityBuilder;
import org.springframework.security.config.annotation.SecurityConfigurer;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.PortMapper;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.access.channel.ChannelDecisionManagerImpl;
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
import org.springframework.security.web.access.channel.ChannelProcessor;
@@ -75,6 +77,7 @@ import org.springframework.security.web.util.matcher.RequestMatcher;
*
* @param <H> the type of {@link HttpSecurityBuilder} that is being configured
* @author Rob Winch
* @author Onur Kagan Ozcan
* @since 3.2
*/
public final class ChannelSecurityConfigurer<H extends HttpSecurityBuilder<H>>
@@ -86,6 +89,8 @@ public final class ChannelSecurityConfigurer<H extends HttpSecurityBuilder<H>>
private List<ChannelProcessor> channelProcessors;
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
private final ChannelRequestMatcherRegistry REGISTRY;
/**
@@ -123,9 +128,11 @@ public final class ChannelSecurityConfigurer<H extends HttpSecurityBuilder<H>>
if (portMapper != null) {
RetryWithHttpEntryPoint httpEntryPoint = new RetryWithHttpEntryPoint();
httpEntryPoint.setPortMapper(portMapper);
httpEntryPoint.setRedirectStrategy(this.redirectStrategy);
insecureChannelProcessor.setEntryPoint(httpEntryPoint);
RetryWithHttpsEntryPoint httpsEntryPoint = new RetryWithHttpsEntryPoint();
httpsEntryPoint.setPortMapper(portMapper);
httpsEntryPoint.setRedirectStrategy(this.redirectStrategy);
secureChannelProcessor.setEntryPoint(httpsEntryPoint);
}
insecureChannelProcessor = postProcess(insecureChannelProcessor);
@@ -185,6 +192,17 @@ public final class ChannelSecurityConfigurer<H extends HttpSecurityBuilder<H>>
return this;
}
/**
* Sets the {@link RedirectStrategy} instances to use in
* {@link RetryWithHttpEntryPoint} and {@link RetryWithHttpsEntryPoint}
* @param redirectStrategy
* @return the {@link ChannelSecurityConfigurer} for further customizations
*/
public ChannelRequestMatcherRegistry redirectStrategy(RedirectStrategy redirectStrategy) {
ChannelSecurityConfigurer.this.redirectStrategy = redirectStrategy;
return this;
}
/**
* Return the {@link SecurityBuilder} when done using the
* {@link SecurityConfigurer}. This is useful for method chaining.
@@ -237,8 +237,8 @@ public final class CsrfConfigurer<H extends HttpSecurityBuilder<H>>
/**
* Gets the default {@link AccessDeniedHandler} from the
* {@link ExceptionHandlingConfigurer#getAccessDeniedHandler()} or create a
* {@link AccessDeniedHandlerImpl} if not available.
* {@link ExceptionHandlingConfigurer#getAccessDeniedHandler(HttpSecurityBuilder)} or
* create a {@link AccessDeniedHandlerImpl} if not available.
* @param http the {@link HttpSecurityBuilder}
* @return the {@link AccessDeniedHandler}
*/
@@ -247,7 +247,7 @@ public final class CsrfConfigurer<H extends HttpSecurityBuilder<H>>
ExceptionHandlingConfigurer<H> exceptionConfig = http.getConfigurer(ExceptionHandlingConfigurer.class);
AccessDeniedHandler handler = null;
if (exceptionConfig != null) {
handler = exceptionConfig.getAccessDeniedHandler();
handler = exceptionConfig.getAccessDeniedHandler(http);
}
if (handler == null) {
handler = new AccessDeniedHandlerImpl();
@@ -369,9 +369,7 @@ public final class ExpressionUrlAuthorizationConfigurer<H extends HttpSecurityBu
}
/**
* Specify that URLs requires a specific IP Address or <a href=
* "https://forum.spring.io/showthread.php?102783-How-to-use-hasIpAddress&p=343971#post343971"
* >subnet</a>.
* Specify that URLs requires a specific IP Address or subnet.
* @param ipaddressExpression the ipaddress (i.e. 192.168.1.79) or local subnet
* (i.e. 192.168.0/24)
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further
@@ -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.
@@ -31,6 +31,9 @@ import org.springframework.security.web.header.HeaderWriter;
import org.springframework.security.web.header.HeaderWriterFilter;
import org.springframework.security.web.header.writers.CacheControlHeadersWriter;
import org.springframework.security.web.header.writers.ContentSecurityPolicyHeaderWriter;
import org.springframework.security.web.header.writers.CrossOriginEmbedderPolicyHeaderWriter;
import org.springframework.security.web.header.writers.CrossOriginOpenerPolicyHeaderWriter;
import org.springframework.security.web.header.writers.CrossOriginResourcePolicyHeaderWriter;
import org.springframework.security.web.header.writers.FeaturePolicyHeaderWriter;
import org.springframework.security.web.header.writers.HpkpHeaderWriter;
import org.springframework.security.web.header.writers.HstsHeaderWriter;
@@ -97,6 +100,12 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
private final PermissionsPolicyConfig permissionsPolicy = new PermissionsPolicyConfig();
private final CrossOriginOpenerPolicyConfig crossOriginOpenerPolicy = new CrossOriginOpenerPolicyConfig();
private final CrossOriginEmbedderPolicyConfig crossOriginEmbedderPolicy = new CrossOriginEmbedderPolicyConfig();
private final CrossOriginResourcePolicyConfig crossOriginResourcePolicy = new CrossOriginResourcePolicyConfig();
/**
* Creates a new instance
*
@@ -392,6 +401,9 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
addIfNotNull(writers, this.referrerPolicy.writer);
addIfNotNull(writers, this.featurePolicy.writer);
addIfNotNull(writers, this.permissionsPolicy.writer);
addIfNotNull(writers, this.crossOriginOpenerPolicy.writer);
addIfNotNull(writers, this.crossOriginEmbedderPolicy.writer);
addIfNotNull(writers, this.crossOriginResourcePolicy.writer);
writers.addAll(this.headerWriters);
return writers;
}
@@ -544,6 +556,129 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
return this.permissionsPolicy;
}
/**
* Allows configuration for <a href=
* "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy">
* Cross-Origin-Opener-Policy</a> header.
* <p>
* Configuration is provided to the {@link CrossOriginOpenerPolicyHeaderWriter} which
* responsible for writing the header.
* </p>
* @return the {@link CrossOriginOpenerPolicyConfig} for additional confniguration
* @since 5.7
* @see CrossOriginOpenerPolicyHeaderWriter
*/
public CrossOriginOpenerPolicyConfig crossOriginOpenerPolicy() {
this.crossOriginOpenerPolicy.writer = new CrossOriginOpenerPolicyHeaderWriter();
return this.crossOriginOpenerPolicy;
}
/**
* Allows configuration for <a href=
* "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy">
* Cross-Origin-Opener-Policy</a> header.
* <p>
* Calling this method automatically enables (includes) the
* {@code Cross-Origin-Opener-Policy} header in the response using the supplied
* policy.
* <p>
* <p>
* Configuration is provided to the {@link CrossOriginOpenerPolicyHeaderWriter} which
* responsible for writing the header.
* </p>
* @return the {@link HeadersConfigurer} for additional customizations
* @since 5.7
* @see CrossOriginOpenerPolicyHeaderWriter
*/
public HeadersConfigurer<H> crossOriginOpenerPolicy(
Customizer<CrossOriginOpenerPolicyConfig> crossOriginOpenerPolicyCustomizer) {
this.crossOriginOpenerPolicy.writer = new CrossOriginOpenerPolicyHeaderWriter();
crossOriginOpenerPolicyCustomizer.customize(this.crossOriginOpenerPolicy);
return HeadersConfigurer.this;
}
/**
* Allows configuration for <a href=
* "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy">
* Cross-Origin-Embedder-Policy</a> header.
* <p>
* Configuration is provided to the {@link CrossOriginEmbedderPolicyHeaderWriter}
* which is responsible for writing the header.
* </p>
* @return the {@link CrossOriginEmbedderPolicyConfig} for additional customizations
* @since 5.7
* @see CrossOriginEmbedderPolicyHeaderWriter
*/
public CrossOriginEmbedderPolicyConfig crossOriginEmbedderPolicy() {
this.crossOriginEmbedderPolicy.writer = new CrossOriginEmbedderPolicyHeaderWriter();
return this.crossOriginEmbedderPolicy;
}
/**
* Allows configuration for <a href=
* "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy">
* Cross-Origin-Embedder-Policy</a> header.
* <p>
* Calling this method automatically enables (includes) the
* {@code Cross-Origin-Embedder-Policy} header in the response using the supplied
* policy.
* <p>
* <p>
* Configuration is provided to the {@link CrossOriginEmbedderPolicyHeaderWriter}
* which is responsible for writing the header.
* </p>
* @return the {@link HeadersConfigurer} for additional customizations
* @since 5.7
* @see CrossOriginEmbedderPolicyHeaderWriter
*/
public HeadersConfigurer<H> crossOriginEmbedderPolicy(
Customizer<CrossOriginEmbedderPolicyConfig> crossOriginEmbedderPolicyCustomizer) {
this.crossOriginEmbedderPolicy.writer = new CrossOriginEmbedderPolicyHeaderWriter();
crossOriginEmbedderPolicyCustomizer.customize(this.crossOriginEmbedderPolicy);
return HeadersConfigurer.this;
}
/**
* Allows configuration for <a href=
* "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy">
* Cross-Origin-Resource-Policy</a> header.
* <p>
* Configuration is provided to the {@link CrossOriginResourcePolicyHeaderWriter}
* which is responsible for writing the header:
* </p>
* @return the {@link HeadersConfigurer} for additional customizations
* @since 5.7
* @see CrossOriginResourcePolicyHeaderWriter
*/
public CrossOriginResourcePolicyConfig crossOriginResourcePolicy() {
this.crossOriginResourcePolicy.writer = new CrossOriginResourcePolicyHeaderWriter();
return this.crossOriginResourcePolicy;
}
/**
* Allows configuration for <a href=
* "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy">
* Cross-Origin-Resource-Policy</a> header.
* <p>
* Calling this method automatically enables (includes) the
* {@code Cross-Origin-Resource-Policy} header in the response using the supplied
* policy.
* <p>
* <p>
* Configuration is provided to the {@link CrossOriginResourcePolicyHeaderWriter}
* which is responsible for writing the header:
* </p>
* @return the {@link HeadersConfigurer} for additional customizations
* @since 5.7
* @see CrossOriginResourcePolicyHeaderWriter
*/
public HeadersConfigurer<H> crossOriginResourcePolicy(
Customizer<CrossOriginResourcePolicyConfig> crossOriginResourcePolicyCustomizer) {
this.crossOriginResourcePolicy.writer = new CrossOriginResourcePolicyHeaderWriter();
crossOriginResourcePolicyCustomizer.customize(this.crossOriginResourcePolicy);
return HeadersConfigurer.this;
}
public final class ContentTypeOptionsConfig {
private XContentTypeOptionsHeaderWriter writer;
@@ -1142,4 +1277,96 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
}
public final class CrossOriginOpenerPolicyConfig {
private CrossOriginOpenerPolicyHeaderWriter writer;
public CrossOriginOpenerPolicyConfig() {
}
/**
* Sets the policy to be used in the {@code Cross-Origin-Opener-Policy} header
* @param openerPolicy a {@code Cross-Origin-Opener-Policy}
* @return the {@link CrossOriginOpenerPolicyConfig} for additional configuration
* @throws IllegalArgumentException if openerPolicy is null
*/
public CrossOriginOpenerPolicyConfig policy(
CrossOriginOpenerPolicyHeaderWriter.CrossOriginOpenerPolicy openerPolicy) {
this.writer.setPolicy(openerPolicy);
return this;
}
/**
* Allows completing configuration of Cross Origin Opener Policy and continuing
* configuration of headers.
* @return the {@link HeadersConfigurer} for additional configuration
*/
public HeadersConfigurer<H> and() {
return HeadersConfigurer.this;
}
}
public final class CrossOriginEmbedderPolicyConfig {
private CrossOriginEmbedderPolicyHeaderWriter writer;
public CrossOriginEmbedderPolicyConfig() {
}
/**
* Sets the policy to be used in the {@code Cross-Origin-Embedder-Policy} header
* @param embedderPolicy a {@code Cross-Origin-Embedder-Policy}
* @return the {@link CrossOriginEmbedderPolicyConfig} for additional
* configuration
* @throws IllegalArgumentException if embedderPolicy is null
*/
public CrossOriginEmbedderPolicyConfig policy(
CrossOriginEmbedderPolicyHeaderWriter.CrossOriginEmbedderPolicy embedderPolicy) {
this.writer.setPolicy(embedderPolicy);
return this;
}
/**
* Allows completing configuration of Cross-Origin-Embedder-Policy and continuing
* configuration of headers.
* @return the {@link HeadersConfigurer} for additional configuration
*/
public HeadersConfigurer<H> and() {
return HeadersConfigurer.this;
}
}
public final class CrossOriginResourcePolicyConfig {
private CrossOriginResourcePolicyHeaderWriter writer;
public CrossOriginResourcePolicyConfig() {
}
/**
* Sets the policy to be used in the {@code Cross-Origin-Resource-Policy} header
* @param resourcePolicy a {@code Cross-Origin-Resource-Policy}
* @return the {@link CrossOriginResourcePolicyConfig} for additional
* configuration
* @throws IllegalArgumentException if resourcePolicy is null
*/
public CrossOriginResourcePolicyConfig policy(
CrossOriginResourcePolicyHeaderWriter.CrossOriginResourcePolicy resourcePolicy) {
this.writer.setPolicy(resourcePolicy);
return this;
}
/**
* Allows completing configuration of Cross-Origin-Resource-Policy and continuing
* configuration of headers.
* @return the {@link HeadersConfigurer} for additional configuration
*/
public HeadersConfigurer<H> and() {
return HeadersConfigurer.this;
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 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.
@@ -48,11 +48,22 @@ final class PermitAllSupport {
RequestMatcher... requestMatchers) {
ExpressionUrlAuthorizationConfigurer<?> configurer = http
.getConfigurer(ExpressionUrlAuthorizationConfigurer.class);
Assert.state(configurer != null, "permitAll only works with HttpSecurity.authorizeRequests()");
AuthorizeHttpRequestsConfigurer<?> httpConfigurer = http.getConfigurer(AuthorizeHttpRequestsConfigurer.class);
boolean oneConfigurerPresent = configurer == null ^ httpConfigurer == null;
Assert.state(oneConfigurerPresent,
"permitAll only works with either HttpSecurity.authorizeRequests() or HttpSecurity.authorizeHttpRequests(). "
+ "Please define one or the other but not both.");
for (RequestMatcher matcher : requestMatchers) {
if (matcher != null) {
configurer.getRegistry().addMapping(0, new UrlMapping(matcher,
SecurityConfig.createList(ExpressionUrlAuthorizationConfigurer.permitAll)));
if (configurer != null) {
configurer.getRegistry().addMapping(0, new UrlMapping(matcher,
SecurityConfig.createList(ExpressionUrlAuthorizationConfigurer.permitAll)));
}
else {
httpConfigurer.addFirst(matcher, AuthorizeHttpRequestsConfigurer.permitAllAuthorizationManager);
}
}
}
}
@@ -201,6 +201,12 @@ public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
* {@link HttpServletResponse#encodeRedirectURL(String)} or
* {@link HttpServletResponse#encodeURL(String)}, otherwise disallows HTTP sessions to
* be included in the URL. This prevents leaking information to external domains.
* <p>
* This is achieved by guarding {@link HttpServletResponse#encodeURL} and
* {@link HttpServletResponse#encodeRedirectURL} invocations. Any code that also
* overrides either of these two methods, like
* {@link org.springframework.web.servlet.resource.ResourceUrlEncodingFilter}, needs
* to come after the security filter chain or risk being skipped.
* @param enableSessionUrlRewriting true if should allow the JSESSIONID to be
* rewritten into the URLs, else false (default)
* @return the {@link SessionManagementConfigurer} for further customization
@@ -18,6 +18,9 @@ package org.springframework.security.config.annotation.web.configurers.oauth2.se
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Supplier;
import javax.servlet.http.HttpServletRequest;
@@ -25,6 +28,7 @@ import javax.servlet.http.HttpServletRequest;
import org.springframework.context.ApplicationContext;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.MediaType;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationManagerResolver;
@@ -51,6 +55,9 @@ import org.springframework.security.oauth2.server.resource.web.DefaultBearerToke
import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.access.AccessDeniedHandlerImpl;
import org.springframework.security.web.access.DelegatingAccessDeniedHandler;
import org.springframework.security.web.csrf.CsrfException;
import org.springframework.security.web.util.matcher.AndRequestMatcher;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
@@ -153,12 +160,19 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
private OpaqueTokenConfigurer opaqueTokenConfigurer;
private AccessDeniedHandler accessDeniedHandler = new BearerTokenAccessDeniedHandler();
private AccessDeniedHandler accessDeniedHandler = new DelegatingAccessDeniedHandler(
new LinkedHashMap<>(createAccessDeniedHandlers()), new BearerTokenAccessDeniedHandler());
private AuthenticationEntryPoint authenticationEntryPoint = new BearerTokenAuthenticationEntryPoint();
private BearerTokenRequestMatcher requestMatcher = new BearerTokenRequestMatcher();
private static Map<Class<? extends AccessDeniedException>, AccessDeniedHandler> createAccessDeniedHandlers() {
Map<Class<? extends AccessDeniedException>, AccessDeniedHandler> handlers = new HashMap<>();
handlers.put(CsrfException.class, new AccessDeniedHandlerImpl());
return handlers;
}
public OAuth2ResourceServerConfigurer(ApplicationContext context) {
Assert.notNull(context, "context cannot be null");
this.context = context;
@@ -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.
@@ -19,8 +19,6 @@ package org.springframework.security.config.annotation.web.configurers.saml2;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.servlet.Filter;
import org.opensaml.core.Version;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
@@ -50,6 +48,7 @@ import org.springframework.security.saml2.provider.service.web.RelyingPartyRegis
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestContextResolver;
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestRepository;
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationTokenConverter;
import org.springframework.security.saml2.provider.service.web.authentication.Saml2AuthenticationRequestResolver;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
@@ -115,9 +114,11 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
private String loginPage;
private String loginProcessingUrl = Saml2WebSsoAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI;
private String authenticationRequestUri = "/saml2/authenticate/{registrationId}";
private AuthenticationRequestEndpointConfig authenticationRequestEndpoint = new AuthenticationRequestEndpointConfig();
private Saml2AuthenticationRequestResolver authenticationRequestResolver;
private String loginProcessingUrl = Saml2WebSsoAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI;
private RelyingPartyRegistrationRepository relyingPartyRegistrationRepository;
@@ -176,6 +177,20 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
return this;
}
/**
* Use this {@link Saml2AuthenticationRequestResolver} for generating SAML 2.0
* Authentication Requests.
* @param authenticationRequestResolver
* @return the {@link Saml2LoginConfigurer} for further configuration
* @since 5.7
*/
public Saml2LoginConfigurer<B> authenticationRequestResolver(
Saml2AuthenticationRequestResolver authenticationRequestResolver) {
Assert.notNull(authenticationRequestResolver, "authenticationRequestResolver cannot be null");
this.authenticationRequestResolver = authenticationRequestResolver;
return this;
}
/**
* Specifies the URL to validate the credentials. If specified a custom URL, consider
* specifying a custom {@link AuthenticationConverter} via
@@ -200,7 +215,7 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
/**
* {@inheritDoc}
*
* <p>
* Initializes this filter chain for SAML 2 Login. The following actions are taken:
* <ul>
* <li>The WebSSO endpoint has CSRF disabled, typically {@code /login/saml2/sso}</li>
@@ -226,8 +241,8 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
super.init(http);
}
else {
Map<String, String> providerUrlMap = getIdentityProviderUrlMap(
this.authenticationRequestEndpoint.filterProcessingUrl, this.relyingPartyRegistrationRepository);
Map<String, String> providerUrlMap = getIdentityProviderUrlMap(this.authenticationRequestUri,
this.relyingPartyRegistrationRepository);
boolean singleProvider = providerUrlMap.size() == 1;
if (singleProvider) {
// Setup auto-redirect to provider login page
@@ -247,14 +262,16 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
/**
* {@inheritDoc}
*
* <p>
* During the {@code configure} phase, a
* {@link Saml2WebSsoAuthenticationRequestFilter} is added to handle SAML 2.0
* AuthNRequest redirects
*/
@Override
public void configure(B http) throws Exception {
http.addFilter(this.authenticationRequestEndpoint.build(http));
Saml2WebSsoAuthenticationRequestFilter filter = getAuthenticationRequestFilter(http);
filter.setAuthenticationRequestRepository(getAuthenticationRequestRepository(http));
http.addFilter(postProcess(filter));
super.configure(http);
if (this.authenticationManager == null) {
registerDefaultAuthenticationProvider(http);
@@ -264,6 +281,11 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
}
}
private RelyingPartyRegistrationResolver relyingPartyRegistrationResolver(B http) {
RelyingPartyRegistrationRepository registrations = relyingPartyRegistrationRepository(http);
return new DefaultRelyingPartyRegistrationResolver(registrations);
}
RelyingPartyRegistrationRepository relyingPartyRegistrationRepository(B http) {
if (this.relyingPartyRegistrationRepository == null) {
this.relyingPartyRegistrationRepository = getSharedOrBean(http, RelyingPartyRegistrationRepository.class);
@@ -276,6 +298,46 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
saml2WebSsoAuthenticationFilter.setAuthenticationRequestRepository(getAuthenticationRequestRepository(http));
}
private Saml2WebSsoAuthenticationRequestFilter getAuthenticationRequestFilter(B http) {
Saml2AuthenticationRequestResolver authenticationRequestResolver = getAuthenticationRequestResolver(http);
if (authenticationRequestResolver != null) {
return new Saml2WebSsoAuthenticationRequestFilter(authenticationRequestResolver);
}
return new Saml2WebSsoAuthenticationRequestFilter(getAuthenticationRequestContextResolver(http),
getAuthenticationRequestFactory(http));
}
private Saml2AuthenticationRequestResolver getAuthenticationRequestResolver(B http) {
if (this.authenticationRequestResolver != null) {
return this.authenticationRequestResolver;
}
return getBeanOrNull(http, Saml2AuthenticationRequestResolver.class);
}
private Saml2AuthenticationRequestFactory getAuthenticationRequestFactory(B http) {
Saml2AuthenticationRequestFactory resolver = getSharedOrBean(http, Saml2AuthenticationRequestFactory.class);
if (resolver != null) {
return resolver;
}
if (version().startsWith("4")) {
return new OpenSaml4AuthenticationRequestFactory();
}
else {
return new OpenSamlAuthenticationRequestFactory();
}
}
private Saml2AuthenticationRequestContextResolver getAuthenticationRequestContextResolver(B http) {
Saml2AuthenticationRequestContextResolver resolver = getBeanOrNull(http,
Saml2AuthenticationRequestContextResolver.class);
if (resolver != null) {
return resolver;
}
RelyingPartyRegistrationResolver registrationResolver = new DefaultRelyingPartyRegistrationResolver(
this.relyingPartyRegistrationRepository);
return new DefaultSaml2AuthenticationRequestContextResolver(registrationResolver);
}
private AuthenticationConverter getAuthenticationConverter(B http) {
if (this.authenticationConverter != null) {
return this.authenticationConverter;
@@ -297,8 +359,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) {
@@ -325,8 +386,8 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
return;
}
loginPageGeneratingFilter.setSaml2LoginEnabled(true);
loginPageGeneratingFilter.setSaml2AuthenticationUrlToProviderName(this.getIdentityProviderUrlMap(
this.authenticationRequestEndpoint.filterProcessingUrl, this.relyingPartyRegistrationRepository));
loginPageGeneratingFilter.setSaml2AuthenticationUrlToProviderName(
this.getIdentityProviderUrlMap(this.authenticationRequestUri, this.relyingPartyRegistrationRepository));
loginPageGeneratingFilter.setLoginPageUrl(this.getLoginPage());
loginPageGeneratingFilter.setFailureUrl(this.getFailureUrl());
}
@@ -380,46 +441,4 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
}
}
private final class AuthenticationRequestEndpointConfig {
private String filterProcessingUrl = "/saml2/authenticate/{registrationId}";
private AuthenticationRequestEndpointConfig() {
}
private Filter build(B http) {
Saml2AuthenticationRequestFactory authenticationRequestResolver = getResolver(http);
Saml2AuthenticationRequestContextResolver contextResolver = getContextResolver(http);
Saml2AuthenticationRequestRepository<AbstractSaml2AuthenticationRequest> repository = getAuthenticationRequestRepository(
http);
Saml2WebSsoAuthenticationRequestFilter filter = new Saml2WebSsoAuthenticationRequestFilter(contextResolver,
authenticationRequestResolver);
filter.setAuthenticationRequestRepository(repository);
return postProcess(filter);
}
private Saml2AuthenticationRequestFactory getResolver(B http) {
Saml2AuthenticationRequestFactory resolver = getSharedOrBean(http, Saml2AuthenticationRequestFactory.class);
if (resolver == null) {
if (version().startsWith("4")) {
return new OpenSaml4AuthenticationRequestFactory();
}
return new OpenSamlAuthenticationRequestFactory();
}
return resolver;
}
private Saml2AuthenticationRequestContextResolver getContextResolver(B http) {
Saml2AuthenticationRequestContextResolver resolver = getBeanOrNull(http,
Saml2AuthenticationRequestContextResolver.class);
if (resolver == null) {
RelyingPartyRegistrationResolver relyingPartyRegistrationResolver = new DefaultRelyingPartyRegistrationResolver(
Saml2LoginConfigurer.this.relyingPartyRegistrationRepository);
return new DefaultSaml2AuthenticationRequestContextResolver(relyingPartyRegistrationResolver);
}
return resolver;
}
}
}
@@ -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-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.
@@ -36,6 +36,9 @@ import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.security.web.header.HeaderWriterFilter;
import org.springframework.security.web.header.writers.CacheControlHeadersWriter;
import org.springframework.security.web.header.writers.ContentSecurityPolicyHeaderWriter;
import org.springframework.security.web.header.writers.CrossOriginEmbedderPolicyHeaderWriter;
import org.springframework.security.web.header.writers.CrossOriginOpenerPolicyHeaderWriter;
import org.springframework.security.web.header.writers.CrossOriginResourcePolicyHeaderWriter;
import org.springframework.security.web.header.writers.FeaturePolicyHeaderWriter;
import org.springframework.security.web.header.writers.HpkpHeaderWriter;
import org.springframework.security.web.header.writers.HstsHeaderWriter;
@@ -122,6 +125,12 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
private static final String PERMISSIONS_POLICY_ELEMENT = "permissions-policy";
private static final String CROSS_ORIGIN_OPENER_POLICY_ELEMENT = "cross-origin-opener-policy";
private static final String CROSS_ORIGIN_EMBEDDER_POLICY_ELEMENT = "cross-origin-embedder-policy";
private static final String CROSS_ORIGIN_RESOURCE_POLICY_ELEMENT = "cross-origin-resource-policy";
private static final String ALLOW_FROM = "ALLOW-FROM";
private ManagedList<BeanMetadataElement> headerWriters;
@@ -144,6 +153,9 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
parseReferrerPolicyElement(element, parserContext);
parseFeaturePolicyElement(element, parserContext);
parsePermissionsPolicyElement(element, parserContext);
parseCrossOriginOpenerPolicy(disabled, element);
parseCrossOriginEmbedderPolicy(disabled, element);
parseCrossOriginResourcePolicy(disabled, element);
parseHeaderElements(element);
boolean noWriters = this.headerWriters.isEmpty();
if (disabled && !noWriters) {
@@ -376,6 +388,75 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
this.headerWriters.add(headersWriter.getBeanDefinition());
}
private void parseCrossOriginOpenerPolicy(boolean elementDisabled, Element element) {
if (elementDisabled || element == null) {
return;
}
CrossOriginOpenerPolicyHeaderWriter writer = new CrossOriginOpenerPolicyHeaderWriter();
Element crossOriginOpenerPolicyElement = DomUtils.getChildElementByTagName(element,
CROSS_ORIGIN_OPENER_POLICY_ELEMENT);
if (crossOriginOpenerPolicyElement != null) {
addCrossOriginOpenerPolicy(crossOriginOpenerPolicyElement, writer);
}
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(CrossOriginOpenerPolicyHeaderWriter.class, () -> writer);
this.headerWriters.add(builder.getBeanDefinition());
}
private void parseCrossOriginEmbedderPolicy(boolean elementDisabled, Element element) {
if (elementDisabled || element == null) {
return;
}
CrossOriginEmbedderPolicyHeaderWriter writer = new CrossOriginEmbedderPolicyHeaderWriter();
Element crossOriginEmbedderPolicyElement = DomUtils.getChildElementByTagName(element,
CROSS_ORIGIN_EMBEDDER_POLICY_ELEMENT);
if (crossOriginEmbedderPolicyElement != null) {
addCrossOriginEmbedderPolicy(crossOriginEmbedderPolicyElement, writer);
}
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(CrossOriginEmbedderPolicyHeaderWriter.class, () -> writer);
this.headerWriters.add(builder.getBeanDefinition());
}
private void parseCrossOriginResourcePolicy(boolean elementDisabled, Element element) {
if (elementDisabled || element == null) {
return;
}
CrossOriginResourcePolicyHeaderWriter writer = new CrossOriginResourcePolicyHeaderWriter();
Element crossOriginResourcePolicyElement = DomUtils.getChildElementByTagName(element,
CROSS_ORIGIN_RESOURCE_POLICY_ELEMENT);
if (crossOriginResourcePolicyElement != null) {
addCrossOriginResourcePolicy(crossOriginResourcePolicyElement, writer);
}
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(CrossOriginResourcePolicyHeaderWriter.class, () -> writer);
this.headerWriters.add(builder.getBeanDefinition());
}
private void addCrossOriginResourcePolicy(Element crossOriginResourcePolicyElement,
CrossOriginResourcePolicyHeaderWriter writer) {
String policy = crossOriginResourcePolicyElement.getAttribute(ATT_POLICY);
if (StringUtils.hasText(policy)) {
writer.setPolicy(CrossOriginResourcePolicyHeaderWriter.CrossOriginResourcePolicy.from(policy));
}
}
private void addCrossOriginEmbedderPolicy(Element crossOriginEmbedderPolicyElement,
CrossOriginEmbedderPolicyHeaderWriter writer) {
String policy = crossOriginEmbedderPolicyElement.getAttribute(ATT_POLICY);
if (StringUtils.hasText(policy)) {
writer.setPolicy(CrossOriginEmbedderPolicyHeaderWriter.CrossOriginEmbedderPolicy.from(policy));
}
}
private void addCrossOriginOpenerPolicy(Element crossOriginOpenerPolicyElement,
CrossOriginOpenerPolicyHeaderWriter writer) {
String policy = crossOriginOpenerPolicyElement.getAttribute(ATT_POLICY);
if (StringUtils.hasText(policy)) {
writer.setPolicy(CrossOriginOpenerPolicyHeaderWriter.CrossOriginOpenerPolicy.from(policy));
}
}
private void attrNotAllowed(ParserContext context, String attrName, String otherAttrName, Element element) {
context.getReaderContext().error("Only one of '" + attrName + "' or '" + otherAttrName + "' can be set.",
element);
@@ -0,0 +1,183 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.ldap;
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.ldap.authentication.AbstractLdapAuthenticator;
import org.springframework.security.ldap.authentication.LdapAuthenticationProvider;
import org.springframework.security.ldap.search.FilterBasedLdapUserSearch;
import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator;
import org.springframework.security.ldap.userdetails.UserDetailsContextMapper;
/**
* Creates an {@link AuthenticationManager} that can perform LDAP authentication.
*
* @author Eleftheria Stein
* @since 5.7
*/
public abstract class AbstractLdapAuthenticationManagerFactory<T extends AbstractLdapAuthenticator> {
AbstractLdapAuthenticationManagerFactory(BaseLdapPathContextSource contextSource) {
this.contextSource = contextSource;
}
private BaseLdapPathContextSource contextSource;
private String[] userDnPatterns;
private LdapAuthoritiesPopulator ldapAuthoritiesPopulator;
private GrantedAuthoritiesMapper authoritiesMapper;
private UserDetailsContextMapper userDetailsContextMapper;
private String userSearchFilter;
private String userSearchBase = "";
/**
* Sets the {@link BaseLdapPathContextSource} used to perform LDAP authentication.
* @param contextSource the {@link BaseLdapPathContextSource} used to perform LDAP
* authentication
*/
public void setContextSource(BaseLdapPathContextSource contextSource) {
this.contextSource = contextSource;
}
/**
* Gets the {@link BaseLdapPathContextSource} used to perform LDAP authentication.
* @return the {@link BaseLdapPathContextSource} used to perform LDAP authentication
*/
protected final BaseLdapPathContextSource getContextSource() {
return this.contextSource;
}
/**
* Sets the {@link LdapAuthoritiesPopulator} used to obtain a list of granted
* authorities for an LDAP user.
* @param ldapAuthoritiesPopulator the {@link LdapAuthoritiesPopulator} to use
*/
public void setLdapAuthoritiesPopulator(LdapAuthoritiesPopulator ldapAuthoritiesPopulator) {
this.ldapAuthoritiesPopulator = ldapAuthoritiesPopulator;
}
/**
* Sets the {@link GrantedAuthoritiesMapper} used for converting the authorities
* loaded from storage to a new set of authorities which will be associated to the
* {@link UsernamePasswordAuthenticationToken}.
* @param authoritiesMapper the {@link GrantedAuthoritiesMapper} used for mapping the
* user's authorities
*/
public void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) {
this.authoritiesMapper = authoritiesMapper;
}
/**
* Sets a custom strategy to be used for creating the {@link UserDetails} which will
* be stored as the principal in the {@link Authentication}.
* @param userDetailsContextMapper the strategy instance
*/
public void setUserDetailsContextMapper(UserDetailsContextMapper userDetailsContextMapper) {
this.userDetailsContextMapper = userDetailsContextMapper;
}
/**
* If your users are at a fixed location in the directory (i.e. you can work out the
* DN directly from the username without doing a directory search), you can use this
* attribute to map directly to the DN. It maps directly to the userDnPatterns
* property of AbstractLdapAuthenticator. The value is a specific pattern used to
* build the user's DN, for example "uid={0},ou=people". The key "{0}" must be present
* and will be substituted with the username.
* @param userDnPatterns the LDAP patterns for finding the usernames
*/
public void setUserDnPatterns(String... userDnPatterns) {
this.userDnPatterns = userDnPatterns;
}
/**
* The LDAP filter used to search for users (optional). For example "(uid={0})". The
* substituted parameter is the user's login name.
* @param userSearchFilter the LDAP filter used to search for users
*/
public void setUserSearchFilter(String userSearchFilter) {
this.userSearchFilter = userSearchFilter;
}
/**
* Search base for user searches. Defaults to "". Only used with
* {@link #setUserSearchFilter(String)}.
* @param userSearchBase search base for user searches
*/
public void setUserSearchBase(String userSearchBase) {
this.userSearchBase = userSearchBase;
}
/**
* Returns the configured {@link AuthenticationManager} that can be used to perform
* LDAP authentication.
* @return the configured {@link AuthenticationManager}
*/
public final AuthenticationManager createAuthenticationManager() {
LdapAuthenticationProvider ldapAuthenticationProvider = getProvider();
return new ProviderManager(ldapAuthenticationProvider);
}
private LdapAuthenticationProvider getProvider() {
AbstractLdapAuthenticator authenticator = getAuthenticator();
LdapAuthenticationProvider provider;
if (this.ldapAuthoritiesPopulator != null) {
provider = new LdapAuthenticationProvider(authenticator, this.ldapAuthoritiesPopulator);
}
else {
provider = new LdapAuthenticationProvider(authenticator);
}
if (this.authoritiesMapper != null) {
provider.setAuthoritiesMapper(this.authoritiesMapper);
}
if (this.userDetailsContextMapper != null) {
provider.setUserDetailsContextMapper(this.userDetailsContextMapper);
}
return provider;
}
private AbstractLdapAuthenticator getAuthenticator() {
AbstractLdapAuthenticator authenticator = createDefaultLdapAuthenticator();
if (this.userSearchFilter != null) {
authenticator.setUserSearch(
new FilterBasedLdapUserSearch(this.userSearchBase, this.userSearchFilter, this.contextSource));
}
if (this.userDnPatterns != null && this.userDnPatterns.length > 0) {
authenticator.setUserDnPatterns(this.userDnPatterns);
}
authenticator.afterPropertiesSet();
return authenticator;
}
/**
* Allows subclasses to supply the default {@link AbstractLdapAuthenticator}.
* @return the {@link AbstractLdapAuthenticator} that will be configured for LDAP
* authentication
*/
protected abstract T createDefaultLdapAuthenticator();
}
@@ -0,0 +1,185 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.ldap;
import java.io.IOException;
import java.net.ServerSocket;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.Lifecycle;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
import org.springframework.security.ldap.server.EmbeddedLdapServerContainer;
import org.springframework.security.ldap.server.UnboundIdContainer;
import org.springframework.util.ClassUtils;
/**
* Creates a {@link DefaultSpringSecurityContextSource} used to perform LDAP
* authentication and starts and in-memory LDAP server.
*
* @author Eleftheria Stein
* @since 5.7
*/
public class EmbeddedLdapServerContextSourceFactoryBean
implements FactoryBean<DefaultSpringSecurityContextSource>, DisposableBean, ApplicationContextAware {
private static final String UNBOUNDID_CLASSNAME = "com.unboundid.ldap.listener.InMemoryDirectoryServer";
private static final int DEFAULT_PORT = 33389;
private static final int RANDOM_PORT = 0;
private Integer port;
private String ldif = "classpath*:*.ldif";
private String root = "dc=springframework,dc=org";
private ApplicationContext context;
private String managerDn;
private String managerPassword;
private EmbeddedLdapServerContainer container;
/**
* Create an EmbeddedLdapServerContextSourceFactoryBean that will use an embedded LDAP
* server to perform LDAP authentication. This requires a dependency on
* `com.unboundid:unboundid-ldapsdk`.
* @return the EmbeddedLdapServerContextSourceFactoryBean
*/
public static EmbeddedLdapServerContextSourceFactoryBean fromEmbeddedLdapServer() {
return new EmbeddedLdapServerContextSourceFactoryBean();
}
/**
* Specifies an LDIF to load at startup for an embedded LDAP server. The default is
* "classpath*:*.ldif".
* @param ldif the ldif to load at startup for an embedded LDAP server.
*/
public void setLdif(String ldif) {
this.ldif = ldif;
}
/**
* The port to connect to LDAP to (the default is 33389 or random available port if
* unavailable). Supplying 0 as the port indicates that a random available port should
* be selected.
* @param port the port to connect to
*/
public void setPort(int port) {
this.port = port;
}
/**
* Optional root suffix for the embedded LDAP server. Default is
* "dc=springframework,dc=org".
* @param root root suffix for the embedded LDAP server
*/
public void setRoot(String root) {
this.root = root;
}
/**
* Username (DN) of the "manager" user identity (i.e. "uid=admin,ou=system") which
* will be used to authenticate to an LDAP server. If omitted, anonymous access will
* be used.
* @param managerDn the username (DN) of the "manager" user identity used to
* authenticate to a LDAP server.
*/
public void setManagerDn(String managerDn) {
this.managerDn = managerDn;
}
/**
* The password for the manager DN. This is required if the
* {@link #setManagerDn(String)} is specified.
* @param managerPassword password for the manager DN
*/
public void setManagerPassword(String managerPassword) {
this.managerPassword = managerPassword;
}
@Override
public DefaultSpringSecurityContextSource getObject() throws Exception {
if (!ClassUtils.isPresent(UNBOUNDID_CLASSNAME, getClass().getClassLoader())) {
throw new IllegalStateException("Embedded LDAP server is not provided");
}
this.container = getContainer();
this.port = this.container.getPort();
DefaultSpringSecurityContextSource contextSourceFromProviderUrl = new DefaultSpringSecurityContextSource(
"ldap://127.0.0.1:" + this.port + "/" + this.root);
if (this.managerDn != null) {
contextSourceFromProviderUrl.setUserDn(this.managerDn);
if (this.managerPassword == null) {
throw new IllegalStateException("managerPassword is required if managerDn is supplied");
}
contextSourceFromProviderUrl.setPassword(this.managerPassword);
}
contextSourceFromProviderUrl.afterPropertiesSet();
return contextSourceFromProviderUrl;
}
@Override
public Class<?> getObjectType() {
return DefaultSpringSecurityContextSource.class;
}
@Override
public void destroy() {
if (this.container instanceof Lifecycle) {
((Lifecycle) this.container).stop();
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
}
private EmbeddedLdapServerContainer getContainer() {
if (!ClassUtils.isPresent(UNBOUNDID_CLASSNAME, getClass().getClassLoader())) {
throw new IllegalStateException("Embedded LDAP server is not provided");
}
UnboundIdContainer unboundIdContainer = new UnboundIdContainer(this.root, this.ldif);
unboundIdContainer.setApplicationContext(this.context);
unboundIdContainer.setPort(getEmbeddedServerPort());
unboundIdContainer.afterPropertiesSet();
return unboundIdContainer;
}
private int getEmbeddedServerPort() {
if (this.port == null) {
this.port = getDefaultEmbeddedServerPort();
}
return this.port;
}
private int getDefaultEmbeddedServerPort() {
try (ServerSocket serverSocket = new ServerSocket(DEFAULT_PORT)) {
return serverSocket.getLocalPort();
}
catch (IOException ex) {
return RANDOM_PORT;
}
}
}
@@ -0,0 +1,41 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.ldap;
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.ldap.authentication.BindAuthenticator;
/**
* Creates an {@link AuthenticationManager} that can perform LDAP authentication using
* bind authentication.
*
* @author Eleftheria Stein
* @since 5.7
*/
public class LdapBindAuthenticationManagerFactory extends AbstractLdapAuthenticationManagerFactory<BindAuthenticator> {
public LdapBindAuthenticationManagerFactory(BaseLdapPathContextSource contextSource) {
super(contextSource);
}
@Override
protected BindAuthenticator createDefaultLdapAuthenticator() {
return new BindAuthenticator(getContextSource());
}
}
@@ -0,0 +1,75 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.ldap;
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.ldap.authentication.PasswordComparisonAuthenticator;
import org.springframework.util.Assert;
/**
* Creates an {@link AuthenticationManager} that can perform LDAP authentication using
* password comparison.
*
* @author Eleftheria Stein
* @since 5.7
*/
public class LdapPasswordComparisonAuthenticationManagerFactory
extends AbstractLdapAuthenticationManagerFactory<PasswordComparisonAuthenticator> {
private PasswordEncoder passwordEncoder;
private String passwordAttribute;
public LdapPasswordComparisonAuthenticationManagerFactory(BaseLdapPathContextSource contextSource,
PasswordEncoder passwordEncoder) {
super(contextSource);
setPasswordEncoder(passwordEncoder);
}
/**
* Specifies the {@link PasswordEncoder} to be used when authenticating with password
* comparison.
* @param passwordEncoder the {@link PasswordEncoder} to use
*/
public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
Assert.notNull(passwordEncoder, "passwordEncoder must not be null.");
this.passwordEncoder = passwordEncoder;
}
/**
* The attribute in the directory which contains the user password. Only used when
* authenticating with password comparison. Defaults to "userPassword".
* @param passwordAttribute the attribute in the directory which contains the user
* password
*/
public void setPasswordAttribute(String passwordAttribute) {
this.passwordAttribute = passwordAttribute;
}
@Override
protected PasswordComparisonAuthenticator createDefaultLdapAuthenticator() {
PasswordComparisonAuthenticator ldapAuthenticator = new PasswordComparisonAuthenticator(getContextSource());
if (this.passwordAttribute != null) {
ldapAuthenticator.setPasswordAttributeName(this.passwordAttribute);
}
ldapAuthenticator.setPasswordEncoder(this.passwordEncoder);
return ldapAuthenticator;
}
}
@@ -133,6 +133,7 @@ import org.springframework.security.web.server.authorization.AuthorizationContex
import org.springframework.security.web.server.authorization.AuthorizationWebFilter;
import org.springframework.security.web.server.authorization.DelegatingReactiveAuthorizationManager;
import org.springframework.security.web.server.authorization.ExceptionTranslationWebFilter;
import org.springframework.security.web.server.authorization.IpAddressReactiveAuthorizationManager;
import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler;
import org.springframework.security.web.server.authorization.ServerWebExchangeDelegatingServerAccessDeniedHandler;
import org.springframework.security.web.server.context.NoOpServerSecurityContextRepository;
@@ -148,6 +149,12 @@ import org.springframework.security.web.server.header.CacheControlServerHttpHead
import org.springframework.security.web.server.header.CompositeServerHttpHeadersWriter;
import org.springframework.security.web.server.header.ContentSecurityPolicyServerHttpHeadersWriter;
import org.springframework.security.web.server.header.ContentTypeOptionsServerHttpHeadersWriter;
import org.springframework.security.web.server.header.CrossOriginEmbedderPolicyServerHttpHeadersWriter;
import org.springframework.security.web.server.header.CrossOriginEmbedderPolicyServerHttpHeadersWriter.CrossOriginEmbedderPolicy;
import org.springframework.security.web.server.header.CrossOriginOpenerPolicyServerHttpHeadersWriter;
import org.springframework.security.web.server.header.CrossOriginOpenerPolicyServerHttpHeadersWriter.CrossOriginOpenerPolicy;
import org.springframework.security.web.server.header.CrossOriginResourcePolicyServerHttpHeadersWriter;
import org.springframework.security.web.server.header.CrossOriginResourcePolicyServerHttpHeadersWriter.CrossOriginResourcePolicy;
import org.springframework.security.web.server.header.FeaturePolicyServerHttpHeadersWriter;
import org.springframework.security.web.server.header.HttpHeaderWriterWebFilter;
import org.springframework.security.web.server.header.PermissionsPolicyServerHttpHeadersWriter;
@@ -1682,6 +1689,18 @@ public class ServerHttpSecurity {
return access(AuthenticatedReactiveAuthorizationManager.authenticated());
}
/**
* Require a specific IP address or range using an IP/Netmask (e.g.
* 192.168.1.0/24).
* @param ipAddress the address or range of addresses from which the request
* must come.
* @return the {@link AuthorizeExchangeSpec} to configure
* @since 5.7
*/
public AuthorizeExchangeSpec hasIpAddress(String ipAddress) {
return access(IpAddressReactiveAuthorizationManager.hasIpAddress(ipAddress));
}
/**
* Allows plugging in a custom authorization strategy
* @param manager the authorization manager to use
@@ -2367,10 +2386,17 @@ public class ServerHttpSecurity {
private ReferrerPolicyServerHttpHeadersWriter referrerPolicy = new ReferrerPolicyServerHttpHeadersWriter();
private CrossOriginOpenerPolicyServerHttpHeadersWriter crossOriginOpenerPolicy = new CrossOriginOpenerPolicyServerHttpHeadersWriter();
private CrossOriginEmbedderPolicyServerHttpHeadersWriter crossOriginEmbedderPolicy = new CrossOriginEmbedderPolicyServerHttpHeadersWriter();
private CrossOriginResourcePolicyServerHttpHeadersWriter crossOriginResourcePolicy = new CrossOriginResourcePolicyServerHttpHeadersWriter();
private HeaderSpec() {
this.writers = new ArrayList<>(Arrays.asList(this.cacheControl, this.contentTypeOptions, this.hsts,
this.frameOptions, this.xss, this.featurePolicy, this.permissionsPolicy, this.contentSecurityPolicy,
this.referrerPolicy));
this.referrerPolicy, this.crossOriginOpenerPolicy, this.crossOriginEmbedderPolicy,
this.crossOriginResourcePolicy));
}
/**
@@ -2582,6 +2608,84 @@ public class ServerHttpSecurity {
return this;
}
/**
* Configures the <a href=
* "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy">
* Cross-Origin-Opener-Policy</a> header.
* @return the {@link CrossOriginOpenerPolicySpec} to configure
* @since 5.7
* @see CrossOriginOpenerPolicyServerHttpHeadersWriter
*/
public CrossOriginOpenerPolicySpec crossOriginOpenerPolicy() {
return new CrossOriginOpenerPolicySpec();
}
/**
* Configures the <a href=
* "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy">
* Cross-Origin-Opener-Policy</a> header.
* @return the {@link HeaderSpec} to customize
* @since 5.7
* @see CrossOriginOpenerPolicyServerHttpHeadersWriter
*/
public HeaderSpec crossOriginOpenerPolicy(
Customizer<CrossOriginOpenerPolicySpec> crossOriginOpenerPolicyCustomizer) {
crossOriginOpenerPolicyCustomizer.customize(new CrossOriginOpenerPolicySpec());
return this;
}
/**
* Configures the <a href=
* "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy">
* Cross-Origin-Embedder-Policy</a> header.
* @return the {@link CrossOriginEmbedderPolicySpec} to configure
* @since 5.7
* @see CrossOriginEmbedderPolicyServerHttpHeadersWriter
*/
public CrossOriginEmbedderPolicySpec crossOriginEmbedderPolicy() {
return new CrossOriginEmbedderPolicySpec();
}
/**
* Configures the <a href=
* "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy">
* Cross-Origin-Embedder-Policy</a> header.
* @return the {@link HeaderSpec} to customize
* @since 5.7
* @see CrossOriginEmbedderPolicyServerHttpHeadersWriter
*/
public HeaderSpec crossOriginEmbedderPolicy(
Customizer<CrossOriginEmbedderPolicySpec> crossOriginEmbedderPolicyCustomizer) {
crossOriginEmbedderPolicyCustomizer.customize(new CrossOriginEmbedderPolicySpec());
return this;
}
/**
* Configures the <a href=
* "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy">
* Cross-Origin-Resource-Policy</a> header.
* @return the {@link CrossOriginResourcePolicySpec} to configure
* @since 5.7
* @see CrossOriginResourcePolicyServerHttpHeadersWriter
*/
public CrossOriginResourcePolicySpec crossOriginResourcePolicy() {
return new CrossOriginResourcePolicySpec();
}
/**
* Configures the <a href=
* "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy">
* Cross-Origin-Resource-Policy</a> header.
* @return the {@link HeaderSpec} to customize
* @since 5.7
* @see CrossOriginResourcePolicyServerHttpHeadersWriter
*/
public HeaderSpec crossOriginResourcePolicy(
Customizer<CrossOriginResourcePolicySpec> crossOriginResourcePolicyCustomizer) {
crossOriginResourcePolicyCustomizer.customize(new CrossOriginResourcePolicySpec());
return this;
}
/**
* Configures cache control headers
*
@@ -2897,6 +3001,99 @@ public class ServerHttpSecurity {
}
/**
* Configures the Cross-Origin-Opener-Policy header
*
* @since 5.7
*/
public final class CrossOriginOpenerPolicySpec {
private CrossOriginOpenerPolicySpec() {
}
/**
* Sets the value to be used in the `Cross-Origin-Opener-Policy` header
* @param openerPolicy a opener policy
* @return the {@link CrossOriginOpenerPolicySpec} to continue configuring
*/
public CrossOriginOpenerPolicySpec policy(CrossOriginOpenerPolicy openerPolicy) {
HeaderSpec.this.crossOriginOpenerPolicy.setPolicy(openerPolicy);
return this;
}
/**
* Allows method chaining to continue configuring the
* {@link ServerHttpSecurity}.
* @return the {@link HeaderSpec} to continue configuring
*/
public HeaderSpec and() {
return HeaderSpec.this;
}
}
/**
* Configures the Cross-Origin-Embedder-Policy header
*
* @since 5.7
*/
public final class CrossOriginEmbedderPolicySpec {
private CrossOriginEmbedderPolicySpec() {
}
/**
* Sets the value to be used in the `Cross-Origin-Embedder-Policy` header
* @param embedderPolicy a opener policy
* @return the {@link CrossOriginEmbedderPolicySpec} to continue configuring
*/
public CrossOriginEmbedderPolicySpec policy(CrossOriginEmbedderPolicy embedderPolicy) {
HeaderSpec.this.crossOriginEmbedderPolicy.setPolicy(embedderPolicy);
return this;
}
/**
* Allows method chaining to continue configuring the
* {@link ServerHttpSecurity}.
* @return the {@link HeaderSpec} to continue configuring
*/
public HeaderSpec and() {
return HeaderSpec.this;
}
}
/**
* Configures the Cross-Origin-Resource-Policy header
*
* @since 5.7
*/
public final class CrossOriginResourcePolicySpec {
private CrossOriginResourcePolicySpec() {
}
/**
* Sets the value to be used in the `Cross-Origin-Resource-Policy` header
* @param resourcePolicy a opener policy
* @return the {@link CrossOriginResourcePolicySpec} to continue configuring
*/
public CrossOriginResourcePolicySpec policy(CrossOriginResourcePolicy resourcePolicy) {
HeaderSpec.this.crossOriginResourcePolicy.setPolicy(resourcePolicy);
return this;
}
/**
* Allows method chaining to continue configuring the
* {@link ServerHttpSecurity}.
* @return the {@link HeaderSpec} to continue configuring
*/
public HeaderSpec and() {
return HeaderSpec.this;
}
}
}
/**
@@ -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.
@@ -22,6 +22,7 @@ import org.springframework.security.authorization.AuthorizationDecision
import org.springframework.security.authorization.ReactiveAuthorizationManager
import org.springframework.security.core.Authentication
import org.springframework.security.web.server.authorization.AuthorizationContext
import org.springframework.security.web.server.authorization.IpAddressReactiveAuthorizationManager
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers
import org.springframework.security.web.util.matcher.RequestMatcher
@@ -108,6 +109,13 @@ class AuthorizeExchangeDsl {
fun hasAnyAuthority(vararg authorities: String): ReactiveAuthorizationManager<AuthorizationContext> =
AuthorityReactiveAuthorizationManager.hasAnyAuthority<AuthorizationContext>(*authorities)
/**
* Require a specific IP or range of IP addresses.
* @since 5.7
*/
fun hasIpAddress(ipAddress: String): ReactiveAuthorizationManager<AuthorizationContext> =
IpAddressReactiveAuthorizationManager.hasIpAddress(ipAddress)
/**
* Require an authenticated user.
*/
@@ -0,0 +1,42 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.web.server
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.web.server.header.CrossOriginEmbedderPolicyServerHttpHeadersWriter
/**
* A Kotlin DSL to configure the [HttpSecurity] Cross-Origin-Embedder-Policy header using
* idiomatic Kotlin code.
*
* @author Marcus Da Coregio
* @since 5.7
* @property policy the policy to be used in the response header.
*/
@ServerSecurityMarker
class ServerCrossOriginEmbedderPolicyDsl {
var policy: CrossOriginEmbedderPolicyServerHttpHeadersWriter.CrossOriginEmbedderPolicy? = null
internal fun get(): (ServerHttpSecurity.HeaderSpec.CrossOriginEmbedderPolicySpec) -> Unit {
return { crossOriginEmbedderPolicy ->
policy?.also {
crossOriginEmbedderPolicy.policy(policy)
}
}
}
}
@@ -0,0 +1,42 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.web.server
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.web.server.header.CrossOriginOpenerPolicyServerHttpHeadersWriter
/**
* A Kotlin DSL to configure the [HttpSecurity] Cross-Origin-Opener-Policy header using
* idiomatic Kotlin code.
*
* @author Marcus Da Coregio
* @since 5.7
* @property policy the policy to be used in the response header.
*/
@ServerSecurityMarker
class ServerCrossOriginOpenerPolicyDsl {
var policy: CrossOriginOpenerPolicyServerHttpHeadersWriter.CrossOriginOpenerPolicy? = null
internal fun get(): (ServerHttpSecurity.HeaderSpec.CrossOriginOpenerPolicySpec) -> Unit {
return { crossOriginOpenerPolicy ->
policy?.also {
crossOriginOpenerPolicy.policy(policy)
}
}
}
}
@@ -0,0 +1,42 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.web.server
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.web.server.header.CrossOriginResourcePolicyServerHttpHeadersWriter
/**
* A Kotlin DSL to configure the [HttpSecurity] Cross-Origin-Resource-Policy header using
* idiomatic Kotlin code.
*
* @author Marcus Da Coregio
* @since 5.7
* @property policy the policy to be used in the response header.
*/
@ServerSecurityMarker
class ServerCrossOriginResourcePolicyDsl {
var policy: CrossOriginResourcePolicyServerHttpHeadersWriter.CrossOriginResourcePolicy? = null
internal fun get(): (ServerHttpSecurity.HeaderSpec.CrossOriginResourcePolicySpec) -> Unit {
return { crossOriginResourcePolicy ->
policy?.also {
crossOriginResourcePolicy.policy(policy)
}
}
}
}
@@ -16,7 +16,12 @@
package org.springframework.security.config.web.server
import org.springframework.security.web.server.header.*
import org.springframework.security.web.server.header.CacheControlServerHttpHeadersWriter
import org.springframework.security.web.server.header.ContentTypeOptionsServerHttpHeadersWriter
import org.springframework.security.web.server.header.ReferrerPolicyServerHttpHeadersWriter
import org.springframework.security.web.server.header.StrictTransportSecurityServerHttpHeadersWriter
import org.springframework.security.web.server.header.XFrameOptionsServerHttpHeadersWriter
import org.springframework.security.web.server.header.XXssProtectionServerHttpHeadersWriter
/**
* A Kotlin DSL to configure [ServerHttpSecurity] headers using idiomatic Kotlin code.
@@ -35,6 +40,9 @@ class ServerHeadersDsl {
private var referrerPolicy: ((ServerHttpSecurity.HeaderSpec.ReferrerPolicySpec) -> Unit)? = null
private var featurePolicyDirectives: String? = null
private var permissionsPolicy: ((ServerHttpSecurity.HeaderSpec.PermissionsPolicySpec) -> Unit)? = null
private var crossOriginOpenerPolicy: ((ServerHttpSecurity.HeaderSpec.CrossOriginOpenerPolicySpec) -> Unit)? = null
private var crossOriginEmbedderPolicy: ((ServerHttpSecurity.HeaderSpec.CrossOriginEmbedderPolicySpec) -> Unit)? = null
private var crossOriginResourcePolicy: ((ServerHttpSecurity.HeaderSpec.CrossOriginResourcePolicySpec) -> Unit)? = null
private var disabled = false
@@ -157,6 +165,39 @@ class ServerHeadersDsl {
this.permissionsPolicy = ServerPermissionsPolicyDsl().apply(permissionsPolicyConfig).get()
}
/**
* Allows configuration for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy">
* Cross-Origin-Opener-Policy</a> header.
*
* @since 5.7
* @param crossOriginOpenerPolicyConfig the customization to apply to the header
*/
fun crossOriginOpenerPolicy(crossOriginOpenerPolicyConfig: ServerCrossOriginOpenerPolicyDsl.() -> Unit) {
this.crossOriginOpenerPolicy = ServerCrossOriginOpenerPolicyDsl().apply(crossOriginOpenerPolicyConfig).get()
}
/**
* Allows configuration for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy">
* Cross-Origin-Embedder-Policy</a> header.
*
* @since 5.7
* @param crossOriginEmbedderPolicyConfig the customization to apply to the header
*/
fun crossOriginEmbedderPolicy(crossOriginEmbedderPolicyConfig: ServerCrossOriginEmbedderPolicyDsl.() -> Unit) {
this.crossOriginEmbedderPolicy = ServerCrossOriginEmbedderPolicyDsl().apply(crossOriginEmbedderPolicyConfig).get()
}
/**
* Allows configuration for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy">
* Cross-Origin-Resource-Policy</a> header.
*
* @since 5.7
* @param crossOriginResourcePolicyConfig the customization to apply to the header
*/
fun crossOriginResourcePolicy(crossOriginResourcePolicyConfig: ServerCrossOriginResourcePolicyDsl.() -> Unit) {
this.crossOriginResourcePolicy = ServerCrossOriginResourcePolicyDsl().apply(crossOriginResourcePolicyConfig).get()
}
/**
* Disables HTTP response headers.
*/
@@ -194,6 +235,15 @@ class ServerHeadersDsl {
referrerPolicy?.also {
headers.referrerPolicy(referrerPolicy)
}
crossOriginOpenerPolicy?.also {
headers.crossOriginOpenerPolicy(crossOriginOpenerPolicy)
}
crossOriginEmbedderPolicy?.also {
headers.crossOriginEmbedderPolicy(crossOriginEmbedderPolicy)
}
crossOriginResourcePolicy?.also {
headers.crossOriginResourcePolicy(crossOriginResourcePolicy)
}
if (disabled) {
headers.disable()
}
@@ -42,6 +42,9 @@ class HeadersDsl {
private var referrerPolicy: ((HeadersConfigurer<HttpSecurity>.ReferrerPolicyConfig) -> Unit)? = null
private var featurePolicyDirectives: String? = null
private var permissionsPolicy: ((HeadersConfigurer<HttpSecurity>.PermissionsPolicyConfig) -> Unit)? = null
private var crossOriginOpenerPolicy: ((HeadersConfigurer<HttpSecurity>.CrossOriginOpenerPolicyConfig) -> Unit)? = null
private var crossOriginEmbedderPolicy: ((HeadersConfigurer<HttpSecurity>.CrossOriginEmbedderPolicyConfig) -> Unit)? = null
private var crossOriginResourcePolicy: ((HeadersConfigurer<HttpSecurity>.CrossOriginResourcePolicyConfig) -> Unit)? = null
private var disabled = false
private var headerWriters = mutableListOf<HeaderWriter>()
@@ -181,6 +184,54 @@ class HeadersDsl {
this.permissionsPolicy = PermissionsPolicyDsl().apply(permissionsPolicyConfig).get()
}
/**
* Allows configuration for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy">
* Cross-Origin-Opener-Policy</a> header.
*
* <p>
* Calling this method automatically enables (includes) the Cross-Origin-Opener-Policy
* header in the response using the supplied policy.
* <p>
*
* @since 5.7
* @param crossOriginOpenerPolicyConfig the customization to apply to the header
*/
fun crossOriginOpenerPolicy(crossOriginOpenerPolicyConfig: CrossOriginOpenerPolicyDsl.() -> Unit) {
this.crossOriginOpenerPolicy = CrossOriginOpenerPolicyDsl().apply(crossOriginOpenerPolicyConfig).get()
}
/**
* Allows configuration for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy">
* Cross-Origin-Embedder-Policy</a> header.
*
* <p>
* Calling this method automatically enables (includes) the Cross-Origin-Embedder-Policy
* header in the response using the supplied policy.
* <p>
*
* @since 5.7
* @param crossOriginEmbedderPolicyConfig the customization to apply to the header
*/
fun crossOriginEmbedderPolicy(crossOriginEmbedderPolicyConfig: CrossOriginEmbedderPolicyDsl.() -> Unit) {
this.crossOriginEmbedderPolicy = CrossOriginEmbedderPolicyDsl().apply(crossOriginEmbedderPolicyConfig).get()
}
/**
* Configures the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy">
* Cross-Origin-Resource-Policy</a> header.
*
* <p>
* Calling this method automatically enables (includes) the Cross-Origin-Resource-Policy
* header in the response using the supplied policy.
* <p>
*
* @since 5.7
* @param crossOriginResourcePolicyConfig the customization to apply to the header
*/
fun crossOriginResourcePolicy(crossOriginResourcePolicyConfig: CrossOriginResourcePolicyDsl.() -> Unit) {
this.crossOriginResourcePolicy = CrossOriginResourcePolicyDsl().apply(crossOriginResourcePolicyConfig).get()
}
/**
* Adds a [HeaderWriter] instance.
*
@@ -238,6 +289,15 @@ class HeadersDsl {
permissionsPolicy?.also {
headers.permissionsPolicy(permissionsPolicy)
}
crossOriginOpenerPolicy?.also {
headers.crossOriginOpenerPolicy(crossOriginOpenerPolicy)
}
crossOriginEmbedderPolicy?.also {
headers.crossOriginEmbedderPolicy(crossOriginEmbedderPolicy)
}
crossOriginResourcePolicy?.also {
headers.crossOriginResourcePolicy(crossOriginResourcePolicy)
}
headerWriters.forEach { headerWriter ->
headers.addHeaderWriter(headerWriter)
}
@@ -0,0 +1,43 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.web.servlet.headers
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer
import org.springframework.security.web.header.writers.CrossOriginEmbedderPolicyHeaderWriter
/**
* A Kotlin DSL to configure the [HttpSecurity] Cross-Origin-Embedder-Policy header using
* idiomatic Kotlin code.
*
* @author Marcus Da Coregio
* @since 5.7
* @property policy the policy to be used in the response header.
*/
@HeadersSecurityMarker
class CrossOriginEmbedderPolicyDsl {
var policy: CrossOriginEmbedderPolicyHeaderWriter.CrossOriginEmbedderPolicy? = null
internal fun get(): (HeadersConfigurer<HttpSecurity>.CrossOriginEmbedderPolicyConfig) -> Unit {
return { crossOriginEmbedderPolicy ->
policy?.also {
crossOriginEmbedderPolicy.policy(policy)
}
}
}
}
@@ -0,0 +1,43 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.web.servlet.headers
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer
import org.springframework.security.web.header.writers.CrossOriginOpenerPolicyHeaderWriter
/**
* A Kotlin DSL to configure the [HttpSecurity] Cross-Origin-Opener-Policy header using
* idiomatic Kotlin code.
*
* @author Marcus Da Coregio
* @since 5.7
* @property policy the policy to be used in the response header.
*/
@HeadersSecurityMarker
class CrossOriginOpenerPolicyDsl {
var policy: CrossOriginOpenerPolicyHeaderWriter.CrossOriginOpenerPolicy? = null
internal fun get(): (HeadersConfigurer<HttpSecurity>.CrossOriginOpenerPolicyConfig) -> Unit {
return { crossOriginOpenerPolicy ->
policy?.also {
crossOriginOpenerPolicy.policy(policy)
}
}
}
}
@@ -0,0 +1,43 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.web.servlet.headers
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer
import org.springframework.security.web.header.writers.CrossOriginResourcePolicyHeaderWriter
/**
* A Kotlin DSL to configure the [HttpSecurity] Cross-Origin-Resource-Policy header using
* idiomatic Kotlin code.
*
* @author Marcus Da Coregio
* @since 5.7
* @property policy the policy to be used in the response header.
*/
@HeadersSecurityMarker
class CrossOriginResourcePolicyDsl {
var policy: CrossOriginResourcePolicyHeaderWriter.CrossOriginResourcePolicy? = null
internal fun get(): (HeadersConfigurer<HttpSecurity>.CrossOriginResourcePolicyConfig) -> Unit {
return { crossOriginResourcePolicy ->
policy?.also {
crossOriginResourcePolicy.policy(policy)
}
}
}
}
@@ -1,4 +1,5 @@
http\://www.springframework.org/schema/security/spring-security.xsd=org/springframework/security/config/spring-security-5.6.xsd
http\://www.springframework.org/schema/security/spring-security.xsd=org/springframework/security/config/spring-security-5.7.xsd
http\://www.springframework.org/schema/security/spring-security-5.7.xsd=org/springframework/security/config/spring-security-5.7.xsd
http\://www.springframework.org/schema/security/spring-security-5.6.xsd=org/springframework/security/config/spring-security-5.6.xsd
http\://www.springframework.org/schema/security/spring-security-5.5.xsd=org/springframework/security/config/spring-security-5.5.xsd
http\://www.springframework.org/schema/security/spring-security-5.4.xsd=org/springframework/security/config/spring-security-5.4.xsd
@@ -17,7 +18,8 @@ http\://www.springframework.org/schema/security/spring-security-2.0.xsd=org/spri
http\://www.springframework.org/schema/security/spring-security-2.0.1.xsd=org/springframework/security/config/spring-security-2.0.1.xsd
http\://www.springframework.org/schema/security/spring-security-2.0.2.xsd=org/springframework/security/config/spring-security-2.0.2.xsd
http\://www.springframework.org/schema/security/spring-security-2.0.4.xsd=org/springframework/security/config/spring-security-2.0.4.xsd
https\://www.springframework.org/schema/security/spring-security.xsd=org/springframework/security/config/spring-security-5.6.xsd
https\://www.springframework.org/schema/security/spring-security.xsd=org/springframework/security/config/spring-security-5.7.xsd
https\://www.springframework.org/schema/security/spring-security-5.7.xsd=org/springframework/security/config/spring-security-5.7.xsd
https\://www.springframework.org/schema/security/spring-security-5.6.xsd=org/springframework/security/config/spring-security-5.6.xsd
https\://www.springframework.org/schema/security/spring-security-5.5.xsd=org/springframework/security/config/spring-security-5.5.xsd
https\://www.springframework.org/schema/security/spring-security-5.4.xsd=org/springframework/security/config/spring-security-5.4.xsd
@@ -16,6 +16,9 @@
package org.springframework.security.config.annotation.web.builders;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.AfterEach;
@@ -24,6 +27,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
@@ -32,6 +36,7 @@ import org.springframework.security.config.annotation.authentication.builders.Au
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.firewall.HttpStatusRequestRejectedHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
@@ -92,6 +97,15 @@ public class WebSecurityTests {
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
}
@Test
public void requestRejectedHandlerInvoked() throws ServletException, IOException {
loadConfig(RequestRejectedHandlerConfig.class);
this.request.setServletPath("/spring");
this.request.setRequestURI("/spring/\u0019path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_BAD_REQUEST);
}
@Test
public void ignoringMvcMatcherServletPath() throws Exception {
loadConfig(MvcMatcherServletPathConfig.class, LegacyMvcMatchingConfig.class);
@@ -223,4 +237,14 @@ public class WebSecurityTests {
}
@EnableWebSecurity
static class RequestRejectedHandlerConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(WebSecurity web) throws Exception {
web.requestRejectedHandler(new HttpStatusRequestRejectedHandler(HttpStatus.BAD_REQUEST.value()));
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
package org.springframework.security.config.annotation.web.configuration;
import java.util.Arrays;
import java.util.concurrent.Callable;
import javax.servlet.http.HttpServletRequest;
@@ -23,14 +24,20 @@ import javax.servlet.http.HttpServletRequest;
import com.google.common.net.HttpHeaders;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.core.context.SecurityContextHolder;
@@ -47,6 +54,7 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.springframework.security.config.Customizer.withDefaults;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
@@ -65,7 +73,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
*
* @author Eleftheria Stein
*/
@ExtendWith(SpringTestContextExtension.class)
@ExtendWith({ MockitoExtension.class, SpringTestContextExtension.class })
public class HttpSecurityConfigurationTests {
public final SpringTestContext spring = new SpringTestContext(this);
@@ -73,6 +81,9 @@ public class HttpSecurityConfigurationTests {
@Autowired
private MockMvc mockMvc;
@Mock
private MockedStatic<SpringFactoriesLoader> springFactoriesLoader;
@Test
public void postWhenDefaultFilterChainBeanThenRespondsWithForbidden() throws Exception {
this.spring.register(DefaultWithFilterChainConfig.class).autowire();
@@ -200,6 +211,35 @@ public class HttpSecurityConfigurationTests {
this.mockMvc.perform(get("/login?logout")).andExpect(status().isOk());
}
@Test
public void configureWhenAuthorizeHttpRequestsBeforeAuthorizeRequestThenException() {
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(
() -> this.spring.register(AuthorizeHttpRequestsBeforeAuthorizeRequestsConfig.class).autowire())
.withMessageContaining(
"authorizeHttpRequests cannot be used in conjunction with authorizeRequests. Please select just one.");
}
@Test
public void configureWhenAuthorizeHttpRequestsAfterAuthorizeRequestThenException() {
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(
() -> this.spring.register(AuthorizeHttpRequestsAfterAuthorizeRequestsConfig.class).autowire())
.withMessageContaining(
"authorizeHttpRequests cannot be used in conjunction with authorizeRequests. Please select just one.");
}
@Test
public void configureWhenDefaultConfigurerAsSpringFactoryThenDefaultConfigurerApplied() {
DefaultConfigurer configurer = new DefaultConfigurer();
this.springFactoriesLoader.when(
() -> SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, getClass().getClassLoader()))
.thenReturn(Arrays.asList(configurer));
this.spring.register(DefaultWithFilterChainConfig.class).autowire();
assertThat(configurer.init).isTrue();
assertThat(configurer.configure).isTrue();
}
@RestController
static class NameController {
@@ -270,6 +310,44 @@ public class HttpSecurityConfigurationTests {
}
@EnableWebSecurity
static class AuthorizeHttpRequestsBeforeAuthorizeRequestsConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
return http
.authorizeHttpRequests((requests) -> requests
.anyRequest().authenticated()
)
.authorizeRequests((requests) -> requests
.anyRequest().authenticated()
)
.build();
// @formatter:on
}
}
@EnableWebSecurity
static class AuthorizeHttpRequestsAfterAuthorizeRequestsConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
return http
.authorizeRequests((requests) -> requests
.anyRequest().authenticated()
)
.authorizeHttpRequests((requests) -> requests
.anyRequest().authenticated()
)
.build();
// @formatter:on
}
}
@RestController
static class BaseController {
@@ -291,4 +369,22 @@ public class HttpSecurityConfigurationTests {
}
static class DefaultConfigurer extends AbstractHttpConfigurer<DefaultConfigurer, HttpSecurity> {
boolean init;
boolean configure;
@Override
public void init(HttpSecurity builder) {
this.init = true;
}
@Override
public void configure(HttpSecurity builder) {
this.configure = true;
}
}
}
@@ -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-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,6 +16,11 @@
package org.springframework.security.config.annotation.web.configurers;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -27,6 +32,8 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
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.RedirectStrategy;
import org.springframework.security.web.access.channel.ChannelDecisionManagerImpl;
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
import org.springframework.security.web.access.channel.InsecureChannelProcessor;
@@ -44,6 +51,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 +101,12 @@ public class ChannelSecurityConfigurerTests {
this.mvc.perform(get("/")).andExpect(redirectedUrl("https://localhost/"));
}
@Test
public void requestWhenRequiresChannelConfiguredWithUrlRedirectThenRedirectsToUrlWithHttps() throws Exception {
this.spring.register(RequiresChannelWithTestUrlRedirectStrategy.class).autowire();
this.mvc.perform(get("/")).andExpect(redirectedUrl("https://localhost/test"));
}
@EnableWebSecurity
static class ObjectPostProcessorConfig extends WebSecurityConfigurerAdapter {
@@ -155,4 +169,35 @@ public class ChannelSecurityConfigurerTests {
}
@EnableWebSecurity
static class RequiresChannelWithTestUrlRedirectStrategy extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.portMapper()
.portMapper(new PortMapperImpl())
.and()
.requiresChannel()
.redirectStrategy(new TestUrlRedirectStrategy())
.anyRequest()
.requiresSecure();
// @formatter:on
}
}
static class TestUrlRedirectStrategy implements RedirectStrategy {
@Override
public void sendRedirect(HttpServletRequest request, HttpServletResponse response, String url)
throws IOException {
String redirectUrl = url + "test";
redirectUrl = response.encodeRedirectURL(redirectUrl);
response.sendRedirect(redirectUrl);
}
}
}
@@ -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.
@@ -320,6 +320,17 @@ public class CsrfConfigurerTests {
any(HttpServletResponse.class), any());
}
@Test
public void getWhenCustomDefaultAccessDeniedHandlerForThenHandlerIsUsed() throws Exception {
DefaultAccessDeniedHandlerForConfig.DENIED_HANDLER = mock(AccessDeniedHandler.class);
DefaultAccessDeniedHandlerForConfig.MATCHER = mock(RequestMatcher.class);
given(DefaultAccessDeniedHandlerForConfig.MATCHER.matches(any())).willReturn(true);
this.spring.register(DefaultAccessDeniedHandlerForConfig.class, BasicController.class).autowire();
this.mvc.perform(post("/")).andExpect(status().isOk());
verify(DefaultAccessDeniedHandlerForConfig.DENIED_HANDLER).handle(any(HttpServletRequest.class),
any(HttpServletResponse.class), any());
}
@Test
public void loginWhenNoCsrfTokenThenRespondsWithForbidden() throws Exception {
this.spring.register(FormLoginConfig.class).autowire();
@@ -608,6 +619,24 @@ public class CsrfConfigurerTests {
}
@EnableWebSecurity
static class DefaultAccessDeniedHandlerForConfig extends WebSecurityConfigurerAdapter {
static AccessDeniedHandler DENIED_HANDLER;
static RequestMatcher MATCHER;
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.exceptionHandling()
.defaultAccessDeniedHandlerFor(DENIED_HANDLER, MATCHER);
// @formatter:on
}
}
@EnableWebSecurity
static class FormLoginConfig extends WebSecurityConfigurerAdapter {
@@ -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.
@@ -26,11 +26,16 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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.SecurityFilterChain;
import org.springframework.security.web.header.writers.CrossOriginEmbedderPolicyHeaderWriter;
import org.springframework.security.web.header.writers.CrossOriginOpenerPolicyHeaderWriter;
import org.springframework.security.web.header.writers.CrossOriginResourcePolicyHeaderWriter;
import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter.ReferrerPolicy;
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter.XFrameOptionsMode;
import org.springframework.test.web.servlet.MockMvc;
@@ -52,6 +57,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
* @author Eddú Meléndez
* @author Vedran Pavic
* @author Eleftheria Stein
* @author Marcus Da Coregio
*/
@ExtendWith(SpringTestContextExtension.class)
public class HeadersConfigurerTests {
@@ -514,6 +520,30 @@ public class HeadersConfigurerTests {
assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.STRICT_TRANSPORT_SECURITY);
}
@Test
public void getWhenCustomCrossOriginPoliciesInLambdaThenCrossOriginPolicyHeadersWithCustomValuesInResponse()
throws Exception {
this.spring.register(CrossOriginCustomPoliciesInLambdaConfig.class).autowire();
MvcResult mvcResult = this.mvc.perform(get("/"))
.andExpect(header().string(HttpHeaders.CROSS_ORIGIN_OPENER_POLICY, "same-origin"))
.andExpect(header().string(HttpHeaders.CROSS_ORIGIN_EMBEDDER_POLICY, "require-corp"))
.andExpect(header().string(HttpHeaders.CROSS_ORIGIN_RESOURCE_POLICY, "same-origin")).andReturn();
assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.CROSS_ORIGIN_OPENER_POLICY,
HttpHeaders.CROSS_ORIGIN_EMBEDDER_POLICY, HttpHeaders.CROSS_ORIGIN_RESOURCE_POLICY);
}
@Test
public void getWhenCustomCrossOriginPoliciesThenCrossOriginPolicyHeadersWithCustomValuesInResponse()
throws Exception {
this.spring.register(CrossOriginCustomPoliciesConfig.class).autowire();
MvcResult mvcResult = this.mvc.perform(get("/"))
.andExpect(header().string(HttpHeaders.CROSS_ORIGIN_OPENER_POLICY, "same-origin"))
.andExpect(header().string(HttpHeaders.CROSS_ORIGIN_EMBEDDER_POLICY, "require-corp"))
.andExpect(header().string(HttpHeaders.CROSS_ORIGIN_RESOURCE_POLICY, "same-origin")).andReturn();
assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.CROSS_ORIGIN_OPENER_POLICY,
HttpHeaders.CROSS_ORIGIN_EMBEDDER_POLICY, HttpHeaders.CROSS_ORIGIN_RESOURCE_POLICY);
}
@EnableWebSecurity
static class HeadersConfig extends WebSecurityConfigurerAdapter {
@@ -1146,4 +1176,50 @@ public class HeadersConfigurerTests {
}
@EnableWebSecurity
static class CrossOriginCustomPoliciesInLambdaConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// @formatter:off
http.headers((headers) -> headers
.defaultsDisabled()
.crossOriginOpenerPolicy((policy) -> policy
.policy(CrossOriginOpenerPolicyHeaderWriter.CrossOriginOpenerPolicy.SAME_ORIGIN)
)
.crossOriginEmbedderPolicy((policy) -> policy
.policy(CrossOriginEmbedderPolicyHeaderWriter.CrossOriginEmbedderPolicy.REQUIRE_CORP)
)
.crossOriginResourcePolicy((policy) -> policy
.policy(CrossOriginResourcePolicyHeaderWriter.CrossOriginResourcePolicy.SAME_ORIGIN)
)
);
// @formatter:on
return http.build();
}
}
@EnableWebSecurity
static class CrossOriginCustomPoliciesConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// @formatter:off
http.headers()
.defaultsDisabled()
.crossOriginOpenerPolicy()
.policy(CrossOriginOpenerPolicyHeaderWriter.CrossOriginOpenerPolicy.SAME_ORIGIN)
.and()
.crossOriginEmbedderPolicy()
.policy(CrossOriginEmbedderPolicyHeaderWriter.CrossOriginEmbedderPolicy.REQUIRE_CORP)
.and()
.crossOriginResourcePolicy()
.policy(CrossOriginResourcePolicyHeaderWriter.CrossOriginResourcePolicy.SAME_ORIGIN);
// @formatter:on
return http.build();
}
}
}
@@ -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-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.
@@ -61,11 +61,32 @@ public class PermitAllSupportTests {
this.mvc.perform(getWithCsrf).andExpect(status().isFound());
}
@Test
public void performWhenUsingPermitAllExactUrlRequestMatcherThenMatchesExactUrlWithAuthorizeHttp() throws Exception {
this.spring.register(PermitAllConfigAuthorizeHttpRequests.class).autowire();
MockHttpServletRequestBuilder request = get("/app/xyz").contextPath("/app");
this.mvc.perform(request).andExpect(status().isNotFound());
MockHttpServletRequestBuilder getWithQuery = get("/app/xyz?def").contextPath("/app");
this.mvc.perform(getWithQuery).andExpect(status().isFound());
MockHttpServletRequestBuilder postWithQueryAndCsrf = post("/app/abc?def").with(csrf()).contextPath("/app");
this.mvc.perform(postWithQueryAndCsrf).andExpect(status().isNotFound());
MockHttpServletRequestBuilder getWithCsrf = get("/app/abc").with(csrf()).contextPath("/app");
this.mvc.perform(getWithCsrf).andExpect(status().isFound());
}
@Test
public void configureWhenNotAuthorizeRequestsThenException() {
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(() -> this.spring.register(NoAuthorizedUrlsConfig.class).autowire())
.withMessageContaining("permitAll only works with HttpSecurity.authorizeRequests");
.isThrownBy(() -> this.spring.register(NoAuthorizedUrlsConfig.class).autowire()).withMessageContaining(
"permitAll only works with either HttpSecurity.authorizeRequests() or HttpSecurity.authorizeHttpRequests()");
}
@Test
public void configureWhenBothAuthorizeRequestsAndAuthorizeHttpRequestsThenException() {
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(() -> this.spring.register(PermitAllConfigWithBothConfigs.class).autowire())
.withMessageContaining(
"permitAll only works with either HttpSecurity.authorizeRequests() or HttpSecurity.authorizeHttpRequests()");
}
@EnableWebSecurity
@@ -86,6 +107,45 @@ public class PermitAllSupportTests {
}
@EnableWebSecurity
static class PermitAllConfigAuthorizeHttpRequests extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/xyz").permitAll()
.loginProcessingUrl("/abc?def").permitAll();
// @formatter:on
}
}
@EnableWebSecurity
static class PermitAllConfigWithBothConfigs extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.authorizeHttpRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/xyz").permitAll()
.loginProcessingUrl("/abc?def").permitAll();
// @formatter:on
}
}
@EnableWebSecurity
static class NoAuthorizedUrlsConfig extends WebSecurityConfigurerAdapter {
@@ -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.
@@ -80,9 +80,13 @@ import org.springframework.security.saml2.provider.service.registration.RelyingP
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations;
import org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationFilter;
import org.springframework.security.saml2.provider.service.web.DefaultRelyingPartyRegistrationResolver;
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver;
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestContextResolver;
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestRepository;
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationTokenConverter;
import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver;
import org.springframework.security.saml2.provider.service.web.authentication.Saml2AuthenticationRequestResolver;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationConverter;
@@ -104,6 +108,7 @@ import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.springframework.security.config.Customizer.withDefaults;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
@@ -211,6 +216,41 @@ public class Saml2LoginConfigurerTests {
assertThat(inflated).contains("ForceAuthn=\"true\"");
}
@Test
public void authenticationRequestWhenAuthenticationRequestResolverBeanThenUses() throws Exception {
this.spring.register(CustomAuthenticationRequestResolverBean.class).autowire();
MvcResult result = this.mvc.perform(get("/saml2/authenticate/registration-id")).andReturn();
UriComponents components = UriComponentsBuilder.fromHttpUrl(result.getResponse().getRedirectedUrl()).build();
String samlRequest = components.getQueryParams().getFirst("SAMLRequest");
String decoded = URLDecoder.decode(samlRequest, "UTF-8");
String inflated = Saml2Utils.samlInflate(Saml2Utils.samlDecode(decoded));
assertThat(inflated).contains("ForceAuthn=\"true\"");
}
@Test
public void authenticationRequestWhenAuthenticationRequestResolverDslThenUses() throws Exception {
this.spring.register(CustomAuthenticationRequestResolverDsl.class).autowire();
MvcResult result = this.mvc.perform(get("/saml2/authenticate/registration-id")).andReturn();
UriComponents components = UriComponentsBuilder.fromHttpUrl(result.getResponse().getRedirectedUrl()).build();
String samlRequest = components.getQueryParams().getFirst("SAMLRequest");
String decoded = URLDecoder.decode(samlRequest, "UTF-8");
String inflated = Saml2Utils.samlInflate(Saml2Utils.samlDecode(decoded));
assertThat(inflated).contains("ForceAuthn=\"true\"");
}
@Test
public void authenticationRequestWhenAuthenticationRequestResolverAndFactoryThenResolverTakesPrecedence()
throws Exception {
this.spring.register(CustomAuthenticationRequestResolverPrecedence.class).autowire();
MvcResult result = this.mvc.perform(get("/saml2/authenticate/registration-id")).andReturn();
UriComponents components = UriComponentsBuilder.fromHttpUrl(result.getResponse().getRedirectedUrl()).build();
String samlRequest = components.getQueryParams().getFirst("SAMLRequest");
String decoded = URLDecoder.decode(samlRequest, "UTF-8");
String inflated = Saml2Utils.samlInflate(Saml2Utils.samlDecode(decoded));
assertThat(inflated).contains("ForceAuthn=\"true\"");
verifyNoInteractions(this.spring.getContext().getBean(Saml2AuthenticationRequestFactory.class));
}
@Test
public void authenticateWhenCustomAuthenticationConverterThenUses() throws Exception {
this.spring.register(CustomAuthenticationConverter.class).autowire();
@@ -506,6 +546,103 @@ public class Saml2LoginConfigurerTests {
}
@EnableWebSecurity
@Import(Saml2LoginConfigBeans.class)
static class CustomAuthenticationRequestResolverBean {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeRequests((authz) -> authz
.anyRequest().authenticated()
)
.saml2Login(Customizer.withDefaults());
// @formatter:on
return http.build();
}
@Bean
Saml2AuthenticationRequestResolver authenticationRequestResolver(
RelyingPartyRegistrationRepository registrations) {
RelyingPartyRegistrationResolver registrationResolver = new DefaultRelyingPartyRegistrationResolver(
registrations);
OpenSaml4AuthenticationRequestResolver delegate = new OpenSaml4AuthenticationRequestResolver(
registrationResolver);
delegate.setAuthnRequestCustomizer((parameters) -> parameters.getAuthnRequest().setForceAuthn(true));
return delegate;
}
}
@EnableWebSecurity
@Import(Saml2LoginConfigBeans.class)
static class CustomAuthenticationRequestResolverDsl {
@Bean
SecurityFilterChain filterChain(HttpSecurity http, RelyingPartyRegistrationRepository registrations)
throws Exception {
// @formatter:off
http
.authorizeRequests((authz) -> authz
.anyRequest().authenticated()
)
.saml2Login((saml2) -> saml2
.authenticationRequestResolver(authenticationRequestResolver(registrations))
);
// @formatter:on
return http.build();
}
Saml2AuthenticationRequestResolver authenticationRequestResolver(
RelyingPartyRegistrationRepository registrations) {
RelyingPartyRegistrationResolver registrationResolver = new DefaultRelyingPartyRegistrationResolver(
registrations);
OpenSaml4AuthenticationRequestResolver delegate = new OpenSaml4AuthenticationRequestResolver(
registrationResolver);
delegate.setAuthnRequestCustomizer((parameters) -> parameters.getAuthnRequest().setForceAuthn(true));
return delegate;
}
}
@EnableWebSecurity
@Import(Saml2LoginConfigBeans.class)
static class CustomAuthenticationRequestResolverPrecedence {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeRequests((authz) -> authz
.anyRequest().authenticated()
)
.saml2Login(Customizer.withDefaults());
// @formatter:on
return http.build();
}
@Bean
Saml2AuthenticationRequestFactory authenticationRequestFactory() {
return mock(Saml2AuthenticationRequestFactory.class);
}
@Bean
Saml2AuthenticationRequestResolver authenticationRequestResolver(
RelyingPartyRegistrationRepository registrations) {
RelyingPartyRegistrationResolver registrationResolver = new DefaultRelyingPartyRegistrationResolver(
registrations);
OpenSaml4AuthenticationRequestResolver delegate = new OpenSaml4AuthenticationRequestResolver(
registrationResolver);
delegate.setAuthnRequestCustomizer((parameters) -> parameters.getAuthnRequest().setForceAuthn(true));
return delegate;
}
}
@EnableWebSecurity
@Import(Saml2LoginConfigBeans.class)
static class CustomAuthenticationConverter extends WebSecurityConfigurerAdapter {
@@ -65,7 +65,7 @@ public class XsdDocumentedTests {
String schema31xDocumentLocation = "org/springframework/security/config/spring-security-3.1.xsd";
String schemaDocumentLocation = "org/springframework/security/config/spring-security-5.6.xsd";
String schemaDocumentLocation = "org/springframework/security/config/spring-security-5.7.xsd";
XmlSupport xml = new XmlSupport();
@@ -150,8 +150,8 @@ public class XsdDocumentedTests {
.getParentFile()
.list((dir, name) -> name.endsWith(".xsd"));
// @formatter:on
assertThat(schemas.length).isEqualTo(18)
.withFailMessage("the count is equal to 18, if not then schemaDocument needs updating");
assertThat(schemas.length).isEqualTo(19)
.withFailMessage("the count is equal to 19, if not then schemaDocument needs updating");
}
/**
@@ -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.
@@ -48,6 +48,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
* @author Tim Ysewyn
* @author Josh Cummings
* @author Rafiullah Hamedy
* @author Marcus Da Coregio
*/
@ExtendWith(SpringTestContextExtension.class)
public class HttpHeadersConfigTests {
@@ -733,6 +734,53 @@ public class HttpHeadersConfigTests {
// @formatter:on
}
@Test
public void requestWhenCrossOriginOpenerPolicyWithSameOriginAllowPopupsThenRespondsWithSameOriginAllowPopups()
throws Exception {
this.spring.configLocations(this.xml("DefaultsDisabledWithCrossOriginOpenerPolicy")).autowire();
// @formatter:off
this.mvc.perform(get("/"))
.andExpect(status().isOk())
.andExpect(excludesDefaults())
.andExpect(header().string("Cross-Origin-Opener-Policy", "same-origin-allow-popups"));
// @formatter:on
}
@Test
public void requestWhenCrossOriginEmbedderPolicyWithRequireCorpThenRespondsWithRequireCorp() throws Exception {
this.spring.configLocations(this.xml("DefaultsDisabledWithCrossOriginEmbedderPolicy")).autowire();
// @formatter:off
this.mvc.perform(get("/"))
.andExpect(status().isOk())
.andExpect(excludesDefaults())
.andExpect(header().string("Cross-Origin-Embedder-Policy", "require-corp"));
// @formatter:on
}
@Test
public void requestWhenCrossOriginResourcePolicyWithSameOriginThenRespondsWithSameOrigin() throws Exception {
this.spring.configLocations(this.xml("DefaultsDisabledWithCrossOriginResourcePolicy")).autowire();
// @formatter:off
this.mvc.perform(get("/"))
.andExpect(status().isOk())
.andExpect(excludesDefaults())
.andExpect(header().string("Cross-Origin-Resource-Policy", "same-origin"));
// @formatter:on
}
@Test
public void requestWhenCrossOriginPoliciesRespondsCrossOriginPolicies() throws Exception {
this.spring.configLocations(this.xml("DefaultsDisabledWithCrossOriginPolicies")).autowire();
// @formatter:off
this.mvc.perform(get("/"))
.andExpect(status().isOk())
.andExpect(excludesDefaults())
.andExpect(header().string("Cross-Origin-Opener-Policy", "same-origin"))
.andExpect(header().string("Cross-Origin-Embedder-Policy", "require-corp"))
.andExpect(header().string("Cross-Origin-Resource-Policy", "same-origin"));
// @formatter:on
}
private static ResultMatcher includesDefaults() {
return includes(defaultHeaders);
}
@@ -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.
@@ -30,6 +30,9 @@ import org.springframework.http.HttpHeaders;
import org.springframework.security.test.web.reactive.server.WebTestClientBuilder;
import org.springframework.security.web.server.header.ContentSecurityPolicyServerHttpHeadersWriter;
import org.springframework.security.web.server.header.ContentTypeOptionsServerHttpHeadersWriter;
import org.springframework.security.web.server.header.CrossOriginEmbedderPolicyServerHttpHeadersWriter;
import org.springframework.security.web.server.header.CrossOriginOpenerPolicyServerHttpHeadersWriter;
import org.springframework.security.web.server.header.CrossOriginResourcePolicyServerHttpHeadersWriter;
import org.springframework.security.web.server.header.FeaturePolicyServerHttpHeadersWriter;
import org.springframework.security.web.server.header.ReferrerPolicyServerHttpHeadersWriter;
import org.springframework.security.web.server.header.ReferrerPolicyServerHttpHeadersWriter.ReferrerPolicy;
@@ -48,6 +51,7 @@ import static org.springframework.security.config.Customizer.withDefaults;
* @author Rob Winch
* @author Vedran Pavic
* @author Ankur Pathak
* @author Marcus Da Coregio
* @since 5.0
*/
public class HeaderSpecTests {
@@ -406,6 +410,53 @@ public class HeaderSpecTests {
assertHeaders();
}
@Test
public void headersWhenCrossOriginPoliciesCustomEnabledThenCustomCrossOriginPoliciesWritten() {
this.expectedHeaders.add(CrossOriginOpenerPolicyServerHttpHeadersWriter.OPENER_POLICY,
CrossOriginOpenerPolicyServerHttpHeadersWriter.CrossOriginOpenerPolicy.SAME_ORIGIN_ALLOW_POPUPS
.getPolicy());
this.expectedHeaders.add(CrossOriginEmbedderPolicyServerHttpHeadersWriter.EMBEDDER_POLICY,
CrossOriginEmbedderPolicyServerHttpHeadersWriter.CrossOriginEmbedderPolicy.REQUIRE_CORP.getPolicy());
this.expectedHeaders.add(CrossOriginResourcePolicyServerHttpHeadersWriter.RESOURCE_POLICY,
CrossOriginResourcePolicyServerHttpHeadersWriter.CrossOriginResourcePolicy.SAME_ORIGIN.getPolicy());
// @formatter:off
this.http.headers()
.crossOriginOpenerPolicy()
.policy(CrossOriginOpenerPolicyServerHttpHeadersWriter.CrossOriginOpenerPolicy.SAME_ORIGIN_ALLOW_POPUPS)
.and()
.crossOriginEmbedderPolicy()
.policy(CrossOriginEmbedderPolicyServerHttpHeadersWriter.CrossOriginEmbedderPolicy.REQUIRE_CORP)
.and()
.crossOriginResourcePolicy()
.policy(CrossOriginResourcePolicyServerHttpHeadersWriter.CrossOriginResourcePolicy.SAME_ORIGIN);
// @formatter:on
assertHeaders();
}
@Test
public void headersWhenCrossOriginPoliciesCustomEnabledInLambdaThenCustomCrossOriginPoliciesWritten() {
this.expectedHeaders.add(CrossOriginOpenerPolicyServerHttpHeadersWriter.OPENER_POLICY,
CrossOriginOpenerPolicyServerHttpHeadersWriter.CrossOriginOpenerPolicy.SAME_ORIGIN_ALLOW_POPUPS
.getPolicy());
this.expectedHeaders.add(CrossOriginEmbedderPolicyServerHttpHeadersWriter.EMBEDDER_POLICY,
CrossOriginEmbedderPolicyServerHttpHeadersWriter.CrossOriginEmbedderPolicy.REQUIRE_CORP.getPolicy());
this.expectedHeaders.add(CrossOriginResourcePolicyServerHttpHeadersWriter.RESOURCE_POLICY,
CrossOriginResourcePolicyServerHttpHeadersWriter.CrossOriginResourcePolicy.SAME_ORIGIN.getPolicy());
// @formatter:off
this.http.headers()
.crossOriginOpenerPolicy((policy) -> policy
.policy(CrossOriginOpenerPolicyServerHttpHeadersWriter.CrossOriginOpenerPolicy.SAME_ORIGIN_ALLOW_POPUPS)
)
.crossOriginEmbedderPolicy((policy) -> policy
.policy(CrossOriginEmbedderPolicyServerHttpHeadersWriter.CrossOriginEmbedderPolicy.REQUIRE_CORP)
)
.crossOriginResourcePolicy((policy) -> policy
.policy(CrossOriginResourcePolicyServerHttpHeadersWriter.CrossOriginResourcePolicy.SAME_ORIGIN)
);
// @formatter:on
assertHeaders();
}
private void expectHeaderNamesNotPresent(String... headerNames) {
for (String headerName : headerNames) {
this.expectedHeaders.remove(headerName);
@@ -22,16 +22,16 @@ import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.ApplicationContext
import org.springframework.context.annotation.Bean
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService
import org.springframework.security.core.userdetails.User
import org.springframework.security.config.test.SpringTestContext
import org.springframework.security.config.test.SpringTestContextExtension
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService
import org.springframework.security.core.userdetails.User
import org.springframework.security.web.server.SecurityWebFilterChain
import org.springframework.test.web.reactive.server.WebTestClient
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.reactive.config.EnableWebFlux
import java.util.*
import java.util.Base64
/**
* Tests for [AuthorizeExchangeDsl]
@@ -181,4 +181,40 @@ class AuthorizeExchangeDslTests {
return MapReactiveUserDetailsService(user)
}
}
@Test
fun `request when ip address does not match then responds with forbidden`() {
this.spring.register(HasIpAddressConfig::class.java).autowire()
this.client
.get()
.uri("/")
.header("Authorization", "Basic " + Base64.getEncoder().encodeToString("user:password".toByteArray()))
.exchange()
.expectStatus().isForbidden
}
@EnableWebFluxSecurity
@EnableWebFlux
open class HasIpAddressConfig {
@Bean
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
authorizeExchange {
authorize(anyExchange, hasIpAddress("10.0.0.0/24"))
}
httpBasic { }
}
}
@Bean
open fun userDetailsService(): MapReactiveUserDetailsService {
val user = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build()
return MapReactiveUserDetailsService(user)
}
}
}
@@ -28,6 +28,9 @@ import org.springframework.security.config.test.SpringTestContextExtension
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter
import org.springframework.security.web.server.SecurityWebFilterChain
import org.springframework.security.web.server.header.ContentTypeOptionsServerHttpHeadersWriter
import org.springframework.security.web.server.header.CrossOriginEmbedderPolicyServerHttpHeadersWriter
import org.springframework.security.web.server.header.CrossOriginOpenerPolicyServerHttpHeadersWriter
import org.springframework.security.web.server.header.CrossOriginResourcePolicyServerHttpHeadersWriter
import org.springframework.security.web.server.header.StrictTransportSecurityServerHttpHeadersWriter
import org.springframework.security.web.server.header.XFrameOptionsServerHttpHeadersWriter
import org.springframework.security.web.server.header.XXssProtectionServerHttpHeadersWriter
@@ -133,4 +136,60 @@ class ServerHeadersDslTests {
}
}
}
@Test
fun `request when no cross-origin policies configured then does not write cross-origin policies headers in response`() {
this.spring.register(CrossOriginPoliciesConfig::class.java).autowire()
this.client.get()
.uri("/")
.exchange()
.expectHeader().doesNotExist("Cross-Origin-Opener-Policy")
.expectHeader().doesNotExist("Cross-Origin-Embedder-Policy")
.expectHeader().doesNotExist("Cross-Origin-Resource-Policy")
}
@EnableWebFluxSecurity
@EnableWebFlux
open class CrossOriginPoliciesConfig {
@Bean
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
headers { }
}
}
}
@Test
fun `request when cross-origin custom policies configured then cross-origin custom policies headers in response`() {
this.spring.register(CrossOriginPoliciesCustomConfig::class.java).autowire()
this.client.get()
.uri("/")
.exchange()
.expectHeader().valueEquals("Cross-Origin-Opener-Policy", "same-origin")
.expectHeader().valueEquals("Cross-Origin-Embedder-Policy", "require-corp")
.expectHeader().valueEquals("Cross-Origin-Resource-Policy", "same-origin")
}
@EnableWebFluxSecurity
@EnableWebFlux
open class CrossOriginPoliciesCustomConfig {
@Bean
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
headers {
crossOriginOpenerPolicy {
policy = CrossOriginOpenerPolicyServerHttpHeadersWriter.CrossOriginOpenerPolicy.SAME_ORIGIN
}
crossOriginEmbedderPolicy {
policy = CrossOriginEmbedderPolicyServerHttpHeadersWriter.CrossOriginEmbedderPolicy.REQUIRE_CORP
}
crossOriginResourcePolicy {
policy = CrossOriginResourcePolicyServerHttpHeadersWriter.CrossOriginResourcePolicy.SAME_ORIGIN
}
}
}
}
}
}
@@ -19,13 +19,13 @@ package org.springframework.security.config.web.servlet
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Bean
import org.springframework.http.HttpHeaders
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.config.web.servlet.headers.PermissionsPolicyDsl
import org.springframework.security.web.header.writers.StaticHeadersWriter
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter
import org.springframework.security.web.server.header.ContentTypeOptionsServerHttpHeadersWriter
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/security"
xsi:schemaLocation="
http://www.springframework.org/schema/security
https://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<http auto-config="true">
<headers defaults-disabled="true">
<cross-origin-embedder-policy policy="require-corp"/>
</headers>
</http>
<b:bean name="simple" class="org.springframework.security.config.http.HttpHeadersConfigTests.SimpleController"/>
<b:import resource="userservice.xml"/>
</b:beans>
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/security"
xsi:schemaLocation="
http://www.springframework.org/schema/security
https://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<http auto-config="true">
<headers defaults-disabled="true">
<cross-origin-opener-policy policy="same-origin-allow-popups"/>
</headers>
</http>
<b:bean name="simple" class="org.springframework.security.config.http.HttpHeadersConfigTests.SimpleController"/>
<b:import resource="userservice.xml"/>
</b:beans>
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/security"
xsi:schemaLocation="
http://www.springframework.org/schema/security
https://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<http auto-config="true">
<headers defaults-disabled="true">
<cross-origin-opener-policy policy="same-origin"/>
<cross-origin-embedder-policy policy="require-corp"/>
<cross-origin-resource-policy policy="same-origin"/>
</headers>
</http>
<b:bean name="simple" class="org.springframework.security.config.http.HttpHeadersConfigTests.SimpleController"/>
<b:import resource="userservice.xml"/>
</b:beans>
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/security"
xsi:schemaLocation="
http://www.springframework.org/schema/security
https://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<http auto-config="true">
<headers defaults-disabled="true">
<cross-origin-resource-policy policy="same-origin"/>
</headers>
</http>
<b:bean name="simple" class="org.springframework.security.config.http.HttpHeadersConfigTests.SimpleController"/>
<b:import resource="userservice.xml"/>
</b:beans>
+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
@@ -43,7 +43,7 @@ public final class SpringSecurityCoreVersion {
* N.B. Classes are not intended to be serializable between different versions. See
* SEC-1709 for why we still need a serial version.
*/
public static final long SERIAL_VERSION_UID = 560L;
public static final long SERIAL_VERSION_UID = 570L;
static final String MIN_SPRING_VERSION = getSpringVersion();

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