1
0
mirror of synced 2026-07-08 20:30:04 +00:00

Compare commits

...

196 Commits

Author SHA1 Message Date
github-actions[bot] d0b2b33dce Release 5.7.14 2024-11-18 16:36:56 +00:00
Joe Grandja 0eaffb37e7 Require Locale argument for toLower/toUpperCase usage 2024-11-15 11:01:23 -05:00
Rob Winch e86d88d0cf Support ServerExchangeRejectedHandler @Bean
Closes gh-15975
2024-11-08 14:48:40 -06:00
Rob Winch e48d6b039b Support ServerWebExchangeFirewall @Bean
Closes gh-15974
2024-10-22 18:25:28 -05:00
github-actions[bot] dd3c6892e9 Next development version 2024-10-21 17:58:56 +00:00
github-actions[bot] c552366a78 Release 5.7.13 2024-10-21 17:23:29 +00:00
Steve Riesenberg 7215c72373 Migrate slack notifications to GChat
Issue gh-15503

[skip ci]
2024-10-21 11:24:40 -05:00
Rob Winch 4ce7cde155 Add Firewall for WebFlux
Closes gh-15967
2024-10-21 08:46:13 -05:00
Rob Winch c8342fcfae Use antora-extensions 1.10.0 2024-04-19 14:16:02 -05:00
github-actions[bot] 1b0c4d68da Next development version 2024-03-18 11:38:53 +00:00
github-actions[bot] b38b495630 Release 5.7.12 2024-03-18 11:10:08 +00:00
Marcus Hert Da Coregio 5a7f12f1a9 Check for null Authentication
Closes gh-14715
2024-03-18 06:39:08 -03:00
github-actions[bot] c614422f44 Next development version 2023-09-18 16:10:51 +00:00
github-actions[bot] 3656fa6e0c Release 5.7.11 2023-09-18 15:24:51 +00:00
Rob Winch 5b293d2116 Automate spring-security.xsd
Closes gh-13819
2023-09-14 16:01:50 -05:00
Marcus Da Coregio ff4745a461 Merge branch '5.6.x' into 5.7.x 2023-07-18 09:51:19 -03:00
github-actions[bot] 38a7ffcc9b Next development version 2023-07-17 21:52:14 +00:00
github-actions[bot] d0491c07e9 Next development version 2023-07-17 21:42:28 +00:00
github-actions[bot] 486c5118d2 Release 5.7.10 2023-07-17 21:05:15 +00:00
github-actions[bot] 17d8293be1 Release 5.6.12 2023-07-17 21:00:49 +00:00
Marcus Da Coregio fe9bc26bdc Merge branch '5.6.x' into 5.7.x 2023-07-17 09:13:28 -03:00
Marcus Da Coregio 7813a9ba26 Use default PathPatternParser instance 2023-07-17 09:12:28 -03:00
Marcus Da Coregio dc3cb68c76 Merge branch '5.6.x' into 5.7.x
Closes gh-dry-run
2023-07-14 11:04:01 -03:00
Marcus Da Coregio 56292c9971 Update org.springframework.data to 2021.2.14
Closes gh-13512
2023-07-14 10:52:14 -03:00
Marcus Da Coregio 4fc938555e Update org.springframework to 5.3.29
Closes gh-13511
2023-07-14 10:52:11 -03:00
Marcus Da Coregio e574415244 Update io.projectreactor to 2020.0.34
Closes gh-13509
2023-07-14 10:52:03 -03:00
Marcus Da Coregio c5da886310 Update org.springframework to 5.3.29
Closes gh-13508
2023-07-14 10:42:27 -03:00
Marcus Da Coregio 3bef5fd3ed Update io.projectreactor to 2020.0.34
Closes gh-13505
2023-07-14 10:42:27 -03:00
Steve Riesenberg e7201c48d1 Fix copy/paste from main
Issue gh-13500
2023-07-12 16:04:35 -05:00
Steve Riesenberg 991b398c55 Create release with full docs path
Closes gh-13500
2023-07-12 15:51:44 -05:00
Marcus Da Coregio 488b6ea531 Merge branch '5.6.x' into 5.7.x 2023-06-19 14:18:23 -03:00
github-actions[bot] ef04ea9d68 Next development version 2023-06-19 17:05:13 +00:00
github-actions[bot] b7be4c462c Next development version 2023-06-19 16:55:18 +00:00
Marcus Da Coregio aa8440e1ee Release 5.6.11 2023-06-19 13:23:20 -03:00
github-actions[bot] 6d3f1699c0 Release 5.7.9 2023-06-19 16:09:53 +00:00
Rob Winch 0cf95dbf61 Merge branch '5.6.x' into 5.7.x
Closes gh-13404
2023-06-18 21:31:35 -05:00
Rob Winch 39c43159f4 Convert to Asciidoctor Tabs
Closes gh-13403
2023-06-18 21:30:41 -05:00
Marcus Da Coregio 0a022a348c Merge branch '5.6.x' into 5.7.x
Closes gh-dry-run
2023-06-18 11:16:54 -03:00
Marcus Da Coregio afec90c8e8 Remove /plugins-release and /plugins-snapshot from build
Those repositories now need authentication, and we do not actually need them, the dependencies can be resolved from Maven Central

Closes gh-13245

(cherry picked from commit 663f5cf76b)
2023-06-17 19:32:48 -03:00
Marcus Da Coregio c7ccba66c7 Merge branch '5.6.x' into 5.7.x
Closes gh-dry-run
2023-06-17 18:23:15 -03:00
Marcus Da Coregio a6599f9874 Update org.springframework to 5.3.28
Closes gh-13401
2023-06-17 17:58:50 -03:00
Marcus Da Coregio 9f581850a1 Update hibernate-entitymanager to 5.6.15.Final
Closes gh-13400
2023-06-17 17:58:50 -03:00
Marcus Da Coregio 6e13927b48 Update org.eclipse.jetty to 9.4.51.v20230217
Closes gh-13399
2023-06-17 17:58:49 -03:00
Marcus Da Coregio cead207c5b Update org.aspectj to 1.9.19
Closes gh-13398
2023-06-17 17:58:49 -03:00
Marcus Da Coregio 9e22d40264 Update io.spring.nohttp to 0.0.11
Closes gh-13394
2023-06-17 17:58:49 -03:00
Marcus Da Coregio c54a191042 Update io.rsocket to 1.1.4
Closes gh-13392
2023-06-17 17:58:49 -03:00
Marcus Da Coregio 0da2e5a33e Update blockhound to 1.0.8.RELEASE
Closes gh-13390
2023-06-17 17:58:45 -03:00
Marcus Da Coregio 08b84502e3 Update io.projectreactor to 2020.0.33
Closes gh-13387
2023-06-17 17:58:22 -03:00
Marcus Da Coregio dbcff3f1af Update org.springframework.data to 2021.2.13
Closes gh-13385
2023-06-17 17:16:33 -03:00
Marcus Da Coregio e87fae40df Update org.springframework to 5.3.28
Closes gh-13382
2023-06-17 17:16:29 -03:00
Marcus Da Coregio 32d25e8f23 Update io.rsocket to 1.1.4
Closes gh-13379
2023-06-17 17:16:25 -03:00
Marcus Da Coregio 9656f79afe Update jackson-bom to 2.13.5
Closes gh-13375
2023-06-17 17:16:21 -03:00
Marcus Da Coregio 81723fca9a Update io.projectreactor to 2020.0.33
Closes gh-13373
2023-06-17 17:16:19 -03:00
Marcus Da Coregio ac32095bc8 Update logback-classic to 1.2.12
Closes gh-13372
2023-06-17 17:16:17 -03:00
Rob Winch 30206b6d0c Merge branch '5.6.x' into 5.7.x
Closes gh-13328
2023-06-16 20:23:19 -05:00
Rob Winch 3e22c1e8de Use antora name of security
Closes gh-13327
2023-06-16 17:14:56 -05:00
Marcus Da Coregio d67c11cfbd Add Proxy Server Configuration to nav
Closes gh-13313
2023-06-15 08:45:49 -03:00
Rob Winch a387f401b7 Merge branch '5.6.x' into 5.7.x
Closes gh-13315
2023-06-14 16:20:02 -05:00
Rob Winch 9bd04ed0e0 Fix Documentation Title
Closes gh-13314
2023-06-14 16:18:46 -05:00
Rob Winch 68cec50bad Merge branch '5.6.x' into 5.7.x
Closes gh-13291
2023-06-08 17:08:54 -05:00
Rob Winch 04692d9ee8 Fix Antora Warnings
Closes gh-13210
2023-06-08 17:02:19 -05:00
Anubhav Ahlawat 2279f9fd39 Typos in persistence section 2023-06-05 14:27:43 -06:00
Josh Cummings e834543eed Change possessives to use two ticks 2023-06-05 13:17:15 -06:00
1993heqiang 783f674704 Fix typo authentication.adoc 2023-06-05 13:17:15 -06:00
1993heqiang 933debebeb Fix typo overview.adoc 2023-06-05 13:17:15 -06:00
Marcus Da Coregio 7250abc185 Does not apply a Configurer when disabled from another DSL
Closes gh-13203
2023-06-05 16:01:20 -03:00
Christoph Zuleger 06e58e4c34 Update JavaDoc of BasicAuthenticationFilter
Remove deprecated hint to use Digest Auth in favor of Basic Auth.
2023-06-05 12:46:30 -06:00
Josh Cummings bcc1cfc28a Restore OAuth2AuthorizedClientRepository Test Instrumentation
Closes gh-13113
2023-05-31 15:30:03 -06:00
Marcus Da Coregio 663f5cf76b Remove /plugins-release and /plugins-snapshot from build
Those repositories now need authentication, and we do not actually need them, the dependencies can be resolved from Maven Central

Closes gh-13245
2023-05-30 15:25:54 -03:00
Marcus Da Coregio a53cbb838b Polish
Issue gh-13155
2023-05-24 14:59:16 -03:00
joerg-richter-5234 8287289bcb Fix XContentTypeOptionsServerHttpHeadersWriter
set constant value to X-Content-Type-Options

Closes gh-13155
2023-05-24 14:59:14 -03:00
Josh Cummings 1eefd433b6 Add spring-security.xsd symlink
Closes gh-13131
2023-05-22 15:42:02 -06:00
moli b6f3cb71e6 Add Missing AuthorizationRequestRepository in Snippet
Closes PR-13099
2023-05-18 10:54:19 -06:00
Josh Cummings 3469bcb822 Address Antora Warnings 2023-05-10 15:51:49 -06:00
Josh Cummings b4083f1b9e Revert "gh-13136 fixed log level related bug"
This reverts commit 1e093db1b6.
2023-05-08 15:23:17 -06:00
stnor 1e093db1b6 gh-13136 fixed log level related bug 2023-05-08 15:18:14 -06:00
Marcus Da Coregio e61adcb0cd Clarify that Kotlin DSL needs an import
Closes gh-13092
2023-04-26 15:56:47 -03:00
Ruslan Stelmachenko caa4093619 Fix javadoc for migration from WebSecurityConfigurerAdapter 2023-04-24 16:32:16 -06:00
Amal Krishna 8bec14009e Fix typo in SecurityMockMvcResultMatchers.java
Change the first parameter's name of the AuthenticatedMatcher.withAuthentication() method from assesrtAuthentication to assertAuthentication
2023-04-19 11:25:55 -03:00
SeasonPan 556ae316ba fix typo of modules.adoc 2023-04-19 11:25:55 -03:00
github-actions[bot] 53d007f062 Next development version 2023-04-17 16:04:08 +00:00
github-actions[bot] d6eeafb10c Release 5.7.8 2023-04-17 15:21:42 +00:00
Marcus Da Coregio 2d52fb8e4b Clear Repository on Logout 2023-04-17 06:47:57 -03:00
Marcus Da Coregio 37d8846652 Update org.springframework.data to 2021.2.11
Closes gh-13019
2023-04-14 13:57:28 -03:00
Marcus Da Coregio 74b8c6715b Update org.springframework to 5.3.27
Closes gh-13018
2023-04-14 10:41:45 -03:00
Marcus Da Coregio 8c9275067e Update org.eclipse.jetty to 9.4.51.v20230217
Closes gh-13017
2023-04-14 10:41:41 -03:00
Marcus Da Coregio 4b8bfee7c5 Update blockhound to 1.0.8.RELEASE
Closes gh-13016
2023-04-14 10:41:37 -03:00
Marcus Da Coregio b727f24c95 Update io.projectreactor to 2020.0.31
Closes gh-13014
2023-04-14 10:41:31 -03:00
Marcus Da Coregio d55c8aac4f Update logback-classic to 1.2.12
Closes gh-13013
2023-04-14 10:41:27 -03:00
Nick Meverden c4e9fb885d Delay JWSVerificationKeySelector Construction
Updating the NimbusReactiveJwtDecoder.JwkSetUriReactiveJwtDecoderBuilder processor to execute the ConfigurableJWTProcessor customizations before holding a reference to the JWSKeySelector.

Closes gh-12960
2023-04-12 12:45:00 -06:00
Christian Marck 442faccb5f Avoid NPE in FilterInvocation
Handle unknown headers in dummy request wrapper.

Closes gh-12998
2023-04-11 17:01:59 -06:00
Marcus Da Coregio e25117856e Add static imports to code snippets
Closes gh-6597
2023-04-11 09:56:19 -03:00
Yuanhang Guo c69df9fba0 Fix javadoc typo in ReactiveAuthorizationManager
Closes gh-12978
2023-04-10 11:24:49 -05:00
Josh Cummings 39cee36065 Use SingletonSupplier
Issue gh-9991
2023-04-04 13:25:31 -06:00
Tatiana Slednikova a106188add Add nameIdFormat processing to OpenSamlAuthenticationRequestResolver
Closes gh-12825
2023-03-27 16:33:11 -06:00
Martin Tarjányi 5eefe9dcff Fix typo in SessionManagementConfigurer javadoc 2023-03-22 10:07:44 -03:00
Siva Kumar Edupuganti 98321b769a Fixed Broken Support Link
Closes gh-12759
2023-03-20 16:59:04 -06:00
twosom 3d7e22a4e9 Add test to SimpleUrlAuthenticationSuccessHandlerTests 2023-03-20 16:20:30 -06:00
Marcus Da Coregio 84cca81edf Use HttpSessionSecurityContextRepository by default in SwitchUserFilter
Closes gh-12834
2023-03-07 13:27:18 -03:00
bist 094bf1b527 Validate hasRole Input
There are no check for role prefix in AuthorizeHttpRequestsConfigurer#XXXrole
methods. This PR adds check for the same. Now the configuration
will fail if role/s start with prefix for hasRole and hasAnyRole methods.

Closes #12581
2023-03-03 15:00:34 -07:00
Josh Cummings 66665344c5 Formatting
Issue gh-12664
2023-03-01 11:23:45 -07:00
stnor 6c7703789a Ignore SPSSODescriptor elements
Closes gh-12664
2023-03-01 11:23:45 -07:00
Steve Riesenberg fabf7f649c Fix path of copied schema files
Issue gh-12804
2023-03-01 11:49:36 -06:00
Steve Riesenberg 7b88ab289d Add nohttp exclusion
Issue gh-12804
2023-02-28 16:52:19 -06:00
Steve Riesenberg 26566af431 Copy spring-security-oauth schemas on deploy
Closes gh-12804
2023-02-28 16:16:08 -06:00
Steve Riesenberg 5257e36ffc Update SaganApi to work with Contentful backend
Also fixed an issue with incorrect referenceDocUrl for Antora
with SNAPSHOT versions.

Closes gh-12575
2023-02-28 14:09:51 -06:00
Josh Cummings 0421e25cba Document Common SAML URI Endpoints
Issue gh-12764
2023-02-28 12:45:48 -07:00
Josh Cummings 1c885cf3a3 Document Federation Usecase
Closes gh-12764
2023-02-28 12:35:04 -07:00
Marcus Da Coregio 1c3ce1e401 Fix entity-id ignored in RelyingPartyRegistration XML config
Closes gh-11898
2023-02-23 15:16:40 -03:00
github-actions[bot] b851a8bf6d Next development version 2023-02-21 15:20:58 +00:00
github-actions[bot] faadadf71a Release 5.7.7 2023-02-21 14:41:42 +00:00
Josh Cummings 2fcf48a11b Update org.springframework.data to 2021.2.8
Closes gh-12738
2023-02-17 15:53:49 -07:00
Josh Cummings 0398d86f39 Update org.springframework to 5.3.25
Closes gh-12737
2023-02-17 15:53:40 -07:00
Josh Cummings c9112dfaab Update hibernate-entitymanager to 5.6.15.Final
Closes gh-12736
2023-02-17 15:53:32 -07:00
Josh Cummings 4156766fe0 Update org.aspectj to 1.9.19
Closes gh-12735
2023-02-17 15:53:24 -07:00
Josh Cummings f125785328 Update io.spring.nohttp to 0.0.11
Closes gh-12734
2023-02-17 15:53:16 -07:00
Josh Cummings 4643c0d999 Update blockhound to 1.0.7.RELEASE
Closes gh-12733
2023-02-17 15:53:08 -07:00
Josh Cummings d2d82b5e94 Update io.projectreactor to 2020.0.28
Closes gh-12732
2023-02-17 15:52:54 -07:00
Josh Cummings d4d03ea146 Update jackson-bom to 2.13.5
Closes gh-12731
2023-02-17 15:52:32 -07:00
Josh Cummings 2db4430dcd Preserve OpenSamlAssertingPartyDetails Instance
Closes gh-12667
2023-02-17 10:02:17 -07:00
Leonid Rozenblyum 000b4bc495 Fix NPE in HttpSecurity#addFilterBefore, HttpSecurity#addFilterAfter
Before the fix, these methods would throw a NPE in case when the filter class passed as the second parameter, is not registered yet.

In particular, this exception can occur when mixing standard and custom DSL to register filters.

The fix doesn't change the situation that standard DSL for registration of filters cannot refer to filters that are registered via custom DSL even though those calls were done earlier.

It just provides more user-friendly error handling for this and most likely other scenarios of calls of HttpSecurity#addFilterBefore, HttpSecurity#addFilterAfter.

The error handling is implemented similarly to HttpSecurity#addFilter.

Closes gh-12637
2023-02-16 14:54:44 -07:00
jongwooo aad3e61f4d chore: Use cache in continuous-integration-workflow.yml
Signed-off-by: jongwooo <jongwooo.han@gmail.com>
2023-02-01 14:26:25 -07:00
Dan Allen 51d5fb9c03 upgrade the Gradle Antora Plugin to 1.0.0 2023-01-30 16:04:40 -06:00
Rob Winch 7db357d36a Use io.spring.antora.generate-antora-yml 2023-01-17 17:05:55 -06:00
Dan Allen f5bc6ce665 fix unclosed block in docs 2023-01-17 15:02:30 -06:00
Dan Allen 4cbb057b97 enable fetch option in Antora unless Gradle is running in offline mode 2023-01-17 14:42:54 -03:00
Marcus Da Coregio c0f7cecc6d Merge branch '5.6.x' into 5.7.x 2023-01-17 09:46:41 -03:00
Dan Allen 22ffa833ca upgrade Antora plugin and configure playbook provider to support local build 2023-01-17 09:46:24 -03:00
Josh Cummings f41b77a4db Fix Diagram to Say SecurityContextHolderFilter
Closes gh-11800
2023-01-11 12:47:07 -07:00
Josh Cummings 2028507bf8 Fix Typo in Sample
Closes gh-11095
2023-01-10 10:38:28 -07:00
Marcus Da Coregio ffdb397830 Save the SecurityContext when switching user
Closes gh-12504
2023-01-10 09:27:56 -03:00
Jon Kjennbakken 225dc593a8 Polish NimbusJwtDecoderTests
- Add missing mock

Closes gh-12238
2023-01-06 12:53:36 -07:00
Josh Cummings 3cfaf0d11d Avoid LinkedMultiValueMap in Serializable Object
Closes gh-11785
2022-12-23 15:54:00 -07:00
Eleftheria Stein-Kousathana fbfa13bd47 Fix OAuth 2.0 testing docs 2022-12-19 16:52:25 -03:00
Marcus Da Coregio 0c6b427132 Merge branch '5.6.x' into 5.7.x 2022-12-19 15:03:50 -03:00
github-actions[bot] 0f7d17bae6 Next development version 2022-12-19 15:59:53 +00:00
github-actions[bot] c11248b589 Next development version 2022-12-19 15:57:16 +00:00
github-actions[bot] 972075677f Release 5.6.10 2022-12-19 15:23:12 +00:00
github-actions[bot] 11614edcfe Release 5.7.6 2022-12-19 15:22:32 +00:00
Steve Riesenberg 2461d00f4d Merge branch '5.6.x' into 5.7.x
Closes gh-12407
2022-12-15 15:52:02 -06:00
Dayan Kodippily eec6729125 Fix DefaultLdapAuthoritiesPopulator NPE
Closes gh-12090
2022-12-15 15:51:07 -06:00
Marcus Da Coregio fc9c2513d8 Merge branch '5.6.x' into 5.7.x
Closes gh-dry-run
2022-12-15 15:23:13 -03:00
Marcus Da Coregio 1f3c4707f3 Update org.springframework.data to 2021.2.6
Closes gh-12399
2022-12-15 15:11:54 -03:00
Marcus Da Coregio 579fc565ae Update org.springframework to 5.3.24
Closes gh-12398
2022-12-15 15:11:49 -03:00
Marcus Da Coregio 5cf23585c0 Update hibernate-entitymanager to 5.6.14.Final
Closes gh-12397
2022-12-15 15:11:41 -03:00
Marcus Da Coregio 50d793eab2 Update org.eclipse.jetty to 9.4.50.v20221201
Closes gh-12396
2022-12-15 15:11:36 -03:00
Marcus Da Coregio c715df687a Update httpclient to 4.5.14
Closes gh-12395
2022-12-15 15:11:30 -03:00
Marcus Da Coregio 4ddecdbe36 Update io.projectreactor to 2020.0.26
Closes gh-12393
2022-12-15 15:11:22 -03:00
Marcus Da Coregio 94c8830f76 Update jackson-bom to 2.13.4.20221013
Closes gh-12391
2022-12-15 15:11:12 -03:00
Marcus Da Coregio 885f1d3165 Update org.springframework.data to 2021.1.10
Closes gh-12390
2022-12-15 14:59:27 -03:00
Marcus Da Coregio e414d2bd5c Update org.springframework to 5.3.24
Closes gh-12389
2022-12-15 14:59:27 -03:00
Marcus Da Coregio dbcc3130ee Update hibernate-entitymanager to 5.6.14.Final
Closes gh-12388
2022-12-15 14:59:27 -03:00
Marcus Da Coregio ac9c398fea Update org.eclipse.jetty to 9.4.50.v20221201
Closes gh-12387
2022-12-15 14:59:26 -03:00
Marcus Da Coregio 7c428fb75e Update httpclient to 4.5.14
Closes gh-12386
2022-12-15 14:59:26 -03:00
Marcus Da Coregio aa1934ee95 Update io.projectreactor to 2020.0.26
Closes gh-12384
2022-12-15 14:59:26 -03:00
Marcus Da Coregio 58e8fe837f Update jackson-bom to 2.13.4.20221013
Closes gh-12381
2022-12-15 14:58:07 -03:00
Marcus Da Coregio fc25b87967 Merge branch '5.6.x' into 5.7.x 2022-12-05 14:40:38 -08:00
Mitja Kotnik f39f215140 Replace javadoc with SecurityFilterChain bean definition 2022-12-05 14:40:05 -08:00
Sellami 626e53d121 Fix: Replace tenantRepository with tenants 2022-12-05 14:31:24 -08:00
Guillaume Husta a5464ed819 Fix typo in DefaultLoginPageConfigurer Javadoc
'isLogoutRequest' seems to have nothing to do here.
2022-12-05 14:31:15 -08:00
Josh Cummings 667cab6cda Merge branch '5.6.x' into 5.7.x
Closes gh-12323
2022-11-30 14:38:16 -07:00
이경욱 52c7141aac Save Request Before Response Is Committed
Specifically important for cookie-based authorization request
repositories.

Closes gh-11602
2022-11-30 14:33:08 -07:00
Josh Cummings e23c1cf7a7 Merge branch '5.6.x' into 5.7.x 2022-11-30 14:18:12 -07:00
Josh Cummings 14a48ea939 Fix formatting
Issue gh-12143
2022-11-29 20:15:13 -07:00
Junsung Cho 709de43e89 Fix typo in JavaDoc
Closes gh-12143
2022-11-29 20:15:12 -07:00
Marcus Da Coregio c2e86ad067 Merge branch '5.6.x' into 5.7.x
Closes gh-12297
2022-11-25 09:22:10 -03:00
Kirk Lin 9c6d90490d ci: fix GitHub Actions Deprecating save-state and set-output commands
Closes gh-12032
2022-11-25 09:05:28 -03:00
Marcus Da Coregio 9b3f834bff Merge branch '5.6.x' into 5.7.x
Closes gh-12285
2022-11-24 08:47:46 -03:00
Marcus Da Coregio 70bfc39418 Fix AuthorizationFilter diagram in docs
Closes gh-12274
2022-11-24 08:46:16 -03:00
Steve Riesenberg 621889fa18 Merge branch '5.6.x' into 5.7.x 2022-11-23 17:12:30 -06:00
Dan Allen fe252f5057 sync docs build; upgrade Antora and Antora Collector to latest alpha 2022-11-21 17:26:51 -07:00
Marcus Da Coregio f561d3784e Improve deprecation notice in WebSecurityConfigurerAdapter
Closes gh-12260
2022-11-21 10:05:08 -03:00
Marcus Da Coregio 2d19d972f4 Merge branch '5.6.x' into 5.7.x 2022-11-16 14:51:07 -03:00
Marcus Da Coregio d5aabd721a Specify UTF8 for response content
This is needed because in some other platforms, like Windows, the default charset might be different

Issue gh-12026
2022-11-16 14:50:31 -03:00
Marcus Da Coregio 8441e755d3 Merge branch '5.6.x' into 5.7.x
Closes gh-12221
2022-11-16 13:44:16 -03:00
Marcus Da Coregio 53148dc7b5 Use UTF-8 in Saml2MetadataFilter response writer
Closes gh-12026
2022-11-16 13:43:46 -03:00
Josh Cummings 79483b2bc9 Merge branch '5.6.x' into 5.7.x
Closes gh-12208
2022-11-14 18:16:22 -07:00
Sabina Palakova ed3af6482d Fix SAML logout log messages
Fixes SAML logout log messages incorrectly referring to logout
response instead of logout request and vice versa.

Closes gh-12129
2022-11-14 18:11:49 -07:00
Steve Riesenberg 67a1f0836b Merge branch '5.6.x' into 5.7.x
Closes gh-12205
2022-11-14 12:10:55 -06:00
Steve Riesenberg fde26e003a Request user info when AS returns no scopes
Closes gh-12144
2022-11-10 16:29:43 -06:00
Marcus Da Coregio 40548eb963 Merge branch '5.6.x' into 5.7.x
Closes gh-12157
2022-11-08 13:27:51 -03:00
Marcus Da Coregio 8cde8fb363 Update Gradle to 7.5.1
Closes gh-11779
2022-11-08 13:27:25 -03:00
Marcus Da Coregio ab5299b6a3 Merge branch '5.6.x' into 5.7.x
Closes gh-12153
2022-11-08 09:47:59 -03:00
Marcus Da Coregio 1c9ea52e44 Change gradle.plugin.org.gretty:gretty:3.0.1 to org.gretty:gretty:3.0.9
Closes gh-12152
2022-11-08 09:47:02 -03:00
Marcus Da Coregio 067fc1678c Merge branch '5.6.x' into 5.7.x 2022-11-03 08:22:09 -03:00
Rivaldi 01a37dd678 Fix typo
(cherry picked from commit 20e89e3eca0823bfa329b5de80448bac1f5e0f30)
2022-11-03 08:21:48 -03:00
Márk Kővári aad01447c3 docs: fix realm typo 2022-11-03 08:21:26 -03:00
Josh Cummings 6efac34ca7 Merge branch '5.6.x' into 5.7.x
Closes gh-12125
2022-11-01 18:06:01 -06:00
Koos Gadellaa 5c4362bbc4 Refresh parsers when not found
Closes gh-3065
2022-11-01 18:05:15 -06:00
Josh Cummings c94e33b6c8 Merge branch '5.6.x' into 5.7.x 2022-11-01 13:42:35 -06:00
Ger Roza 8315545144 Update RP-Initiated Logout target URLs.
The URLs we're using are not actually pointing to the OIDC RP-Initiated Logout Specs.

Fixes: gh-12081
2022-11-01 12:35:39 -06:00
Marcus Da Coregio 752e943492 Merge branch '5.6.x' into 5.7.x 2022-11-01 08:17:03 -03:00
Dan Allen e9db852d6e update generateAntora task to keep prerelease segment other than -SNAPSHOT in docs version 2022-11-01 08:13:43 -03:00
github-actions[bot] 92c1281a56 Next development version 2022-10-31 16:35:14 +00:00
github-actions[bot] ef41b8c2f2 Next development version 2022-10-31 16:11:44 +00:00
github-actions[bot] 60f44b347f Release 5.6.9 2022-10-31 15:33:06 +00:00
252 changed files with 9664 additions and 3280 deletions
-5
View File
@@ -1,5 +0,0 @@
REPOSITORY_REF="$1"
TOKEN="$2"
curl -H "Accept: application/vnd.github.everest-preview+json" -H "Authorization: token ${TOKEN}" --request POST --data '{"event_type": "request-build-reference"}' https://api.github.com/repos/${REPOSITORY_REF}/dispatches
echo "Requested Build for $REPOSITORY_REF"
-41
View File
@@ -1,41 +0,0 @@
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 }}
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
if: ${{ github.repository == 'spring-projects/spring-security' }}
steps:
- name: Checkout Source
uses: actions/checkout@v2
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
with:
java-version: '11'
distribution: 'adopt'
- name: Generate antora.yml
run: ./gradlew :spring-security-docs:generateAntora
- name: Extract Branch Name
id: extract_branch_name
run: echo "##[set-output name=generated_branch_name;]$(echo ${GITHUB_REPOSITORY}/${GITHUB_REF##*/})"
- name: Push generated antora files to the spring-generated-docs
uses: JamesIves/github-pages-deploy-action@4.1.4
with:
branch: ${{ steps.extract_branch_name.outputs.generated_branch_name }} # The branch the action should deploy to.
folder: "docs/build/generateAntora" # The folder the action should deploy.
repository-name: "spring-io/spring-generated-docs"
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
- name: Dispatch Build Request
run: ${GITHUB_WORKSPACE}/.github/actions/dispatch.sh 'spring-projects/spring-security' "$GH_ACTIONS_REPO_TOKEN"
@@ -9,7 +9,6 @@ on:
workflow_dispatch: # Manual trigger
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
GRADLE_ENTERPRISE_CACHE_USER: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }}
GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }}
GRADLE_ENTERPRISE_SECRET_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
@@ -31,15 +30,15 @@ jobs:
runjobs: ${{ steps.continue.outputs.runjobs }}
project_version: ${{ steps.continue.outputs.project_version }}
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- id: continue
name: Determine if should continue
run: |
# Run jobs if in upstream repository
echo "::set-output name=runjobs::true"
echo "runjobs=true" >>$GITHUB_OUTPUT
# Extract version from gradle.properties
version=$(cat gradle.properties | grep "version=" | awk -F'=' '{print $2}')
echo "::set-output name=project_version::$version"
echo "project_version=$version" >>$GITHUB_OUTPUT
build_jdk_11:
name: Build JDK 11
needs: [prerequisites]
@@ -49,12 +48,13 @@ jobs:
runs-on: ${{ matrix.os }}
if: needs.prerequisites.outputs.runjobs
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Set up JDK 11
uses: actions/setup-java@v2
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'adopt'
cache: 'gradle'
- name: Set up Gradle
uses: gradle/gradle-build-action@v2
- name: Set up gradle user name
@@ -71,7 +71,7 @@ jobs:
runs-on: ubuntu-latest
if: needs.prerequisites.outputs.runjobs
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
with:
@@ -89,7 +89,7 @@ jobs:
runs-on: ubuntu-latest
if: needs.prerequisites.outputs.runjobs
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
with:
@@ -113,7 +113,7 @@ jobs:
runs-on: ubuntu-latest
if: needs.prerequisites.outputs.runjobs
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
with:
@@ -130,7 +130,7 @@ jobs:
needs: [build_jdk_11, snapshot_tests, check_samples, check_tangles]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
with:
@@ -154,7 +154,7 @@ jobs:
needs: [build_jdk_11, snapshot_tests, check_samples, check_tangles]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
with:
@@ -175,7 +175,7 @@ jobs:
needs: [build_jdk_11, snapshot_tests, check_samples, check_tangles]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
with:
@@ -205,7 +205,7 @@ jobs:
TOKEN: ${{ github.token }}
VERSION: ${{ needs.prerequisites.outputs.project_version }}
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
- name: Set up gradle
@@ -240,26 +240,11 @@ jobs:
export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY"
echo "Tagging and publishing $REPO@$VERSION release on GitHub."
./gradlew createGitHubRelease -PnextVersion=$VERSION -Pbranch=$BRANCH -PcreateRelease=true -PgitHubAccessToken=$TOKEN
- name: Announce Release on Slack
id: spring-security-announcing
uses: slackapi/slack-github-action@v1.19.0
with:
payload: |
{
"text": "spring-security-announcing `${{ env.VERSION }}` is available now",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "spring-security-announcing `${{ env.VERSION }}` is available now"
}
}
]
}
- name: Announce Release on GChat
env:
SLACK_WEBHOOK_URL: ${{ secrets.SPRING_RELEASE_SLACK_WEBHOOK_URL }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
WEBHOOK_URL: ${{ secrets.SPRING_RELEASE_GCHAT_WEBHOOK_URL }}
run: |
curl -X POST '${{ env.WEBHOOK_URL }}' -H 'Content-Type: application/json' -d '{ "text": "spring-security-announcing `${{ env.VERSION }}` is available now" }' || true
- name: Setup git config
run: |
git config user.name 'github-actions[bot]'
@@ -286,7 +271,7 @@ jobs:
TOKEN: ${{ github.token }}
VERSION: ${{ needs.prerequisites.outputs.project_version }}
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
with:
@@ -294,20 +279,13 @@ jobs:
distribution: 'adopt'
- name: Schedule next release (if not already scheduled)
run: ./gradlew scheduleNextRelease -PnextVersion=$VERSION -PgitHubAccessToken=$TOKEN
notify_result:
name: Check for failures
needs: [perform_release, perform_post_release]
if: failure()
send-notification:
name: Send Notification
needs: [ perform_release, perform_post_release ]
if: ${{ failure() || cancelled() }}
runs-on: ubuntu-latest
permissions:
actions: read
steps:
- name: Send Slack message
# Workaround while waiting for Gamesight/slack-workflow-status#38 to be fixed
# See https://github.com/Gamesight/slack-workflow-status/issues/38
uses: sjohnr/slack-workflow-status@v1-beta
- name: Send Notification
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@v1
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
slack_webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }}
channel: '#spring-security-ci'
name: 'CI Notifier'
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
+31
View File
@@ -0,0 +1,31 @@
name: Deploy Docs
on:
push:
branches-ignore: [ gh-pages ]
tags: '**'
repository_dispatch:
types: request-build-reference # legacy
schedule:
- cron: '0 10 * * *' # Once per day at 10am UTC
workflow_dispatch:
permissions: read-all
jobs:
build:
runs-on: ubuntu-latest
if: github.repository_owner == 'spring-projects'
steps:
- name: Checkout
uses: actions/checkout@v3
with:
ref: docs-build
fetch-depth: 1
- name: Dispatch (partial build)
if: github.ref_type == 'branch'
env:
GH_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
run: gh workflow run deploy-docs.yml -r $(git rev-parse --abbrev-ref HEAD) -f build-refname=${{ github.ref_name }}
- name: Dispatch (full build)
if: github.ref_type == 'tag'
env:
GH_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
run: gh workflow run deploy-docs.yml -r $(git rev-parse --abbrev-ref HEAD)
+1 -1
View File
@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
if: ${{ github.repository == 'spring-projects/spring-security' }}
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
with:
@@ -23,7 +23,7 @@ jobs:
steps:
- id: checkout-source
name: Checkout Source Code
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
- name: Set up gradle
@@ -38,7 +38,7 @@ jobs:
export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD"
export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY"
./gradlew gitHubCheckNextVersionDueToday
echo "::set-output name=is_due_today::$(cat build/github/milestones/is-due-today)"
echo "is_due_today=$(cat build/github/milestones/is-due-today)" >>$GITHUB_OUTPUT
- id: check-open-issues
name: Check for open issues
if: steps.check-release-due.outputs.is_due_today == 'true'
@@ -47,7 +47,7 @@ jobs:
export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD"
export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY"
./gradlew gitHubCheckMilestoneHasNoOpenIssues
echo "::set-output name=is_open_issues::$(cat build/github/milestones/is-open-issues)"
echo "is_open_issues=$(cat build/github/milestones/is-open-issues)" >>$GITHUB_OUTPUT
- id: validate-release-state
name: Validate State of Release
if: steps.check-release-due.outputs.is_due_today == 'true' && steps.check-open-issues.outputs.is_open_issues == 'true'
+2
View File
@@ -28,3 +28,5 @@ s101plugin.state
!.idea/checkstyle-idea.xml
!.idea/externalDependencies.xml
node_modules
+1 -1
View File
@@ -57,7 +57,7 @@ See also the https://github.com/spring-projects/spring-framework/wiki/Gradle-bui
== Getting Support
Check out the https://stackoverflow.com/questions/tagged/spring-security[Spring Security tags on Stack Overflow].
https://spring.io/services[Commercial support] is available too.
https://spring.io/support[Commercial support] is available too.
== Contributing
https://help.github.com/articles/creating-a-pull-request[Pull requests] are welcome; see the https://github.com/spring-projects/spring-security/blob/main/CONTRIBUTING.adoc[contributor guidelines] for details.
+1 -2
View File
@@ -3,7 +3,7 @@ import io.spring.gradle.IncludeRepoTask
buildscript {
dependencies {
classpath "io.spring.javaformat:spring-javaformat-gradle-plugin:$springJavaformatVersion"
classpath 'io.spring.nohttp:nohttp-gradle:0.0.10'
classpath 'io.spring.nohttp:nohttp-gradle:0.0.11'
classpath "io.freefair.gradle:aspectj-plugin:6.4.3.1"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
classpath "com.netflix.nebula:nebula-project-plugin:8.2.0"
@@ -196,7 +196,6 @@ if (hasProperty('buildScan')) {
}
nohttp {
allowlistFile = project.file("etc/nohttp/allowlist.lines")
source.exclude "buildSrc/build/**"
}
+3 -4
View File
@@ -10,7 +10,6 @@ sourceCompatibility = 1.8
repositories {
gradlePluginPortal()
mavenCentral()
maven { url 'https://repo.spring.io/plugins-release/' }
}
sourceSets {
@@ -87,13 +86,13 @@ dependencies {
implementation localGroovy()
implementation 'io.github.gradle-nexus:publish-plugin:1.1.0'
implementation 'io.projectreactor:reactor-core:3.5.0-M1'
implementation 'gradle.plugin.org.gretty:gretty:3.0.1'
implementation 'io.projectreactor:reactor-core:3.5.0'
implementation 'org.gretty:gretty:3.0.9'
implementation 'com.apollographql.apollo:apollo-runtime:2.4.5'
implementation 'com.github.ben-manes:gradle-versions-plugin:0.38.0'
implementation 'com.github.spullara.mustache.java:compiler:0.9.4'
implementation 'io.spring.javaformat:spring-javaformat-gradle-plugin:0.0.15'
implementation 'io.spring.nohttp:nohttp-gradle:0.0.10'
implementation 'io.spring.nohttp:nohttp-gradle:0.0.11'
implementation 'net.sourceforge.htmlunit:htmlunit:2.37.0'
implementation 'org.hidetake:gradle-ssh-plugin:2.10.1'
implementation 'org.jfrog.buildinfo:build-info-extractor-gradle:4.29.0'
Binary file not shown.
+1 -1
View File
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored Executable
+240
View File
@@ -0,0 +1,240 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
+91
View File
@@ -0,0 +1,91 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
@@ -36,7 +36,7 @@ class CheckstylePlugin implements Plugin<Project> {
if (checkstyleDir.exists() && checkstyleDir.directory) {
project.getPluginManager().apply('checkstyle')
project.dependencies.add('checkstyle', 'io.spring.javaformat:spring-javaformat-checkstyle:0.0.15')
project.dependencies.add('checkstyle', 'io.spring.nohttp:nohttp-checkstyle:0.0.10')
project.dependencies.add('checkstyle', 'io.spring.nohttp:nohttp-checkstyle:0.0.11')
project.checkstyle {
configDirectory = checkstyleDir
@@ -62,6 +62,33 @@ public class SchemaDeployPlugin implements Plugin<Project> {
execute "rm -f $tempPath*.zip"
execute "rm -rf $extractPath*"
execute "mv $tempPath/* $extractPath"
/*
* Copy spring-security-oauth schemas so that legacy projects using the "http" scheme can
* resolve the following schema locations:
*
* - http://www.springframework.org/schema/security/spring-security-oauth-1.0.xsd
* - http://www.springframework.org/schema/security/spring-security-oauth2-1.0.xsd
* - http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd
*
* Note: The directory:
* https://www.springframework.org/schema/security/
*
* is a symbolic link pointing to:
* https://docs.spring.io/autorepo/schema/spring-security/current/security/
*
* which in turn points to the latest:
* https://docs.spring.io/autorepo/schema/spring-security/$version/security/
*
* with the help of the autoln command which is run regularly via a cron job.
*
* See https://github.com/spring-io/autoln for more info.
*/
if (name == "spring-security") {
def springSecurityOauthPath = "/var/www/domains/spring.io/docs/htdocs/autorepo/schema/spring-security-oauth/current/security"
execute "cp $springSecurityOauthPath/* ${extractPath}security"
}
execute "chmod -R g+w $extractPath"
}
}
@@ -1,9 +1,10 @@
package io.spring.gradle.convention
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.tasks.bundling.Zip
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.tasks.bundling.Zip
import org.springframework.gradle.xsd.CreateVersionlessXsdTask
public class SchemaZipPlugin implements Plugin<Project> {
@@ -15,7 +16,10 @@ public class SchemaZipPlugin implements Plugin<Project> {
schemaZip.archiveClassifier = 'schema'
schemaZip.description = "Builds -${schemaZip.archiveClassifier} archive containing all " +
"XSDs for deployment at static.springframework.org/schema."
def versionlessXsd = project.tasks.create("versionlessXsd", CreateVersionlessXsdTask) {
description = "Generates spring-security.xsd"
versionlessXsdFile = project.layout.buildDirectory.file("versionlessXsd/spring-security.xsd")
}
project.rootProject.subprojects.each { module ->
module.getPlugins().withType(JavaPlugin.class).all {
@@ -36,8 +40,14 @@ public class SchemaZipPlugin implements Plugin<Project> {
duplicatesStrategy 'exclude'
from xsdFile.path
}
versionlessXsd.getInputFiles().from(xsdFile.path)
}
}
}
schemaZip.into("security") {
from(versionlessXsd.getOutputs())
}
}
}
@@ -0,0 +1,82 @@
/*
* Copyright 2020-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.gradle.github.user;
import java.io.IOException;
import com.google.gson.Gson;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
/**
* @author Steve Riesenberg
*/
public class GitHubUserApi {
private String baseUrl = "https://api.github.com";
private final OkHttpClient httpClient;
private Gson gson = new Gson();
public GitHubUserApi(String gitHubAccessToken) {
this.httpClient = new OkHttpClient.Builder()
.addInterceptor(new AuthorizationInterceptor(gitHubAccessToken))
.build();
}
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
/**
* Retrieve a GitHub user by the personal access token.
*
* @return The GitHub user
*/
public User getUser() {
String url = this.baseUrl + "/user";
Request request = new Request.Builder().url(url).get().build();
try (Response response = this.httpClient.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new RuntimeException(
String.format("Unable to retrieve GitHub user." +
" Please check the personal access token and try again." +
" Got response %s", response));
}
return this.gson.fromJson(response.body().charStream(), User.class);
} catch (IOException ex) {
throw new RuntimeException("Unable to retrieve GitHub user.", 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,53 @@
package org.springframework.gradle.github.user;
/**
* @author Steve Riesenberg
*/
public class User {
private Long id;
private String login;
private String name;
private String url;
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
public String getLogin() {
return this.login;
}
public void setLogin(String login) {
this.login = login;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getUrl() {
return this.url;
}
public void setUrl(String url) {
this.url = url;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", login='" + login + '\'' +
", name='" + name + '\'' +
", url='" + url + '\'' +
'}';
}
}
@@ -26,14 +26,14 @@ import java.util.Base64;
* Implements necessary calls to the Sagan API See https://spring.io/restdocs/index.html
*/
public class SaganApi {
private String baseUrl = "https://spring.io/api";
private String baseUrl = "https://api.spring.io";
private OkHttpClient client;
private Gson gson = new Gson();
public SaganApi(String gitHubToken) {
public SaganApi(String username, String gitHubToken) {
this.client = new OkHttpClient.Builder()
.addInterceptor(new BasicInterceptor("not-used", gitHubToken))
.addInterceptor(new BasicInterceptor(username, gitHubToken))
.build();
}
@@ -16,12 +16,21 @@
package org.springframework.gradle.sagan;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.core.runtime.Assert;
import org.gradle.api.DefaultTask;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.TaskAction;
import org.springframework.gradle.github.user.GitHubUserApi;
import org.springframework.gradle.github.user.User;
public class SaganCreateReleaseTask extends DefaultTask {
private static final Pattern VERSION_PATTERN = Pattern.compile("^([0-9]+)\\.([0-9]+)\\.([0-9]+)(-.+)?$");
@Input
private String gitHubAccessToken;
@Input
@@ -35,11 +44,25 @@ public class SaganCreateReleaseTask extends DefaultTask {
@TaskAction
public void saganCreateRelease() {
SaganApi sagan = new SaganApi(this.gitHubAccessToken);
GitHubUserApi github = new GitHubUserApi(this.gitHubAccessToken);
User user = github.getUser();
// Antora reference docs URLs for snapshots do not contain -SNAPSHOT
String referenceDocUrl = this.referenceDocUrl;
if (this.version.endsWith("-SNAPSHOT")) {
Matcher versionMatcher = VERSION_PATTERN.matcher(this.version);
Assert.isTrue(versionMatcher.matches(), "Version " + this.version + " does not match expected pattern");
String majorVersion = versionMatcher.group(1);
String minorVersion = versionMatcher.group(2);
String majorMinorVersion = String.format("%s.%s-SNAPSHOT", majorVersion, minorVersion);
referenceDocUrl = this.referenceDocUrl.replace("{version}", majorMinorVersion);
}
SaganApi sagan = new SaganApi(user.getLogin(), this.gitHubAccessToken);
Release release = new Release();
release.setVersion(this.version);
release.setApiDocUrl(this.apiDocUrl);
release.setReferenceDocUrl(this.referenceDocUrl);
release.setReferenceDocUrl(referenceDocUrl);
sagan.createReleaseForProject(release, this.projectName);
}
@@ -20,6 +20,9 @@ import org.gradle.api.DefaultTask;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.TaskAction;
import org.springframework.gradle.github.user.GitHubUserApi;
import org.springframework.gradle.github.user.User;
public class SaganDeleteReleaseTask extends DefaultTask {
@Input
@@ -31,7 +34,10 @@ public class SaganDeleteReleaseTask extends DefaultTask {
@TaskAction
public void saganCreateRelease() {
SaganApi sagan = new SaganApi(this.gitHubAccessToken);
GitHubUserApi github = new GitHubUserApi(this.gitHubAccessToken);
User user = github.getUser();
SaganApi sagan = new SaganApi(user.getLogin(), this.gitHubAccessToken);
sagan.deleteReleaseForProject(this.version, this.projectName);
}
@@ -0,0 +1,147 @@
/*
* Copyright 2019-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.gradle.xsd;
import org.gradle.api.DefaultTask;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.tasks.*;
import org.gradle.work.DisableCachingByDefault;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Creates the spring-security.xsd automatically
*
* @author Rob Winch
*/
@DisableCachingByDefault(because = "not worth it")
public abstract class CreateVersionlessXsdTask extends DefaultTask {
@InputFiles
public abstract ConfigurableFileCollection getInputFiles();
@OutputFile
abstract RegularFileProperty getVersionlessXsdFile();
@TaskAction
void createVersionlessXsd() throws IOException {
XsdFileMajorMinorVersion largest = null;
ConfigurableFileCollection inputFiles = getInputFiles();
if (inputFiles.isEmpty()) {
throw new IllegalStateException("No Inputs configured");
}
for (File file : inputFiles) {
XsdFileMajorMinorVersion current = XsdFileMajorMinorVersion.create(file);
if (current == null) {
continue;
}
if (largest == null) {
largest = current;
}
else if (current.getVersion().isGreaterThan(largest.getVersion())) {
largest = current;
}
}
if (largest == null) {
throw new IllegalStateException("Could not create versionless xsd file because no files matching spring-security-<digit>.xsd were found in " + inputFiles.getFiles());
}
Path to = getVersionlessXsdFile().getAsFile().get().toPath();
Path from = largest.getFile().toPath();
Files.copy(from, to, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES);
}
static class XsdFileMajorMinorVersion {
private final File file;
private final MajorMinorVersion version;
private XsdFileMajorMinorVersion(File file, MajorMinorVersion version) {
this.file = file;
this.version = version;
}
private static final Pattern FILE_MAJOR_MINOR_VERSION_PATTERN = Pattern.compile("^spring-security-(\\d+)\\.(\\d+)\\.xsd$");
/**
* If matches xsd with major minor version (e.g. spring-security-5.1.xsd returns it, otherwise null
* @param file
* @return
*/
static XsdFileMajorMinorVersion create(File file) {
String fileName = file.getName();
Matcher matcher = FILE_MAJOR_MINOR_VERSION_PATTERN.matcher(fileName);
if (!matcher.find()) {
return null;
}
int major = Integer.parseInt(matcher.group(1));
int minor = Integer.parseInt(matcher.group(2));
MajorMinorVersion version = new MajorMinorVersion(major, minor);
return new XsdFileMajorMinorVersion(file, version);
}
public File getFile() {
return file;
}
public MajorMinorVersion getVersion() {
return version;
}
}
static class MajorMinorVersion {
private final int major;
private final int minor;
MajorMinorVersion(int major, int minor) {
this.major = major;
this.minor = minor;
}
public int getMajor() {
return major;
}
public int getMinor() {
return minor;
}
public boolean isGreaterThan(MajorMinorVersion version) {
if (getMajor() > version.getMajor()) {
return true;
}
if (getMajor() < version.getMajor()) {
return false;
}
if (getMinor() > version.getMinor()) {
return true;
}
if (getMinor() < version.getMinor()) {
return false;
}
// they are equal
return false;
}
}
}
@@ -42,7 +42,7 @@ public class SaganApiTests {
public void setup() throws Exception {
this.server = new MockWebServer();
this.server.start();
this.sagan = new SaganApi("mock-oauth-token");
this.sagan = new SaganApi("user", "mock-oauth-token");
this.baseUrl = this.server.url("/api").toString();
this.sagan.setBaseUrl(this.baseUrl);
}
@@ -63,7 +63,7 @@ public class SaganApiTests {
RecordedRequest request = this.server.takeRequest(1, TimeUnit.SECONDS);
assertThat(request.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/projects/spring-security/releases");
assertThat(request.getMethod()).isEqualToIgnoringCase("post");
assertThat(request.getHeaders().get("Authorization")).isEqualTo("Basic bm90LXVzZWQ6bW9jay1vYXV0aC10b2tlbg==");
assertThat(request.getHeaders().get("Authorization")).isEqualTo("Basic dXNlcjptb2NrLW9hdXRoLXRva2Vu");
assertThat(request.getBody().readString(Charset.defaultCharset())).isEqualToIgnoringWhitespace("{\n" +
" \"version\":\"5.6.0-SNAPSHOT\",\n" +
" \"current\":false,\n" +
@@ -79,7 +79,7 @@ public class SaganApiTests {
RecordedRequest request = this.server.takeRequest(1, TimeUnit.SECONDS);
assertThat(request.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/projects/spring-security/releases/5.6.0-SNAPSHOT");
assertThat(request.getMethod()).isEqualToIgnoringCase("delete");
assertThat(request.getHeaders().get("Authorization")).isEqualTo("Basic bm90LXVzZWQ6bW9jay1vYXV0aC10b2tlbg==");
assertThat(request.getHeaders().get("Authorization")).isEqualTo("Basic dXNlcjptb2NrLW9hdXRoLXRva2Vu");
assertThat(request.getBody().readString(Charset.defaultCharset())).isEmpty();
}
}
@@ -0,0 +1,106 @@
/*
* Copyright 2020-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.gradle.github.user;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* @author Steve Riesenberg
*/
public class GitHubUserApiTests {
private GitHubUserApi gitHubUserApi;
private MockWebServer server;
private String baseUrl;
@BeforeEach
public void setup() throws Exception {
this.server = new MockWebServer();
this.server.start();
this.baseUrl = this.server.url("/api").toString();
this.gitHubUserApi = new GitHubUserApi("mock-oauth-token");
this.gitHubUserApi.setBaseUrl(this.baseUrl);
}
@AfterEach
public void cleanup() throws Exception {
this.server.shutdown();
}
@Test
public void getUserWhenValidParametersThenSuccess() {
// @formatter:off
String responseJson = "{\n" +
" \"avatar_url\": \"https://avatars.githubusercontent.com/u/583231?v=4\",\n" +
" \"bio\": null,\n" +
" \"blog\": \"https://github.blog\",\n" +
" \"company\": \"@github\",\n" +
" \"created_at\": \"2011-01-25T18:44:36Z\",\n" +
" \"email\": null,\n" +
" \"events_url\": \"https://api.github.com/users/octocat/events{/privacy}\",\n" +
" \"followers\": 8481,\n" +
" \"followers_url\": \"https://api.github.com/users/octocat/followers\",\n" +
" \"following\": 9,\n" +
" \"following_url\": \"https://api.github.com/users/octocat/following{/other_user}\",\n" +
" \"gists_url\": \"https://api.github.com/users/octocat/gists{/gist_id}\",\n" +
" \"gravatar_id\": \"\",\n" +
" \"hireable\": null,\n" +
" \"html_url\": \"https://github.com/octocat\",\n" +
" \"id\": 583231,\n" +
" \"location\": \"San Francisco\",\n" +
" \"login\": \"octocat\",\n" +
" \"name\": \"The Octocat\",\n" +
" \"node_id\": \"MDQ6VXNlcjU4MzIzMQ==\",\n" +
" \"organizations_url\": \"https://api.github.com/users/octocat/orgs\",\n" +
" \"public_gists\": 8,\n" +
" \"public_repos\": 8,\n" +
" \"received_events_url\": \"https://api.github.com/users/octocat/received_events\",\n" +
" \"repos_url\": \"https://api.github.com/users/octocat/repos\",\n" +
" \"site_admin\": false,\n" +
" \"starred_url\": \"https://api.github.com/users/octocat/starred{/owner}{/repo}\",\n" +
" \"subscriptions_url\": \"https://api.github.com/users/octocat/subscriptions\",\n" +
" \"twitter_username\": null,\n" +
" \"type\": \"User\",\n" +
" \"updated_at\": \"2023-02-25T12:14:58Z\",\n" +
" \"url\": \"https://api.github.com/users/octocat\"\n" +
"}";
// @formatter:on
this.server.enqueue(new MockResponse().setBody(responseJson));
User user = this.gitHubUserApi.getUser();
assertThat(user.getId()).isEqualTo(583231);
assertThat(user.getLogin()).isEqualTo("octocat");
assertThat(user.getName()).isEqualTo("The Octocat");
assertThat(user.getUrl()).isEqualTo("https://api.github.com/users/octocat");
}
@Test
public void getUserWhenErrorResponseThenException() {
this.server.enqueue(new MockResponse().setResponseCode(400));
// @formatter:off
assertThatExceptionOfType(RuntimeException.class)
.isThrownBy(() -> this.gitHubUserApi.getUser());
// @formatter:on
}
}
@@ -0,0 +1,94 @@
/*
* Copyright 2019-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.gradle.xsd;
import org.junit.jupiter.api.Test;
import org.springframework.gradle.xsd.CreateVersionlessXsdTask.MajorMinorVersion;
import org.springframework.gradle.xsd.CreateVersionlessXsdTask.XsdFileMajorMinorVersion;
import java.io.File;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
/**
* @author Rob Winch
*/
class CreateVersionlessXsdTaskTests {
@Test
void xsdCreateWhenValid() {
File file = new File("spring-security-2.0.xsd");
XsdFileMajorMinorVersion xsdFile = XsdFileMajorMinorVersion.create(file);
assertThat(xsdFile).isNotNull();
assertThat(xsdFile.getFile()).isEqualTo(file);
assertThat(xsdFile.getVersion().getMajor()).isEqualTo(2);
assertThat(xsdFile.getVersion().getMinor()).isEqualTo(0);
}
@Test
void xsdCreateWhenPatchReleaseThenNull() {
File file = new File("spring-security-2.0.1.xsd");
XsdFileMajorMinorVersion xsdFile = XsdFileMajorMinorVersion.create(file);
assertThat(xsdFile).isNull();
}
@Test
void xsdCreateWhenNotXsdFileThenNull() {
File file = new File("spring-security-2.0.txt");
XsdFileMajorMinorVersion xsdFile = XsdFileMajorMinorVersion.create(file);
assertThat(xsdFile).isNull();
}
@Test
void xsdCreateWhenNotStartWithSpringSecurityThenNull() {
File file = new File("spring-securityNO-2.0.xsd");
XsdFileMajorMinorVersion xsdFile = XsdFileMajorMinorVersion.create(file);
assertThat(xsdFile).isNull();
}
@Test
void isGreaterWhenMajorLarger() {
MajorMinorVersion larger = new MajorMinorVersion(2,0);
MajorMinorVersion smaller = new MajorMinorVersion(1,0);
assertThat(larger.isGreaterThan(smaller)).isTrue();
assertThat(smaller.isGreaterThan(larger)).isFalse();
}
@Test
void isGreaterWhenMinorLarger() {
MajorMinorVersion larger = new MajorMinorVersion(1,1);
MajorMinorVersion smaller = new MajorMinorVersion(1,0);
assertThat(larger.isGreaterThan(smaller)).isTrue();
assertThat(smaller.isGreaterThan(larger)).isFalse();
}
@Test
void isGreaterWhenMajorAndMinorLarger() {
MajorMinorVersion larger = new MajorMinorVersion(2,1);
MajorMinorVersion smaller = new MajorMinorVersion(1,0);
assertThat(larger.isGreaterThan(smaller)).isTrue();
assertThat(smaller.isGreaterThan(larger)).isFalse();
}
@Test
void isGreaterWhenSame() {
MajorMinorVersion first = new MajorMinorVersion(1,0);
MajorMinorVersion second = new MajorMinorVersion(1,0);
assertThat(first.isGreaterThan(second)).isFalse();
assertThat(second.isGreaterThan(first)).isFalse();
}
}
@@ -18,6 +18,7 @@ package org.springframework.security.cas.userdetails;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import org.jasig.cas.client.validation.Assertion;
@@ -73,7 +74,8 @@ public final class GrantedAuthorityFromAssertionAttributesUserDetailsService
}
private SimpleGrantedAuthority createSimpleGrantedAuthority(Object o) {
return new SimpleGrantedAuthority(this.convertToUpperCase ? o.toString().toUpperCase() : o.toString());
return new SimpleGrantedAuthority(
this.convertToUpperCase ? o.toString().toUpperCase(Locale.ROOT) : o.toString());
}
/**
+11
View File
@@ -1,4 +1,5 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.springframework.gradle.xsd.CreateVersionlessXsdTask
apply plugin: 'io.spring.convention.spring-module'
apply plugin: 'trang'
@@ -113,6 +114,16 @@ dependencies {
testRuntimeOnly 'org.hsqldb:hsqldb'
}
def versionlessXsd = project.tasks.create("versionlessXsd", CreateVersionlessXsdTask) {
inputFiles.from(project.sourceSets.main.resources)
versionlessXsdFile = project.layout.buildDirectory.file("versionlessXsd/spring-security.xsd")
}
processResources {
from(versionlessXsd) {
into 'org/springframework/security/config/'
}
}
rncToXsd {
rncDir = file('src/main/resources/org/springframework/security/config/')
@@ -102,6 +102,7 @@ public final class SecurityNamespaceHandler implements NamespaceHandler {
if (parser == null) {
// SEC-1455. Load parsers when required, not just on init().
loadParsers();
parser = this.parsers.get(name);
}
if (parser != null) {
return parser.parse(element, pc);
@@ -221,6 +221,7 @@ public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBui
if (configs == null) {
return new ArrayList<>();
}
removeFromConfigurersAddedInInitializing(clazz);
return new ArrayList<>(configs);
}
@@ -253,11 +254,16 @@ public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBui
if (configs == null) {
return null;
}
removeFromConfigurersAddedInInitializing(clazz);
Assert.state(configs.size() == 1,
() -> "Only one configurer expected for type " + clazz + ", but got " + configs);
return (C) configs.get(0);
}
private <C extends SecurityConfigurer<O, B>> void removeFromConfigurersAddedInInitializing(Class<C> clazz) {
this.configurersAddedInInitializing.removeIf(clazz::isInstance);
}
/**
* Specifies the {@link ObjectPostProcessor} to use.
* @param objectPostProcessor the {@link ObjectPostProcessor} to use. Cannot be null
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -3254,7 +3254,12 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
}
private HttpSecurity addFilterAtOffsetOf(Filter filter, int offset, Class<? extends Filter> registeredFilter) {
int order = this.filterOrders.getOrder(registeredFilter) + offset;
Integer registeredFilterOrder = this.filterOrders.getOrder(registeredFilter);
if (registeredFilterOrder == null) {
throw new IllegalArgumentException(
"The Filter class " + registeredFilter.getName() + " does not have a registered order");
}
int order = registeredFilterOrder + offset;
this.filters.add(new OrderedFilter(filter, order));
this.filterOrders.put(filter.getClass(), order);
return this;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -91,7 +91,24 @@ import org.springframework.web.accept.HeaderContentNegotiationStrategy;
* @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}
* {@link WebSecurity}. <pre>
* &#64;Bean
* public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
* http
* .authorizeHttpRequests((authz) ->
* authz.anyRequest().authenticated()
* );
* // ...
* return http.build();
* }
*
* &#64;Bean
* public WebSecurityCustomizer webSecurityCustomizer() {
* return (web) -> web.ignoring().antMatchers("/resources/**");
* }
* </pre> See the <a href=
* "https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter">Spring
* Security without WebSecurityConfigurerAdapter</a> for more details.
*/
@Order(100)
@Deprecated
@@ -50,7 +50,8 @@ import org.springframework.security.web.csrf.CsrfToken;
*
* <h2>Shared Objects Created</h2>
*
* No shared objects are created. isLogoutRequest
* No shared objects are created.
*
* <h2>Shared Objects Used</h2>
*
* The following shared objects are used:
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,6 +35,8 @@ import org.springframework.security.web.authentication.logout.LogoutSuccessHandl
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
@@ -325,6 +327,7 @@ public final class LogoutConfigurer<H extends HttpSecurityBuilder<H>>
* @return the {@link LogoutFilter} to use.
*/
private LogoutFilter createLogoutFilter(H http) {
this.contextLogoutHandler.setSecurityContextRepository(getSecurityContextRepository(http));
this.logoutHandlers.add(this.contextLogoutHandler);
this.logoutHandlers.add(postProcess(new LogoutSuccessEventPublishingLogoutHandler()));
LogoutHandler[] handlers = this.logoutHandlers.toArray(new LogoutHandler[0]);
@@ -334,6 +337,14 @@ public final class LogoutConfigurer<H extends HttpSecurityBuilder<H>>
return result;
}
private SecurityContextRepository getSecurityContextRepository(H http) {
SecurityContextRepository securityContextRepository = http.getSharedObject(SecurityContextRepository.class);
if (securityContextRepository == null) {
securityContextRepository = new HttpSessionSecurityContextRepository();
}
return securityContextRepository;
}
private RequestMatcher getLogoutRequestMatcher(H http) {
if (this.logoutRequestMatcher != null) {
return this.logoutRequestMatcher;
@@ -288,7 +288,7 @@ public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
/**
* Controls the maximum number of sessions for a user. The default is to allow any
* number of users.
* number of sessions.
* @param maximumSessions the maximum number of sessions for a user
* @return the {@link SessionManagementConfigurer} for further customizations
*/
@@ -49,10 +49,11 @@ import org.springframework.util.Assert;
* </p>
*
* <pre>
* protected void configure(HttpSecurity http) throws Exception {
* http.apply(new UrlAuthorizationConfigurer&lt;HttpSecurity&gt;()).getRegistry()
* .antMatchers(&quot;/users**&quot;, &quot;/sessions/**&quot;).hasRole(&quot;USER&quot;)
* .antMatchers(&quot;/signup&quot;).hasRole(&quot;ANONYMOUS&quot;).anyRequest().hasRole(&quot;USER&quot;);
* &#64;Bean
* public SecurityFilterChain filterChain(HttpSecurity http, ApplicationContext context) throws Exception {
* http.apply(new UrlAuthorizationConfigurer&lt;HttpSecurity&gt;(context)).getRegistry()
* .requestMatchers(&quot;/users**&quot;, &quot;/sessions/**&quot;).hasRole(&quot;USER&quot;)
* .requestMatchers(&quot;/signup&quot;).hasRole(&quot;ANONYMOUS&quot;).anyRequest().hasRole(&quot;USER&quot;);
* }
* </pre>
*
@@ -19,6 +19,7 @@ package org.springframework.security.config.annotation.web.reactive;
import java.util.Arrays;
import java.util.List;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.context.ApplicationContext;
@@ -30,6 +31,8 @@ import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.reactive.result.view.CsrfRequestDataValueProcessor;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.WebFilterChainProxy;
import org.springframework.security.web.server.firewall.ServerExchangeRejectedHandler;
import org.springframework.security.web.server.firewall.ServerWebExchangeFirewall;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.web.reactive.result.view.AbstractView;
@@ -65,8 +68,12 @@ class WebFluxSecurityConfiguration {
@Bean(SPRING_SECURITY_WEBFILTERCHAINFILTER_BEAN_NAME)
@Order(WEB_FILTER_CHAIN_FILTER_ORDER)
WebFilterChainProxy springSecurityWebFilterChainFilter() {
return new WebFilterChainProxy(getSecurityWebFilterChains());
WebFilterChainProxy springSecurityWebFilterChainFilter(ObjectProvider<ServerWebExchangeFirewall> firewall,
ObjectProvider<ServerExchangeRejectedHandler> rejectedHandler) {
WebFilterChainProxy webFilterChainProxy = new WebFilterChainProxy(getSecurityWebFilterChains());
firewall.ifUnique(webFilterChainProxy::setFirewall);
rejectedHandler.ifUnique(webFilterChainProxy::setExchangeRejectedHandler);
return webFilterChainProxy;
}
@Bean(name = AbstractView.REQUEST_DATA_VALUE_PROCESSOR_BEAN_NAME)
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.security.config.http;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import javax.servlet.ServletRequest;
@@ -286,7 +287,7 @@ class HttpConfigurationBuilder {
// Needed to account for placeholders
static String createPath(String path, boolean lowerCase) {
return lowerCase ? path.toLowerCase() : path;
return lowerCase ? path.toLowerCase(Locale.ENGLISH) : path;
}
BeanReference getSecurityContextRepositoryForAuthenticationFilters() {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -208,30 +208,49 @@ public final class RelyingPartyRegistrationsBeanDefinitionParser implements Bean
ParserContext parserContext) {
String registrationId = relyingPartyRegistrationElt.getAttribute(ATT_REGISTRATION_ID);
String metadataLocation = relyingPartyRegistrationElt.getAttribute(ATT_METADATA_LOCATION);
RelyingPartyRegistration.Builder builder;
if (StringUtils.hasText(metadataLocation)) {
builder = RelyingPartyRegistrations.fromMetadataLocation(metadataLocation).registrationId(registrationId);
}
else {
builder = RelyingPartyRegistration.withRegistrationId(registrationId)
.assertingPartyDetails((apBuilder) -> buildAssertingParty(relyingPartyRegistrationElt,
assertingParties, apBuilder, parserContext));
}
addRemainingProperties(relyingPartyRegistrationElt, builder);
return builder;
}
private static void addRemainingProperties(Element relyingPartyRegistrationElt,
RelyingPartyRegistration.Builder builder) {
String entityId = relyingPartyRegistrationElt.getAttribute(ATT_ENTITY_ID);
String singleLogoutServiceLocation = relyingPartyRegistrationElt
.getAttribute(ATT_SINGLE_LOGOUT_SERVICE_LOCATION);
String singleLogoutServiceResponseLocation = relyingPartyRegistrationElt
.getAttribute(ATT_SINGLE_LOGOUT_SERVICE_RESPONSE_LOCATION);
Saml2MessageBinding singleLogoutServiceBinding = getSingleLogoutServiceBinding(relyingPartyRegistrationElt);
if (StringUtils.hasText(metadataLocation)) {
return RelyingPartyRegistrations.fromMetadataLocation(metadataLocation).registrationId(registrationId)
.singleLogoutServiceLocation(singleLogoutServiceLocation)
.singleLogoutServiceResponseLocation(singleLogoutServiceResponseLocation)
.singleLogoutServiceBinding(singleLogoutServiceBinding);
}
String entityId = relyingPartyRegistrationElt.getAttribute(ATT_ENTITY_ID);
String assertionConsumerServiceLocation = relyingPartyRegistrationElt
.getAttribute(ATT_ASSERTION_CONSUMER_SERVICE_LOCATION);
Saml2MessageBinding assertionConsumerServiceBinding = getAssertionConsumerServiceBinding(
relyingPartyRegistrationElt);
return RelyingPartyRegistration.withRegistrationId(registrationId).entityId(entityId)
.assertionConsumerServiceLocation(assertionConsumerServiceLocation)
.assertionConsumerServiceBinding(assertionConsumerServiceBinding)
.singleLogoutServiceLocation(singleLogoutServiceLocation)
.singleLogoutServiceResponseLocation(singleLogoutServiceResponseLocation)
.singleLogoutServiceBinding(singleLogoutServiceBinding)
.assertingPartyDetails((builder) -> buildAssertingParty(relyingPartyRegistrationElt, assertingParties,
builder, parserContext));
if (StringUtils.hasText(entityId)) {
builder.entityId(entityId);
}
if (StringUtils.hasText(singleLogoutServiceLocation)) {
builder.singleLogoutServiceLocation(singleLogoutServiceLocation);
}
if (StringUtils.hasText(singleLogoutServiceResponseLocation)) {
builder.singleLogoutServiceResponseLocation(singleLogoutServiceResponseLocation);
}
if (singleLogoutServiceBinding != null) {
builder.singleLogoutServiceBinding(singleLogoutServiceBinding);
}
if (StringUtils.hasText(assertionConsumerServiceLocation)) {
builder.assertionConsumerServiceLocation(assertionConsumerServiceLocation);
}
if (assertionConsumerServiceBinding != null) {
builder.assertionConsumerServiceBinding(assertionConsumerServiceBinding);
}
}
private static void buildAssertingParty(Element relyingPartyElt, Map<String, Map<String, Object>> assertingParties,
@@ -309,7 +328,7 @@ public final class RelyingPartyRegistrationsBeanDefinitionParser implements Bean
if (StringUtils.hasText(assertionConsumerServiceBinding)) {
return Saml2MessageBinding.valueOf(assertionConsumerServiceBinding);
}
return Saml2MessageBinding.REDIRECT;
return null;
}
private static Saml2MessageBinding getSingleLogoutServiceBinding(Element relyingPartyRegistrationElt) {
@@ -317,7 +336,7 @@ public final class RelyingPartyRegistrationsBeanDefinitionParser implements Bean
if (StringUtils.hasText(singleLogoutServiceBinding)) {
return Saml2MessageBinding.valueOf(singleLogoutServiceBinding);
}
return Saml2MessageBinding.POST;
return null;
}
private static Saml2X509Credential getSaml2VerificationCredential(String certificateLocation) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
package org.springframework.security.config.web.server;
import java.util.ArrayList;
import java.util.List;
import org.springframework.http.HttpMethod;
@@ -23,6 +24,8 @@ import org.springframework.security.web.server.util.matcher.OrServerWebExchangeM
import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPatternParser;
/**
* @author Rob Winch
@@ -62,7 +65,8 @@ public abstract class AbstractServerWebExchangeMatcherRegistry<T> {
* {@link ServerWebExchangeMatcher}
*/
public T pathMatchers(HttpMethod method, String... antPatterns) {
return matcher(ServerWebExchangeMatchers.pathMatchers(method, antPatterns));
List<PathPattern> pathPatterns = parsePatterns(antPatterns);
return matcher(ServerWebExchangeMatchers.pathMatchers(method, pathPatterns.toArray(new PathPattern[0])));
}
/**
@@ -74,7 +78,19 @@ public abstract class AbstractServerWebExchangeMatcherRegistry<T> {
* {@link ServerWebExchangeMatcher}
*/
public T pathMatchers(String... antPatterns) {
return matcher(ServerWebExchangeMatchers.pathMatchers(antPatterns));
List<PathPattern> pathPatterns = parsePatterns(antPatterns);
return matcher(ServerWebExchangeMatchers.pathMatchers(pathPatterns.toArray(new PathPattern[0])));
}
private List<PathPattern> parsePatterns(String[] antPatterns) {
PathPatternParser parser = getPathPatternParser();
List<PathPattern> pathPatterns = new ArrayList<>(antPatterns.length);
for (String pattern : antPatterns) {
pattern = parser.initFullPathPattern(pattern);
PathPattern pathPattern = parser.parse(pattern);
pathPatterns.add(pathPattern);
}
return pathPatterns;
}
/**
@@ -96,6 +112,10 @@ public abstract class AbstractServerWebExchangeMatcherRegistry<T> {
*/
protected abstract T registerMatcher(ServerWebExchangeMatcher matcher);
protected PathPatternParser getPathPatternParser() {
return PathPatternParser.defaultInstance;
}
/**
* Associates a {@link ServerWebExchangeMatcher} instances
* @param matcher the {@link ServerWebExchangeMatcher} instance
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -185,9 +185,11 @@ import org.springframework.web.cors.reactive.CorsConfigurationSource;
import org.springframework.web.cors.reactive.CorsProcessor;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.DefaultCorsProcessor;
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import org.springframework.web.util.pattern.PathPatternParser;
/**
* A {@link ServerHttpSecurity} is similar to Spring Security's {@code HttpSecurity} but
@@ -1557,6 +1559,18 @@ public class ServerHttpSecurity {
return null;
}
private <T> T getBeanOrNull(String beanName, Class<T> requiredClass) {
if (this.context == null) {
return null;
}
try {
return this.context.getBean(beanName, requiredClass);
}
catch (Exception ex) {
return null;
}
}
private <T> String[] getBeanNamesForTypeOrEmpty(Class<T> beanClass) {
if (this.context == null) {
return new String[0];
@@ -1577,6 +1591,8 @@ public class ServerHttpSecurity {
*/
public class AuthorizeExchangeSpec extends AbstractServerWebExchangeMatcherRegistry<AuthorizeExchangeSpec.Access> {
private static final String REQUEST_MAPPING_HANDLER_MAPPING_BEAN_NAME = "requestMappingHandlerMapping";
private DelegatingReactiveAuthorizationManager.Builder managerBldr = DelegatingReactiveAuthorizationManager
.builder();
@@ -1584,6 +1600,8 @@ public class ServerHttpSecurity {
private boolean anyExchangeRegistered;
private PathPatternParser pathPatternParser;
/**
* Allows method chaining to continue configuring the {@link ServerHttpSecurity}
* @return the {@link ServerHttpSecurity} to continue configuring
@@ -1603,6 +1621,22 @@ public class ServerHttpSecurity {
return result;
}
@Override
protected PathPatternParser getPathPatternParser() {
if (this.pathPatternParser != null) {
return this.pathPatternParser;
}
RequestMappingHandlerMapping requestMappingHandlerMapping = getBeanOrNull(
REQUEST_MAPPING_HANDLER_MAPPING_BEAN_NAME, RequestMappingHandlerMapping.class);
if (requestMappingHandlerMapping != null) {
this.pathPatternParser = requestMappingHandlerMapping.getPathPatternParser();
}
if (this.pathPatternParser == null) {
this.pathPatternParser = PathPatternParser.defaultInstance;
}
return this.pathPatternParser;
}
@Override
protected Access registerMatcher(ServerWebExchangeMatcher matcher) {
Assert.state(!this.anyExchangeRegistered, () -> "Cannot register " + matcher
@@ -30,6 +30,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
/**
@@ -83,6 +84,24 @@ public class AbstractConfiguredSecurityBuilderTests {
verify(DelegateSecurityConfigurer.CONFIGURER).configure(this.builder);
}
@Test
public void buildWhenConfigurerAppliesAndRemoveAnotherConfigurerThenNotConfigured() throws Exception {
ApplyAndRemoveSecurityConfigurer.CONFIGURER = mock(SecurityConfigurer.class);
this.builder.apply(new ApplyAndRemoveSecurityConfigurer());
this.builder.build();
verify(ApplyAndRemoveSecurityConfigurer.CONFIGURER, never()).init(this.builder);
verify(ApplyAndRemoveSecurityConfigurer.CONFIGURER, never()).configure(this.builder);
}
@Test
public void buildWhenConfigurerAppliesAndRemoveAnotherConfigurersThenNotConfigured() throws Exception {
ApplyAndRemoveAllSecurityConfigurer.CONFIGURER = mock(SecurityConfigurer.class);
this.builder.apply(new ApplyAndRemoveAllSecurityConfigurer());
this.builder.build();
verify(ApplyAndRemoveAllSecurityConfigurer.CONFIGURER, never()).init(this.builder);
verify(ApplyAndRemoveAllSecurityConfigurer.CONFIGURER, never()).configure(this.builder);
}
@Test
public void getConfigurerWhenMultipleConfigurersThenThrowIllegalStateException() throws Exception {
TestConfiguredSecurityBuilder builder = new TestConfiguredSecurityBuilder(mock(ObjectPostProcessor.class),
@@ -130,6 +149,32 @@ public class AbstractConfiguredSecurityBuilderTests {
assertThat(builder.getConfigurers(DelegateSecurityConfigurer.class)).hasSize(2);
}
private static class ApplyAndRemoveSecurityConfigurer
extends SecurityConfigurerAdapter<Object, TestConfiguredSecurityBuilder> {
private static SecurityConfigurer<Object, TestConfiguredSecurityBuilder> CONFIGURER;
@Override
public void init(TestConfiguredSecurityBuilder builder) throws Exception {
builder.apply(CONFIGURER);
builder.removeConfigurer(CONFIGURER.getClass());
}
}
private static class ApplyAndRemoveAllSecurityConfigurer
extends SecurityConfigurerAdapter<Object, TestConfiguredSecurityBuilder> {
private static SecurityConfigurer<Object, TestConfiguredSecurityBuilder> CONFIGURER;
@Override
public void init(TestConfiguredSecurityBuilder builder) throws Exception {
builder.apply(CONFIGURER);
builder.removeConfigurers(CONFIGURER.getClass());
}
}
private static class DelegateSecurityConfigurer
extends SecurityConfigurerAdapter<Object, TestConfiguredSecurityBuilder> {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -30,6 +30,7 @@ import org.assertj.core.api.ListAssert;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@@ -45,12 +46,29 @@ import org.springframework.security.web.context.request.async.WebAsyncManagerInt
import org.springframework.security.web.header.HeaderWriterFilter;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@ExtendWith(SpringTestContextExtension.class)
public class HttpSecurityAddFilterTest {
public final SpringTestContext spring = new SpringTestContext(this);
@Test
public void addFilterAfterFilterNotRegisteredYetThenThrowIllegalArgument() {
assertThatExceptionOfType(UnsatisfiedDependencyException.class)
.isThrownBy(
() -> this.spring.register(MyOtherFilterAfterMyFilterNotRegisteredYetConfig.class).autowire())
.havingRootCause().isInstanceOf(IllegalArgumentException.class);
}
@Test
public void addFilterBeforeFilterNotRegisteredYetThenThrowIllegalArgument() {
assertThatExceptionOfType(UnsatisfiedDependencyException.class)
.isThrownBy(
() -> this.spring.register(MyOtherFilterBeforeMyFilterNotRegisteredYetConfig.class).autowire())
.havingRootCause().isInstanceOf(IllegalArgumentException.class);
}
@Test
public void addFilterAfterWhenSameFilterDifferentPlacesThenOrderCorrect() {
this.spring.register(MyFilterMultipleAfterConfig.class).autowire();
@@ -209,6 +227,34 @@ public class HttpSecurityAddFilterTest {
}
@EnableWebSecurity
static class MyOtherFilterAfterMyFilterNotRegisteredYetConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.addFilterAfter(new MyOtherFilter(), MyFilter.class);
// @formatter:on
return http.build();
}
}
@EnableWebSecurity
static class MyOtherFilterBeforeMyFilterNotRegisteredYetConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.addFilterBefore(new MyOtherFilter(), MyFilter.class);
// @formatter:on
return http.build();
}
}
@EnableWebSecurity
static class MyOtherFilterRelativeToMyFilterBeforeConfig {
@@ -21,6 +21,7 @@ import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;
import javax.servlet.Filter;
import javax.servlet.http.HttpServletRequest;
import com.google.common.net.HttpHeaders;
@@ -45,6 +46,7 @@ import org.springframework.security.authentication.event.AbstractAuthenticationF
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
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.FormLoginConfigurer;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.core.Authentication;
@@ -55,6 +57,8 @@ import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
import org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter;
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
@@ -292,6 +296,16 @@ public class HttpSecurityConfigurationTests {
assertThat(configurer.configure).isTrue();
}
// gh-13203
@Test
public void disableConfigurerWhenAppliedByAnotherConfigurerThenNotApplied() {
this.spring.register(ApplyCustomDslConfig.class).autowire();
SecurityFilterChain filterChain = this.spring.getContext().getBean(SecurityFilterChain.class);
List<Filter> filters = filterChain.getFilters();
assertThat(filters).doesNotHaveAnyElementsOfTypes(DefaultLoginPageGeneratingFilter.class,
DefaultLogoutPageGeneratingFilter.class);
}
@RestController
static class NameController {
@@ -470,6 +484,31 @@ public class HttpSecurityConfigurationTests {
}
@Configuration
@EnableWebSecurity
static class ApplyCustomDslConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.apply(CustomDsl.customDsl());
return http.build();
}
}
static class CustomDsl extends AbstractHttpConfigurer<CustomDsl, HttpSecurity> {
@Override
public void init(HttpSecurity http) throws Exception {
http.formLogin(FormLoginConfigurer::disable);
}
static CustomDsl customDsl() {
return new CustomDsl();
}
}
static class DefaultConfigurer extends AbstractHttpConfigurer<DefaultConfigurer, HttpSecurity> {
boolean init;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,9 @@
package org.springframework.security.config.annotation.web.configurers;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.http.HttpHeaders;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -23,7 +26,10 @@ 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.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@@ -31,9 +37,12 @@ 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.SecurityFilterChain;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
@@ -42,6 +51,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
@@ -302,6 +312,80 @@ public class LogoutConfigurerTests {
this.mvc.perform(post("/logout").with(csrf())).andExpect(status().isNotFound());
}
@Test
public void logoutWhenCustomSecurityContextRepositoryThenUses() throws Exception {
CustomSecurityContextRepositoryConfig.repository = mock(SecurityContextRepository.class);
this.spring.register(CustomSecurityContextRepositoryConfig.class).autowire();
// @formatter:off
MockHttpServletRequestBuilder logoutRequest = post("/logout")
.with(csrf())
.with(user("user"))
.header(HttpHeaders.ACCEPT, MediaType.TEXT_HTML_VALUE);
this.mvc.perform(logoutRequest)
.andExpect(status().isFound())
.andExpect(redirectedUrl("/login?logout"));
// @formatter:on
int invocationCount = 2; // 1 from user() post processor and 1 from
// SecurityContextLogoutHandler
verify(CustomSecurityContextRepositoryConfig.repository, times(invocationCount)).saveContext(any(),
any(HttpServletRequest.class), any(HttpServletResponse.class));
}
@Test
public void logoutWhenNoSecurityContextRepositoryThenHttpSessionSecurityContextRepository() throws Exception {
this.spring.register(InvalidateHttpSessionFalseConfig.class).autowire();
MockHttpSession session = mock(MockHttpSession.class);
// @formatter:off
MockHttpServletRequestBuilder logoutRequest = post("/logout")
.with(csrf())
.with(user("user"))
.session(session)
.header(HttpHeaders.ACCEPT, MediaType.TEXT_HTML_VALUE);
this.mvc.perform(logoutRequest)
.andExpect(status().isFound())
.andExpect(redirectedUrl("/login?logout"))
.andReturn();
// @formatter:on
verify(session).removeAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
}
@Configuration
@EnableWebSecurity
static class InvalidateHttpSessionFalseConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.logout((logout) -> logout.invalidateHttpSession(false))
.securityContext((context) -> context.requireExplicitSave(true));
return http.build();
// @formatter:on
}
}
@Configuration
@EnableWebSecurity
static class CustomSecurityContextRepositoryConfig {
static SecurityContextRepository repository;
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.logout(Customizer.withDefaults())
.securityContext((context) -> context
.requireExplicitSave(true)
.securityContextRepository(repository)
);
return http.build();
// @formatter:on
}
}
@EnableWebSecurity
static class NullLogoutSuccessHandlerConfig extends WebSecurityConfigurerAdapter {
@@ -16,14 +16,26 @@
package org.springframework.security.config.annotation.web.reactive;
import java.util.Collections;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import reactor.core.publisher.Mono;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.config.users.ReactiveAuthenticationTestConfiguration;
import org.springframework.security.web.server.WebFilterChainProxy;
import org.springframework.security.web.server.firewall.HttpStatusExchangeRejectedHandler;
import org.springframework.security.web.server.firewall.ServerExchangeRejectedHandler;
import org.springframework.security.web.server.firewall.ServerWebExchangeFirewall;
import org.springframework.web.server.handler.DefaultWebFilterChain;
import static org.assertj.core.api.Assertions.assertThat;
@@ -45,6 +57,40 @@ public class WebFluxSecurityConfigurationTests {
assertThat(webFilterChainProxy).isNotNull();
}
@Test
void loadConfigWhenDefaultThenFirewalled() throws Exception {
this.spring.register(ServerHttpSecurityConfiguration.class, ReactiveAuthenticationTestConfiguration.class,
WebFluxSecurityConfiguration.class).autowire();
WebFilterChainProxy webFilterChainProxy = this.spring.getContext().getBean(WebFilterChainProxy.class);
MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/;/").build());
DefaultWebFilterChain chain = emptyChain();
webFilterChainProxy.filter(exchange, chain).block();
assertThat(exchange.getResponse().getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
}
@Test
void loadConfigWhenCustomRejectedHandler() throws Exception {
this.spring.register(ServerHttpSecurityConfiguration.class, ReactiveAuthenticationTestConfiguration.class,
WebFluxSecurityConfiguration.class, CustomServerExchangeRejectedHandlerConfig.class).autowire();
WebFilterChainProxy webFilterChainProxy = this.spring.getContext().getBean(WebFilterChainProxy.class);
MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/;/").build());
DefaultWebFilterChain chain = emptyChain();
webFilterChainProxy.filter(exchange, chain).block();
assertThat(exchange.getResponse().getStatusCode())
.isEqualTo(CustomServerExchangeRejectedHandlerConfig.EXPECTED_STATUS);
}
@Test
void loadConfigWhenFirewallBeanThenCustomized() throws Exception {
this.spring.register(ServerHttpSecurityConfiguration.class, ReactiveAuthenticationTestConfiguration.class,
WebFluxSecurityConfiguration.class, NoOpFirewallConfig.class).autowire();
WebFilterChainProxy webFilterChainProxy = this.spring.getContext().getBean(WebFilterChainProxy.class);
MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/;/").build());
DefaultWebFilterChain chain = emptyChain();
webFilterChainProxy.filter(exchange, chain).block();
assertThat(exchange.getResponse().getStatusCode()).isNotEqualTo(HttpStatus.BAD_REQUEST);
}
@Test
public void loadConfigWhenBeanProxyingEnabledAndSubclassThenWebFilterChainProxyExists() {
this.spring.register(ServerHttpSecurityConfiguration.class, ReactiveAuthenticationTestConfiguration.class,
@@ -53,6 +99,32 @@ public class WebFluxSecurityConfigurationTests {
assertThat(webFilterChainProxy).isNotNull();
}
private static @NotNull DefaultWebFilterChain emptyChain() {
return new DefaultWebFilterChain((webExchange) -> Mono.empty(), Collections.emptyList());
}
@Configuration
static class NoOpFirewallConfig {
@Bean
ServerWebExchangeFirewall noOpFirewall() {
return ServerWebExchangeFirewall.INSECURE_NOOP;
}
}
@Configuration
static class CustomServerExchangeRejectedHandlerConfig {
static HttpStatus EXPECTED_STATUS = HttpStatus.I_AM_A_TEAPOT;
@Bean
ServerExchangeRejectedHandler rejectedHandler() {
return new HttpStatusExchangeRejectedHandler(EXPECTED_STATUS);
}
}
@Configuration
static class SubclassConfig extends WebFluxSecurityConfiguration {
@@ -150,8 +150,8 @@ public class XsdDocumentedTests {
.getParentFile()
.list((dir, name) -> name.endsWith(".xsd"));
// @formatter:on
assertThat(schemas.length).isEqualTo(19)
.withFailMessage("the count is equal to 19, if not then schemaDocument needs updating");
assertThat(schemas.length).isEqualTo(20)
.withFailMessage("the count is equal to 20, if not then schemaDocument needs updating");
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -62,6 +62,27 @@ public class RelyingPartyRegistrationsBeanDefinitionParserTests {
"</b:beans>\n";
// @formatter:on
// @formatter:off
private static final String METADATA_LOCATION_OVERRIDE_PROPERTIES_XML_CONFIG = "<b:beans xmlns:b=\"http://www.springframework.org/schema/beans\"\n" +
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
" xmlns=\"http://www.springframework.org/schema/security\"\n" +
" xsi:schemaLocation=\"\n" +
"\t\t\thttp://www.springframework.org/schema/security\n" +
"\t\t\thttps://www.springframework.org/schema/security/spring-security.xsd\n" +
"\t\t\thttp://www.springframework.org/schema/beans\n" +
"\t\t\thttps://www.springframework.org/schema/beans/spring-beans.xsd\">\n" +
" \n" +
" <relying-party-registrations>\n" +
" <relying-party-registration registration-id=\"one\"\n" +
" entity-id=\"https://rp.example.org\"\n" +
" metadata-location=\"${metadata-location}\"\n" +
" assertion-consumer-service-location=\"https://rp.example.org/location\"\n" +
" assertion-consumer-service-binding=\"REDIRECT\"/>" +
" </relying-party-registrations>\n" +
"\n" +
"</b:beans>\n";
// @formatter:on
// @formatter:off
private static final String METADATA_RESPONSE = "<?xml version=\"1.0\"?>\n" +
"<md:EntityDescriptor xmlns:md=\"urn:oasis:names:tc:SAML:2.0:metadata\" xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\" entityID=\"https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/metadata.php\" ID=\"_e793a707d3e1a9ee6cbec7454fdad2c7cd793dd3703179a527b9620a6e9682af\"><ds:Signature>\n" +
@@ -143,6 +164,41 @@ public class RelyingPartyRegistrationsBeanDefinitionParserTests {
.containsExactly("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256");
}
@Test
public void parseWhenMetadataLocationConfiguredAndRegistrationHasPropertiesThenDoNotOverrideSpecifiedProperties()
throws Exception {
this.server = new MockWebServer();
this.server.start();
String serverUrl = this.server.url("/").toString();
this.server.enqueue(xmlResponse(METADATA_RESPONSE));
String metadataConfig = METADATA_LOCATION_OVERRIDE_PROPERTIES_XML_CONFIG.replace("${metadata-location}",
serverUrl);
this.spring.context(metadataConfig).autowire();
assertThat(this.relyingPartyRegistrationRepository)
.isInstanceOf(InMemoryRelyingPartyRegistrationRepository.class);
RelyingPartyRegistration relyingPartyRegistration = this.relyingPartyRegistrationRepository
.findByRegistrationId("one");
RelyingPartyRegistration.AssertingPartyDetails assertingPartyDetails = relyingPartyRegistration
.getAssertingPartyDetails();
assertThat(relyingPartyRegistration).isNotNull();
assertThat(relyingPartyRegistration.getRegistrationId()).isEqualTo("one");
assertThat(relyingPartyRegistration.getEntityId()).isEqualTo("https://rp.example.org");
assertThat(relyingPartyRegistration.getAssertionConsumerServiceLocation())
.isEqualTo("https://rp.example.org/location");
assertThat(relyingPartyRegistration.getAssertionConsumerServiceBinding())
.isEqualTo(Saml2MessageBinding.REDIRECT);
assertThat(assertingPartyDetails.getEntityId())
.isEqualTo("https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/metadata.php");
assertThat(assertingPartyDetails.getWantAuthnRequestsSigned()).isFalse();
assertThat(assertingPartyDetails.getVerificationX509Credentials()).hasSize(1);
assertThat(assertingPartyDetails.getEncryptionX509Credentials()).hasSize(1);
assertThat(assertingPartyDetails.getSingleSignOnServiceLocation())
.isEqualTo("https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/SSOService.php");
assertThat(assertingPartyDetails.getSingleSignOnServiceBinding()).isEqualTo(Saml2MessageBinding.REDIRECT);
assertThat(assertingPartyDetails.getSigningAlgorithms())
.containsExactly("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256");
}
@Test
public void parseWhenSingleRelyingPartyRegistrationThenAvailableInRepository() {
this.spring.configLocations(xml("SingleRegistration")).autowire();
@@ -57,7 +57,7 @@ public class AuthenticatedVoter implements AccessDecisionVoter<Object> {
private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();
private boolean isFullyAuthenticated(Authentication authentication) {
return (!this.authenticationTrustResolver.isAnonymous(authentication)
return authentication != null && (!this.authenticationTrustResolver.isAnonymous(authentication)
&& !this.authenticationTrustResolver.isRememberMe(authentication));
}
@@ -40,8 +40,9 @@ import org.springframework.util.Assert;
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
/**
* The plaintext password used to perform PasswordEncoder#matches(CharSequence,
* String)} on when the user is not found to avoid SEC-2056.
* The plaintext password used to perform
* {@link PasswordEncoder#matches(CharSequence, String)} on when the user is not found
* to avoid SEC-2056.
*/
private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -46,12 +46,15 @@ public final class AuthorityAuthorizationManager<T> implements AuthorizationMana
/**
* Creates an instance of {@link AuthorityAuthorizationManager} with the provided
* authority.
* @param role the authority to check for prefixed with "ROLE_"
* @param role the authority to check for prefixed with "ROLE_". Role should not start
* with "ROLE_" since it is automatically prepended already.
* @param <T> the type of object being authorized
* @return the new instance
*/
public static <T> AuthorityAuthorizationManager<T> hasRole(String role) {
Assert.notNull(role, "role cannot be null");
Assert.isTrue(!role.startsWith(ROLE_PREFIX), () -> role + " should not start with " + ROLE_PREFIX + " since "
+ ROLE_PREFIX + " is automatically prepended when using hasRole. Consider using hasAuthority instead.");
return hasAuthority(ROLE_PREFIX + role);
}
@@ -70,7 +73,8 @@ public final class AuthorityAuthorizationManager<T> implements AuthorizationMana
/**
* Creates an instance of {@link AuthorityAuthorizationManager} with the provided
* authorities.
* @param roles the authorities to check for prefixed with "ROLE_"
* @param roles the authorities to check for prefixed with "ROLE_". Each role should
* not start with "ROLE_" since it is automatically prepended already.
* @param <T> the type of object being authorized
* @return the new instance
*/
@@ -109,7 +113,11 @@ public final class AuthorityAuthorizationManager<T> implements AuthorizationMana
private static String[] toNamedRolesArray(String rolePrefix, String[] roles) {
String[] result = new String[roles.length];
for (int i = 0; i < roles.length; i++) {
result[i] = rolePrefix + roles[i];
String role = roles[i];
Assert.isTrue(!role.startsWith(rolePrefix), () -> role + " should not start with " + rolePrefix + " since "
+ rolePrefix
+ " is automatically prepended when using hasAnyRole. Consider using hasAnyAuthority instead.");
result[i] = rolePrefix + role;
}
return result;
}
@@ -25,7 +25,7 @@ import org.springframework.security.core.Authentication;
* A reactive authorization manager which can determine if an {@link Authentication} has
* access to a specific object.
*
* @param <T> the type of object that the authorization check is being done one.
* @param <T> the type of object that the authorization check is being done on.
* @author Rob Winch
* @since 5.0
*/
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2024 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.
@@ -79,10 +79,10 @@ public class SimpleAttributes2GrantedAuthoritiesMapper
*/
private GrantedAuthority getGrantedAuthority(String attribute) {
if (isConvertAttributeToLowerCase()) {
attribute = attribute.toLowerCase(Locale.getDefault());
attribute = attribute.toLowerCase(Locale.ROOT);
}
else if (isConvertAttributeToUpperCase()) {
attribute = attribute.toUpperCase(Locale.getDefault());
attribute = attribute.toUpperCase(Locale.ROOT);
}
if (isAddPrefixIfAlreadyExisting() || !attribute.startsWith(getAttributePrefix())) {
return new SimpleGrantedAuthority(getAttributePrefix() + attribute);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.security.core.authority.mapping;
import java.util.Collection;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import org.springframework.beans.factory.InitializingBean;
@@ -71,10 +72,10 @@ public final class SimpleAuthorityMapper implements GrantedAuthoritiesMapper, In
private GrantedAuthority mapAuthority(String name) {
if (this.convertToUpperCase) {
name = name.toUpperCase();
name = name.toUpperCase(Locale.ROOT);
}
else if (this.convertToLowerCase) {
name = name.toLowerCase();
name = name.toLowerCase(Locale.ROOT);
}
if (this.prefix.length() > 0 && !name.startsWith(this.prefix)) {
name = this.prefix + name;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.security.core.userdetails;
import java.util.Arrays;
import java.util.Collection;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -91,7 +92,7 @@ public class MapReactiveUserDetailsService implements ReactiveUserDetailsService
}
private String getKey(String username) {
return username.toLowerCase();
return username.toLowerCase(Locale.ROOT);
}
}
@@ -19,6 +19,7 @@ package org.springframework.security.core.userdetails.memory;
import java.beans.PropertyEditorSupport;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import org.springframework.util.StringUtils;
@@ -45,10 +46,10 @@ public class UserAttributeEditor extends PropertyEditorSupport {
userAttrib.setPassword(currentToken);
}
else {
if (currentToken.toLowerCase().equals("enabled")) {
if (currentToken.toLowerCase(Locale.ENGLISH).equals("enabled")) {
userAttrib.setEnabled(true);
}
else if (currentToken.toLowerCase().equals("disabled")) {
else if (currentToken.toLowerCase(Locale.ENGLISH).equals("disabled")) {
userAttrib.setEnabled(false);
}
else {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 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,6 +19,7 @@ package org.springframework.security.provisioning;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
@@ -92,23 +93,23 @@ public class InMemoryUserDetailsManager implements UserDetailsManager, UserDetai
@Override
public void createUser(UserDetails user) {
Assert.isTrue(!userExists(user.getUsername()), "user should not exist");
this.users.put(user.getUsername().toLowerCase(), new MutableUser(user));
this.users.put(user.getUsername().toLowerCase(Locale.ROOT), new MutableUser(user));
}
@Override
public void deleteUser(String username) {
this.users.remove(username.toLowerCase());
this.users.remove(username.toLowerCase(Locale.ROOT));
}
@Override
public void updateUser(UserDetails user) {
Assert.isTrue(userExists(user.getUsername()), "user should exist");
this.users.put(user.getUsername().toLowerCase(), new MutableUser(user));
this.users.put(user.getUsername().toLowerCase(Locale.ROOT), new MutableUser(user));
}
@Override
public boolean userExists(String username) {
return this.users.containsKey(username.toLowerCase());
return this.users.containsKey(username.toLowerCase(Locale.ROOT));
}
@Override
@@ -139,14 +140,14 @@ public class InMemoryUserDetailsManager implements UserDetailsManager, UserDetai
@Override
public UserDetails updatePassword(UserDetails user, String newPassword) {
String username = user.getUsername();
MutableUserDetails mutableUser = this.users.get(username.toLowerCase());
MutableUserDetails mutableUser = this.users.get(username.toLowerCase(Locale.ROOT));
mutableUser.setPassword(newPassword);
return mutableUser;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserDetails user = this.users.get(username.toLowerCase());
UserDetails user = this.users.get(username.toLowerCase(Locale.ROOT));
if (user == null) {
throw new UsernameNotFoundException(username);
}
@@ -59,6 +59,7 @@ public class AuthenticatedVoterTests {
assertThat(AccessDecisionVoter.ACCESS_GRANTED).isEqualTo(voter.vote(createAnonymous(), null, def));
assertThat(AccessDecisionVoter.ACCESS_GRANTED).isEqualTo(voter.vote(createRememberMe(), null, def));
assertThat(AccessDecisionVoter.ACCESS_GRANTED).isEqualTo(voter.vote(createFullyAuthenticated(), null, def));
assertThat(AccessDecisionVoter.ACCESS_DENIED).isEqualTo(voter.vote(null, null, def));
}
@Test
@@ -68,6 +69,7 @@ public class AuthenticatedVoterTests {
assertThat(AccessDecisionVoter.ACCESS_DENIED).isEqualTo(voter.vote(createAnonymous(), null, def));
assertThat(AccessDecisionVoter.ACCESS_DENIED).isEqualTo(voter.vote(createRememberMe(), null, def));
assertThat(AccessDecisionVoter.ACCESS_GRANTED).isEqualTo(voter.vote(createFullyAuthenticated(), null, def));
assertThat(AccessDecisionVoter.ACCESS_DENIED).isEqualTo(voter.vote(null, null, def));
}
@Test
@@ -77,6 +79,7 @@ public class AuthenticatedVoterTests {
assertThat(AccessDecisionVoter.ACCESS_DENIED).isEqualTo(voter.vote(createAnonymous(), null, def));
assertThat(AccessDecisionVoter.ACCESS_GRANTED).isEqualTo(voter.vote(createRememberMe(), null, def));
assertThat(AccessDecisionVoter.ACCESS_GRANTED).isEqualTo(voter.vote(createFullyAuthenticated(), null, def));
assertThat(AccessDecisionVoter.ACCESS_DENIED).isEqualTo(voter.vote(null, null, def));
}
@Test
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -41,6 +41,15 @@ public class AuthorityAuthorizationManagerTests {
.withMessage("role cannot be null");
}
@Test
public void hasRoleWhenContainRoleWithRolePrefixThenException() {
String ROLE_PREFIX = "ROLE_";
String ROLE_USER = ROLE_PREFIX + "USER";
assertThatIllegalArgumentException().isThrownBy(() -> AuthorityAuthorizationManager.hasRole(ROLE_USER))
.withMessage(ROLE_USER + " should not start with " + ROLE_PREFIX + " since " + ROLE_PREFIX
+ " is automatically prepended when using hasRole. Consider using hasAuthority instead.");
}
@Test
public void hasAuthorityWhenNullThenException() {
assertThatIllegalArgumentException().isThrownBy(() -> AuthorityAuthorizationManager.hasAuthority(null))
@@ -73,6 +82,16 @@ public class AuthorityAuthorizationManagerTests {
.withMessage("rolePrefix cannot be null");
}
@Test
public void hasAnyRoleWhenContainRoleWithRolePrefixThenException() {
String ROLE_PREFIX = "ROLE_";
String ROLE_USER = ROLE_PREFIX + "USER";
assertThatIllegalArgumentException()
.isThrownBy(() -> AuthorityAuthorizationManager.hasAnyRole(new String[] { ROLE_USER }))
.withMessage(ROLE_USER + " should not start with " + ROLE_PREFIX + " since " + ROLE_PREFIX
+ " is automatically prepended when using hasAnyRole. Consider using hasAnyAuthority instead.");
}
@Test
public void hasAnyAuthorityWhenNullThenException() {
assertThatIllegalArgumentException().isThrownBy(() -> AuthorityAuthorizationManager.hasAnyAuthority(null))
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.security.crypto.password;
import java.security.MessageDigest;
import java.util.Base64;
import java.util.Locale;
import org.springframework.security.crypto.codec.Utf8;
import org.springframework.security.crypto.keygen.BytesKeyGenerator;
@@ -50,11 +51,11 @@ public class LdapShaPasswordEncoder implements PasswordEncoder {
private static final String SSHA_PREFIX = "{SSHA}";
private static final String SSHA_PREFIX_LC = SSHA_PREFIX.toLowerCase();
private static final String SSHA_PREFIX_LC = SSHA_PREFIX.toLowerCase(Locale.ENGLISH);
private static final String SHA_PREFIX = "{SHA}";
private static final String SHA_PREFIX_LC = SHA_PREFIX.toLowerCase();
private static final String SHA_PREFIX_LC = SHA_PREFIX.toLowerCase(Locale.ENGLISH);
private BytesKeyGenerator saltGenerator;
+10 -10
View File
@@ -8,15 +8,15 @@ javaPlatform {
dependencies {
api platform("org.springframework:spring-framework-bom:$springFrameworkVersion")
api platform("io.projectreactor:reactor-bom:2020.0.24")
api platform("io.rsocket:rsocket-bom:1.1.3")
api platform("io.projectreactor:reactor-bom:2020.0.34")
api platform("io.rsocket:rsocket-bom:1.1.4")
api platform("org.junit:junit-bom:5.8.2")
api platform("org.springframework.data:spring-data-bom:2021.2.4")
api platform("org.springframework.data:spring-data-bom:2021.2.14")
api platform("org.jetbrains.kotlin:kotlin-bom:$kotlinVersion")
api platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.6.4")
api platform("com.fasterxml.jackson:jackson-bom:2.13.4.20221012")
api platform("com.fasterxml.jackson:jackson-bom:2.13.5")
constraints {
api "ch.qos.logback:logback-classic:1.2.11"
api "ch.qos.logback:logback-classic:1.2.12"
api "com.google.inject:guice:3.0"
api "com.nimbusds:nimbus-jose-jwt:9.22"
api "com.nimbusds:oauth2-oidc-sdk:9.35"
@@ -26,7 +26,7 @@ dependencies {
api "commons-codec:commons-codec:1.15"
api "commons-collections:commons-collections:3.2.2"
api "io.mockk:mockk:1.12.8"
api "io.projectreactor.tools:blockhound:1.0.6.RELEASE"
api "io.projectreactor.tools:blockhound:1.0.8.RELEASE"
api "jakarta.inject:jakarta.inject-api:1.0.5"
api "jakarta.annotation:jakarta.annotation-api:1.3.5"
api "jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api:1.2.7"
@@ -44,17 +44,17 @@ dependencies {
api "org.apache.directory.server:apacheds-protocol-shared:1.5.5"
api "org.apache.directory.server:apacheds-server-jndi:1.5.5"
api "org.apache.directory.shared:shared-ldap:0.9.15"
api "org.apache.httpcomponents:httpclient:4.5.13"
api "org.apache.httpcomponents:httpclient:4.5.14"
api "org.aspectj:aspectjrt:$aspectjVersion"
api "org.aspectj:aspectjweaver:$aspectjVersion"
api "org.assertj:assertj-core:3.22.0"
api "org.bouncycastle:bcpkix-jdk15on:1.70"
api "org.bouncycastle:bcprov-jdk15on:1.70"
api "org.eclipse.jetty:jetty-server:9.4.49.v20220914"
api "org.eclipse.jetty:jetty-servlet:9.4.49.v20220914"
api "org.eclipse.jetty:jetty-server:9.4.51.v20230217"
api "org.eclipse.jetty:jetty-servlet:9.4.51.v20230217"
api "org.eclipse.persistence:javax.persistence:2.2.1"
api "org.hamcrest:hamcrest:2.2"
api "org.hibernate:hibernate-entitymanager:5.6.12.Final"
api "org.hibernate:hibernate-entitymanager:5.6.15.Final"
api "org.hsqldb:hsqldb:2.6.1"
api "org.jasig.cas.client:cas-client-core:3.6.4"
api "org.mockito:mockito-core:3.12.4"
+1 -2
View File
@@ -1,2 +1 @@
/package-lock.json
/node_modules/
/*-antora-playbook.yml
-53
View File
@@ -1,53 +0,0 @@
'use strict'
/* Copyright (c) 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.
*/
const { promises: fsp } = require('fs')
const ospath = require('path')
/**
* Rewrites local content sources to support the use of linked worktrees.
*
* @author Dan Allen <dan@opendevise.com>
*/
module.exports.register = function () {
this.once('playbookBuilt', async ({ playbook }) => {
const expandPath = this.require('@antora/expand-path-helper')
for (const contentSource of playbook.content.sources) {
const { url, branches } = contentSource
if (url.charAt() !== '.') continue
const absdir = expandPath(url, { dot: playbook.dir })
const gitfile = ospath.join(absdir, '.git')
if (await fsp.stat(gitfile).then((stat) => !stat.isDirectory(), () => false)) {
const worktreeGitdir = await fsp.readFile(gitfile, 'utf8')
.then((contents) => contents.trimRight().substr(8))
const worktreeBranch = await fsp.readFile(ospath.join(worktreeGitdir, 'HEAD'), 'utf8')
.then((contents) => contents.trimRight().replace(/^ref: (?:refs\/heads\/)?/, ''))
const reldir = ospath.relative(
playbook.dir,
await fsp.readFile(ospath.join(worktreeGitdir, 'commondir'), 'utf8')
.then((contents) => {
const gitdir = ospath.join(worktreeGitdir, contents.trimRight())
return ospath.basename(gitdir) === '.git' ? ospath.dirname(gitdir) : gitdir
})
)
contentSource.url = reldir ? `.${ospath.sep}${reldir}` : '.'
if (!branches) continue
contentSource.branches = (branches.constructor === Array ? branches : [branches])
.map((pattern) => pattern.replaceAll('HEAD', worktreeBranch))
}
}
})
}
+37
View File
@@ -0,0 +1,37 @@
antora:
extensions:
- require: '@springio/antora-extensions'
root_component_name: 'security'
site:
title: Spring Security
url: https://docs.spring.io/spring-security/reference
robots: allow
git:
ensure_git_suffix: false
content:
sources:
- url: https://github.com/spring-projects/spring-security
branches: [main, '5.{{6..9},{1..9}+({0..9})}.x', '6.+({0..9}).x']
tags: ['5.{{6..9},{1..9}+({0..9})}.{0..99}?(-RC+({0..9}))', '6.+({0..9}).+({0..9})?(-{RC,M}*)','!(5.6.{0..10}*)', '!(5.7.{0..8}*)', '!(5.8.{0..3}?({-RC,-M}+({0..9})))','!(6.0.{0..3}*)','!(6.1.0*)']
start_path: docs
asciidoc:
attributes:
page-stackoverflow-url: https://stackoverflow.com/tags/spring-security
page-related-doc-categories: security
page-related-doc-projects: framework,graphql
hide-uri-scheme: '@'
tabs-sync-option: '@'
extensions:
- '@asciidoctor/tabs'
- '@springio/asciidoctor-extensions'
urls:
latest_version_segment_strategy: redirect:to
latest_version_segment: ''
redirect_facility: httpd
ui:
bundle:
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.11/ui-bundle.zip
snapshot: true
runtime:
log:
failure_level: warn
+10 -4
View File
@@ -1,12 +1,18 @@
name: ROOT
name: security
version: true
title: Documentation
title: Spring Security
nav:
- modules/ROOT/nav.adoc
ext:
collector:
run:
command: gradlew -q -PbuildSrc.skipTests=true "-Dorg.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError" :spring-security-docs:generateAntora
command: gradlew -q -PbuildSrc.skipTests=true "-Dorg.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError" :spring-security-docs:generateAntoraYml
local: true
scan:
dir: ./build/generateAntora
dir: ./build/generated-antora-resources
asciidoc:
attributes:
icondir: icons
gh-old-samples-url: 'https://github.com/spring-projects/spring-security/tree/5.4.x/samples'
gh-samples-url: "https://github.com/spring-projects/spring-security-samples/tree/{gh-tag}"
gh-url: "https://github.com/spring-projects/spring-security/tree/{gh-tag}"
-24
View File
@@ -1,24 +0,0 @@
# The purpose of this Antora playbook is to generate a preview of the docs in the current branch.
antora:
extensions:
- ./antora-linked-worktree-patch.js
- '@antora/collector-extension'
site:
title: Spring Security
url: https://docs.spring.io/spring-security/reference
content:
sources:
- url: ./..
branches: HEAD
start_path: docs
worktrees: true
asciidoc:
attributes:
page-pagination: ''
hide-uri-scheme: '@'
urls:
latest_version_segment: ''
ui:
bundle:
url: https://github.com/spring-io/antora-ui-spring/releases/download/latest/ui-bundle.zip
snapshot: true
Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 143 KiB

+2
View File
@@ -119,6 +119,7 @@
**** xref:servlet/appendix/namespace/method-security.adoc[Method Security]
**** xref:servlet/appendix/namespace/ldap.adoc[LDAP Security]
**** xref:servlet/appendix/namespace/websocket.adoc[WebSocket Security]
*** xref:servlet/appendix/proxy-server.adoc[Proxy Server Configuration]
*** xref:servlet/appendix/faq.adoc[FAQ]
* xref:reactive/index.adoc[Reactive Applications]
** xref:reactive/getting-started.adoc[Getting Started]
@@ -146,6 +147,7 @@
*** xref:reactive/exploits/csrf.adoc[CSRF]
*** xref:reactive/exploits/headers.adoc[Headers]
*** xref:reactive/exploits/http.adoc[HTTP Requests]
*** xref:reactive/exploits/firewall.adoc[]
** Integrations
*** xref:reactive/integrations/cors.adoc[CORS]
*** xref:reactive/integrations/rsocket.adoc[RSocket]
@@ -67,26 +67,31 @@ Instead Spring Security introduces `DelegatingPasswordEncoder` which solves all
You can easily construct an instance of `DelegatingPasswordEncoder` using `PasswordEncoderFactories`.
.Create Default DelegatingPasswordEncoder
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
PasswordEncoder passwordEncoder =
PasswordEncoderFactories.createDelegatingPasswordEncoder();
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
val passwordEncoder: PasswordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder()
----
====
======
Alternatively, you may create your own custom instance. For example:
.Create Custom DelegatingPasswordEncoder
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
String idForEncode = "bcrypt";
@@ -101,7 +106,8 @@ PasswordEncoder passwordEncoder =
new DelegatingPasswordEncoder(idForEncode, encoders);
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
val idForEncode = "bcrypt"
@@ -114,7 +120,7 @@ encoders["sha256"] = StandardPasswordEncoder()
val passwordEncoder: PasswordEncoder = DelegatingPasswordEncoder(idForEncode, encoders)
----
====
======
[[authentication-password-storage-dpe-format]]
=== Password Storage Format
@@ -122,12 +128,10 @@ val passwordEncoder: PasswordEncoder = DelegatingPasswordEncoder(idForEncode, en
The general format for a password is:
.DelegatingPasswordEncoder Storage Format
====
[source,text,attrs="-attributes"]
----
{id}encodedPassword
----
====
Such that `id` is an identifier used to look up which `PasswordEncoder` should be used and `encodedPassword` is the original encoded password for the selected `PasswordEncoder`.
The `id` must be at the beginning of the password, start with `{` and end with `}`.
@@ -136,7 +140,6 @@ For example, the following might be a list of passwords encoded using different
All of the original passwords are "password".
.DelegatingPasswordEncoder Encoded Passwords Example
====
[source,text,attrs="-attributes"]
----
{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG // <1>
@@ -145,7 +148,6 @@ All of the original passwords are "password".
{scrypt}$e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc= // <4>
{sha256}97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0 // <5>
----
====
<1> The first password would have a `PasswordEncoder` id of `bcrypt` and encodedPassword of `$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG`.
When matching it would delegate to `BCryptPasswordEncoder`
@@ -174,12 +176,10 @@ In the `DelegatingPasswordEncoder` we constructed above, that means that the res
The end result would look like:
.DelegatingPasswordEncoder Encode Example
====
[source,text,attrs="-attributes"]
----
{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
----
====
[[authentication-password-storage-dpe-matching]]
=== Password Matching
@@ -201,8 +201,10 @@ If you are putting together a demo or a sample, it is a bit cumbersome to take t
There are convenience mechanisms to make this easier, but this is still not intended for production.
.withDefaultPasswordEncoder Example
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary",attrs="-attributes"]
----
User user = User.withDefaultPasswordEncoder()
@@ -214,7 +216,8 @@ System.out.println(user.getPassword());
// {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary",attrs="-attributes"]
----
val user = User.withDefaultPasswordEncoder()
@@ -225,13 +228,15 @@ val user = User.withDefaultPasswordEncoder()
println(user.password)
// {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
----
====
======
If you are creating multiple users, you can also reuse the builder.
.withDefaultPasswordEncoder Reusing the Builder
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
UserBuilder users = User.withDefaultPasswordEncoder();
@@ -247,7 +252,8 @@ User admin = users
.build();
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
val users = User.withDefaultPasswordEncoder()
@@ -262,7 +268,7 @@ val admin = users
.roles("USER", "ADMIN")
.build()
----
====
======
This does hash the password that is stored, but the passwords are still exposed in memory and in the compiled source code.
Therefore, it is still not considered secure for a production environment.
@@ -276,13 +282,11 @@ The easiest way to properly encode your password is to use the https://docs.spri
For example, the following will encode the password of `password` for use with <<authentication-password-storage-dpe>>:
.Spring Boot CLI encodepassword Example
====
[source,attrs="-attributes"]
----
spring encodepassword password
{bcrypt}$2a$10$X5wFBtLrL/kHcmrOGGTrGufsBX8CJ0WpQpF3pgeuxBB/H73BK1DW6
----
====
[[authentication-password-storage-dpe-troubleshoot]]
=== Troubleshooting
@@ -328,8 +332,10 @@ The default implementation of `BCryptPasswordEncoder` uses strength 10 as mentio
tune and test the strength parameter on your own system so that it takes roughly 1 second to verify a password.
.BCryptPasswordEncoder
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
// Create an encoder with strength 16
@@ -338,7 +344,8 @@ String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
// Create an encoder with strength 16
@@ -346,7 +353,7 @@ val encoder = BCryptPasswordEncoder(16)
val result: String = encoder.encode("myPassword")
assertTrue(encoder.matches("myPassword", result))
----
====
======
[[authentication-password-storage-argon2]]
== Argon2PasswordEncoder
@@ -358,8 +365,10 @@ Like other adaptive one-way functions, it should be tuned to take about 1 second
The current implementation of the `Argon2PasswordEncoder` requires BouncyCastle.
.Argon2PasswordEncoder
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
// Create an encoder with all the defaults
@@ -368,7 +377,8 @@ String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
// Create an encoder with all the defaults
@@ -376,7 +386,7 @@ val encoder = Argon2PasswordEncoder()
val result: String = encoder.encode("myPassword")
assertTrue(encoder.matches("myPassword", result))
----
====
======
[[authentication-password-storage-pbkdf2]]
== Pbkdf2PasswordEncoder
@@ -387,8 +397,10 @@ Like other adaptive one-way functions, it should be tuned to take about 1 second
This algorithm is a good choice when FIPS certification is required.
.Pbkdf2PasswordEncoder
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
// Create an encoder with all the defaults
@@ -397,7 +409,8 @@ String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
// Create an encoder with all the defaults
@@ -405,7 +418,7 @@ val encoder = Pbkdf2PasswordEncoder()
val result: String = encoder.encode("myPassword")
assertTrue(encoder.matches("myPassword", result))
----
====
======
[[authentication-password-storage-scrypt]]
== SCryptPasswordEncoder
@@ -415,8 +428,10 @@ In order to defeat password cracking on custom hardware scrypt is a deliberately
Like other adaptive one-way functions, it should be tuned to take about 1 second to verify a password on your system.
.SCryptPasswordEncoder
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
// Create an encoder with all the defaults
@@ -425,7 +440,8 @@ String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
// Create an encoder with all the defaults
@@ -433,7 +449,7 @@ val encoder = SCryptPasswordEncoder()
val result: String = encoder.encode("myPassword")
assertTrue(encoder.matches("myPassword", result))
----
====
======
[[authentication-password-storage-other]]
== Other PasswordEncoders
@@ -458,8 +474,10 @@ You should instead migrate to using `DelegatingPasswordEncoder` to support secur
====
.NoOpPasswordEncoder
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -468,14 +486,16 @@ public static PasswordEncoder passwordEncoder() {
}
----
.XML
XML::
+
[source,xml,role="secondary"]
----
<b:bean id="passwordEncoder"
class="org.springframework.security.crypto.password.NoOpPasswordEncoder" factory-method="getInstance"/>
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -483,7 +503,7 @@ fun passwordEncoder(): PasswordEncoder {
return NoOpPasswordEncoder.getInstance();
}
----
====
======
[NOTE]
====
@@ -501,36 +521,42 @@ You can configure Spring Security to provide this discovery endpoint.
For example, if the change password endpoint in your application is `/change-password`, then you can configure Spring Security like so:
.Default Change Password Endpoint
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
http
.passwordManagement(Customizer.withDefaults())
----
.XML
XML::
+
[source,xml,role="secondary"]
----
<sec:password-management/>
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
http {
passwordManagement { }
}
----
====
======
Then, when a password manager navigates to `/.well-known/change-password` then Spring Security will redirect your endpoint, `/change-password`.
Or, if your endpoint is something other than `/change-password`, you can also specify that like so:
.Change Password Endpoint
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
http
@@ -539,13 +565,15 @@ http
)
----
.XML
XML::
+
[source,xml,role="secondary"]
----
<sec:password-management change-password-page="/update-password"/>
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
http {
@@ -554,6 +582,6 @@ http {
}
}
----
====
======
With the above configuration, when a password manager navigates to `/.well-known/change-password`, then Spring Security will redirect to `/update-password`.
@@ -25,7 +25,6 @@ Assume that your bank's website provides a form that allows transferring money f
For example, the transfer form might look like:
.Transfer form
====
[source,html]
----
<form method="post"
@@ -40,12 +39,10 @@ For example, the transfer form might look like:
value="Transfer"/>
</form>
----
====
The corresponding HTTP request might look like:
.Transfer HTTP request
====
[source]
----
POST /transfer HTTP/1.1
@@ -55,13 +52,11 @@ Content-Type: application/x-www-form-urlencoded
amount=100.00&routingNumber=1234&account=9876
----
====
Now pretend you authenticate to your bank's website and then, without logging out, visit an evil website.
The evil website contains an HTML page with the following form:
.Evil transfer form
====
[source,html]
----
<form method="post"
@@ -79,7 +74,6 @@ The evil website contains an HTML page with the following form:
value="Win Money!"/>
</form>
----
====
You like to win money, so you click on the submit button.
In the process, you have unintentionally transferred $100 to a malicious user.
@@ -134,7 +128,6 @@ Assume the actual CSRF token is required to be in an HTTP parameter named `_csrf
Our application's transfer form would look like:
.Synchronizer Token Form
====
[source,html]
----
<form method="post"
@@ -152,7 +145,6 @@ Our application's transfer form would look like:
value="Transfer"/>
</form>
----
====
The form now contains a hidden input with the value of the CSRF token.
External sites cannot read the CSRF token since the same origin policy ensures the evil site cannot read the response.
@@ -160,7 +152,6 @@ External sites cannot read the CSRF token since the same origin policy ensures t
The corresponding HTTP request to transfer money would look like this:
.Synchronizer Token request
====
[source]
----
POST /transfer HTTP/1.1
@@ -170,7 +161,6 @@ Content-Type: application/x-www-form-urlencoded
amount=100.00&routingNumber=1234&account=9876&_csrf=4bfd1575-3ad1-4d21-96c7-4ef2d9f86721
----
====
You will notice that the HTTP request now contains the `_csrf` parameter with a secure random value.
@@ -191,12 +181,10 @@ Spring Framework's https://docs.spring.io/spring-framework/docs/current/javadoc-
An example, HTTP response header with the `SameSite` attribute might look like:
.SameSite HTTP response
====
[source]
----
Set-Cookie: JSESSIONID=randomid; Domain=bank.example.com; Secure; HttpOnly; SameSite=Lax
----
====
Valid values for the `SameSite` attribute are:
@@ -245,7 +233,6 @@ However, you must be very careful as there are CSRF exploits that can impact JSO
For example, a malicious user can create a http://blog.opensecurityresearch.com/2012/02/json-csrf-with-parameter-padding.html[CSRF with JSON using the following form]:
.CSRF with JSON form
====
[source,html]
----
<form action="https://bank.example.com/transfer" method="post" enctype="text/plain">
@@ -254,13 +241,11 @@ For example, a malicious user can create a http://blog.opensecurityresearch.com/
value="Win Money!"/>
</form>
----
====
This will produce the following JSON structure
.CSRF with JSON request
====
[source,javascript]
----
{ "amount": 100,
@@ -269,13 +254,11 @@ This will produce the following JSON structure
"ignore_me": "=test"
}
----
====
If an application were not validating the Content-Type, then it would be exposed to this exploit.
Depending on the setup, a Spring MVC application that validates the Content-Type could still be exploited by updating the URL suffix to end with `.json` as shown below:
.CSRF with JSON Spring MVC form
====
[source,html]
----
<form action="https://bank.example.com/transfer.json" method="post" enctype="text/plain">
@@ -284,7 +267,6 @@ Depending on the setup, a Spring MVC application that validates the Content-Type
value="Win Money!"/>
</form>
----
====
[[csrf-when-stateless]]
=== CSRF and Stateless Browser Applications
@@ -393,7 +375,6 @@ In some applications a form parameter can be used to override the HTTP method.
For example, the form below could be used to treat the HTTP method as a `delete` rather than a `post`.
.CSRF Hidden HTTP Method Form
====
[source,html]
----
<form action="/process"
@@ -404,7 +385,6 @@ For example, the form below could be used to treat the HTTP method as a `delete`
value="delete"/>
</form>
----
====
Overriding the HTTP method occurs in a filter.
@@ -24,7 +24,6 @@ Spring Security provides a default set of security related HTTP response headers
The default for Spring Security is to include the following headers:
.Default Security HTTP Response Headers
====
[source,http]
----
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
@@ -35,7 +34,6 @@ Strict-Transport-Security: max-age=31536000 ; includeSubDomains
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
----
====
NOTE: Strict-Transport-Security is only added on HTTPS requests
@@ -62,14 +60,12 @@ If a user authenticates to view sensitive information and then logs out, we don'
The cache control headers that are sent by default are:
.Default Cache Control HTTP Response Headers
====
[source]
----
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
----
====
In order to be secure by default, Spring Security adds these headers by default.
However, if your application provides its own cache control headers Spring Security will back out of the way.
@@ -102,12 +98,10 @@ A malicious user might create a http://webblaze.cs.berkeley.edu/papers/barth-cab
Spring Security disables content sniffing by default by adding the following header to HTTP responses:
.nosniff HTTP Response Header
====
[source,http]
----
X-Content-Type-Options: nosniff
----
====
[[headers-hsts]]
== HTTP Strict Transport Security (HSTS)
@@ -137,12 +131,10 @@ For example, Spring Security's default behavior is to add the following header w
.Strict Transport Security HTTP Response Header
====
[source]
----
Strict-Transport-Security: max-age=31536000 ; includeSubDomains ; preload
----
====
The optional `includeSubDomains` directive instructs the browser that subdomains (e.g. secure.mybank.example.com) should also be treated as an HSTS domain.
@@ -247,12 +239,10 @@ A security policy contains a set of security policy directives, each responsible
For example, a web application can declare that it expects to load scripts from specific, trusted sources, by including the following header in the response:
.Content Security Policy Example
====
[source]
----
Content-Security-Policy: script-src https://trustedscripts.example.com
----
====
An attempt to load a script from another source other than what is declared in the `script-src` directive will be blocked by the user-agent.
Additionally, if the https://www.w3.org/TR/CSP2/#directive-report-uri[report-uri] directive is declared in the security policy, then the violation will be reported by the user-agent to the declared URL.
@@ -260,12 +250,10 @@ Additionally, if the https://www.w3.org/TR/CSP2/#directive-report-uri[report-uri
For example, if a web application violates the declared security policy, the following response header will instruct the user-agent to send violation reports to the URL specified in the policy's `report-uri` directive.
.Content Security Policy with report-uri
====
[source]
----
Content-Security-Policy: script-src https://trustedscripts.example.com; report-uri /csp-report-endpoint/
----
====
https://www.w3.org/TR/CSP2/#violation-reports[Violation reports] are standard JSON structures that can be captured either by the web application's own API or by a publicly hosted CSP violation reporting service, such as, https://report-uri.com/.
@@ -276,12 +264,10 @@ When a policy is deemed effective, it can be enforced by using the `Content-Secu
Given the following response header, the policy declares that scripts may be loaded from one of two possible sources.
.Content Security Policy Report Only
====
[source]
----
Content-Security-Policy-Report-Only: script-src 'self' https://trustedscripts.example.com; report-uri /csp-report-endpoint/
----
====
If the site violates this policy, by attempting to load a script from _evil.com_, the user-agent will send a violation report to the declared URL specified by the _report-uri_ directive, but still allow the violating resource to load nevertheless.
@@ -308,12 +294,10 @@ page the user was on.
Spring Security's approach is to use https://www.w3.org/TR/referrer-policy/[Referrer Policy] header, which provides different https://www.w3.org/TR/referrer-policy/#referrer-policies[policies]:
.Referrer Policy Example
====
[source]
----
Referrer-Policy: same-origin
----
====
The Referrer-Policy response header instructs the browser to let the destination knows the source where the user was previously.
@@ -328,12 +312,10 @@ Refer to the relevant sections to see how to configure both xref:servlet/exploit
https://wicg.github.io/feature-policy/[Feature Policy] is a mechanism that allows web developers to selectively enable, disable, and modify the behavior of certain APIs and web features in the browser.
.Feature Policy Example
====
[source]
----
Feature-Policy: geolocation 'self'
----
====
With Feature Policy, developers can opt-in to a set of "policies" for the browser to enforce on specific features used throughout your site.
These policies restrict what APIs the site can access or modify the browser's default behavior for certain features.
@@ -350,12 +332,10 @@ Refer to the relevant sections to see how to configure both xref:servlet/exploit
https://w3c.github.io/webappsec-permissions-policy/[Permissions Policy] is a mechanism that allows web developers to selectively enable, disable, and modify the behavior of certain APIs and web features in the browser.
.Permissions Policy Example
====
[source]
----
Permissions-Policy: geolocation=(self)
----
====
With Permissions Policy, developers can opt-in to a set of "policies" for the browser to enforce on specific features used throughout your site.
These policies restrict what APIs the site can access or modify the browser's default behavior for certain features.
@@ -14,8 +14,10 @@ It wraps a delegate `Runnable` in order to initialize the `SecurityContextHolder
It then invokes the delegate Runnable ensuring to clear the `SecurityContextHolder` afterwards.
The `DelegatingSecurityContextRunnable` looks something like this:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
public void run() {
@@ -28,7 +30,8 @@ try {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
fun run() {
@@ -40,7 +43,7 @@ fun run() {
}
}
----
====
======
While very simple, it makes it seamless to transfer the SecurityContext from one Thread to another.
This is important since, in most cases, the SecurityContextHolder acts on a per Thread basis.
@@ -48,8 +51,10 @@ For example, you might have used Spring Security's xref:servlet/appendix/namespa
You can now easily transfer the `SecurityContext` of the current `Thread` to the `Thread` that invokes the secured service.
An example of how you might do this can be found below:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
Runnable originalRunnable = new Runnable() {
@@ -65,7 +70,8 @@ DelegatingSecurityContextRunnable wrappedRunnable =
new Thread(wrappedRunnable).start();
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
val originalRunnable = Runnable {
@@ -76,7 +82,7 @@ val wrappedRunnable = DelegatingSecurityContextRunnable(originalRunnable, contex
Thread(wrappedRunnable).start()
----
====
======
The code above performs the following steps:
@@ -90,8 +96,10 @@ Since it is quite common to create a `DelegatingSecurityContextRunnable` with th
The following code is the same as the code above:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
Runnable originalRunnable = new Runnable() {
@@ -106,7 +114,8 @@ DelegatingSecurityContextRunnable wrappedRunnable =
new Thread(wrappedRunnable).start();
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
val originalRunnable = Runnable {
@@ -117,7 +126,7 @@ val wrappedRunnable = DelegatingSecurityContextRunnable(originalRunnable)
Thread(wrappedRunnable).start()
----
====
======
The code we have is simple to use, but it still requires knowledge that we are using Spring Security.
In the next section we will take a look at how we can utilize `DelegatingSecurityContextExecutor` to hide the fact that we are using Spring Security.
@@ -131,8 +140,10 @@ The design of `DelegatingSecurityContextExecutor` is very similar to that of `De
You can see an example of how it might be used below:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
SecurityContext context = SecurityContextHolder.createEmptyContext();
@@ -154,7 +165,8 @@ public void run() {
executor.execute(originalRunnable);
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
val context: SecurityContext = SecurityContextHolder.createEmptyContext()
@@ -171,7 +183,7 @@ val originalRunnable = Runnable {
executor.execute(originalRunnable)
----
====
======
The code performs the following steps:
@@ -185,8 +197,10 @@ In this instance, the same `SecurityContext` will be used for every Runnable sub
This is nice if we are running background tasks that need to be run by a user with elevated privileges.
* At this point you may be asking yourself "How does this shield my code of any knowledge of Spring Security?" Instead of creating the `SecurityContext` and the `DelegatingSecurityContextExecutor` in our own code, we can inject an already initialized instance of `DelegatingSecurityContextExecutor`.
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Autowired
@@ -202,7 +216,8 @@ executor.execute(originalRunnable);
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Autowired
@@ -215,7 +230,7 @@ fun submitRunnable() {
executor.execute(originalRunnable)
}
----
====
======
Now our code is unaware that the `SecurityContext` is being propagated to the `Thread`, then the `originalRunnable` is run, and then the `SecurityContextHolder` is cleared out.
In this example, the same user is being used to run each thread.
@@ -224,8 +239,10 @@ This can be done by removing the `SecurityContext` argument from our `Delegating
For example:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
SimpleAsyncTaskExecutor delegateExecutor = new SimpleAsyncTaskExecutor();
@@ -233,13 +250,14 @@ DelegatingSecurityContextExecutor executor =
new DelegatingSecurityContextExecutor(delegateExecutor);
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
val delegateExecutor = SimpleAsyncTaskExecutor()
val executor = DelegatingSecurityContextExecutor(delegateExecutor)
----
====
======
Now anytime `executor.execute(Runnable)` is executed the `SecurityContext` is first obtained by the `SecurityContextHolder` and then that `SecurityContext` is used to create our `DelegatingSecurityContextRunnable`.
This means that we are running our `Runnable` with the same user that was used to invoke the `executor.execute(Runnable)` code.
@@ -20,19 +20,22 @@ Encryptors are thread-safe.
Use the `Encryptors.stronger` factory method to construct a BytesEncryptor:
.BytesEncryptor
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
Encryptors.stronger("password", "salt");
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
Encryptors.stronger("password", "salt")
----
====
======
The "stronger" encryption method creates an encryptor using 256 bit AES encryption with
Galois Counter Mode (GCM).
@@ -46,19 +49,22 @@ The provided salt should be in hex-encoded String form, be random, and be at lea
Such a salt may be generated using a KeyGenerator:
.Generating a key
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
String salt = KeyGenerators.string().generateKey(); // generates a random 8-byte salt that is then hex-encoded
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
val salt = KeyGenerators.string().generateKey() // generates a random 8-byte salt that is then hex-encoded
----
====
======
Users may also use the `standard` encryption method, which is 256-bit AES in Cipher Block Chaining (CBC) Mode.
This mode is not https://en.wikipedia.org/wiki/Authenticated_encryption[authenticated] and does not provide any
@@ -70,19 +76,22 @@ For a more secure alternative, users should prefer `Encryptors.stronger`.
Use the Encryptors.text factory method to construct a standard TextEncryptor:
.TextEncryptor
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
Encryptors.text("password", "salt");
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
Encryptors.text("password", "salt")
----
====
======
A TextEncryptor uses a standard BytesEncryptor to encrypt text data.
Encrypted results are returned as hex-encoded strings for easy storage on the filesystem or in the database.
@@ -90,19 +99,22 @@ Encrypted results are returned as hex-encoded strings for easy storage on the fi
Use the Encryptors.queryableText factory method to construct a "queryable" TextEncryptor:
.Queryable TextEncryptor
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
Encryptors.queryableText("password", "salt");
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
Encryptors.queryableText("password", "salt")
----
====
======
The difference between a queryable TextEncryptor and a standard TextEncryptor has to do with initialization vector (iv) handling.
The iv used in a queryable TextEncryptor#encrypt operation is shared, or constant, and is not randomly generated.
@@ -121,74 +133,86 @@ KeyGenerators are thread-safe.
Use the KeyGenerators.secureRandom factory methods to generate a BytesKeyGenerator backed by a SecureRandom instance:
.BytesKeyGenerator
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
BytesKeyGenerator generator = KeyGenerators.secureRandom();
byte[] key = generator.generateKey();
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
val generator = KeyGenerators.secureRandom()
val key = generator.generateKey()
----
====
======
The default key length is 8 bytes.
There is also a KeyGenerators.secureRandom variant that provides control over the key length:
.KeyGenerators.secureRandom
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
KeyGenerators.secureRandom(16);
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
KeyGenerators.secureRandom(16)
----
====
======
Use the KeyGenerators.shared factory method to construct a BytesKeyGenerator that always returns the same key on every invocation:
.KeyGenerators.shared
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
KeyGenerators.shared(16);
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
KeyGenerators.shared(16)
----
====
======
=== StringKeyGenerator
Use the KeyGenerators.string factory method to construct a 8-byte, SecureRandom KeyGenerator that hex-encodes each key as a String:
.StringKeyGenerator
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
KeyGenerators.string();
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
KeyGenerators.string()
----
====
======
[[spring-security-crypto-passwordencoders]]
== Password Encoding
@@ -219,8 +243,10 @@ The default value is 10.
You can change this value in your deployed system without affecting existing passwords, as the value is also stored in the encoded hash.
.BCryptPasswordEncoder
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@@ -230,7 +256,8 @@ String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@@ -239,15 +266,17 @@ val encoder = BCryptPasswordEncoder(16)
val result: String = encoder.encode("myPassword")
assertTrue(encoder.matches("myPassword", result))
----
====
======
The `Pbkdf2PasswordEncoder` implementation uses PBKDF2 algorithm to hash the passwords.
In order to defeat password cracking PBKDF2 is a deliberately slow algorithm and should be tuned to take about .5 seconds to verify a password on your system.
.Pbkdf2PasswordEncoder
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
// Create an encoder with all the defaults
@@ -256,7 +285,8 @@ String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
// Create an encoder with all the defaults
@@ -264,4 +294,4 @@ val encoder = Pbkdf2PasswordEncoder()
val result: String = encoder.encode("myPassword")
assertTrue(encoder.matches("myPassword", result))
----
====
======
@@ -10,8 +10,10 @@ It is not only useful but necessary to include the user in the queries to suppor
To use this support, add `org.springframework.security:spring-security-data` dependency and provide a bean of type `SecurityEvaluationContextExtension`.
In Java Configuration, this would look like:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -20,7 +22,8 @@ public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -28,7 +31,7 @@ fun securityEvaluationContextExtension(): SecurityEvaluationContextExtension {
return SecurityEvaluationContextExtension()
}
----
====
======
In XML Configuration, this would look like:
@@ -43,8 +46,10 @@ In XML Configuration, this would look like:
Now Spring Security can be used within your queries.
For example:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Repository
@@ -54,7 +59,8 @@ public interface MessageRepository extends PagingAndSortingRepository<Message,Lo
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Repository
@@ -63,7 +69,7 @@ interface MessageRepository : PagingAndSortingRepository<Message?, Long?> {
fun findInbox(pageable: Pageable?): Page<Message?>?
}
----
====
======
This checks to see if the `Authentication.getPrincipal().getId()` is equal to the recipient of the `Message`.
Note that this example assumes you have customized the principal to be an Object that has an id property.
@@ -6,8 +6,10 @@ This can improve the performance of serializing Spring Security related classes
To use it, register the `SecurityJackson2Modules.getModules(ClassLoader)` with `ObjectMapper` (https://github.com/FasterXML/jackson-databind[jackson-databind]):
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
ObjectMapper mapper = new ObjectMapper();
@@ -21,7 +23,8 @@ SecurityContext context = new SecurityContextImpl();
String json = mapper.writeValueAsString(context);
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
val mapper = ObjectMapper()
@@ -34,7 +37,7 @@ val context: SecurityContext = SecurityContextImpl()
// ...
val json: String = mapper.writeValueAsString(context)
----
====
======
[NOTE]
====
@@ -30,7 +30,6 @@ Alternatively, you can manually add the starter, as the following example shows:
.pom.xml
====
[source,xml,subs="verbatim,attributes"]
----
<dependencies>
@@ -41,13 +40,11 @@ Alternatively, you can manually add the starter, as the following example shows:
</dependency>
</dependencies>
----
====
Since Spring Boot provides a Maven BOM to manage dependency versions, you do not need to specify a version.
If you wish to override the Spring Security version, you may do so by providing a Maven property, as the following example shows:
.pom.xml
====
[source,xml,subs="verbatim,attributes"]
----
<properties>
@@ -55,14 +52,12 @@ If you wish to override the Spring Security version, you may do so by providing
<spring-security.version>{spring-security-version}</spring-security.version>
</properties>
----
====
Since Spring Security makes breaking changes only in major releases, it is safe to use a newer version of Spring Security with Spring Boot.
However, at times, you may need to update the version of Spring Framework as well.
You can do so by adding a Maven property, as the following example shows:
.pom.xml
====
[source,xml,subs="verbatim,attributes"]
----
<properties>
@@ -70,7 +65,6 @@ You can do so by adding a Maven property, as the following example shows:
<spring.version>{spring-core-version}</spring.version>
</properties>
----
====
If you use additional features (such as LDAP, OpenID, and others), you need to also include the appropriate xref:modules.adoc#modules[Project Modules and Dependencies].
@@ -80,7 +74,6 @@ If you use additional features (such as LDAP, OpenID, and others), you need to a
When you use Spring Security without Spring Boot, the preferred way is to use Spring Security's BOM to ensure a consistent version of Spring Security is used throughout the entire project. The following example shows how to do so:
.pom.xml
====
[source,xml,ubs="verbatim,attributes"]
----
<dependencyManagement>
@@ -96,12 +89,10 @@ When you use Spring Security without Spring Boot, the preferred way is to use Sp
</dependencies>
</dependencyManagement>
----
====
A minimal Spring Security Maven set of dependencies typically looks like the following:
.pom.xml
====
[source,xml,subs="verbatim,attributes"]
----
<dependencies>
@@ -116,7 +107,6 @@ A minimal Spring Security Maven set of dependencies typically looks like the fol
</dependency>
</dependencies>
----
====
If you use additional features (such as LDAP, OpenID, and others), you need to also include the appropriate xref:modules.adoc#modules[Project Modules and Dependencies].
@@ -125,7 +115,6 @@ Many users are likely to run afoul of the fact that Spring Security's transitive
The easiest way to resolve this is to use the `spring-framework-bom` within the `<dependencyManagement>` section of your `pom.xml` as the following example shows:
.pom.xml
====
[source,xml,subs="verbatim,attributes"]
----
<dependencyManagement>
@@ -141,7 +130,6 @@ The easiest way to resolve this is to use the `spring-framework-bom` within the
</dependencies>
</dependencyManagement>
----
====
The preceding example ensures that all the transitive dependencies of Spring Security use the Spring {spring-core-version} modules.
@@ -155,7 +143,6 @@ All GA releases (that is, versions ending in .RELEASE) are deployed to Maven Cen
If you use a SNAPSHOT version, you need to ensure that you have the Spring Snapshot repository defined, as the following example shows:
.pom.xml
====
[source,xml]
----
<repositories>
@@ -167,12 +154,10 @@ If you use a SNAPSHOT version, you need to ensure that you have the Spring Snaps
</repository>
</repositories>
----
====
If you use a milestone or release candidate version, you need to ensure that you have the Spring Milestone repository defined, as the following example shows:
.pom.xml
====
[source,xml]
----
<repositories>
@@ -184,7 +169,6 @@ If you use a milestone or release candidate version, you need to ensure that you
</repository>
</repositories>
----
====
[[getting-gradle]]
== Gradle
@@ -201,7 +185,6 @@ The simplest and preferred method to use the starter is to use https://docs.spri
Alternatively, you can manually add the starter, as the following example shows:
.build.gradle
====
[source,groovy]
[subs="verbatim,attributes"]
----
@@ -209,32 +192,27 @@ dependencies {
compile "org.springframework.boot:spring-boot-starter-security"
}
----
====
Since Spring Boot provides a Maven BOM to manage dependency versions, you need not specify a version.
If you wish to override the Spring Security version, you may do so by providing a Gradle property, as the following example shows:
.build.gradle
====
[source,groovy]
[subs="verbatim,attributes"]
----
ext['spring-security.version']='{spring-security-version}'
----
====
Since Spring Security makes breaking changes only in major releases, it is safe to use a newer version of Spring Security with Spring Boot.
However, at times, you may need to update the version of Spring Framework as well.
You can do so by adding a Gradle property, as the following example shows:
.build.gradle
====
[source,groovy]
[subs="verbatim,attributes"]
----
ext['spring.version']='{spring-core-version}'
----
====
If you use additional features (such as LDAP, OpenID, and others), you need to also include the appropriate xref:modules.adoc#modules[Project Modules and Dependencies].
@@ -244,7 +222,6 @@ When you use Spring Security without Spring Boot, the preferred way is to use Sp
You can do so by using the https://github.com/spring-gradle-plugins/dependency-management-plugin[Dependency Management Plugin], as the following example shows:
.build.gradle
====
[source,groovy]
[subs="verbatim,attributes"]
----
@@ -258,12 +235,10 @@ dependencyManagement {
}
}
----
====
A minimal Spring Security Maven set of dependencies typically looks like the following:
.build.gradle
====
[source,groovy]
[subs="verbatim,attributes"]
----
@@ -272,7 +247,6 @@ dependencies {
compile "org.springframework.security:spring-security-config"
}
----
====
If you use additional features (such as LDAP, OpenID, and others), you need to also include the appropriate xref:modules.adoc#modules[Project Modules and Dependencies].
@@ -282,7 +256,6 @@ The easiest way to resolve this is to use the `spring-framework-bom` within your
You can do so by using the https://github.com/spring-gradle-plugins/dependency-management-plugin[Dependency Management Plugin], as the following example shows:
.build.gradle
====
[source,groovy]
[subs="verbatim,attributes"]
----
@@ -296,7 +269,6 @@ dependencyManagement {
}
}
----
====
The preceding example ensures that all the transitive dependencies of Spring Security use the Spring {spring-core-version} modules.
@@ -305,35 +277,29 @@ The preceding example ensures that all the transitive dependencies of Spring Sec
All GA releases (that is, versions ending in .RELEASE) are deployed to Maven Central, so using the mavenCentral() repository is sufficient for GA releases. The following example shows how to do so:
.build.gradle
====
[source,groovy]
----
repositories {
mavenCentral()
}
----
====
If you use a SNAPSHOT version, you need to ensure you have the Spring Snapshot repository defined, as the following example shows:
.build.gradle
====
[source,groovy]
----
repositories {
maven { url 'https://repo.spring.io/snapshot' }
}
----
====
If you use a milestone or release candidate version, you need to ensure that you have the Spring Milestone repository defined, as the following example shows:
.build.gradle
====
[source,groovy]
----
repositories {
maven { url 'https://repo.spring.io/milestone' }
}
----
====
+2 -2
View File
@@ -21,7 +21,7 @@ Where a module depends on another Spring Security module, the non-optional depen
[[spring-security-core]]
== Core -- `spring-security-core.jar`
This module contains core authentication and access-contol classes and interfaces, remoting support, and basic provisioning APIs.
This module contains core authentication and access-control classes and interfaces, remoting support, and basic provisioning APIs.
It is required by any application that uses Spring Security.
It supports standalone applications, remote clients, method (service layer) security, and JDBC user provisioning.
It contains the following top-level packages:
@@ -317,7 +317,7 @@ It requires OpenID4Java.
This module contains support for testing with Spring Security.
[[spring-security-taglibs]]
== Taglibs -- `spring-secuity-taglibs.jar`
== Taglibs -- `spring-security-taglibs.jar`
Provides Spring Security's JSP tag implementations.
.Taglib Dependencies
@@ -11,7 +11,10 @@ This will:
Often, you will want to also invalidate the session on logout.
To achieve this, you can add the `WebSessionServerLogoutHandler` to your logout configuration, like so:
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -28,7 +31,8 @@ SecurityWebFilterChain http(ServerHttpSecurity http) throws Exception {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -47,3 +51,4 @@ fun http(http: ServerHttpSecurity): SecurityWebFilterChain {
}
}
----
======
@@ -4,8 +4,10 @@
Similar to xref:servlet/authentication/x509.adoc#servlet-x509[Servlet X.509 authentication], reactive x509 authentication filter allows extracting an authentication token from a certificate provided by a client.
Below is an example of a reactive x509 security configuration:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -19,7 +21,8 @@ public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -32,14 +35,16 @@ fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
}
}
----
====
======
In the configuration above, when neither `principalExtractor` nor `authenticationManager` is provided defaults will be used. The default principal extractor is `SubjectDnX509PrincipalExtractor` which extracts the CN (common name) field from a certificate provided by a client. The default authentication manager is `ReactivePreAuthenticatedAuthenticationManager` which performs user account validation, checking that user account with a name extracted by `principalExtractor` exists and it is not locked, disabled, or expired.
The next example demonstrates how these defaults can be overridden.
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -66,7 +71,8 @@ public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -88,7 +94,7 @@ fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain? {
}
}
----
====
======
In this example, a username is extracted from the OU field of a client certificate instead of CN, and account lookup using `ReactiveUserDetailsService` is not performed at all. Instead, if the provided certificate issued to an OU named "Trusted Org Unit", a request will be authenticated.
@@ -6,8 +6,10 @@ By default, Spring Securitys authorization will require all requests to be au
The explicit configuration looks like:
.All Requests Require Authenticated User
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -22,7 +24,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -36,14 +39,16 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
}
}
----
====
======
We can configure Spring Security to have different rules by adding more rules in order of precedence.
.Multiple Authorize Requests Rules
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
import static org.springframework.security.authorization.AuthorityReactiveAuthorizationManager.hasRole;
@@ -68,7 +73,8 @@ SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -88,7 +94,7 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
}
}
----
====
======
<1> There are multiple authorization rules specified.
Each rule is considered in the order they were declared.
@@ -10,8 +10,10 @@ For this to work the return type of the method must be a `org.reactivestreams.Pu
This is necessary to integrate with Reactor's `Context`.
====
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
Authentication authentication = new TestingAuthenticationToken("user", "password", "ROLE_USER");
@@ -28,7 +30,8 @@ StepVerifier.create(messageByUsername)
.verifyComplete();
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
val authentication: Authentication = TestingAuthenticationToken("user", "password", "ROLE_USER")
@@ -43,12 +46,14 @@ StepVerifier.create(messageByUsername)
.expectNext("Hi user")
.verifyComplete()
----
====
======
with `this::findMessageByUsername` defined as:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
Mono<String> findMessageByUsername(String username) {
@@ -56,19 +61,22 @@ Mono<String> findMessageByUsername(String username) {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
fun findMessageByUsername(username: String): Mono<String> {
return Mono.just("Hi $username")
}
----
====
======
Below is a minimal method security configuration when using method security in reactive applications.
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@EnableReactiveMethodSecurity
@@ -89,7 +97,8 @@ public class SecurityConfig {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@EnableReactiveMethodSecurity
@@ -109,12 +118,14 @@ class SecurityConfig {
}
}
----
====
======
Consider the following class:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Component
@@ -126,7 +137,8 @@ public class HelloWorldMessageService {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Component
@@ -137,12 +149,14 @@ class HelloWorldMessageService {
}
}
----
====
======
Or, the following class using Kotlin coroutines:
====
.Kotlin
[tabs]
======
Kotlin::
+
[source,kotlin,role="primary"]
----
@Component
@@ -154,7 +168,7 @@ class HelloWorldMessageService {
}
}
----
====
======
Combined with our configuration above, `@PreAuthorize("hasRole('ADMIN')")` will ensure that `findByMessage` is only invoked by a user with the role `ADMIN`.
@@ -164,8 +178,10 @@ This means that the expression must not block.
When integrating with xref:reactive/configuration/webflux.adoc#jc-webflux[WebFlux Security], the Reactor Context is automatically established by Spring Security according to the authenticated user.
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@EnableWebFluxSecurity
@@ -201,7 +217,8 @@ public class SecurityConfig {
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@EnableWebFluxSecurity
@@ -232,6 +249,6 @@ class SecurityConfig {
}
}
----
====
======
You can find a complete sample in {gh-samples-url}/reactive/webflux/java/method[hellowebflux-method]
@@ -14,8 +14,10 @@ You can find a few sample applications that demonstrate the code below:
You can find a minimal WebFlux Security configuration below:
.Minimal WebFlux Security Configuration
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
-----
@@ -34,7 +36,8 @@ public class HelloWebfluxSecurityConfig {
}
-----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
-----
@EnableWebFluxSecurity
@@ -51,7 +54,7 @@ class HelloWebfluxSecurityConfig {
}
}
-----
====
======
This configuration provides form and http basic authentication, sets up authorization to require an authenticated user for accessing any page, sets up a default log in page and a default log out page, sets up security related HTTP headers, CSRF protection, and more.
@@ -60,8 +63,10 @@ This configuration provides form and http basic authentication, sets up authoriz
You can find an explicit version of the minimal WebFlux Security configuration below:
.Explicit WebFlux Security Configuration
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
-----
@Configuration
@@ -91,9 +96,12 @@ public class HelloWebfluxSecurityConfig {
}
-----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
-----
import org.springframework.security.config.web.server.invoke
@Configuration
@EnableWebFluxSecurity
class HelloWebfluxSecurityConfig {
@@ -120,7 +128,10 @@ class HelloWebfluxSecurityConfig {
}
}
-----
====
======
[NOTE]
Make sure that you import the `invoke` function in your Kotlin class, sometimes the IDE will not auto-import it causing compilation issues.
This configuration explicitly sets up all the same things as our minimal configuration.
From here you can easily make the changes to the defaults.
@@ -134,8 +145,10 @@ You can configure multiple `SecurityWebFilterChain` instances to separate config
For example, you can isolate configuration for URLs that start with `/api`, like so:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Configuration
@@ -173,9 +186,12 @@ static class MultiSecurityHttpConfig {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
import org.springframework.security.config.web.server.invoke
@Configuration
@EnableWebFluxSecurity
open class MultiSecurityHttpConfig {
@@ -211,7 +227,7 @@ open class MultiSecurityHttpConfig {
}
}
----
====
======
<1> Configure a `SecurityWebFilterChain` with an `@Order` to specify which `SecurityWebFilterChain` Spring Security should consider first
<2> Use `PathPatternParserServerWebExchangeMatcher` to state that this `SecurityWebFilterChain` will only apply to URL paths that start with `/api/`
@@ -35,8 +35,10 @@ These defaults come from https://docs.angularjs.org/api/ng/service/$http#cross-s
You can configure `CookieServerCsrfTokenRepository` in Java Configuration using:
.Store CSRF Token in a Cookie
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
-----
@Bean
@@ -48,7 +50,8 @@ public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http)
}
-----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
-----
@Bean
@@ -61,7 +64,7 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
}
}
-----
====
======
[NOTE]
====
@@ -78,8 +81,10 @@ However, it is simple to disable CSRF protection if it xref:features/exploits/cs
The Java configuration below will disable CSRF protection.
.Disable CSRF Configuration
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -91,7 +96,8 @@ public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http)
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
-----
@Bean
@@ -104,7 +110,7 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
}
}
-----
====
======
[[webflux-csrf-include]]
=== Include the CSRF Token
@@ -120,8 +126,10 @@ If your view technology does not provide a simple way to subscribe to the `Mono<
For example, the following code will place the `CsrfToken` on the default attribute name (`_csrf`) used by Spring Security's <<webflux-csrf-include-form-auto,CsrfRequestDataValueProcessor>> to automatically include the CSRF token as a hidden input.
.`CsrfToken` as `@ModelAttribute`
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@ControllerAdvice
@@ -135,7 +143,8 @@ public class SecurityControllerAdvice {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@ControllerAdvice
@@ -149,7 +158,7 @@ class SecurityControllerAdvice {
}
}
----
====
======
Fortunately, Thymeleaf provides <<webflux-csrf-include-form-auto,integration>> that works without any additional work.
@@ -159,14 +168,12 @@ In order to post an HTML form the CSRF token must be included in the form as a h
For example, the rendered HTML might look like:
.CSRF Token HTML
====
[source,html]
----
<input type="hidden"
name="_csrf"
value="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>
----
====
Next we will discuss various ways of including the CSRF token in a form as a hidden input.
@@ -186,7 +193,6 @@ If the <<webflux-csrf-include,other options>> for including the actual CSRF toke
The Thymeleaf sample below assumes that you <<webflux-csrf-include-subscribe,expose>> the `CsrfToken` on an attribute named `_csrf`.
.CSRF Token in Form with Request Attribute
====
[source,html]
----
<form th:action="@{/logout}"
@@ -198,7 +204,6 @@ The Thymeleaf sample below assumes that you <<webflux-csrf-include-subscribe,exp
th:value="${_csrf.token}"/>
</form>
----
====
[[webflux-csrf-include-ajax]]
==== Ajax and JSON Requests
@@ -220,7 +225,6 @@ An alternative pattern to <<webflux-csrf-include-form-auto,exposing the CSRF in
The HTML might look something like this:
.CSRF meta tag HTML
====
[source,html]
----
<html>
@@ -231,13 +235,11 @@ The HTML might look something like this:
</head>
<!-- ... -->
----
====
Once the meta tags contained the CSRF token, the JavaScript code would read the meta tags and include the CSRF token as a header.
If you were using jQuery, this could be done with the following:
.AJAX send CSRF Token
====
[source,javascript]
----
$(function () {
@@ -248,13 +250,11 @@ $(function () {
});
});
----
====
The sample below assumes that you <<webflux-csrf-include-subscribe,expose>> the `CsrfToken` on an attribute named `_csrf`.
An example of doing this with Thymeleaf is shown below:
.CSRF meta tag JSP
====
[source,html]
----
<html>
@@ -266,7 +266,6 @@ An example of doing this with Thymeleaf is shown below:
</head>
<!-- ... -->
----
====
[[webflux-csrf-considerations]]
== CSRF Considerations
@@ -298,8 +297,10 @@ For example, the following Java Configuration will perform logout with the URL `
// FIXME: This should be a link to log out documentation
.Log out with HTTP GET
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -311,7 +312,8 @@ public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http)
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -324,7 +326,7 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
}
}
----
====
======
[[webflux-considerations-csrf-timeouts]]
@@ -360,8 +362,10 @@ We have xref:features/exploits/csrf.adoc#csrf-considerations-multipart[already d
In a WebFlux application, this can be configured with the following configuration:
.Enable obtaining CSRF token from multipart/form-data
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -373,7 +377,8 @@ public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http)
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -386,7 +391,7 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
}
}
----
====
======
[[webflux-csrf-considerations-multipart-url]]
==== Include CSRF Token in URL
@@ -396,14 +401,12 @@ Since the `CsrfToken` is exposed as an `ServerHttpRequest` <<webflux-csrf-includ
An example with Thymeleaf is shown below:
.CSRF Token in Action
====
[source,html]
----
<form method="post"
th:action="@{/upload(${_csrf.parameterName}=${_csrf.token})}"
enctype="multipart/form-data">
----
====
[[webflux-csrf-considerations-override-method]]
=== HiddenHttpMethodFilter
@@ -0,0 +1,234 @@
[[webflux-serverwebexchangefirewall]]
= ServerWebExchangeFirewall
There are various ways a request can be created by malicious users that can exploit applications.
Spring Security provides the `ServerWebExchangeFirewall` to allow rejecting requests that look malicious.
The default implementation is `StrictServerWebExchangeFirewall` which rejects malicious requests.
For example a request could contain path-traversal sequences (such as `/../`) or multiple forward slashes (`//`) that could also cause pattern-matches to fail.
Some containers normalize these out before performing the servlet mapping, but others do not.
To protect against issues like these, `WebFilterChainProxy` uses a `ServerWebExchangeFirewall` strategy to check and wrap the request.
By default, un-normalized requests are automatically rejected, and path parameters are removed for matching purposes.
(So, for example, an original request path of `/secure;hack=1/somefile.html;hack=2` is returned as `/secure/somefile.html`.)
It is, therefore, essential that a `WebFilterChainProxy` is used.
In practice, we recommend that you use method security at your service layer, to control access to your application, rather than rely entirely on the use of security constraints defined at the web-application level.
URLs change, and it is difficult to take into account all the possible URLs that an application might support and how requests might be manipulated.
You should restrict yourself to using a few simple patterns that are simple to understand.
Always try to use a "`deny-by-default`" approach, where you have a catch-all wildcard (`/**` or `**`) defined last to deny access.
Security defined at the service layer is much more robust and harder to bypass, so you should always take advantage of Spring Security's method security options.
You can customize the `ServerWebExchangeFirewall` by exposing it as a Bean.
.Allow Matrix Variables
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
public StrictServerWebExchangeFirewall httpFirewall() {
StrictServerWebExchangeFirewall firewall = new StrictServerWebExchangeFirewall();
firewall.setAllowSemicolon(true);
return firewall;
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
fun httpFirewall(): StrictServerWebExchangeFirewall {
val firewall = StrictServerWebExchangeFirewall()
firewall.setAllowSemicolon(true)
return firewall
}
----
======
To protect against https://www.owasp.org/index.php/Cross_Site_Tracing[Cross Site Tracing (XST)] and https://www.owasp.org/index.php/Test_HTTP_Methods_(OTG-CONFIG-006)[HTTP Verb Tampering], the `StrictServerWebExchangeFirewall` provides an allowed list of valid HTTP methods that are allowed.
The default valid methods are `DELETE`, `GET`, `HEAD`, `OPTIONS`, `PATCH`, `POST`, and `PUT`.
If your application needs to modify the valid methods, you can configure a custom `StrictServerWebExchangeFirewall` bean.
The following example allows only HTTP `GET` and `POST` methods:
.Allow Only GET & POST
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
public StrictServerWebExchangeFirewall httpFirewall() {
StrictServerWebExchangeFirewall firewall = new StrictServerWebExchangeFirewall();
firewall.setAllowedHttpMethods(Arrays.asList("GET", "POST"));
return firewall;
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
fun httpFirewall(): StrictServerWebExchangeFirewall {
val firewall = StrictServerWebExchangeFirewall()
firewall.setAllowedHttpMethods(listOf("GET", "POST"))
return firewall
}
----
======
If you must allow any HTTP method (not recommended), you can use `StrictServerWebExchangeFirewall.setUnsafeAllowAnyHttpMethod(true)`.
Doing so entirely disables validation of the HTTP method.
[[webflux-serverwebexchangefirewall-headers-parameters]]
`StrictServerWebExchangeFirewall` also checks header names and values and parameter names.
It requires that each character have a defined code point and not be a control character.
This requirement can be relaxed or adjusted as necessary by using the following methods:
* `StrictServerWebExchangeFirewall#setAllowedHeaderNames(Predicate)`
* `StrictServerWebExchangeFirewall#setAllowedHeaderValues(Predicate)`
* `StrictServerWebExchangeFirewall#setAllowedParameterNames(Predicate)`
[NOTE]
====
Parameter values can be also controlled with `setAllowedParameterValues(Predicate)`.
====
For example, to switch off this check, you can wire your `StrictServerWebExchangeFirewall` with `Predicate` instances that always return `true`:
.Allow Any Header Name, Header Value, and Parameter Name
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
public StrictServerWebExchangeFirewall httpFirewall() {
StrictServerWebExchangeFirewall firewall = new StrictServerWebExchangeFirewall();
firewall.setAllowedHeaderNames((header) -> true);
firewall.setAllowedHeaderValues((header) -> true);
firewall.setAllowedParameterNames((parameter) -> true);
return firewall;
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
fun httpFirewall(): StrictServerWebExchangeFirewall {
val firewall = StrictServerWebExchangeFirewall()
firewall.setAllowedHeaderNames { true }
firewall.setAllowedHeaderValues { true }
firewall.setAllowedParameterNames { true }
return firewall
}
----
======
Alternatively, there might be a specific value that you need to allow.
For example, iPhone Xʀ uses a `User-Agent` that includes a character that is not in the ISO-8859-1 charset.
Due to this fact, some application servers parse this value into two separate characters, the latter being an undefined character.
You can address this with the `setAllowedHeaderValues` method:
.Allow Certain User Agents
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
public StrictServerWebExchangeFirewall httpFirewall() {
StrictServerWebExchangeFirewall firewall = new StrictServerWebExchangeFirewall();
Pattern allowed = Pattern.compile("[\\p{IsAssigned}&&[^\\p{IsControl}]]*");
Pattern userAgent = ...;
firewall.setAllowedHeaderValues((header) -> allowed.matcher(header).matches() || userAgent.matcher(header).matches());
return firewall;
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
fun httpFirewall(): StrictServerWebExchangeFirewall {
val firewall = StrictServerWebExchangeFirewall()
val allowed = Pattern.compile("[\\p{IsAssigned}&&[^\\p{IsControl}]]*")
val userAgent = Pattern.compile(...)
firewall.setAllowedHeaderValues { allowed.matcher(it).matches() || userAgent.matcher(it).matches() }
return firewall
}
----
======
In the case of header values, you may instead consider parsing them as UTF-8 at verification time:
.Parse Headers As UTF-8
[tabs]
======
Java::
+
[source,java,role="primary"]
----
firewall.setAllowedHeaderValues((header) -> {
String parsed = new String(header.getBytes(ISO_8859_1), UTF_8);
return allowed.matcher(parsed).matches();
});
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
firewall.setAllowedHeaderValues {
val parsed = String(header.getBytes(ISO_8859_1), UTF_8)
return allowed.matcher(parsed).matches()
}
----
======
The `ServerExchangeRejectedHandler` interface is used to handle `ServerExchangeRejectedException` throw by Spring Security's `ServerWebExchangeFirewall`.
By default `HttpStatusExchangeRejectedHandler` is used to send an HTTP 400 response to clients when a request is rejected.
To customize the behavior, users can expose a `ServerExchangeRejectedHandler` Bean.
For example, the following will send an HTTP 404 when the request is rejected:
.Send 404 on Request Rejected
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
ServerExchangeRejectedHandler rejectedHandler() {
return new HttpStatusExchangeRejectedHandler(HttpStatus.NOT_FOUND);
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
fun rejectedHandler(): ServerExchangeRejectedHandler {
return HttpStatusExchangeRejectedHandler(HttpStatus.NOT_FOUND)
}
----
======
Handling can be completely customized by creating a custom `ServerExchangeRejectedHandler` implementation.
@@ -16,8 +16,10 @@ For example, assume that you want the defaults except you wish to specify `SAMEO
You can easily do this with the following Configuration:
.Customize Default Security Headers
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -33,7 +35,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -48,14 +51,16 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
}
}
----
====
======
If you do not want the defaults to be added and want explicit control over what should be used, you can disable the defaults.
An example is provided below:
.Disable HTTP Security Response Headers
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -67,7 +72,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -80,7 +86,7 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
}
}
----
====
======
[[webflux-headers-cache-control]]
== Cache Control
@@ -96,8 +102,10 @@ Details on how to do this can be found in the https://docs.spring.io/spring/docs
If necessary, you can also disable Spring Security's cache control HTTP response headers.
.Cache Control Disabled
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -111,7 +119,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -126,7 +135,7 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
}
}
----
====
======
[[webflux-headers-content-type-options]]
@@ -135,8 +144,10 @@ Spring Security includes xref:features/exploits/headers.adoc#headers-content-typ
However, you can disable it with:
.Content Type Options Disabled
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -150,7 +161,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -165,7 +177,7 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
}
}
----
====
======
[[webflux-headers-hsts]]
== HTTP Strict Transport Security (HSTS)
@@ -174,8 +186,10 @@ However, you can customize the results explicitly.
For example, the following is an example of explicitly providing HSTS:
.Strict Transport Security
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -193,7 +207,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -210,7 +225,7 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
}
}
----
====
======
[[webflux-headers-frame-options]]
== X-Frame-Options
@@ -219,8 +234,10 @@ By default, Spring Security disables rendering within an iframe using xref:featu
You can customize frame options to use the same origin using the following:
.X-Frame-Options: SAMEORIGIN
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -236,7 +253,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -251,7 +269,7 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
}
}
----
====
======
[[webflux-headers-xss-protection]]
== X-XSS-Protection
@@ -259,8 +277,10 @@ By default, Spring Security instructs browsers to block reflected XSS attacks us
You can disable `X-XSS-Protection` with the following Configuration:
.X-XSS-Protection Customization
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -274,7 +294,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -289,7 +310,7 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
}
}
----
====
======
[[webflux-headers-csp]]
== Content Security Policy (CSP)
@@ -299,18 +320,18 @@ The web application author must declare the security policy(s) to enforce and/or
For example, given the following security policy:
.Content Security Policy Example
====
[source,http]
----
Content-Security-Policy: script-src 'self' https://trustedscripts.example.com; object-src https://trustedplugins.example.com; report-uri /csp-report-endpoint/
----
====
You can enable the CSP header as shown below:
.Content Security Policy
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -326,7 +347,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -341,13 +363,15 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
}
}
----
====
======
To enable the CSP `report-only` header, provide the following configuration:
.Content Security Policy Report Only
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -364,7 +388,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -380,7 +405,7 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
}
}
----
====
======
[[webflux-headers-referrer]]
== Referrer Policy
@@ -389,8 +414,10 @@ Spring Security does not add xref:features/exploits/headers.adoc#headers-referre
You can enable the Referrer Policy header using configuration as shown below:
.Referrer Policy Configuration
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -406,7 +433,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -421,7 +449,7 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
}
}
----
====
======
[[webflux-headers-feature]]
@@ -431,18 +459,18 @@ Spring Security does not add xref:features/exploits/headers.adoc#headers-feature
The following `Feature-Policy` header:
.Feature-Policy Example
====
[source]
----
Feature-Policy: geolocation 'self'
----
====
You can enable the Feature Policy header as shown below:
.Feature-Policy Configuration
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -456,7 +484,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -469,7 +498,7 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
}
}
----
====
======
[[webflux-headers-permissions]]
@@ -479,18 +508,18 @@ Spring Security does not add xref:features/exploits/headers.adoc#headers-permiss
The following `Permissions-Policy` header:
.Permissions-Policy Example
====
[source]
----
Permissions-Policy: geolocation=(self)
----
====
You can enable the Permissions Policy header as shown below:
.Permissions-Policy Configuration
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -506,7 +535,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -521,7 +551,7 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
}
}
----
====
======
[[webflux-headers-clear-site-data]]
@@ -531,17 +561,17 @@ Spring Security does not add xref:features/exploits/headers.adoc#headers-clear-s
The following Clear-Site-Data header:
.Clear-Site-Data Example
====
----
Clear-Site-Data: "cache", "cookies"
----
====
can be sent on log out with the following configuration:
.Clear-Site-Data Configuration
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -559,7 +589,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -577,7 +608,7 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
}
}
----
====
======
[[webflux-headers-cross-origin-policies]]
== Cross-Origin Policies
@@ -595,8 +626,10 @@ Spring Security does not add <<headers-cross-origin-policies,Cross-Origin Polici
The headers can be added with the following configuration:
.Cross-Origin Policies
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@EnableWebFluxSecurity
@@ -613,7 +646,9 @@ public class WebSecurityConfig {
}
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@EnableWebFluxSecurity
@@ -631,7 +666,7 @@ open class CrossOriginPoliciesCustomConfig {
}
}
----
====
======
This configuration will write the headers with the values provided:
[source]
@@ -13,8 +13,10 @@ If a client makes a request using HTTP rather than HTTPS, Spring Security can be
For example, the following Java configuration will redirect any HTTP requests to HTTPS:
.Redirect to HTTPS
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -26,7 +28,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -37,15 +40,17 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
}
}
----
====
======
The configuration can easily be wrapped around an if statement to only be turned on in production.
Alternatively, it can be enabled by looking for a property about the request that only happens in production.
For example, if the production environment adds a header named `X-Forwarded-Proto` the following Java Configuration could be used:
.Redirect to HTTPS when X-Forwarded
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -59,7 +64,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -74,7 +80,7 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
}
}
----
====
======
[[webflux-hsts]]
== Strict Transport Security
@@ -14,8 +14,10 @@ For your convenience, you can download a minimal Reactive Spring Boot + Spring S
You can add Spring Security to your Spring Boot project by adding `spring-boot-starter-security`.
====
.Maven
[tabs]
======
Maven::
+
[source,xml,role="primary"]
----
<dependency>
@@ -24,12 +26,13 @@ You can add Spring Security to your Spring Boot project by adding `spring-boot-s
</dependency>
----
.Gradle
Gradle::
+
[source,groovy,role="secondary"]
----
implementation 'org.springframework.boot:spring-boot-starter-security'
----
====
======
[[servlet-hello-starting]]
@@ -38,10 +41,12 @@ You can add Spring Security to your Spring Boot project by adding `spring-boot-s
You can now https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using-boot-running-with-the-maven-plugin[run the Spring Boot application] by using the Maven Plugin's `run` goal.
The following example shows how to do so (and the beginning of the output from doing so):
.Running Spring Boot Application
====
.Maven
.Running Spring Boot Application
[tabs]
======
Maven::
+
[source,bash,role="primary"]
----
$ ./mvnw spring-boot:run
@@ -53,7 +58,8 @@ Using generated security password: 8e557245-73e2-4286-969a-ff57fe326336
...
----
.Gradle
Gradle::
+
[source,bash,role="secondary"]
----
$ ./gradlew bootRun
@@ -64,7 +70,7 @@ Using generated security password: 8e557245-73e2-4286-969a-ff57fe326336
...
----
====
======
[[authenticating]]
== Authenticating
@@ -10,8 +10,10 @@ The easiest way to ensure that CORS is handled first is to use the `CorsWebFilte
Users can integrate the `CorsWebFilter` with Spring Security by providing a `CorsConfigurationSource`.
For example, the following will integrate CORS support within Spring Security:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -25,7 +27,8 @@ CorsConfigurationSource corsConfigurationSource() {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -38,12 +41,14 @@ fun corsConfigurationSource(): CorsConfigurationSource {
return source
}
----
====
======
The following will disable the CORS integration within Spring Security:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -55,7 +60,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -68,4 +74,4 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
}
}
----
====
======
@@ -14,8 +14,10 @@ You can find a few sample applications that demonstrate the code below:
You can find a minimal RSocket Security configuration below:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
-----
@Configuration
@@ -34,7 +36,8 @@ public class HelloRSocketSecurityConfig {
}
-----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Configuration
@@ -51,7 +54,7 @@ open class HelloRSocketSecurityConfig {
}
}
----
====
======
This configuration enables <<rsocket-authentication-simple,simple authentication>> and sets up <<rsocket-authorization,rsocket-authorization>> to require an authenticated user for any request.
@@ -61,8 +64,10 @@ For Spring Security to work we need to apply `SecuritySocketAcceptorInterceptor`
This is what connects our `PayloadSocketAcceptorInterceptor` we created with the RSocket infrastructure.
In a Spring Boot application this is done automatically using `RSocketSecurityAutoConfiguration` with the following code.
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -71,7 +76,8 @@ RSocketServerCustomizer springSecurityRSocketSecurity(SecuritySocketAcceptorInte
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -83,7 +89,7 @@ fun springSecurityRSocketSecurity(interceptor: SecuritySocketAcceptorInterceptor
}
}
----
====
======
[[rsocket-authentication]]
== RSocket Authentication
@@ -123,8 +129,10 @@ See `RSocketSecurity.basicAuthentication(Customizer)` for setting it up.
The RSocket receiver can decode the credentials using `AuthenticationPayloadExchangeConverter` which is automatically setup using the `simpleAuthentication` portion of the DSL.
An explicit configuration can be found below.
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -140,7 +148,8 @@ PayloadSocketAcceptorInterceptor rsocketInterceptor(RSocketSecurity rsocket) {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -154,30 +163,35 @@ open fun rsocketInterceptor(rsocket: RSocketSecurity): PayloadSocketAcceptorInte
return rsocket.build()
}
----
====
======
The RSocket sender can send credentials using `SimpleAuthenticationEncoder` which can be added to Spring's `RSocketStrategies`.
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
RSocketStrategies.Builder strategies = ...;
strategies.encoder(new SimpleAuthenticationEncoder());
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
var strategies: RSocketStrategies.Builder = ...
strategies.encoder(SimpleAuthenticationEncoder())
----
====
======
It can then be used to send a username and password to the receiver in the setup:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
MimeType authenticationMimeType =
@@ -189,7 +203,8 @@ Mono<RSocketRequester> requester = RSocketRequester.builder()
.connectTcp(host, port);
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
val authenticationMimeType: MimeType =
@@ -200,12 +215,14 @@ val requester: Mono<RSocketRequester> = RSocketRequester.builder()
.rsocketStrategies(strategies.build())
.connectTcp(host, port)
----
====
======
Alternatively or additionally, a username and password can be sent in a request.
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
Mono<RSocketRequester> requester;
@@ -220,7 +237,8 @@ public Mono<AirportLocation> findRadar(String code) {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
import org.springframework.messaging.rsocket.retrieveMono
@@ -238,7 +256,7 @@ open fun findRadar(code: String): Mono<AirportLocation> {
}
}
----
====
======
[[rsocket-authentication-jwt]]
=== JWT
@@ -249,8 +267,10 @@ The support comes in the form of authenticating a JWT (determining the JWT is va
The RSocket receiver can decode the credentials using `BearerPayloadExchangeConverter` which is automatically setup using the `jwt` portion of the DSL.
An example configuration can be found below:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -266,7 +286,8 @@ PayloadSocketAcceptorInterceptor rsocketInterceptor(RSocketSecurity rsocket) {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -280,13 +301,15 @@ fun rsocketInterceptor(rsocket: RSocketSecurity): PayloadSocketAcceptorIntercept
return rsocket.build()
}
----
====
======
The configuration above relies on the existence of a `ReactiveJwtDecoder` `@Bean` being present.
An example of creating one from the issuer can be found below:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -296,7 +319,8 @@ ReactiveJwtDecoder jwtDecoder() {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -305,13 +329,15 @@ fun jwtDecoder(): ReactiveJwtDecoder {
.fromIssuerLocation("https://example.com/auth/realms/demo")
}
----
====
======
The RSocket sender does not need to do anything special to send the token because the value is just a simple String.
For example, the token can be sent at setup time:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
MimeType authenticationMimeType =
@@ -322,7 +348,8 @@ Mono<RSocketRequester> requester = RSocketRequester.builder()
.connectTcp(host, port);
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
val authenticationMimeType: MimeType =
@@ -333,12 +360,14 @@ val requester = RSocketRequester.builder()
.setupMetadata(token, authenticationMimeType)
.connectTcp(host, port)
----
====
======
Alternatively or additionally, the token can be sent in a request.
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
MimeType authenticationMimeType =
@@ -355,7 +384,8 @@ public Mono<AirportLocation> findRadar(String code) {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
val authenticationMimeType: MimeType =
@@ -371,7 +401,7 @@ open fun findRadar(code: String): Mono<AirportLocation> {
}
}
----
====
======
[[rsocket-authorization]]
== RSocket Authorization
@@ -380,8 +410,10 @@ RSocket authorization is performed with `AuthorizationPayloadInterceptor` which
The DSL can be used to setup authorization rules based upon the `PayloadExchange`.
An example configuration can be found below:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
rsocket
@@ -397,7 +429,9 @@ rsocket
.anyExchange().permitAll() // <6>
);
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
rsocket
@@ -413,7 +447,7 @@ rsocket
.anyExchange().permitAll()
} // <6>
----
====
======
<1> Setting up a connection requires the authority `ROLE_SETUP`
<2> If the route is `fetch.profile.me` authorization only requires the user be authenticated
<3> In this rule we setup a custom matcher where authorization requires the user to have the authority `ROLE_CUSTOM`
@@ -111,8 +111,10 @@ OPTIONAL. Space delimited, case sensitive list of ASCII string values that speci
The following example shows how to configure the `DefaultServerOAuth2AuthorizationRequestResolver` with a `Consumer<OAuth2AuthorizationRequest.Builder>` that customizes the Authorization Request for `oauth2Login()`, by including the request parameter `prompt=consent`.
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@EnableWebFluxSecurity
@@ -154,7 +156,8 @@ public class OAuth2LoginSecurityConfig {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@EnableWebFluxSecurity
@@ -192,7 +195,7 @@ class SecurityConfig {
}
}
----
====
======
For the simple use case, where the additional request parameter is always the same for a specific provider, it may be added directly in the `authorization-uri` property.
@@ -217,8 +220,10 @@ Alternatively, if your requirements are more advanced, you can take full control
The following example shows a variation of `authorizationRequestCustomizer()` from the preceding example, and instead overrides the `OAuth2AuthorizationRequest.authorizationRequestUri` property.
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
private Consumer<OAuth2AuthorizationRequest.Builder> authorizationRequestCustomizer() {
@@ -228,7 +233,8 @@ private Consumer<OAuth2AuthorizationRequest.Builder> authorizationRequestCustomi
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
private fun authorizationRequestCustomizer(): Consumer<OAuth2AuthorizationRequest.Builder> {
@@ -241,7 +247,7 @@ private fun authorizationRequestCustomizer(): Consumer<OAuth2AuthorizationReques
}
}
----
====
======
=== Storing the Authorization Request
@@ -256,8 +262,10 @@ The default implementation of `ServerAuthorizationRequestRepository` is `WebSess
If you have a custom implementation of `ServerAuthorizationRequestRepository`, you may configure it as shown in the following example:
.ServerAuthorizationRequestRepository Configuration
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@EnableWebFluxSecurity
@@ -275,7 +283,8 @@ public class OAuth2ClientSecurityConfig {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@EnableWebFluxSecurity
@@ -291,7 +300,7 @@ class OAuth2ClientSecurityConfig {
}
}
----
====
======
=== Requesting an Access Token
@@ -327,8 +336,10 @@ Alternatively, if your requirements are more advanced, you can take full control
Whether you customize `WebClientReactiveAuthorizationCodeTokenResponseClient` or provide your own implementation of `ReactiveOAuth2AccessTokenResponseClient`, youll need to configure it as shown in the following example:
.Access Token Response Configuration
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@EnableWebFluxSecurity
@@ -354,7 +365,8 @@ public class OAuth2ClientSecurityConfig {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@EnableWebFluxSecurity
@@ -377,7 +389,7 @@ class OAuth2ClientSecurityConfig {
}
}
----
====
======
[[oauth2Client-refresh-token-grant]]
@@ -421,8 +433,10 @@ Alternatively, if your requirements are more advanced, you can take full control
Whether you customize `WebClientReactiveRefreshTokenTokenResponseClient` or provide your own implementation of `ReactiveOAuth2AccessTokenResponseClient`, youll need to configure it as shown in the following example:
.Access Token Response Configuration
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
// Customize
@@ -439,7 +453,8 @@ ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
// Customize
@@ -454,7 +469,7 @@ val authorizedClientProvider: ReactiveOAuth2AuthorizedClientProvider = ReactiveO
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
----
====
======
[NOTE]
`ReactiveOAuth2AuthorizedClientProviderBuilder.builder().refreshToken()` configures a `RefreshTokenReactiveOAuth2AuthorizedClientProvider`,
@@ -504,8 +519,10 @@ Alternatively, if your requirements are more advanced, you can take full control
Whether you customize `WebClientReactiveClientCredentialsTokenResponseClient` or provide your own implementation of `ReactiveOAuth2AccessTokenResponseClient`, you'll need to configure it as shown in the following example:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
// Customize
@@ -521,7 +538,8 @@ ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
// Customize
@@ -535,7 +553,7 @@ val authorizedClientProvider: ReactiveOAuth2AuthorizedClientProvider = ReactiveO
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
----
====
======
[NOTE]
`ReactiveOAuth2AuthorizedClientProviderBuilder.builder().clientCredentials()` configures a `ClientCredentialsReactiveOAuth2AuthorizedClientProvider`,
@@ -564,8 +582,10 @@ spring:
...and the `ReactiveOAuth2AuthorizedClientManager` `@Bean`:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -587,7 +607,8 @@ public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -603,12 +624,14 @@ fun authorizedClientManager(
return authorizedClientManager
}
----
====
======
You may obtain the `OAuth2AccessToken` as follows:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Controller
@@ -632,7 +655,8 @@ public class OAuth2ClientController {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
class OAuth2ClientController {
@@ -654,7 +678,7 @@ class OAuth2ClientController {
}
}
----
====
======
[NOTE]
`ServerWebExchange` is an OPTIONAL attribute.
@@ -701,8 +725,10 @@ Alternatively, if your requirements are more advanced, you can take full control
Whether you customize `WebClientReactivePasswordTokenResponseClient` or provide your own implementation of `ReactiveOAuth2AccessTokenResponseClient`, you'll need to configure it as shown in the following example:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
// Customize
@@ -719,7 +745,8 @@ ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
val passwordTokenResponseClient: ReactiveOAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> = ...
@@ -733,7 +760,7 @@ val authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.bui
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
----
====
======
[NOTE]
`ReactiveOAuth2AuthorizedClientProviderBuilder.builder().password()` configures a `PasswordReactiveOAuth2AuthorizedClientProvider`,
@@ -762,8 +789,10 @@ spring:
...and the `ReactiveOAuth2AuthorizedClientManager` `@Bean`:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -807,7 +836,9 @@ private Function<OAuth2AuthorizeRequest, Mono<Map<String, Object>>> contextAttri
};
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -846,12 +877,14 @@ private fun contextAttributesMapper(): Function<OAuth2AuthorizeRequest, Mono<Mut
}
}
----
====
======
You may obtain the `OAuth2AccessToken` as follows:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Controller
@@ -875,7 +908,8 @@ public class OAuth2ClientController {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Controller
@@ -897,7 +931,7 @@ class OAuth2ClientController {
}
}
----
====
======
[NOTE]
`ServerWebExchange` is an OPTIONAL attribute.
@@ -943,8 +977,10 @@ Alternatively, if your requirements are more advanced, you can take full control
Whether you customize `WebClientReactiveJwtBearerTokenResponseClient` or provide your own implementation of `ReactiveOAuth2AccessTokenResponseClient`, you'll need to configure it as shown in the following example:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
// Customize
@@ -963,7 +999,8 @@ ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
// Customize
@@ -980,7 +1017,7 @@ val authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.bui
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
----
====
======
=== Using the Access Token
@@ -1005,8 +1042,10 @@ spring:
...and the `OAuth2AuthorizedClientManager` `@Bean`:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -1031,7 +1070,8 @@ public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -1048,12 +1088,14 @@ fun authorizedClientManager(
return authorizedClientManager
}
----
====
======
You may obtain the `OAuth2AccessToken` as follows:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@RestController
@@ -1075,7 +1117,8 @@ public class OAuth2ResourceServerController {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
class OAuth2ResourceServerController {
@@ -1094,7 +1137,7 @@ class OAuth2ResourceServerController {
}
}
----
====
======
[NOTE]
`JwtBearerReactiveOAuth2AuthorizedClientProvider` resolves the `Jwt` assertion via `OAuth2AuthorizationContext.getPrincipal().getPrincipal()` by default, hence the use of `JwtAuthenticationToken` in the preceding example.
@@ -8,8 +8,10 @@
The `@RegisteredOAuth2AuthorizedClient` annotation provides the capability of resolving a method parameter to an argument value of type `OAuth2AuthorizedClient`.
This is a convenient alternative compared to accessing the `OAuth2AuthorizedClient` using the `ReactiveOAuth2AuthorizedClientManager` or `ReactiveOAuth2AuthorizedClientService`.
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Controller
@@ -24,7 +26,8 @@ public class OAuth2ClientController {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Controller
@@ -37,7 +40,7 @@ class OAuth2ClientController {
}
}
----
====
======
The `@RegisteredOAuth2AuthorizedClient` annotation is handled by `OAuth2AuthorizedClientArgumentResolver`, which directly uses a <<oauth2Client-authorized-manager-provider, ReactiveOAuth2AuthorizedClientManager>> and therefore inherits it's capabilities.
@@ -58,8 +61,10 @@ It directly uses an <<oauth2Client-authorized-manager-provider, ReactiveOAuth2Au
The following code shows an example of how to configure `WebClient` with OAuth 2.0 Client support:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -72,7 +77,8 @@ WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManage
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -83,7 +89,7 @@ fun webClient(authorizedClientManager: ReactiveOAuth2AuthorizedClientManager): W
.build()
}
----
====
======
=== Providing the Authorized Client
@@ -91,8 +97,10 @@ The `ServerOAuth2AuthorizedClientExchangeFilterFunction` determines the client t
The following code shows how to set an `OAuth2AuthorizedClient` as a request attribute:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@GetMapping("/")
@@ -110,7 +118,8 @@ public Mono<String> index(@RegisteredOAuth2AuthorizedClient("okta") OAuth2Author
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@GetMapping("/")
@@ -127,14 +136,16 @@ fun index(@RegisteredOAuth2AuthorizedClient("okta") authorizedClient: OAuth2Auth
.thenReturn("index")
}
----
====
======
<1> `oauth2AuthorizedClient()` is a `static` method in `ServerOAuth2AuthorizedClientExchangeFilterFunction`.
The following code shows how to set the `ClientRegistration.getRegistrationId()` as a request attribute:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@GetMapping("/")
@@ -152,7 +163,8 @@ public Mono<String> index() {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@GetMapping("/")
@@ -169,7 +181,7 @@ fun index(): Mono<String> {
.thenReturn("index")
}
----
====
======
<1> `clientRegistrationId()` is a `static` method in `ServerOAuth2AuthorizedClientExchangeFilterFunction`.
@@ -181,8 +193,10 @@ If `setDefaultOAuth2AuthorizedClient(true)` is configured and the user has authe
The following code shows the specific configuration:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -196,7 +210,8 @@ WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManage
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -208,7 +223,7 @@ fun webClient(authorizedClientManager: ReactiveOAuth2AuthorizedClientManager): W
.build()
}
----
====
======
[WARNING]
It is recommended to be cautious with this feature since all HTTP requests will receive the access token.
@@ -217,8 +232,10 @@ Alternatively, if `setDefaultClientRegistrationId("okta")` is configured with a
The following code shows the specific configuration:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -232,7 +249,8 @@ WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManage
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -244,7 +262,7 @@ fun webClient(authorizedClientManager: ReactiveOAuth2AuthorizedClientManager): W
.build()
}
----
====
======
[WARNING]
It is recommended to be cautious with this feature since all HTTP requests will receive the access token.
@@ -36,8 +36,10 @@ spring:
The following example shows how to configure `WebClientReactiveAuthorizationCodeTokenResponseClient`:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
Function<ClientRegistration, JWK> jwkResolver = (clientRegistration) -> {
@@ -59,7 +61,8 @@ tokenResponseClient.addParametersConverter(
new NimbusJwtClientAuthenticationParametersConverter<>(jwkResolver));
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
val jwkResolver: Function<ClientRegistration, JWK> =
@@ -81,7 +84,7 @@ tokenResponseClient.addParametersConverter(
NimbusJwtClientAuthenticationParametersConverter(jwkResolver)
)
----
====
======
=== Authenticate using `client_secret_jwt`
@@ -105,8 +108,10 @@ spring:
The following example shows how to configure `WebClientReactiveClientCredentialsTokenResponseClient`:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
Function<ClientRegistration, JWK> jwkResolver = (clientRegistration) -> {
@@ -127,7 +132,8 @@ tokenResponseClient.addParametersConverter(
new NimbusJwtClientAuthenticationParametersConverter<>(jwkResolver));
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
val jwkResolver = Function<ClientRegistration, JWK?> { clientRegistration: ClientRegistration ->
@@ -148,14 +154,16 @@ tokenResponseClient.addParametersConverter(
NimbusJwtClientAuthenticationParametersConverter(jwkResolver)
)
----
====
======
=== Customizing the JWT assertion
The JWT produced by `NimbusJwtClientAuthenticationParametersConverter` contains the `iss`, `sub`, `aud`, `jti`, `iat` and `exp` claims by default. You can customize the headers and/or claims by providing a `Consumer<NimbusJwtClientAuthenticationParametersConverter.JwtClientAuthenticationContext<T>>` to `setJwtClientAssertionCustomizer()`. The following example shows how to customize claims of the JWT:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
Function<ClientRegistration, JWK> jwkResolver = ...
@@ -168,7 +176,8 @@ converter.setJwtClientAssertionCustomizer((context) -> {
});
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
val jwkResolver = ...
@@ -180,4 +189,4 @@ converter.setJwtClientAssertionCustomizer { context ->
context.claims.claim("custom-claim", "claim-value")
}
----
====
======
@@ -69,20 +69,23 @@ A `ClientRegistration` can be initially configured using discovery of an OpenID
`ClientRegistrations` provides convenience methods for configuring a `ClientRegistration` in this way, as can be seen in the following example:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
ClientRegistration clientRegistration =
ClientRegistrations.fromIssuerLocation("https://idp.example.com/issuer").build();
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
val clientRegistration = ClientRegistrations.fromIssuerLocation("https://idp.example.com/issuer").build()
----
====
======
The above code will query in series `https://idp.example.com/issuer/.well-known/openid-configuration`, and then `https://idp.example.com/.well-known/openid-configuration/issuer`, and finally `https://idp.example.com/.well-known/oauth-authorization-server/issuer`, stopping at the first to return a 200 response.
@@ -106,8 +109,10 @@ The auto-configuration also registers the `ReactiveClientRegistrationRepository`
The following listing shows an example:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Controller
@@ -125,7 +130,8 @@ public class OAuth2ClientController {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Controller
@@ -142,7 +148,7 @@ class OAuth2ClientController {
}
}
----
====
======
[[oauth2Client-authorized-client]]
== OAuth2AuthorizedClient
@@ -163,8 +169,10 @@ From a developer perspective, the `ServerOAuth2AuthorizedClientRepository` or `R
The following listing shows an example:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Controller
@@ -183,7 +191,8 @@ public class OAuth2ClientController {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Controller
@@ -201,7 +210,7 @@ class OAuth2ClientController {
}
}
----
====
======
[NOTE]
Spring Boot 2.x auto-configuration registers an `ServerOAuth2AuthorizedClientRepository` and/or `ReactiveOAuth2AuthorizedClientService` `@Bean` in the `ApplicationContext`.
@@ -235,8 +244,10 @@ The `ReactiveOAuth2AuthorizedClientProviderBuilder` may be used to configure and
The following code shows an example of how to configure and build a `ReactiveOAuth2AuthorizedClientProvider` composite that provides support for the `authorization_code`, `refresh_token`, `client_credentials` and `password` authorization grant types:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -261,7 +272,8 @@ public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -280,7 +292,7 @@ fun authorizedClientManager(
return authorizedClientManager
}
----
====
======
When an authorization attempt succeeds, the `DefaultReactiveOAuth2AuthorizedClientManager` will delegate to the `ReactiveOAuth2AuthorizationSuccessHandler`, which (by default) will save the `OAuth2AuthorizedClient` via the `ServerOAuth2AuthorizedClientRepository`.
In the case of a re-authorization failure, eg. a refresh token is no longer valid, the previously saved `OAuth2AuthorizedClient` will be removed from the `ServerOAuth2AuthorizedClientRepository` via the `RemoveAuthorizedClientReactiveOAuth2AuthorizationFailureHandler`.
@@ -291,8 +303,10 @@ This can be useful when you need to supply a `ReactiveOAuth2AuthorizedClientProv
The following code shows an example of the `contextAttributesMapper`:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -337,7 +351,8 @@ private Function<OAuth2AuthorizeRequest, Mono<Map<String, Object>>> contextAttri
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -376,7 +391,7 @@ private fun contextAttributesMapper(): Function<OAuth2AuthorizeRequest, Mono<Mut
}
}
----
====
======
The `DefaultReactiveOAuth2AuthorizedClientManager` is designed to be used *_within_* the context of a `ServerWebExchange`.
When operating *_outside_* of a `ServerWebExchange` context, use `AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager` instead.
@@ -387,8 +402,10 @@ An OAuth 2.0 Client configured with the `client_credentials` grant type can be c
The following code shows an example of how to configure an `AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager` that provides support for the `client_credentials` grant type:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -410,7 +427,8 @@ public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -426,4 +444,4 @@ fun authorizedClientManager(
return authorizedClientManager
}
----
====
======
@@ -24,8 +24,10 @@ The `ServerHttpSecurity.oauth2Client()` DSL provides a number of configuration o
The following code shows the complete configuration options provided by the `ServerHttpSecurity.oauth2Client()` DSL:
.OAuth2 Client Configuration Options
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@EnableWebFluxSecurity
@@ -47,7 +49,8 @@ public class OAuth2ClientSecurityConfig {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@EnableWebFluxSecurity
@@ -67,14 +70,16 @@ class OAuth2ClientSecurityConfig {
}
}
----
====
======
The `ReactiveOAuth2AuthorizedClientManager` is responsible for managing the authorization (or re-authorization) of an OAuth 2.0 Client, in collaboration with one or more `ReactiveOAuth2AuthorizedClientProvider`(s).
The following code shows an example of how to register a `ReactiveOAuth2AuthorizedClientManager` `@Bean` and associate it with a `ReactiveOAuth2AuthorizedClientProvider` composite that provides support for the `authorization_code`, `refresh_token`, `client_credentials` and `password` authorization grant types:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -99,7 +104,8 @@ public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -118,4 +124,4 @@ fun authorizedClientManager(
return authorizedClientManager
}
----
====
======
@@ -23,8 +23,10 @@ These claims are normally represented by a JSON object that contains a collectio
The following code shows the complete configuration options available for the `oauth2Login()` DSL:
.OAuth2 Login Configuration Options
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@EnableWebFluxSecurity
@@ -52,7 +54,8 @@ public class OAuth2LoginSecurityConfig {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@EnableWebFluxSecurity
@@ -78,7 +81,7 @@ class OAuth2LoginSecurityConfig {
}
}
----
====
======
The following sections go into more detail on each of the configuration options available:
@@ -115,8 +118,10 @@ To override the default login page, configure the `exceptionHandling().authentic
The following listing shows an example:
.OAuth2 Login Page Configuration
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@EnableWebFluxSecurity
@@ -148,7 +153,8 @@ public class OAuth2LoginSecurityConfig {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@EnableWebFluxSecurity
@@ -179,7 +185,7 @@ class OAuth2LoginSecurityConfig {
...
}
----
====
======
[IMPORTANT]
You need to provide a `@Controller` with a `@RequestMapping("/login/oauth2")` that is capable of rendering the custom login page.
@@ -207,13 +213,15 @@ The Redirection Endpoint is used by the Authorization Server for returning the A
OAuth 2.0 Login leverages the Authorization Code Grant.
Therefore, the authorization credential is the authorization code.
The default Authorization Response redirection endpoint is `/login/oauth2/code/{registrationId}`.
The default Authorization Response redirection endpoint is `+/login/oauth2/code/{registrationId}+`.
If you would like to customize the Authorization Response redirection endpoint, configure it as shown in the following example:
.Redirection Endpoint Configuration
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@EnableWebFluxSecurity
@@ -231,7 +239,8 @@ public class OAuth2LoginSecurityConfig {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@EnableWebFluxSecurity
@@ -247,7 +256,7 @@ class OAuth2LoginSecurityConfig {
}
}
----
====
======
[IMPORTANT]
====
@@ -255,7 +264,10 @@ You also need to ensure the `ClientRegistration.redirectUri` matches the custom
The following listing shows an example:
.Java
[tabs]
======
Java::
+
[source,java,role="primary",attrs="-attributes"]
----
return CommonOAuth2Provider.GOOGLE.getBuilder("google")
@@ -265,7 +277,8 @@ return CommonOAuth2Provider.GOOGLE.getBuilder("google")
.build();
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary",attrs="-attributes"]
----
return CommonOAuth2Provider.GOOGLE.getBuilder("google")
@@ -274,6 +287,7 @@ return CommonOAuth2Provider.GOOGLE.getBuilder("google")
.redirectUri("{baseUrl}/login/oauth2/callback/{registrationId}")
.build()
----
======
====
@@ -307,8 +321,10 @@ There are a couple of options to choose from when mapping user authorities:
Register a `GrantedAuthoritiesMapper` `@Bean` to have it automatically applied to the configuration, as shown in the following example:
.Granted Authorities Mapper Configuration
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@EnableWebFluxSecurity
@@ -355,7 +371,8 @@ public class OAuth2LoginSecurityConfig {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@EnableWebFluxSecurity
@@ -389,7 +406,7 @@ class OAuth2LoginSecurityConfig {
}
}
----
====
======
[[webflux-oauth2-login-advanced-map-authorities-reactiveoauth2userservice]]
==== Delegation-based strategy with ReactiveOAuth2UserService
@@ -401,8 +418,10 @@ The `OAuth2UserRequest` (and `OidcUserRequest`) provides you access to the assoc
The following example shows how to implement and configure a delegation-based strategy using an OpenID Connect 1.0 UserService:
.ReactiveOAuth2UserService Configuration
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@EnableWebFluxSecurity
@@ -442,7 +461,8 @@ public class OAuth2LoginSecurityConfig {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@EnableWebFluxSecurity
@@ -478,7 +498,7 @@ class OAuth2LoginSecurityConfig {
}
}
----
====
======
[[webflux-oauth2-login-advanced-oauth2-user-service]]
@@ -495,8 +515,10 @@ If you need to customize the pre-processing of the UserInfo Request and/or the p
Whether you customize `DefaultReactiveOAuth2UserService` or provide your own implementation of `ReactiveOAuth2UserService`, you'll need to configure it as shown in the following example:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@EnableWebFluxSecurity
@@ -518,7 +540,8 @@ public class OAuth2LoginSecurityConfig {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@EnableWebFluxSecurity
@@ -537,7 +560,7 @@ class OAuth2LoginSecurityConfig {
}
}
----
====
======
[[webflux-oauth2-login-advanced-oidc-user-service]]
@@ -551,8 +574,10 @@ If you need to customize the pre-processing of the UserInfo Request and/or the p
Whether you customize `OidcReactiveOAuth2UserService` or provide your own implementation of `ReactiveOAuth2UserService` for OpenID Connect 1.0 Provider's, you'll need to configure it as shown in the following example:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@EnableWebFluxSecurity
@@ -574,7 +599,8 @@ public class OAuth2LoginSecurityConfig {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@EnableWebFluxSecurity
@@ -593,7 +619,7 @@ class OAuth2LoginSecurityConfig {
}
}
----
====
======
[[webflux-oauth2-login-advanced-idtoken-verify]]
@@ -610,8 +636,10 @@ The JWS algorithm resolver is a `Function` that accepts a `ClientRegistration` a
The following code shows how to configure the `OidcIdTokenDecoderFactory` `@Bean` to default to `MacAlgorithm.HS256` for all `ClientRegistration`:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -622,7 +650,8 @@ public ReactiveJwtDecoderFactory<ClientRegistration> idTokenDecoderFactory() {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -632,7 +661,7 @@ fun idTokenDecoderFactory(): ReactiveJwtDecoderFactory<ClientRegistration> {
return idTokenDecoderFactory
}
----
====
======
[NOTE]
For MAC based algorithms such as `HS256`, `HS384` or `HS512`, the `client-secret` corresponding to the `client-id` is used as the symmetric key for signature verification.
@@ -645,7 +674,7 @@ If more than one `ClientRegistration` is configured for OpenID Connect 1.0 Authe
== OpenID Connect 1.0 Logout
OpenID Connect Session Management 1.0 allows the ability to log out the End-User at the Provider using the Client.
One of the strategies available is https://openid.net/specs/openid-connect-session-1_0.html#RPLogout[RP-Initiated Logout].
One of the strategies available is https://openid.net/specs/openid-connect-rpinitiated-1_0.html[RP-Initiated Logout].
If the OpenID Provider supports both Session Management and https://openid.net/specs/openid-connect-discovery-1_0.html[Discovery], the client may obtain the `end_session_endpoint` `URL` from the OpenID Provider's https://openid.net/specs/openid-connect-session-1_0.html#OPMetadata[Discovery Metadata].
This can be achieved by configuring the `ClientRegistration` with the `issuer-uri`, as in the following example:
@@ -668,8 +697,10 @@ spring:
...and the `OidcClientInitiatedServerLogoutSuccessHandler`, which implements RP-Initiated Logout, may be configured as follows:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@EnableWebFluxSecurity
@@ -705,7 +736,8 @@ public class OAuth2LoginSecurityConfig {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@EnableWebFluxSecurity
@@ -737,7 +769,7 @@ class OAuth2LoginSecurityConfig {
}
}
----
====
======
NOTE: `OidcClientInitiatedServerLogoutSuccessHandler` supports the `{baseUrl}` placeholder.
NOTE: `OidcClientInitiatedServerLogoutSuccessHandler` supports the `+{baseUrl}+` placeholder.
If used, the application's base URL, like `https://app.example.org`, will replace it at request time.
@@ -61,10 +61,8 @@ spring:
----
+
.OAuth Client properties
====
<1> `spring.security.oauth2.client.registration` is the base property prefix for OAuth Client properties.
<2> Following the base property prefix is the ID for the xref:reactive/oauth2/client/core.adoc#oauth2Client-client-registration[`ClientRegistration`], such as google.
====
. Replace the values in the `client-id` and `client-secret` property with the OAuth 2.0 credentials you created earlier.
@@ -244,8 +242,10 @@ If you need to override the auto-configuration based on your specific requiremen
The following example shows how to register a `ReactiveClientRegistrationRepository` `@Bean`:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary",attrs="-attributes"]
----
@Configuration
@@ -275,7 +275,8 @@ public class OAuth2LoginConfig {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary",attrs="-attributes"]
----
@Configuration
@@ -304,7 +305,7 @@ class OAuth2LoginConfig {
}
}
----
====
======
[[webflux-oauth2-login-register-securitywebfilterchain-bean]]
@@ -313,8 +314,10 @@ class OAuth2LoginConfig {
The following example shows how to register a `SecurityWebFilterChain` `@Bean` with `@EnableWebFluxSecurity` and enable OAuth 2.0 login through `serverHttpSecurity.oauth2Login()`:
.OAuth2 Login Configuration
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@EnableWebFluxSecurity
@@ -333,7 +336,8 @@ public class OAuth2LoginSecurityConfig {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@EnableWebFluxSecurity
@@ -350,7 +354,7 @@ class OAuth2LoginSecurityConfig {
}
}
----
====
======
[[webflux-oauth2-login-completely-override-autoconfiguration]]
@@ -359,8 +363,10 @@ class OAuth2LoginSecurityConfig {
The following example shows how to completely override the auto-configuration by registering a `ReactiveClientRegistrationRepository` `@Bean` and a `SecurityWebFilterChain` `@Bean`.
.Overriding the auto-configuration
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary",attrs="-attributes"]
----
@EnableWebFluxSecurity
@@ -401,7 +407,8 @@ public class OAuth2LoginConfig {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary",attrs="-attributes"]
----
@EnableWebFluxSecurity
@@ -440,7 +447,7 @@ class OAuth2LoginConfig {
}
}
----
====
======
[[webflux-oauth2-login-javaconfig-wo-boot]]
@@ -449,8 +456,10 @@ class OAuth2LoginConfig {
If you are not able to use Spring Boot 2.x and would like to configure one of the pre-defined providers in `CommonOAuth2Provider` (for example, Google), apply the following configuration:
.OAuth2 Login Configuration
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@EnableWebFluxSecurity
@@ -493,7 +502,8 @@ public class OAuth2LoginConfig {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@EnableWebFluxSecurity
@@ -536,4 +546,4 @@ class OAuth2LoginConfig {
}
}
----
====
======
@@ -10,8 +10,10 @@ For example, you may have a need to read the bearer token from a custom header.
To achieve this, you can wire an instance of `ServerBearerTokenAuthenticationConverter` into the DSL, as you can see in the following example:
.Custom Bearer Token Header
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
ServerBearerTokenAuthenticationConverter converter = new ServerBearerTokenAuthenticationConverter();
@@ -22,7 +24,8 @@ http
);
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
val converter = ServerBearerTokenAuthenticationConverter()
@@ -33,15 +36,17 @@ return http {
}
}
----
====
======
== Bearer Token Propagation
Now that you're in possession of a bearer token, it might be handy to pass that to downstream services.
This is quite simple with `{security-api-url}org/springframework/security/oauth2/server/resource/web/reactive/function/client/ServerBearerExchangeFilterFunction.html[ServerBearerExchangeFilterFunction]`, which you can see in the following example:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -52,7 +57,8 @@ public WebClient rest() {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -62,15 +68,17 @@ fun rest(): WebClient {
.build()
}
----
====
======
When the above `WebClient` is used to perform requests, Spring Security will look up the current `Authentication` and extract any `{security-api-url}org/springframework/security/oauth2/core/AbstractOAuth2Token.html[AbstractOAuth2Token]` credential.
Then, it will propagate that token in the `Authorization` header.
For example:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
this.rest.get()
@@ -79,7 +87,8 @@ this.rest.get()
.bodyToMono(String.class)
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
this.rest.get()
@@ -87,14 +96,16 @@ this.rest.get()
.retrieve()
.bodyToMono<String>()
----
====
======
Will invoke the `https://other-service.example.com/endpoint`, adding the bearer token `Authorization` header for you.
In places where you need to override this behavior, it's a simple matter of supplying the header yourself, like so:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
this.rest.get()
@@ -104,7 +115,8 @@ this.rest.get()
.bodyToMono(String.class)
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
rest.get()
@@ -113,7 +125,7 @@ rest.get()
.retrieve()
.bodyToMono<String>()
----
====
======
In this case, the filter will fall back and simply forward the request onto the rest of the web filter chain.

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