1
0
mirror of synced 2026-09-10 03:09:47 +00:00

Compare commits

...

147 Commits

Author SHA1 Message Date
Rob Winch 55dd732443 Release 5.1.2.RELEASE 2018-11-28 08:44:40 -06:00
Rob Winch 45cbdc03ba Remove Thymeleaf dependency management
Fixes: gh-6161
2018-11-28 08:20:09 -06:00
Rob Winch d99c7dbfd1 Update to GAE 1.9.68
Fixes: gh-6160
2018-11-28 08:19:56 -06:00
Rob Winch d8de4d06f7 Update to Mockito 2.23.4
Fixes: gh-6159
2018-11-28 08:19:41 -06:00
Rob Winch be2f0f0fae Update to AspectJ 1.9.2
Fixes: gh-6158
2018-11-28 08:19:33 -06:00
Rob Winch 920d5ca6ad Update to unboundid 4.0.9
Fixes: gh-6157
2018-11-28 08:19:20 -06:00
Rob Winch 6dcf5a27b1 Update to Powermock 2.0.0-RC.4
Fixes: gh-6156
2018-11-28 08:19:13 -06:00
Rob Winch c2166c4207 Update to cglib-nodep:3.2.9
Fixes: gh-6155
2018-11-28 08:18:57 -06:00
Rob Winch 1542835268 Update to Spring Data Lovelace-SR3
Fixes: gh-6154
2018-11-28 08:18:40 -06:00
Rob Winch ae471e1456 Update to Spring 5.1.3
Fixes: gh-6153
2018-11-28 08:18:31 -06:00
Rob Winch 853c54eecb Update to Reactor Californium-SR3
Fixes: gh-6152
2018-11-28 08:18:18 -06:00
Joe Grandja 10f6d10f81 Update com.squareup.okhttp3 deps to 3.12.0
Fixes gh-5570
2018-11-26 12:44:30 -05:00
Rafael Dominguez 02d844d528 Enable Code Coverage Reports in Sonar
This commit ensures that jacocoTestReport task is called when running the Sonar stage.
Additionally, a variable is passed instructing Sonar where to find the test result data.

Fixes: gh-6092
2018-11-26 09:29:21 -07:00
Rafael Dominguez c0849ba891 Update to Gradle 4.10.2
Fixes gh-6106
2018-11-21 09:59:12 -06:00
Josh Cummings db145fa03f Register NullRequestCache When Disabled
Fixes: gh-6102
2018-11-20 07:39:54 -07:00
Josh Cummings 42b8b794a8 RequestCacheConfigurerTests groovy->java
Issue: gh-4939
2018-11-20 07:37:54 -07:00
dperezcabrera 8014114225 Make InMemory*ClientRegistrationRepository Consistent
The previous builders with the list argument were inconsistent with their 
respective builders of var args.
2018-11-19 11:12:37 -06:00
Rafael Dominguez a742c0c3f2 WebClientReactiveClientCredentialsTokenResponseClient.getTokenResponse expects 2xx http status code
This ensures that token response is only extracted when ClientResponse has a successful status

Fixes: gh-6089
2018-11-19 10:51:33 -06:00
Rafael Dominguez 1cec4fe3ac OAuth2AccessTokenResponseBodyExtractor supports Object values
This commit ensures the token response is parsed correctly if the values are not a String.

Fixes: gh-6087
2018-11-15 13:24:48 -06:00
Daniel Bustamante Ospina 8655caa2de Update webflux-form sample to use Built in CSRF Support
Remove the CsrfControllerAdvice class and update dependencies to add
org.thymeleaf.extras:thymeleaf-extras-springsecurity5

Issue: gh-6061
2018-11-14 17:19:06 -06:00
Dongmin Shin dc5f5f348d Fix Typo in Reference Docs
Fixes gh-6076
2018-11-14 11:37:22 -06:00
Rafael Dominguez 35e05780ea Updated Spring Boot version from 2.1.0.M4 to 2.1.0.RELEASE 2018-11-14 10:55:26 -06:00
Gunnar Hillert a461abcf32 Docs: Fix Maven Property example spring-security.version 2018-11-13 15:09:46 -06:00
Josh Cummings ba1f5f3873 Leave Issuer As String - Documentation
Update documentation that indicated the iss claim is proactively
coerced into a URL.

Issue: gh-6073
2018-11-13 12:35:08 -07:00
Josh Cummings 5478b74116 Leave Issuer As String
Since StringOrURI is a valid issuer, MappedJwtClaimSetConverter and
JwtIssuerValidator no longer assume it.

Issue: gh-6073
2018-11-13 10:43:36 -07:00
Josh Cummings dc4aa3d017 Favor URL.toExternalForm
Converts URLs to Strings before comparing them. Uses toString(),
which delegates to toExternalForm().

Fixes: gh-6073
2018-11-13 07:39:24 -07:00
Josh Cummings 2bc156ed19 Update to spring-build-conventions:0.0.22.RELEASE
Fixes: gh-6064
2018-11-12 09:31:51 -07:00
dperezcabrera 0270994340 InMemoryUserDetailsManager.updatePassword case-insenstive
Previously updatePassword was case sensitive which was
inconsistent with the rest of the class.

This commit updates updatePassword to be case insensitive.

Fixes: gh-6039
2018-11-09 11:41:44 -06:00
Erik van Paassen a557a324c0 Fix csrf:token-repository-ref XSD documentation
The documentation of the token-repository-ref attribute of the csrf
element in the schema has been updated to make clear the default
repository is lazy. Targets versions 4.2, 5.0 and 5.1.

Fixes gh-6037
2018-11-08 10:22:07 -06:00
Josh Cummings 73022059d4 Update Reactive Resource Server Docs
Resource Server documentation for both Servlet and Reactive now have a
similar feel and offer deeper exposure to common use cases.

Fixes: gh-6054
2018-11-07 12:10:33 -07:00
Josh Cummings 165cbcb723 Write Security Headers Before Servlet Include
HeaderWriterFilter wraps request dispatcher so it can write security
headers before the include occurs.

Fixes: gh-5499
2018-10-31 09:32:40 -06:00
Josh Cummings d99ecc2a7b Add JDK 11 to Jenkins
Fixes: gh-5860
2018-10-26 07:38:55 -06:00
Josh Cummings ccd2664e6c JDK 11 Compatibility
Upgraded dependencies and removed a test in the Java Config LDAP
sample which is arguably an integration test since it starts up an
LDAP container. This test also isn't JDK 11 compatible and the
remaining integration tests in the sample cover the same material.

Issue: gh-5860
2018-10-26 07:38:31 -06:00
Joe Grandja b87ff3c00f Remove charset from Accept header in UserInfo request
Fixes gh-6017
2018-10-25 14:10:41 -04:00
Joe Grandja 8971777908 Set AuthenticationEventPublisher on each AuthenticationManagerBuilder
Fixes gh-6009
2018-10-23 14:41:26 -04:00
Drummond Dawson 3d618d78ac Remove unnecessary concatenation of sql in JdbcUserDetailsManager 2018-10-19 15:30:24 -05:00
Drummond Dawson 697b8cd734 Add missing space to heading for it to render properly 2018-10-19 15:27:35 -05:00
Rob Winch cf03faf58e Update to spring-build-conventions:0.0.20.RELEASE
Fixes: gh-5998
2018-10-18 16:14:59 -05:00
Rob Winch 13159e9f88 Fail the build if deploy fails
Fixes: gh-5997
2018-10-18 16:14:59 -05:00
Brian Demers 41b7c74928 Fixing IllegalStateException message in OAuth2ResourceServerConfigurer
Updated message to include `http.oauth2ResourceServer()`
2018-10-17 15:14:49 -05:00
Josh Cummings 0a4ac4dbfc Add JDK 10 to Jenkins
Fixes: gh-5860
2018-10-17 15:06:14 -05:00
Josh Cummings 1eb56f46a5 JDK 10 Compatibility
Upgrading dependencies and reconfiguring PowerMock

Issue: gh-5860
2018-10-17 15:06:14 -05:00
Josh Cummings cfc3c25304 Update to Spring Build Conventions 0.0.19
Fixes: gh-5981
2018-10-17 15:06:14 -05:00
sunflower-seed 03a3a3b770 Update SubjectDnX509PrincipalExtractor.java
Added missing asterisk
2018-10-17 14:58:31 -05:00
Joe Grandja 921abefaa2 Remove address and phone scope from CommonOAuth2Provider.OKTA
Fixes gh-5987
2018-10-17 11:50:34 -04:00
Rob Winch a472153a31 Next Development Version 2018-10-15 14:20:19 -05:00
Rob Winch 8d5d0fd891 Release 5.1.1.RELEASE
Fixes: gh-5963
2018-10-15 14:19:20 -05:00
Josh Cummings 7269aacbdd Password Modify Extended Operation Support
LdapUserDetailsManager can be configured to either use direct
attribute modification or the LDAP Password Modify Extended Operation
to change a user's password.

Fixes: gh-3392
2018-10-15 12:24:39 -06:00
Josh Cummings b9ca1400e3 UnboundIdContainer Random Port Support
Setting a port of 0 will now induce the container to select a random
port.

Fixes: gh-5920
2018-10-15 12:24:39 -06:00
Josh Cummings b9c499d8c8 Polish UnboundIdContainerTests
Basic server startup test now asserts a successful server startup
instead of a failed one.

Issue: gh-5920
2018-10-15 12:24:39 -06:00
Josh Cummings 22bd8f1c1f Reactive Jwt Authentication Converter Support
Fixes: gh-5092
2018-10-15 11:55:12 -05:00
Rob Winch 01b47a8b2f Update to Thymeleaf 3.0.10.RELEASE
Fixes: gh-5960
2018-10-15 11:42:20 -05:00
Rob Winch c0e9488f1d Update to htmlunit-driver 2.33.0
Fixes: gh-5959
2018-10-15 11:42:13 -05:00
Rob Winch 4b72429b00 Update to Jackson 2.9.7
Fixes: gh-5958
2018-10-15 11:42:07 -05:00
Rob Winch 180c6fe24d Update to GAE 1.9.66
Fixes: gh-5957
2018-10-15 11:42:01 -05:00
Rob Winch d40a8e09c3 Update to Spring Data Lovelace SR1
Fixes: gh-5949
2018-10-15 09:36:45 -05:00
Rob Winch ad05c756de Update to Spring Framework 5.0.1.RELEASE
Fixes: gh-124
2018-10-15 09:36:45 -05:00
Joe Grandja 88f8bfeb57 Polish docs for WebClient OAuth2 Servlet Setup 2018-10-15 09:44:22 -04:00
Joe Grandja 07d2e43d7a Deprecate NimbusAuthorizationCodeTokenResponseClient
Fixes gh-5954
2018-10-15 09:01:19 -04:00
Joe Grandja 26fcde6f8e Document new features for OAuth Client
Fixes gh-5832
2018-10-12 19:24:02 -04:00
Rob Winch a26eadc600 Snapshot Build uses Spring 5.+
Fixes: gh-5951
2018-10-12 16:57:08 -05:00
Rob Winch e782cb09cb Update to Reactor Californium-SR1
Fixes: gh-5950
2018-10-12 16:52:27 -05:00
Rob Winch 7fcc47850f Update to Spring Boot 2.1.0.M4 2018-10-12 16:49:08 -05:00
Rob Winch 93ca455405 OAuth2LoginAuthenticationFilter ignores authenticated Users
This ensures that OAuth2 Client support works with the same log in URL as
oauth2 login.

Fixes: gh-5915
2018-10-12 16:29:27 -05:00
Thomas Jensen e8045838e3 Fix minor typo in secure-objects.adoc 2018-10-10 10:43:34 -05:00
Josh Cummings 2307b01a7a Resource Server Docs - Servlet
Fixes: gh-5912
2018-10-05 15:46:37 -06:00
Rob Winch cbb25f7b87 Polish Getting Spring Security Reference
Fixes: gh-5921
2018-10-04 17:27:09 -05:00
John Lin fe080cadbe Fix method name in the Contact sample code 2018-10-03 16:45:18 -04:00
Johnny Lim 68ffa0fece Add a missing space in Secured.value() signature 2018-10-03 14:47:48 -04:00
Tadaya Tsuyukubo 12050404ad Populate test security context with authentication
Add setAuthentication method on TestSecurityContextHolder.
2018-10-03 14:24:45 -04:00
Rob Winch 44ee15cb4d Next Development Version 2018-09-21 09:32:51 -05:00
Rob Winch c073705d55 Release Spring Security 5.1.0.RELEASE
Fixes: gh-5895
2018-09-21 08:13:44 -05:00
John Lin 69b71ee0ed Remove unused variables in ConsensusBased and UnanimousBased 2018-09-21 08:12:40 -05:00
Rob Winch 5d18bb68ed Add @formatter to @EnableWebFluxSecurity Javadoc
Fixes: gh-5898
2018-09-21 08:11:50 -05:00
Rob Winch e67264ae2c Update to Spring Data Lovelace-RELEASE
Fixes: gh-5885
2018-09-21 07:25:16 -05:00
Rob Winch dd82d114c0 Update to Spring Framework 5.1.0.RELEASE
Fixes: gh-5883
2018-09-21 01:04:16 -05:00
Rob Winch eca0b7bc04 Update to Reactor Californium-RELEASE
Fixes: gh-5884
2018-09-21 01:04:16 -05:00
Rob Winch 45a9c0fd54 Polish Automatically Add CsrfServerLogoutHandler
Issue: gh-5337
2018-09-21 00:59:36 -05:00
Eric Deandrea b060ec050a Automatically add CsrfServerLogoutHandler if csrf enabled
The configuration DSL should automatically add CsrfServerLogoutHandler if csrf is enabled

Fixes gh-5337
2018-09-21 00:59:36 -05:00
Rob Winch 725b3b5482 Fix OAuth2AuthorizationCodeGrantWebFilter works w/ /{action/
Issue: gh-5856
2018-09-20 21:39:09 -05:00
Vedran Pavic 79828d4f7b Polish WebFlux Referrer-Policy header config 2018-09-20 17:14:16 -05:00
Joe Grandja 9565e90b6e Remove oauth2-oidc-sdk dependency from oauth2-jose module
Fixes gh-5891
2018-09-20 15:20:11 -04:00
Joe Grandja d46f83caf4 Ensure consistent matching of redirect_uri
Fixes gh-5890
2018-09-20 14:30:41 -04:00
Josh Cummings 77fa495860 DelegatingOAuth2TokenValidator Varargs Constructor
Fixes: gh-5889
2018-09-20 10:45:51 -06:00
Rob Winch 8a49c431c3 Add OAuth2ClientSpec.and
Fixes: gh-5888
2018-09-20 10:19:21 -05:00
Josh Cummings 73c1abbba0 EnableGlobalMethodSecurity Misconfiguration Check
This polishes the EnableGlobalMethodSecurity misconfiguration check to
not error if the user has specified a custom method security metadata
source.

Issue: gh-5341
2018-09-20 07:55:03 -06:00
artsiom 1e864ad764 Validate @EnableGlobalMethodSecurity usage
Fixes: gh-5341
2018-09-20 07:55:03 -06:00
Josh Cummings d4c50a8fb8 Replace Section Links in What's New
Fixes: gh-5857
2018-09-20 07:50:30 -06:00
Rob Winch 508f0bc870 Remove Deprecated EvaluationContextExtensionSupport Usage
EvaluationContextExtensionSupport is deprecated. Instead classes should
implement EvaluationContextExtension directly.

Fixes: gh-5886
2018-09-19 16:52:16 -05:00
Rob Winch b4cad2c8b9 Update to mockito-core:2.22.0
Fixes: gh-5882
2018-09-19 16:36:41 -05:00
Rob Winch 1483d148d8 Update to htmlunit:2.33
Fixes: gh-5881
2018-09-19 16:36:33 -05:00
Rob Winch 779da0a633 Update to hibernate-validator:6.0.13.Final
Fixes: gh-5880
2018-09-19 16:36:16 -05:00
Rob Winch 352db344a1 Update to Jetty 9.4.12.v20180830
Fixes: gh-5879
2018-09-19 16:36:01 -05:00
Rob Winch 1acc46e622 Update to unboundid-ldapsdk:4.0.8
Fixes: gh-5878
2018-09-19 16:35:45 -05:00
Rob Winch a23830d096 Update to jaxb 2.3.0.1
Fixes: gh-5877
2018-09-19 16:35:35 -05:00
Rob Winch c10a4686bf Update to cglib:cglib-nodep:3.2.8
Fixes: gh-5876
2018-09-19 16:35:26 -05:00
Rob Winch 9e0c7f17b7 Default RequestCache should ignore favicon
Fixes: gh-5875
2018-09-19 14:29:14 -05:00
Rob Winch e4597b5213 WebSessionServerRequestCache ignores favicon and html
Fixes: gh-5874
2018-09-19 14:28:05 -05:00
Rob Winch 8e4d540bfb Default Log Out Pages Use HTTPS for CSS
Fixes: gh-5873
2018-09-19 13:52:35 -05:00
Rob Winch 410f6bae1a Fix ServerOAuth2AuthorizedClientExchangeFilterFunctionTests Merge
Issue: gh-5872
2018-09-19 11:53:21 -05:00
Rob Winch dcbf762a0b WebClient OAuth2 Support for defaultClientRegistrationId
Fixes: gh-5872
2018-09-19 11:47:04 -05:00
Josh Cummings f48055ce47 Polish Section Link in What's New 2018-09-19 10:20:42 -06:00
Joe Grandja 91f6888e9f Remove OAuth2AuthorizedClientRepository @Bean from OAuth2LoginApplication
Fixes gh-5546
2018-09-19 11:22:52 -04:00
Joe Grandja e8d8eb59bf Make OAuth2AuthorizedClient Serializable
Fixes gh-5757
2018-09-19 10:47:30 -04:00
Joe Grandja 2c078c5dd9 Remove expiresAt constructor-arg in OAuth2RefreshToken
Fixes gh-5854
2018-09-19 10:47:30 -04:00
Rob Winch ece5de3f99 Polish What's New
Issue: gh-5857
2018-09-19 09:45:13 -05:00
Joe Grandja 8b0a3a760c Use providedSessionAuthenticationStrategy
Fixes gh-5763
2018-09-19 07:04:49 -04:00
John Lin f5e2ca1b6e Fix truncated javadoc for Secured
Fixes: gh-5861
2018-09-18 21:34:02 -05:00
Rob Winch 62db88ce6a Add WebFlux CORS Reference
Fixes: gh-5870
2018-09-18 21:24:36 -05:00
Rob Winch 501c008526 Add WebFlux Redirect to HTTPS Reference
Fixes: gh-5869
2018-09-18 21:12:37 -05:00
Rob Winch db9248e05a Polish WebFlux HTTP Headers Reference
Issue: gh-5868
2018-09-18 20:30:45 -05:00
Rob Winch 7b7f579746 What's New Links to WebFlux sections
Issue: gh-5857
2018-09-18 17:23:03 -05:00
Rob Winch 54d07b6b8b Add WebFlux HTTP Headers Reference
Fixes: gh-5868
2018-09-18 17:09:41 -05:00
Josh Cummings a7f94b2188 Separate Servlet and WebFlux Feature Lists
Issue: gh-5857
2018-09-18 12:05:20 -06:00
Josh Cummings 2a83e34db5 Remove NOTE bookmarks
Originally, there were two references to "[[NOTE]]" which was
confusing since Asciidoc can't have two bookmarks of the same name.

Changed these to "[NOTE]" so they would display the "note" component
in the docs.

Issue: gh-5866
2018-09-18 12:05:19 -06:00
Rob Winch 87243ea453 Add WebFlux Resource Server Reference
Fixes: gh-5866
2018-09-18 10:58:56 -05:00
Rob Winch 83af2df131 Add WebFlux @RegisteredOAuth2AuthorizedClient Reference
Fixes: gh-5864
2018-09-18 10:25:06 -05:00
Rob Winch 1e1b8ab3e7 Add WebFlux OAuth2 Client Reference
Fixes: gh- 5865
2018-09-18 10:24:59 -05:00
Rob Winch a44d07ecf4 Fix OAuth2 Login Reference Format
Issue: gh-5836
2018-09-18 09:42:09 -05:00
Rob Winch 3ecefab957 Add WebFlux OAuth2 Login Reference
Fixes: gh-5863
2018-09-18 09:41:45 -05:00
Rob Winch 12c17e837b Remove JDK 10/11 from Jenkinsfile
This is necessary until we can figure out how to fix the build.

Issue: gh-5860
2018-09-17 22:40:45 -05:00
Rob Winch 735d755bb1 Add WebClient OAuth2 Documentation
Fixes: gh-5859
2018-09-17 22:26:33 -05:00
Rob Winch cc8935e904 Fix Reactive OIDC to add refresh token
Fixes: gh-5858
2018-09-17 21:21:36 -05:00
Rob Winch 72301e548a Reactive OAuth2 DSL Customizations
Fixes: gh-5855
2018-09-17 21:21:36 -05:00
Rob Winch 385bdfc055 OAuth2AuthorizationCodeGrantWebFilter works with /{action}/
This ensures that the same URL can work for both log in and
authorization code which prevents having to create additional registrations
on the client and potentially on the server (GitHub only allows a single
valid redirect URL).

Fixes: gh-5856
2018-09-17 21:21:36 -05:00
Josh Cummings 26afc18b66 What's New in Spring Security 5.1
An initial skeleton pointing to the various sections of Spring
Security that received an update in Spring Security 5.1

Issue: gh-5857
2018-09-17 16:37:47 -06:00
Josh Cummings b3d33708d1 JAXB for Authorization Server Sample
For Java 9+ compatibility

Issue: gh-5833
2018-09-14 09:21:37 -06:00
Rob Winch 9c749bf556 Fix SwitchUserFilter matchers
Fixes: gh-4249
2018-09-14 09:45:41 -05:00
Rob Winch 8b19f7a71a AntPathRequestMatcher supports UrlPathHelper
Fixes: gh-5846
2018-09-14 09:45:41 -05:00
Josh Cummings 17dd95d525 Polish Authorization Server Sample
Change primary user's username to "subject" to align with
oauth2resourceserver sample.
2018-09-13 16:21:10 -06:00
Josh Cummings f2140dd5f5 Authorization Server Sample
A simple Authorization Server sample based off of the Legacy
Authorization Server project, spring-security-oauth2.

This project is mostly useful as a utility for other samples whose
usage would be clearer if an Authorization Server were introduced.

Note that this server is a barebones OAuth 2.0 Authorization Server
configuration, but is still useful for understanding how to set up an
Authorization Server using spring-security-oauth2.

Fixes: gh-5833
2018-09-13 15:24:32 -06:00
Rob Winch c21b2f31c6 Polish resourceserver samples
- Use ${mockserver.url} instead of mock://
- Consistency between reactive/imperative samples

Fixes: gh-5844
2018-09-13 14:31:23 -05:00
Rob Winch f68141d42a Update to Spring Boot 2.1.0.M3
Fixes: gh-5843
2018-09-13 10:27:16 -05:00
Rob Winch f5c73a566a Fixes: gh-5841 2018-09-13 09:23:13 -05:00
Rob Winch 68bc649a45 Fix XsdDocumentedTests
Issue: gh-5836
2018-09-12 19:56:30 -05:00
Rob Winch 5b8d66e911 Extract Preface
Issue: gh-5836
2018-09-12 17:04:41 -05:00
Rob Winch 57359058dd Separate Servlet and WebFlux
Fixes: gh-5836
2018-09-12 16:48:41 -05:00
Joe Grandja ed9cd478ba Polish
Issue gh-5776
2018-09-12 11:57:53 -04:00
Joe Grandja 8746e71b9a Use OAuth2AuthorizationException in authorization flows 2018-09-11 14:53:42 -05:00
Joe Grandja ef02ab2f8a DefaultOAuth2UserService handles OAuth2AuthorizationException 2018-09-11 14:53:42 -05:00
Joe Grandja 7474d6524e DefaultAuthorizationCodeTokenResponseClient throws OAuth2AuthorizationException 2018-09-11 14:53:42 -05:00
Joe Grandja 56b4576396 DefaultClientCredentialsTokenResponseClient throws OAuth2AuthorizationException
Fixes gh-5726
2018-09-11 14:53:42 -05:00
Joe Grandja e56c048db3 Remove OAuth2ClientException 2018-09-11 14:53:42 -05:00
Joe Grandja b3282957a8 Add OAuth2AuthorizationException 2018-09-11 14:53:42 -05:00
Jisoo Youn 3f3a3e8351 Fix convention 2018-09-11 13:57:23 -05:00
Johnny Lim 42327a0aec Polish OAuth2ResourceServerConfigurerTests 2018-09-10 13:24:16 -06:00
Rob Winch e5e91204fc Next development version
Issue: gh-5823
2018-09-07 15:57:04 -05:00
262 changed files with 7302 additions and 2738 deletions
+1 -1
View File
@@ -47,7 +47,7 @@ Create your topic branch to be submitted as a pull request from master. The Spri
# Use short branch names
Branches used when submitting pull requests should preferably be named according to GitHub issues, e.g. 'gh-1234' or 'gh-1234-fix-npe'. Otherwise, use succinct, lower-case, dash (-) delimited names, such as 'fix-warnings', 'fix-typo', etc. This is important, because branch names show up in the merge commits that result from accepting pull requests, and should be as expressive and concise as possible.
#Keep commits focused
# Keep commits focused
Remember each ticket should be focused on a single item of interest since the tickets are used to produce the changelog. Since each commit should be tied to a single GitHub issue, ensure that your commits are focused. For example, do not include an update to a transitive library in your commit unless the GitHub is to update the library. Reviewing your commits is essential before sending a pull request.
Vendored
+19 -16
View File
@@ -30,7 +30,7 @@ try {
checkout scm
withCredentials([string(credentialsId: 'spring-sonar.login', variable: 'SONAR_LOGIN')]) {
try {
sh "./gradlew clean sonarqube -PexcludeProjects='**/samples/**' -Dsonar.host.url=$SPRING_SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN --refresh-dependencies --no-daemon --stacktrace"
sh "./gradlew clean jacocoTestReport sonarqube -Dsonar.jacoco.reportPaths='**/build/jacoco/*.exec' -PexcludeProjects='**/samples/**' -Dsonar.host.url=$SPRING_SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN --refresh-dependencies --no-daemon --stacktrace"
} catch(Exception e) {
currentBuild.result = 'FAILED: sonar'
throw e
@@ -44,7 +44,7 @@ try {
node {
checkout scm
try {
sh "./gradlew clean test -PforceMavenRepositories=snapshot -PspringVersion='5.1.0.BUILD-SNAPSHOT' -PreactorVersion=Californium-BUILD-SNAPSHOT -PspringDataVersion=Lovelace-BUILD-SNAPSHOT --refresh-dependencies --no-daemon --stacktrace"
sh "./gradlew clean test -PforceMavenRepositories=snapshot -PspringVersion='5.+' -PreactorVersion=Californium-BUILD-SNAPSHOT -PspringDataVersion=Lovelace-BUILD-SNAPSHOT --refresh-dependencies --no-daemon --stacktrace"
} catch(Exception e) {
currentBuild.result = 'FAILED: snapshots'
throw e
@@ -82,20 +82,20 @@ try {
}
}
},
jdk11: {
stage('JDK 11') {
node {
checkout scm
try {
withEnv(["JAVA_HOME=${ tool 'jdk11' }"]) {
sh "./gradlew clean test --refresh-dependencies --no-daemon --stacktrace"
}
} catch(Exception e) {
currentBuild.result = 'FAILED: jdk11'
throw e
}
}
}
jdk11: {
stage('JDK 11') {
node {
checkout scm
try {
withEnv(["JAVA_HOME=${ tool 'jdk11' }"]) {
sh "./gradlew clean test --refresh-dependencies --no-daemon --stacktrace"
}
} catch(Exception e) {
currentBuild.result = 'FAILED: jdk11'
throw e
}
}
}
}
if(currentBuild.result == 'SUCCESS') {
@@ -136,6 +136,9 @@ try {
}
}
}
} catch(Exception e) {
currentBuild.result = 'FAILED: deploys'
throw e
} finally {
def buildStatus = currentBuild.result
def buildNotSuccess = !SUCCESS.equals(buildStatus)
+2 -1
View File
@@ -1,10 +1,11 @@
buildscript {
dependencies {
classpath 'io.spring.gradle:spring-build-conventions:0.0.18.RELEASE'
classpath 'io.spring.gradle:spring-build-conventions:0.0.22.RELEASE'
classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion"
}
repositories {
maven { url 'https://repo.spring.io/plugins-snapshot' }
maven { url 'https://plugins.gradle.org/m2/' }
}
}
apply plugin: 'io.spring.convention.root'
@@ -358,13 +358,24 @@ public class GlobalMethodSecurityConfiguration
if (customMethodSecurityMetadataSource != null) {
sources.add(customMethodSecurityMetadataSource);
}
if (prePostEnabled()) {
boolean hasCustom = customMethodSecurityMetadataSource != null;
boolean isPrePostEnabled = prePostEnabled();
boolean isSecuredEnabled = securedEnabled();
boolean isJsr250Enabled = jsr250Enabled();
if (!isPrePostEnabled && !isSecuredEnabled && !isJsr250Enabled && !hasCustom) {
throw new IllegalStateException("In the composition of all global method configuration, " +
"no annotation support was actually activated");
}
if (isPrePostEnabled) {
sources.add(new PrePostAnnotationSecurityMetadataSource(attributeFactory));
}
if (securedEnabled()) {
if (isSecuredEnabled) {
sources.add(new SecuredAnnotationSecurityMetadataSource());
}
if (jsr250Enabled()) {
if (isJsr250Enabled) {
GrantedAuthorityDefaults grantedAuthorityDefaults =
getSingleBeanOrNull(GrantedAuthorityDefaults.class);
Jsr250MethodSecurityMetadataSource jsr250MethodSecurityMetadataSource = this.context.getBean(Jsr250MethodSecurityMetadataSource.class);
@@ -200,6 +200,7 @@ public abstract class WebSecurityConfigurerAdapter implements
AuthenticationManager authenticationManager = authenticationManager();
authenticationBuilder.parentAuthenticationManager(authenticationManager);
authenticationBuilder.authenticationEventPublisher(eventPublisher);
Map<Class<? extends Object>, Object> sharedObjects = createSharedObjects();
http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
@@ -25,6 +25,7 @@ import org.springframework.http.MediaType;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.NullRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
import org.springframework.security.web.util.matcher.AndRequestMatcher;
@@ -87,6 +88,12 @@ public final class RequestCacheConfigurer<H extends HttpSecurityBuilder<H>> exte
return this;
}
@Override
public H disable() {
getBuilder().setSharedObject(RequestCache.class, new NullRequestCache());
return super.disable();
}
@Override
public void init(H http) throws Exception {
http.setSharedObject(RequestCache.class, getRequestCache(http));
@@ -144,7 +151,7 @@ public final class RequestCacheConfigurer<H extends HttpSecurityBuilder<H>> exte
}
RequestMatcher notFavIcon = new NegatedRequestMatcher(new AntPathRequestMatcher(
"/**/favicon.ico"));
"/**/favicon.*"));
MediaTypeRequestMatcher jsonRequest = new MediaTypeRequestMatcher(
contentNegotiationStrategy, MediaType.APPLICATION_JSON);
@@ -211,24 +211,25 @@ public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
}
/**
* Allows explicitly specifying the {@link SessionAuthenticationStrategy}. The default
* is to use {@link SessionFixationProtectionStrategy}. If restricting the maximum
* number of sessions is configured, then
* Allows explicitly specifying the {@link SessionAuthenticationStrategy}.
* The default is to use {@link SessionFixationProtectionStrategy} for Servlet 3.1 or
* {@link ChangeSessionIdAuthenticationStrategy} for Servlet 3.1+.
* If restricting the maximum number of sessions is configured, then
* {@link CompositeSessionAuthenticationStrategy} delegating to
* {@link ConcurrentSessionControlAuthenticationStrategy},
* {@link SessionFixationProtectionStrategy} (the default) OR
* {@link SessionAuthenticationStrategy} the supplied sessionAuthenticationStrategy,
* the default OR supplied {@code SessionAuthenticationStrategy} and
* {@link RegisterSessionAuthenticationStrategy}.
*
* <p>
* NOTE: Supplying a custom {@link SessionAuthenticationStrategy} will override the
* default provided {@link SessionFixationProtectionStrategy}.
* default session fixation strategy.
*
* @param sessionAuthenticationStrategy
* @return the {@link SessionManagementConfigurer} for further customizations
*/
public SessionManagementConfigurer<H> sessionAuthenticationStrategy(
SessionAuthenticationStrategy sessionAuthenticationStrategy) {
this.sessionFixationAuthenticationStrategy = sessionAuthenticationStrategy;
this.providedSessionAuthenticationStrategy = sessionAuthenticationStrategy;
return this;
}
@@ -592,8 +593,8 @@ public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
List<SessionAuthenticationStrategy> delegateStrategies = this.sessionAuthenticationStrategies;
SessionAuthenticationStrategy defaultSessionAuthenticationStrategy;
if (this.providedSessionAuthenticationStrategy == null) {
// If a user provided SessionAuthenticationStrategy is not supplied
// then default to SessionFixationProtectionStrategy
// If the user did not provide a SessionAuthenticationStrategy
// then default to sessionFixationAuthenticationStrategy
defaultSessionAuthenticationStrategy = postProcess(
this.sessionFixationAuthenticationStrategy);
}
@@ -26,6 +26,7 @@ import org.springframework.security.config.annotation.web.configurers.AbstractHt
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationProvider;
import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken;
@@ -428,6 +429,9 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>> exten
this.loginProcessingUrl);
this.setAuthenticationFilter(authenticationFilter);
super.loginProcessingUrl(this.loginProcessingUrl);
RequestMatcher authenticationNullMatcher = request -> SecurityContextHolder.getContext().getAuthentication() == null;
authenticationFilter.setRequiresAuthenticationRequestMatcher(new AndRequestMatcher(createLoginProcessingUrlMatcher(this.loginProcessingUrl),
authenticationNullMatcher));
if (this.loginPage != null) {
// Set custom login page
@@ -183,9 +183,9 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
if ( this.jwtConfigurer == null ) {
throw new IllegalStateException("Jwt is the only supported format for bearer tokens " +
"in Spring Security and no Jwt configuration was found. Make sure to specify " +
"a jwk set uri by doing http.oauth2().resourceServer().jwt().jwkSetUri(uri), or wire a " +
"JwtDecoder instance by doing http.oauth2().resourceServer().jwt().decoder(decoder), or " +
"expose a JwtDecoder instance as a bean and do http.oauth2().resourceServer().jwt().");
"a jwk set uri by doing http.oauth2ResourceServer().jwt().jwkSetUri(uri), or wire a " +
"JwtDecoder instance by doing http.oauth2ResourceServer().jwt().decoder(decoder), or " +
"expose a JwtDecoder instance as a bean and do http.oauth2ResourceServer().jwt().");
}
JwtDecoder decoder = this.jwtConfigurer.getJwtDecoder();
@@ -54,6 +54,7 @@ import java.lang.annotation.Target;
* <pre class="code">
* &#064;EnableWebFluxSecurity
* public class MyExplicitSecurityConfiguration {
* // @formatter:off
* &#064;Bean
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
* http
@@ -64,7 +65,9 @@ import java.lang.annotation.Target;
* .formLogin();
* return http.build();
* }
* // @formatter:on
*
* // @formatter:off
* &#064;Bean
* public MapReactiveUserDetailsService userDetailsService() {
* UserDetails user = User.withDefaultPasswordEncoder()
@@ -74,6 +77,7 @@ import java.lang.annotation.Target;
* .build();
* return new MapReactiveUserDetailsService(user);
* }
* // @formatter:on
* }
*
* @author Rob Winch
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2018 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.
@@ -86,7 +86,7 @@ public enum CommonOAuth2Provider {
public Builder getBuilder(String registrationId) {
ClientRegistration.Builder builder = getBuilder(registrationId,
ClientAuthenticationMethod.BASIC, DEFAULT_REDIRECT_URL);
builder.scope("openid", "profile", "email", "address", "phone");
builder.scope("openid", "profile", "email");
builder.userNameAttributeName(IdTokenClaimNames.SUB);
builder.clientName("Okta");
return builder;
@@ -48,6 +48,7 @@ public enum SecurityWebFiltersOrder {
*/
FORM_LOGIN,
AUTHENTICATION,
OAUTH2_AUTHORIZATION_CODE,
LOGIN_PAGE_GENERATING,
LOGOUT_PAGE_GENERATING,
/**
@@ -27,6 +27,8 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import reactor.core.publisher.Mono;
import reactor.util.context.Context;
@@ -36,8 +38,10 @@ import org.springframework.context.ApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.DelegatingReactiveAuthenticationManager;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authorization.AuthenticatedReactiveAuthorizationManager;
@@ -46,6 +50,7 @@ import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import org.springframework.security.oauth2.client.InMemoryReactiveOAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeReactiveAuthenticationManager;
@@ -53,9 +58,11 @@ import org.springframework.security.oauth2.client.authentication.OAuth2LoginReac
import org.springframework.security.oauth2.client.endpoint.WebClientReactiveAuthorizationCodeTokenResponseClient;
import org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizationCodeReactiveAuthenticationManager;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcReactiveOAuth2UserService;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.client.userinfo.DefaultReactiveOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.ReactiveOAuth2UserService;
import org.springframework.security.oauth2.client.web.server.AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.client.web.server.OAuth2AuthorizationCodeGrantWebFilter;
@@ -63,9 +70,14 @@ import org.springframework.security.oauth2.client.web.server.OAuth2Authorization
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizationCodeAuthenticationTokenConverter;
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.client.web.server.authentication.OAuth2LoginAuthenticationWebFilter;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtReactiveAuthenticationManager;
import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter;
import org.springframework.security.oauth2.server.resource.web.access.server.BearerTokenServerAccessDeniedHandler;
import org.springframework.security.oauth2.server.resource.web.server.BearerTokenServerAuthenticationEntryPoint;
import org.springframework.security.oauth2.server.resource.web.server.ServerBearerTokenAuthenticationConverter;
@@ -80,12 +92,15 @@ import org.springframework.security.web.server.authentication.HttpBasicServerAut
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationEntryPoint;
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationFailureHandler;
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationSuccessHandler;
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter;
import org.springframework.security.web.server.authentication.ServerAuthenticationEntryPointFailureHandler;
import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler;
import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;
import org.springframework.security.web.server.authentication.ServerFormLoginAuthenticationConverter;
import org.springframework.security.web.server.authentication.ServerHttpBasicAuthenticationConverter;
import org.springframework.security.web.server.authentication.logout.DelegatingServerLogoutHandler;
import org.springframework.security.web.server.authentication.logout.LogoutWebFilter;
import org.springframework.security.web.server.authentication.logout.SecurityContextServerLogoutHandler;
import org.springframework.security.web.server.authentication.logout.ServerLogoutHandler;
import org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler;
import org.springframework.security.web.server.authorization.AuthorizationContext;
@@ -99,8 +114,10 @@ import org.springframework.security.web.server.context.ReactorContextWebFilter;
import org.springframework.security.web.server.context.SecurityContextServerWebExchangeWebFilter;
import org.springframework.security.web.server.context.ServerSecurityContextRepository;
import org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository;
import org.springframework.security.web.server.csrf.CsrfServerLogoutHandler;
import org.springframework.security.web.server.csrf.CsrfWebFilter;
import org.springframework.security.web.server.csrf.ServerCsrfTokenRepository;
import org.springframework.security.web.server.csrf.WebSessionServerCsrfTokenRepository;
import org.springframework.security.web.server.header.CacheControlServerHttpHeadersWriter;
import org.springframework.security.web.server.header.CompositeServerHttpHeadersWriter;
import org.springframework.security.web.server.header.ContentSecurityPolicyServerHttpHeadersWriter;
@@ -511,6 +528,64 @@ public class ServerHttpSecurity {
private ServerOAuth2AuthorizedClientRepository authorizedClientRepository;
private ReactiveAuthenticationManager authenticationManager;
private ServerAuthenticationConverter authenticationConverter;
/**
* Configures the {@link ReactiveAuthenticationManager} to use. The default is
* {@link OAuth2AuthorizationCodeReactiveAuthenticationManager}
* @param authenticationManager the manager to use
* @return the {@link OAuth2LoginSpec} to customize
*/
public OAuth2LoginSpec authenticationManager(ReactiveAuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
return this;
}
/**
* Gets the {@link ReactiveAuthenticationManager} to use. First tries an explicitly configured manager, and
* defaults to {@link OAuth2AuthorizationCodeReactiveAuthenticationManager}
*
* @return the {@link ReactiveAuthenticationManager} to use
*/
private ReactiveAuthenticationManager getAuthenticationManager() {
if (this.authenticationManager == null) {
this.authenticationManager = createDefault();
}
return this.authenticationManager;
}
private ReactiveAuthenticationManager createDefault() {
WebClientReactiveAuthorizationCodeTokenResponseClient client = new WebClientReactiveAuthorizationCodeTokenResponseClient();
ReactiveAuthenticationManager result = new OAuth2LoginReactiveAuthenticationManager(client, getOauth2UserService());
boolean oidcAuthenticationProviderEnabled = ClassUtils.isPresent(
"org.springframework.security.oauth2.jwt.JwtDecoder", this.getClass().getClassLoader());
if (oidcAuthenticationProviderEnabled) {
OidcAuthorizationCodeReactiveAuthenticationManager oidc = new OidcAuthorizationCodeReactiveAuthenticationManager(client, getOidcUserService());
result = new DelegatingReactiveAuthenticationManager(oidc, result);
}
return result;
}
/**
* Sets the converter to use
* @param authenticationConverter the converter to use
* @return the {@link OAuth2LoginSpec} to customize
*/
public OAuth2LoginSpec authenticationConverter(ServerAuthenticationConverter authenticationConverter) {
this.authenticationConverter = authenticationConverter;
return this;
}
private ServerAuthenticationConverter getAuthenticationConverter(ReactiveClientRegistrationRepository clientRegistrationRepository) {
if (this.authenticationConverter == null) {
this.authenticationConverter = new ServerOAuth2AuthorizationCodeAuthenticationTokenConverter(clientRegistrationRepository);
}
return this.authenticationConverter;
}
public OAuth2LoginSpec clientRegistrationRepository(ReactiveClientRegistrationRepository clientRegistrationRepository) {
this.clientRegistrationRepository = clientRegistrationRepository;
return this;
@@ -540,21 +615,11 @@ public class ServerHttpSecurity {
ServerOAuth2AuthorizedClientRepository authorizedClientRepository = getAuthorizedClientRepository();
OAuth2AuthorizationRequestRedirectWebFilter oauthRedirectFilter = new OAuth2AuthorizationRequestRedirectWebFilter(clientRegistrationRepository);
WebClientReactiveAuthorizationCodeTokenResponseClient client = new WebClientReactiveAuthorizationCodeTokenResponseClient();
ReactiveOAuth2UserService userService = new DefaultReactiveOAuth2UserService();
ReactiveAuthenticationManager manager = new OAuth2LoginReactiveAuthenticationManager(client, userService);
boolean oidcAuthenticationProviderEnabled = ClassUtils.isPresent(
"org.springframework.security.oauth2.jwt.JwtDecoder", this.getClass().getClassLoader());
if (oidcAuthenticationProviderEnabled) {
OidcAuthorizationCodeReactiveAuthenticationManager oidc = new OidcAuthorizationCodeReactiveAuthenticationManager(client, new OidcReactiveOAuth2UserService());
manager = new DelegatingReactiveAuthenticationManager(oidc, manager);
}
ReactiveAuthenticationManager manager = getAuthenticationManager();
AuthenticationWebFilter authenticationFilter = new OAuth2LoginAuthenticationWebFilter(manager, authorizedClientRepository);
authenticationFilter.setRequiresAuthenticationMatcher(new PathPatternParserServerWebExchangeMatcher("/login/oauth2/code/{registrationId}"));
authenticationFilter.setServerAuthenticationConverter(new ServerOAuth2AuthorizationCodeAuthenticationTokenConverter(clientRegistrationRepository));
authenticationFilter.setRequiresAuthenticationMatcher(createAttemptAuthenticationRequestMatcher());
authenticationFilter.setServerAuthenticationConverter(getAuthenticationConverter(clientRegistrationRepository));
RedirectServerAuthenticationSuccessHandler redirectHandler = new RedirectServerAuthenticationSuccessHandler();
authenticationFilter.setAuthenticationSuccessHandler(redirectHandler);
@@ -581,6 +646,34 @@ public class ServerHttpSecurity {
http.addFilterAt(authenticationFilter, SecurityWebFiltersOrder.AUTHENTICATION);
}
private ServerWebExchangeMatcher createAttemptAuthenticationRequestMatcher() {
PathPatternParserServerWebExchangeMatcher loginPathMatcher = new PathPatternParserServerWebExchangeMatcher("/login/oauth2/code/{registrationId}");
ServerWebExchangeMatcher notAuthenticatedMatcher = e -> ReactiveSecurityContextHolder.getContext()
.flatMap(p -> ServerWebExchangeMatcher.MatchResult.notMatch())
.switchIfEmpty(ServerWebExchangeMatcher.MatchResult.match());
return new AndServerWebExchangeMatcher(loginPathMatcher, notAuthenticatedMatcher);
}
private ReactiveOAuth2UserService<OidcUserRequest, OidcUser> getOidcUserService() {
ResolvableType type = ResolvableType.forClassWithGenerics(ReactiveOAuth2UserService.class, OidcUserRequest.class, OidcUser.class);
ReactiveOAuth2UserService<OidcUserRequest, OidcUser> bean = getBeanOrNull(type);
if (bean == null) {
return new OidcReactiveOAuth2UserService();
}
return bean;
}
private ReactiveOAuth2UserService<OAuth2UserRequest, OAuth2User> getOauth2UserService() {
ResolvableType type = ResolvableType.forClassWithGenerics(ReactiveOAuth2UserService.class, OAuth2UserRequest.class, OAuth2User.class);
ReactiveOAuth2UserService<OAuth2UserRequest, OAuth2User> bean = getBeanOrNull(type);
if (bean == null) {
return new DefaultReactiveOAuth2UserService();
}
return bean;
}
private Map<String, String> getLinks() {
Iterable<ClientRegistration> registrations = getBeanOrNull(ResolvableType.forClassWithGenerics(Iterable.class, ClientRegistration.class));
if (registrations == null) {
@@ -654,8 +747,53 @@ public class ServerHttpSecurity {
public class OAuth2ClientSpec {
private ReactiveClientRegistrationRepository clientRegistrationRepository;
private ServerAuthenticationConverter authenticationConverter;
private ServerOAuth2AuthorizedClientRepository authorizedClientRepository;
private ReactiveAuthenticationManager authenticationManager;
/**
* Sets the converter to use
* @param authenticationConverter the converter to use
* @return the {@link OAuth2ClientSpec} to customize
*/
public OAuth2ClientSpec authenticationConverter(ServerAuthenticationConverter authenticationConverter) {
this.authenticationConverter = authenticationConverter;
return this;
}
private ServerAuthenticationConverter getAuthenticationConverter() {
if (this.authenticationConverter == null) {
this.authenticationConverter = new ServerOAuth2AuthorizationCodeAuthenticationTokenConverter(getClientRegistrationRepository());
}
return this.authenticationConverter;
}
/**
* Configures the {@link ReactiveAuthenticationManager} to use. The default is
* {@link OAuth2AuthorizationCodeReactiveAuthenticationManager}
* @param authenticationManager the manager to use
* @return the {@link OAuth2ClientSpec} to customize
*/
public OAuth2ClientSpec authenticationManager(ReactiveAuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
return this;
}
/**
* Gets the {@link ReactiveAuthenticationManager} to use. First tries an explicitly configured manager, and
* defaults to {@link OAuth2AuthorizationCodeReactiveAuthenticationManager}
*
* @return the {@link ReactiveAuthenticationManager} to use
*/
private ReactiveAuthenticationManager getAuthenticationManager() {
if (this.authenticationManager == null) {
this.authenticationManager = new OAuth2AuthorizationCodeReactiveAuthenticationManager(new WebClientReactiveAuthorizationCodeTokenResponseClient());
}
return this.authenticationManager;
}
/**
* Configures the {@link ReactiveClientRegistrationRepository}. Default is to look the value up as a Bean.
* @param clientRegistrationRepository the repository to use
@@ -676,17 +814,26 @@ public class ServerHttpSecurity {
return this;
}
/**
* Allows method chaining to continue configuring the {@link ServerHttpSecurity}
* @return the {@link ServerHttpSecurity} to continue configuring
*/
public ServerHttpSecurity and() {
return ServerHttpSecurity.this;
}
protected void configure(ServerHttpSecurity http) {
ReactiveClientRegistrationRepository clientRegistrationRepository = getClientRegistrationRepository();
ServerOAuth2AuthorizedClientRepository authorizedClientRepository = getAuthorizedClientRepository();
ReactiveAuthenticationManager authenticationManager = new OAuth2AuthorizationCodeReactiveAuthenticationManager(new WebClientReactiveAuthorizationCodeTokenResponseClient());
ServerAuthenticationConverter authenticationConverter = getAuthenticationConverter();
ReactiveAuthenticationManager authenticationManager = getAuthenticationManager();
OAuth2AuthorizationCodeGrantWebFilter codeGrantWebFilter = new OAuth2AuthorizationCodeGrantWebFilter(authenticationManager,
clientRegistrationRepository,
authenticationConverter,
authorizedClientRepository);
OAuth2AuthorizationRequestRedirectWebFilter oauthRedirectFilter = new OAuth2AuthorizationRequestRedirectWebFilter(
clientRegistrationRepository);
http.addFilterAt(codeGrantWebFilter, SecurityWebFiltersOrder.AUTHENTICATION);
http.addFilterAt(codeGrantWebFilter, SecurityWebFiltersOrder.OAUTH2_AUTHORIZATION_CODE);
http.addFilterAt(oauthRedirectFilter, SecurityWebFiltersOrder.HTTP_BASIC);
}
@@ -758,6 +905,8 @@ public class ServerHttpSecurity {
public class JwtSpec {
private ReactiveAuthenticationManager authenticationManager;
private ReactiveJwtDecoder jwtDecoder;
private Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> jwtAuthenticationConverter
= new ReactiveJwtAuthenticationConverterAdapter(new JwtAuthenticationConverter());
private BearerTokenServerWebExchangeMatcher bearerTokenServerWebExchangeMatcher =
new BearerTokenServerWebExchangeMatcher();
@@ -773,6 +922,21 @@ public class ServerHttpSecurity {
return this;
}
/**
* Configures the {@link Converter} to use for converting a {@link Jwt} into
* an {@link AbstractAuthenticationToken}.
*
* @param jwtAuthenticationConverter the converter to use
* @return the {@code JwtSpec} for additional configuration
* @since 5.1.1
*/
public JwtSpec jwtAuthenticationConverter
(Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> jwtAuthenticationConverter) {
Assert.notNull(jwtAuthenticationConverter, "jwtAuthenticationConverter cannot be null");
this.jwtAuthenticationConverter = jwtAuthenticationConverter;
return this;
}
/**
* Configures the {@link ReactiveJwtDecoder} to use
* @param jwtDecoder the decoder to use
@@ -834,14 +998,24 @@ public class ServerHttpSecurity {
return this.jwtDecoder;
}
protected Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>>
getJwtAuthenticationConverter() {
return this.jwtAuthenticationConverter;
}
private ReactiveAuthenticationManager getAuthenticationManager() {
if (this.authenticationManager != null) {
return this.authenticationManager;
}
ReactiveJwtDecoder jwtDecoder = getJwtDecoder();
ReactiveAuthenticationManager authenticationManager =
Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> jwtAuthenticationConverter =
getJwtAuthenticationConverter();
JwtReactiveAuthenticationManager authenticationManager =
new JwtReactiveAuthenticationManager(jwtDecoder);
authenticationManager.setJwtAuthenticationConverter(jwtAuthenticationConverter);
return authenticationManager;
}
@@ -1345,6 +1519,22 @@ public class ServerHttpSecurity {
return this;
}
/**
* Configures when this filter should redirect to https
*
* By default, the filter will redirect whenever an exchange's scheme is not https
*
* @param when determines when to redirect to https
* @return the {@link HttpsRedirectSpec} for additional configuration
*/
public HttpsRedirectSpec httpsRedirectWhen(
Function<ServerWebExchange, Boolean> when) {
ServerWebExchangeMatcher matcher = e -> when.apply(e) ?
ServerWebExchangeMatcher.MatchResult.match() :
ServerWebExchangeMatcher.MatchResult.notMatch();
return httpsRedirectWhen(matcher);
}
/**
* Configures a custom HTTPS port to redirect to
*
@@ -1385,6 +1575,7 @@ public class ServerHttpSecurity {
*/
public class CsrfSpec {
private CsrfWebFilter filter = new CsrfWebFilter();
private ServerCsrfTokenRepository csrfTokenRepository = new WebSessionServerCsrfTokenRepository();
private boolean specifiedRequireCsrfProtectionMatcher;
@@ -1410,7 +1601,7 @@ public class ServerHttpSecurity {
*/
public CsrfSpec csrfTokenRepository(
ServerCsrfTokenRepository csrfTokenRepository) {
this.filter.setCsrfTokenRepository(csrfTokenRepository);
this.csrfTokenRepository = csrfTokenRepository;
return this;
}
@@ -1447,6 +1638,10 @@ public class ServerHttpSecurity {
}
protected void configure(ServerHttpSecurity http) {
Optional.ofNullable(this.csrfTokenRepository).ifPresent(serverCsrfTokenRepository -> {
this.filter.setCsrfTokenRepository(serverCsrfTokenRepository);
http.logout().addLogoutHandler(new CsrfServerLogoutHandler(serverCsrfTokenRepository));
});
http.addFilterAt(this.filter, SecurityWebFiltersOrder.CSRF);
}
@@ -1929,6 +2124,15 @@ public class ServerHttpSecurity {
return new FeaturePolicySpec(policyDirectives);
}
/**
* Configures {@code Referrer-Policy} response header.
* @param referrerPolicy the policy to use
* @return the {@link ReferrerPolicySpec} to configure
*/
public ReferrerPolicySpec referrerPolicy(ReferrerPolicy referrerPolicy) {
return new ReferrerPolicySpec(referrerPolicy);
}
/**
* Configures {@code Referrer-Policy} response header.
* @return the {@link ReferrerPolicySpec} to configure
@@ -1980,18 +2184,18 @@ public class ServerHttpSecurity {
* The mode to configure. Default is
* {@link org.springframework.security.web.server.header.XFrameOptionsServerHttpHeadersWriter.Mode#DENY}
* @param mode the mode to use
* @return the {@link FrameOptionsSpec} to configure
* @return the {@link HeaderSpec} to configure
*/
public FrameOptionsSpec mode(XFrameOptionsServerHttpHeadersWriter.Mode mode) {
public HeaderSpec mode(XFrameOptionsServerHttpHeadersWriter.Mode mode) {
HeaderSpec.this.frameOptions.setMode(mode);
return this;
return and();
}
/**
* Allows method chaining to continue configuring the {@link ServerHttpSecurity}
* @return the {@link HeaderSpec} to continue configuring
*/
public HeaderSpec and() {
private HeaderSpec and() {
return HeaderSpec.this;
}
@@ -2001,7 +2205,7 @@ public class ServerHttpSecurity {
*/
public HeaderSpec disable() {
HeaderSpec.this.writers.remove(HeaderSpec.this.frameOptions);
return HeaderSpec.this;
return and();
}
private FrameOptionsSpec() {}
@@ -2130,21 +2334,11 @@ public class ServerHttpSecurity {
* Configures {@code Referrer-Policy} response header.
*
* @see #referrerPolicy()
* @see #referrerPolicy(ReferrerPolicy)
* @since 5.1
*/
public class ReferrerPolicySpec {
/**
* Set the policy to be used in the response header. Defaults to the
* {@link ReferrerPolicy#NO_REFERRER} header.
* @param referrerPolicy the policy
* @return the {@link HeaderSpec} to continue configuring
*/
public HeaderSpec referrerPolicy(ReferrerPolicy referrerPolicy) {
HeaderSpec.this.referrerPolicy.setPolicy(referrerPolicy);
return HeaderSpec.this;
}
/**
* Allows method chaining to continue configuring the
* {@link ServerHttpSecurity}.
@@ -2157,6 +2351,10 @@ public class ServerHttpSecurity {
private ReferrerPolicySpec() {
}
private ReferrerPolicySpec(ReferrerPolicy referrerPolicy) {
HeaderSpec.this.referrerPolicy.setPolicy(referrerPolicy);
}
}
private HeaderSpec() {
@@ -2176,6 +2374,7 @@ public class ServerHttpSecurity {
*/
public final class LogoutSpec {
private LogoutWebFilter logoutWebFilter = new LogoutWebFilter();
private List<ServerLogoutHandler> logoutHandlers = new ArrayList<>(Arrays.asList(new SecurityContextServerLogoutHandler()));
/**
* Configures the logout handler. Default is {@code SecurityContextServerLogoutHandler}
@@ -2183,7 +2382,14 @@ public class ServerHttpSecurity {
* @return the {@link LogoutSpec} to configure
*/
public LogoutSpec logoutHandler(ServerLogoutHandler logoutHandler) {
this.logoutWebFilter.setLogoutHandler(logoutHandler);
Assert.notNull(logoutHandler, "logoutHandler cannot be null");
this.logoutHandlers.clear();
return addLogoutHandler(logoutHandler);
}
private LogoutSpec addLogoutHandler(ServerLogoutHandler logoutHandler) {
Assert.notNull(logoutHandler, "logoutHandler cannot be null");
this.logoutHandlers.add(logoutHandler);
return this;
}
@@ -2231,7 +2437,19 @@ public class ServerHttpSecurity {
return and();
}
private Optional<ServerLogoutHandler> createLogoutHandler() {
if (this.logoutHandlers.isEmpty()) {
return Optional.empty();
}
else if (this.logoutHandlers.size() == 1) {
return Optional.of(this.logoutHandlers.get(0));
}
return Optional.of(new DelegatingServerLogoutHandler(this.logoutHandlers));
}
protected void configure(ServerHttpSecurity http) {
createLogoutHandler().ifPresent(this.logoutWebFilter::setLogoutHandler);
http.addFilterAt(this.logoutWebFilter, SecurityWebFiltersOrder.LOGOUT);
}
@@ -748,7 +748,7 @@ csrf-options.attlist &=
## The RequestMatcher instance to be used to determine if CSRF should be applied. Default is any HTTP method except "GET", "TRACE", "HEAD", "OPTIONS"
attribute request-matcher-ref { xsd:token }?
csrf-options.attlist &=
## The CsrfTokenRepository to use. The default is HttpSessionCsrfTokenRepository
## The CsrfTokenRepository to use. The default is HttpSessionCsrfTokenRepository wrapped by LazyCsrfTokenRepository.
attribute token-repository-ref { xsd:token }?
headers =
@@ -2337,7 +2337,8 @@
</xs:attribute>
<xs:attribute name="token-repository-ref" type="xs:token">
<xs:annotation>
<xs:documentation>The CsrfTokenRepository to use. The default is HttpSessionCsrfTokenRepository
<xs:documentation>The CsrfTokenRepository to use. The default is HttpSessionCsrfTokenRepository wrapped by
LazyCsrfTokenRepository.
</xs:documentation>
</xs:annotation>
</xs:attribute>
@@ -738,7 +738,7 @@ csrf-options.attlist &=
## The RequestMatcher instance to be used to determine if CSRF should be applied. Default is any HTTP method except "GET", "TRACE", "HEAD", "OPTIONS"
attribute request-matcher-ref { xsd:token }?
csrf-options.attlist &=
## The CsrfTokenRepository to use. The default is HttpSessionCsrfTokenRepository
## The CsrfTokenRepository to use. The default is HttpSessionCsrfTokenRepository wrapped by LazyCsrfTokenRepository.
attribute token-repository-ref { xsd:token }?
headers =
@@ -2232,7 +2232,8 @@
</xs:attribute>
<xs:attribute name="token-repository-ref" type="xs:token">
<xs:annotation>
<xs:documentation>The CsrfTokenRepository to use. The default is HttpSessionCsrfTokenRepository
<xs:documentation>The CsrfTokenRepository to use. The default is HttpSessionCsrfTokenRepository wrapped by
LazyCsrfTokenRepository.
</xs:documentation>
</xs:annotation>
</xs:attribute>
@@ -738,7 +738,7 @@ csrf-options.attlist &=
## The RequestMatcher instance to be used to determine if CSRF should be applied. Default is any HTTP method except "GET", "TRACE", "HEAD", "OPTIONS"
attribute request-matcher-ref { xsd:token }?
csrf-options.attlist &=
## The CsrfTokenRepository to use. The default is HttpSessionCsrfTokenRepository
## The CsrfTokenRepository to use. The default is HttpSessionCsrfTokenRepository wrapped by LazyCsrfTokenRepository.
attribute token-repository-ref { xsd:token }?
headers =
@@ -2232,7 +2232,8 @@
</xs:attribute>
<xs:attribute name="token-repository-ref" type="xs:token">
<xs:annotation>
<xs:documentation>The CsrfTokenRepository to use. The default is HttpSessionCsrfTokenRepository
<xs:documentation>The CsrfTokenRepository to use. The default is HttpSessionCsrfTokenRepository wrapped by
LazyCsrfTokenRepository.
</xs:documentation>
</xs:annotation>
</xs:attribute>
@@ -1,186 +0,0 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.configurers
import org.springframework.security.core.userdetails.PasswordEncodedUser
import javax.servlet.http.HttpServletResponse
import org.springframework.context.annotation.Configuration
import org.springframework.http.MediaType
import org.springframework.security.config.annotation.AnyObjectPostProcessor
import org.springframework.security.config.annotation.BaseSpringSpec
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.web.savedrequest.RequestCache
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter
import spock.lang.Unroll;
/**
*
* @author Rob Winch
*/
class RequestCacheConfigurerTests extends BaseSpringSpec {
def "requestCache ObjectPostProcessor"() {
setup:
AnyObjectPostProcessor opp = Mock()
HttpSecurity http = new HttpSecurity(opp, authenticationBldr, [:])
when:
http
.requestCache()
.and()
.build()
then: "RequestCacheAwareFilter is registered with LifecycleManager"
1 * opp.postProcess(_ as RequestCacheAwareFilter) >> {RequestCacheAwareFilter o -> o}
}
def "invoke requestCache twice does not reset"() {
setup:
RequestCache RC = Mock()
AnyObjectPostProcessor opp = Mock()
HttpSecurity http = new HttpSecurity(opp, authenticationBldr, [:])
when:
http
.requestCache()
.requestCache(RC)
.and()
.requestCache()
then:
http.getSharedObject(RequestCache) == RC
}
def "RequestCache disables faviocon.ico"() {
setup:
loadConfig(RequestCacheDefautlsConfig)
request.servletPath = "/favicon.ico"
request.requestURI = "/favicon.ico"
request.method = "GET"
when: "request favicon.ico"
springSecurityFilterChain.doFilter(request,response,chain)
then: "sent to the login page"
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
response.redirectedUrl == "http://localhost/login"
when: "authenticate successfully"
super.setupWeb(request.session)
request.servletPath = "/login"
request.setParameter("username","user")
request.setParameter("password","password")
request.method = "POST"
springSecurityFilterChain.doFilter(request,response,chain)
then: "sent to default URL since it was favicon.ico"
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
response.redirectedUrl == "/"
}
def "SEC-2321: RequestCache disables application/json"() {
setup:
loadConfig(RequestCacheDefautlsConfig)
request.addHeader("Accept", MediaType.APPLICATION_JSON_VALUE)
request.method = "GET"
request.servletPath = "/messages"
request.requestURI = "/messages"
when: "request application/json"
springSecurityFilterChain.doFilter(request,response,chain)
then: "sent to the login page"
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
response.redirectedUrl == "http://localhost/login"
when: "authenticate successfully"
super.setupWeb(request.session)
request.servletPath = "/login"
request.setParameter("username","user")
request.setParameter("password","password")
request.method = "POST"
springSecurityFilterChain.doFilter(request,response,chain)
then: "sent to default URL since it was application/json. This is desirable since JSON requests are typically not invoked directly from the browser and we don't want the browser to replay them"
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
response.redirectedUrl == "/"
}
def "SEC-2321: RequestCache disables X-Requested-With"() {
setup:
loadConfig(RequestCacheDefautlsConfig)
request.addHeader("X-Requested-With", "XMLHttpRequest")
request.method = "GET"
request.servletPath = "/messages"
request.requestURI = "/messages"
when: "request X-Requested-With"
springSecurityFilterChain.doFilter(request,response,chain)
then: "sent to the login page"
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
response.redirectedUrl == "http://localhost/login"
when: "authenticate successfully"
super.setupWeb(request.session)
request.servletPath = "/login"
request.setParameter("username","user")
request.setParameter("password","password")
request.method = "POST"
springSecurityFilterChain.doFilter(request,response,chain)
then: "sent to default URL since it was X-Requested-With"
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
response.redirectedUrl == "/"
}
@Unroll
def "RequestCache saves #headerName: #headerValue"() {
setup:
loadConfig(RequestCacheDefautlsConfig)
request.addHeader(headerName, headerValue)
request.method = "GET"
request.servletPath = "/messages"
request.requestURI = "/messages"
when: "request content type"
springSecurityFilterChain.doFilter(request,response,chain)
super.setupWeb(request.session)
request.servletPath = "/login"
request.setParameter("username","user")
request.setParameter("password","password")
request.method = "POST"
springSecurityFilterChain.doFilter(request,response,chain)
then: "sent to saved URL"
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
response.redirectedUrl == "http://localhost/messages"
where:
headerName << ["Accept", "Accept", "Accept", "X-Requested-With"]
headerValue << [MediaType.ALL_VALUE, MediaType.TEXT_HTML, "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8","com.android"]
}
@EnableWebSecurity
static class RequestCacheDefautlsConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser(PasswordEncodedUser.user());
}
}
}
@@ -15,23 +15,31 @@
*/
package org.springframework.security.config;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Matchers.*;
import static org.powermock.api.mockito.PowerMockito.*;
import org.apache.commons.logging.Log;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.messaging.Message;
import org.springframework.security.config.util.InMemoryXmlApplicationContext;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.util.ClassUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.powermock.api.mockito.PowerMockito.doThrow;
import static org.powermock.api.mockito.PowerMockito.mock;
import static org.powermock.api.mockito.PowerMockito.spy;
import static org.powermock.api.mockito.PowerMockito.verifyStatic;
import static org.powermock.api.mockito.PowerMockito.verifyZeroInteractions;
/**
*
* @author Luke Taylor
@@ -40,6 +48,7 @@ import org.springframework.util.ClassUtils;
*/
@RunWith(PowerMockRunner.class)
@PrepareForTest({ ClassUtils.class })
@PowerMockIgnore({ "org.w3c.dom.*", "org.xml.sax.*", "org.apache.xerces.*", "javax.xml.parsers.*" })
public class SecurityNamespaceHandlerTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
@@ -15,10 +15,18 @@
*/
package org.springframework.security.config.annotation.method.configuration;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.AdviceMode;
@@ -29,6 +37,7 @@ import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor;
import org.springframework.security.access.method.MethodSecurityMetadataSource;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationTrustResolver;
@@ -47,11 +56,6 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import javax.sql.DataSource;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
@@ -64,6 +68,7 @@ import static org.mockito.Mockito.when;
/**
*
* @author Rob Winch
* @author Artsiom Yudovin
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SecurityTestExecutionListeners
@@ -71,6 +76,9 @@ public class GlobalMethodSecurityConfigurationTests {
@Rule
public final SpringTestRule spring = new SpringTestRule();
@Rule
public ExpectedException thrown = ExpectedException.none();
@Autowired(required = false)
private MethodSecurityService service;
@@ -84,6 +92,31 @@ public class GlobalMethodSecurityConfigurationTests {
@Autowired(required = false)
MockEventListener<AbstractAuthenticationEvent> events;
@Test
public void configureWhenGlobalMethodSecurityIsMissingMetadataSourceThenException() {
this.thrown.expect(UnsatisfiedDependencyException.class);
this.spring.register(IllegalStateGlobalMethodSecurityConfig.class).autowire();
}
@EnableGlobalMethodSecurity
public static class IllegalStateGlobalMethodSecurityConfig extends GlobalMethodSecurityConfiguration {
}
@Test
public void configureWhenGlobalMethodSecurityHasCustomMetadataSourceThenNoEnablingAttributeIsNeeded() {
this.spring.register(CustomMetadataSourceConfig.class).autowire();
}
@EnableGlobalMethodSecurity
public static class CustomMetadataSourceConfig extends GlobalMethodSecurityConfiguration {
@Bean
@Override
protected MethodSecurityMetadataSource customMethodSecurityMetadataSource() {
return mock(MethodSecurityMetadataSource.class);
}
}
@Test
public void methodSecurityAuthenticationManagerPublishesEvent() {
this.spring.register(InMemoryAuthWithGlobalMethodSecurityConfig.class).autowire();
@@ -15,16 +15,25 @@
*/
package org.springframework.security.config.annotation.method.configuration;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.*;
import org.springframework.context.annotation.AdviceMode;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.Ordered;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.security.access.AccessDecisionManager;
@@ -46,12 +55,9 @@ import org.springframework.security.test.context.annotation.SecurityTestExecutio
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import static org.assertj.core.api.Assertions.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
*
@@ -263,7 +269,7 @@ public class NamespaceGlobalMethodSecurityTests {
}
@EnableGlobalMethodSecurity(mode = AdviceMode.ASPECTJ)
@EnableGlobalMethodSecurity(mode = AdviceMode.ASPECTJ, securedEnabled = true)
public static class AspectJModeExtendsGMSCConfig extends GlobalMethodSecurityConfiguration {
}
@@ -15,16 +15,15 @@
*/
package org.springframework.security.config.annotation.web;
import static org.assertj.core.api.Assertions.assertThat;
import static org.powermock.api.mockito.PowerMockito.*;
import java.util.Arrays;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@@ -33,6 +32,10 @@ import org.springframework.security.config.annotation.web.configurers.AbstractHt
import org.springframework.web.context.ConfigurableWebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
import static org.powermock.api.mockito.PowerMockito.spy;
import static org.powermock.api.mockito.PowerMockito.when;
/**
*
* @author Rob Winch
@@ -40,6 +43,7 @@ import org.springframework.web.context.support.AnnotationConfigWebApplicationCon
*/
@RunWith(PowerMockRunner.class)
@PrepareForTest({ SpringFactoriesLoader.class })
@PowerMockIgnore({ "org.w3c.dom.*", "org.xml.sax.*", "org.apache.xerces.*", "javax.xml.parsers.*" })
public class WebSecurityConfigurerAdapterPowermockTests {
ConfigurableWebApplicationContext context;
@@ -15,12 +15,22 @@
*/
package org.springframework.security.config.annotation.web;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
@@ -48,14 +58,6 @@ import org.springframework.web.context.request.async.WebAsyncManager;
import org.springframework.web.context.request.async.WebAsyncUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.ThrowableAssert.catchThrowable;
import static org.mockito.ArgumentMatchers.any;
@@ -75,6 +77,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
*/
@PrepareForTest({WebAsyncManager.class})
@RunWith(PowerMockRunner.class)
@PowerMockIgnore({ "org.w3c.dom.*", "org.xml.sax.*", "org.apache.xerces.*", "javax.xml.parsers.*", "javax.xml.transform.*" })
public class WebSecurityConfigurerAdapterTests {
@Rule
public final SpringTestRule spring = new SpringTestRule();
@@ -0,0 +1,295 @@
/*
* Copyright 2002-2018 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.configurers;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.test.SpringTestRule;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.RequestBuilder;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
/**
* Tests for {@link RequestCacheConfigurer}
*
* @author Rob Winch
* @author Josh Cummings
*/
public class RequestCacheConfigurerTests {
@Rule
public final SpringTestRule spring = new SpringTestRule();
@Autowired
MockMvc mvc;
@Test
public void configureWhenRegisteringObjectPostProcessorThenInvokedOnExceptionTranslationFilter() {
this.spring.register(ObjectPostProcessorConfig.class, DefaultSecurityConfig.class).autowire();
verify(ObjectPostProcessorConfig.objectPostProcessor)
.postProcess(any(RequestCacheAwareFilter.class));
}
@EnableWebSecurity
static class ObjectPostProcessorConfig extends WebSecurityConfigurerAdapter {
static ObjectPostProcessor<Object> objectPostProcessor = spy(ReflectingObjectPostProcessor.class);
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.requestCache();
// @formatter:on
}
@Bean
static ObjectPostProcessor<Object> objectPostProcessor() {
return objectPostProcessor;
}
}
static class ReflectingObjectPostProcessor implements ObjectPostProcessor<Object> {
@Override
public <O> O postProcess(O object) {
return object;
}
}
@Test
public void getWhenInvokingExceptionHandlingTwiceThenOriginalEntryPointUsed() throws Exception {
this.spring.register(InvokeTwiceDoesNotOverrideConfig.class).autowire();
this.mvc.perform(get("/"));
verify(InvokeTwiceDoesNotOverrideConfig.requestCache)
.getMatchingRequest(any(HttpServletRequest.class), any(HttpServletResponse.class));
}
@EnableWebSecurity
static class InvokeTwiceDoesNotOverrideConfig extends WebSecurityConfigurerAdapter {
static RequestCache requestCache = mock(RequestCache.class);
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.requestCache()
.requestCache(requestCache)
.and()
.requestCache();
// @formatter:on
}
}
@Test
public void getWhenBookmarkedUrlIsFaviconIcoThenPostAuthenticationRedirectsToRoot() throws Exception {
this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire();
MockHttpSession session = (MockHttpSession)
this.mvc.perform(get("/favicon.ico"))
.andExpect(redirectedUrl("http://localhost/login"))
.andReturn().getRequest().getSession();
this.mvc.perform(formLogin(session))
.andExpect(redirectedUrl("/")); // ignores favicon.ico
}
@Test
public void getWhenBookmarkedUrlIsFaviconPngThenPostAuthenticationRedirectsToRoot() throws Exception {
this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire();
MockHttpSession session = (MockHttpSession)
this.mvc.perform(get("/favicon.png"))
.andExpect(redirectedUrl("http://localhost/login"))
.andReturn().getRequest().getSession();
this.mvc.perform(formLogin(session))
.andExpect(redirectedUrl("/")); // ignores favicon.png
}
// SEC-2321
@Test
public void getWhenBookmarkedRequestIsApplicationJsonThenPostAuthenticationRedirectsToRoot() throws Exception {
this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire();
MockHttpSession session = (MockHttpSession)
this.mvc.perform(get("/messages")
.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON))
.andExpect(redirectedUrl("http://localhost/login"))
.andReturn().getRequest().getSession();
this.mvc.perform(formLogin(session))
.andExpect(redirectedUrl("/")); // ignores application/json
// This is desirable since JSON requests are typically not invoked directly from the browser and we don't want the browser to replay them
}
// SEC-2321
@Test
public void getWhenBookmarkedRequestIsXRequestedWithThenPostAuthenticationRedirectsToRoot() throws Exception {
this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire();
MockHttpSession session = (MockHttpSession)
this.mvc.perform(get("/messages")
.header("X-Requested-With", "XMLHttpRequest"))
.andExpect(redirectedUrl("http://localhost/login"))
.andReturn().getRequest().getSession();
this.mvc.perform(formLogin(session))
.andExpect(redirectedUrl("/"));
// This is desirable since XHR requests are typically not invoked directly from the browser and we don't want the browser to replay them
}
@Test
public void getWhenBookmarkedRequestIsAllMediaTypeThenPostAuthenticationRemembers() throws Exception {
this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire();
MockHttpSession session = (MockHttpSession)
this.mvc.perform(get("/messages")
.header(HttpHeaders.ACCEPT, MediaType.ALL))
.andExpect(redirectedUrl("http://localhost/login"))
.andReturn().getRequest().getSession();
this.mvc.perform(formLogin(session))
.andExpect(redirectedUrl("http://localhost/messages"));
}
@Test
public void getWhenBookmarkedRequestIsTextHtmlThenPostAuthenticationRemembers() throws Exception {
this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire();
MockHttpSession session = (MockHttpSession)
this.mvc.perform(get("/messages")
.header(HttpHeaders.ACCEPT, MediaType.TEXT_HTML))
.andExpect(redirectedUrl("http://localhost/login"))
.andReturn().getRequest().getSession();
this.mvc.perform(formLogin(session))
.andExpect(redirectedUrl("http://localhost/messages"));
}
@Test
public void getWhenBookmarkedRequestIsChromeThenPostAuthenticationRemembers() throws Exception {
this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire();
MockHttpSession session = (MockHttpSession)
this.mvc.perform(get("/messages")
.header(HttpHeaders.ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"))
.andExpect(redirectedUrl("http://localhost/login"))
.andReturn().getRequest().getSession();
this.mvc.perform(formLogin(session))
.andExpect(redirectedUrl("http://localhost/messages"));
}
@Test
public void getWhenBookmarkedRequestIsRequestedWithAndroidThenPostAuthenticationRemembers() throws Exception {
this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire();
MockHttpSession session = (MockHttpSession)
this.mvc.perform(get("/messages")
.header("X-Requested-With", "com.android"))
.andExpect(redirectedUrl("http://localhost/login"))
.andReturn().getRequest().getSession();
this.mvc.perform(formLogin(session))
.andExpect(redirectedUrl("http://localhost/messages"));
}
@EnableWebSecurity
static class RequestCacheDefaultsConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin();
}
}
// gh-6102
@Test
public void getWhenRequestCacheIsDisabledThenExceptionTranslationFilterDoesNotStoreRequest() throws Exception {
this.spring.register(RequestCacheDisabledConfig.class, DefaultSecurityConfig.class).autowire();
MockHttpSession session = (MockHttpSession)
this.mvc.perform(get("/bob"))
.andReturn().getRequest().getSession();
this.mvc.perform(formLogin(session))
.andExpect(redirectedUrl("/"));
}
@EnableWebSecurity
static class RequestCacheDisabledConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.requestCache().disable();
}
}
@EnableWebSecurity
static class DefaultSecurityConfig {
@Bean
public InMemoryUserDetailsManager userDetailsManager() {
return new InMemoryUserDetailsManager(User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build()
);
}
}
private static RequestBuilder formLogin(MockHttpSession session) {
return post("/login")
.param("username", "user")
.param("password", "password")
.session(session)
.with(csrf());
}
}
@@ -15,15 +15,7 @@
*/
package org.springframework.security.config.annotation.web.configurers;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.same;
import static org.powermock.api.mockito.PowerMockito.mock;
import static org.powermock.api.mockito.PowerMockito.spy;
import static org.powermock.api.mockito.PowerMockito.verifyStatic;
import static org.powermock.api.mockito.PowerMockito.when;
import java.lang.reflect.Method;
import javax.servlet.Filter;
import javax.servlet.http.HttpServletRequest;
@@ -32,8 +24,10 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.mock.web.MockFilterChain;
@@ -52,12 +46,20 @@ import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
import org.springframework.util.ReflectionUtils;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.same;
import static org.powermock.api.mockito.PowerMockito.mock;
import static org.powermock.api.mockito.PowerMockito.spy;
import static org.powermock.api.mockito.PowerMockito.verifyStatic;
import static org.powermock.api.mockito.PowerMockito.when;
/**
*
* @author Rob Winch
*/
@RunWith(PowerMockRunner.class)
@PrepareForTest({ ReflectionUtils.class, Method.class })
@PowerMockIgnore({ "org.w3c.dom.*", "org.xml.sax.*", "org.apache.xerces.*", "javax.xml.parsers.*" })
public class SessionManagementConfigurerServlet31Tests {
@Mock
Method method;
@@ -0,0 +1,83 @@
/*
* Copyright 2002-2018 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.configurers;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.test.SpringTestRule;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.PasswordEncodedUser;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.test.web.servlet.MockMvc;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
/**
* @author Joe Grandja
*/
public class SessionManagementConfigurerSessionAuthenticationStrategyTests {
@Autowired
private MockMvc mvc;
@Rule
public final SpringTestRule spring = new SpringTestRule();
// gh-5763
@Test
public void requestWhenCustomSessionAuthenticationStrategyProvidedThenCalled() throws Exception {
this.spring.register(CustomSessionAuthenticationStrategyConfig.class).autowire();
this.mvc.perform(formLogin().user("user").password("password"));
verify(CustomSessionAuthenticationStrategyConfig.customSessionAuthenticationStrategy)
.onAuthentication(any(Authentication.class), any(HttpServletRequest.class), any(HttpServletResponse.class));
}
@EnableWebSecurity
static class CustomSessionAuthenticationStrategyConfig extends WebSecurityConfigurerAdapter {
static SessionAuthenticationStrategy customSessionAuthenticationStrategy = mock(SessionAuthenticationStrategy.class);
// @formatter:off
@Override
public void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.and()
.sessionManagement()
.sessionAuthenticationStrategy(customSessionAuthenticationStrategy);
}
// @formatter:on
// @formatter:off
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser(PasswordEncodedUser.user());
}
// @formatter:on
}
}
@@ -21,12 +21,15 @@ import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@@ -35,6 +38,7 @@ import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
@@ -154,6 +158,55 @@ public class OAuth2LoginConfigurerTests {
.isInstanceOf(OAuth2UserAuthority.class).hasToString("ROLE_USER");
}
// gh-6009
@Test
public void oauth2LoginWhenSuccessThenAuthenticationSuccessEventPublished() throws Exception {
// setup application context
loadConfig(OAuth2LoginConfig.class);
// setup authorization request
OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest();
this.authorizationRequestRepository.saveAuthorizationRequest(
authorizationRequest, this.request, this.response);
// setup authentication parameters
this.request.setParameter("code", "code123");
this.request.setParameter("state", authorizationRequest.getState());
// perform test
this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain);
// assertions
assertThat(OAuth2LoginConfig.EVENTS).isNotEmpty();
assertThat(OAuth2LoginConfig.EVENTS).hasSize(1);
assertThat(OAuth2LoginConfig.EVENTS.get(0)).isInstanceOf(AuthenticationSuccessEvent.class);
}
@Test
public void oauth2LoginWhenAuthenticatedThenIgnored() throws Exception {
// setup application context
loadConfig(OAuth2LoginConfig.class);
// authenticate
TestingAuthenticationToken expectedAuthentication = new TestingAuthenticationToken("a",
"b", "ROLE_TEST");
this.request.getSession().setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, new SecurityContextImpl(expectedAuthentication));
// setup authentication parameters
this.request.setParameter("code", "code123");
this.request.setParameter("state", "state");
// perform test
this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain);
// assertions
Authentication authentication = this.securityContextRepository
.loadContext(new HttpRequestResponseHolder(this.request, this.response))
.getAuthentication();
assertThat(authentication).isEqualTo(expectedAuthentication);
}
@Test
public void oauth2LoginCustomWithConfigurer() throws Exception {
// setup application context
@@ -440,7 +493,9 @@ public class OAuth2LoginConfigurerTests {
}
@EnableWebSecurity
static class OAuth2LoginConfig extends CommonWebSecurityConfigurerAdapter {
static class OAuth2LoginConfig extends CommonWebSecurityConfigurerAdapter implements ApplicationListener<AuthenticationSuccessEvent> {
static List<AuthenticationSuccessEvent> EVENTS = new ArrayList<>();
@Override
protected void configure(HttpSecurity http) throws Exception {
http
@@ -449,6 +504,11 @@ public class OAuth2LoginConfigurerTests {
new InMemoryClientRegistrationRepository(GOOGLE_CLIENT_REGISTRATION));
super.configure(http);
}
@Override
public void onApplicationEvent(AuthenticationSuccessEvent event) {
EVENTS.add(event);
}
}
@EnableWebSecurity
@@ -1300,11 +1300,7 @@ public class OAuth2ResourceServerConfigurerTests {
.and()
.oauth2ResourceServer()
.jwt();
}
@Bean
JwtDecoder jwtDecoder() {
return mock(JwtDecoder.class);
// @formatter:on
}
}
@@ -47,7 +47,7 @@ public class XsdDocumentedTests {
"nsa-method-security",
"nsa-web");
String referenceLocation = "../docs/manual/src/docs/asciidoc/_includes/appendix/namespace.adoc";
String referenceLocation = "../docs/manual/src/docs/asciidoc/_includes/servlet/appendix/namespace.adoc";
String schema31xDocumentLocation = "org/springframework/security/config/spring-security-3.1.xsd";
String schemaDocumentLocation = "org/springframework/security/config/spring-security-5.1.xsd";
@@ -15,15 +15,7 @@
*/
package org.springframework.security.config.http;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.same;
import static org.powermock.api.mockito.PowerMockito.mock;
import static org.powermock.api.mockito.PowerMockito.spy;
import static org.powermock.api.mockito.PowerMockito.verifyStatic;
import static org.powermock.api.mockito.PowerMockito.when;
import java.lang.reflect.Method;
import javax.servlet.Filter;
import javax.servlet.http.HttpServletRequest;
@@ -32,14 +24,14 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.config.util.InMemoryXmlApplicationContext;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextImpl;
@@ -47,12 +39,20 @@ import org.springframework.security.web.context.HttpRequestResponseHolder;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.util.ReflectionUtils;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.same;
import static org.powermock.api.mockito.PowerMockito.mock;
import static org.powermock.api.mockito.PowerMockito.spy;
import static org.powermock.api.mockito.PowerMockito.verifyStatic;
import static org.powermock.api.mockito.PowerMockito.when;
/**
* @author Rob Winch
*
*/
@RunWith(PowerMockRunner.class)
@PrepareForTest({ ReflectionUtils.class, Method.class })
@PowerMockIgnore({ "org.w3c.dom.*", "org.xml.sax.*", "org.apache.xerces.*", "javax.xml.parsers.*" })
public class SessionManagementConfigServlet31Tests {
private static final String XML_AUTHENTICATION_MANAGER = "<authentication-manager>"
+ " <authentication-provider>" + " <user-service>"
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2018 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.
@@ -123,8 +123,7 @@ public class CommonOAuth2ProviderTests {
assertThat(registration.getAuthorizationGrantType())
.isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE);
assertThat(registration.getRedirectUriTemplate()).isEqualTo(DEFAULT_REDIRECT_URL);
assertThat(registration.getScopes()).containsOnly("openid", "profile", "email",
"address", "phone");
assertThat(registration.getScopes()).containsOnly("openid", "profile", "email");
assertThat(registration.getClientName()).isEqualTo("Okta");
assertThat(registration.getRegistrationId()).isEqualTo("123");
}
@@ -182,6 +182,15 @@ public class HeaderSpecTests {
assertHeaders();
}
@Test
public void headersWhenReferrerPolicyCustomEnabledThenFeaturePolicyCustomWritten() {
this.expectedHeaders.add(ReferrerPolicyServerHttpHeadersWriter.REFERRER_POLICY,
ReferrerPolicy.NO_REFERRER_WHEN_DOWNGRADE.getPolicy());
this.headers.referrerPolicy(ReferrerPolicy.NO_REFERRER_WHEN_DOWNGRADE);
assertHeaders();
}
private void expectHeaderNamesNotPresent(String... headerNames) {
for (String headerName : headerNames) {
this.expectedHeaders.remove(headerName);
@@ -22,16 +22,27 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.test.SpringTestRule;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.TestOAuth2AccessTokens;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationExchanges;
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.bind.annotation.GetMapping;
@@ -41,6 +52,7 @@ import reactor.core.publisher.Mono;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
@@ -55,6 +67,8 @@ public class OAuth2ClientSpecTests {
private WebTestClient client;
private ClientRegistration registration = TestClientRegistrations.clientRegistration().build();
@Autowired
public void setApplicationContext(ApplicationContext context) {
this.client = WebTestClient.bindToApplicationContext(context).build();
@@ -117,4 +131,58 @@ public class OAuth2ClientSpecTests {
return "home";
}
}
@Test
public void oauth2ClientWhenCustomObjectsThenUsed() {
this.spring.register(ClientRegistrationConfig.class, OAuth2ClientCustomConfig.class, AuthorizedClientController.class).autowire();
OAuth2ClientCustomConfig config = this.spring.getContext().getBean(OAuth2ClientCustomConfig.class);
ServerAuthenticationConverter converter = config.authenticationConverter;
ReactiveAuthenticationManager manager = config.manager;
OAuth2AuthorizationExchange exchange = TestOAuth2AuthorizationExchanges.success();
OAuth2AccessToken accessToken = TestOAuth2AccessTokens.noScopes();
OAuth2AuthorizationCodeAuthenticationToken result = new OAuth2AuthorizationCodeAuthenticationToken(this.registration, exchange, accessToken);
when(converter.convert(any())).thenReturn(Mono.just(new TestingAuthenticationToken("a", "b", "c")));
when(manager.authenticate(any())).thenReturn(Mono.just(result));
this.client.get()
.uri("/authorize/oauth2/code/registration-id")
.exchange()
.expectStatus().is3xxRedirection();
verify(converter).convert(any());
verify(manager).authenticate(any());
}
@EnableWebFlux
@EnableWebFluxSecurity
static class ClientRegistrationConfig {
private ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration()
.build();
@Bean
InMemoryReactiveClientRegistrationRepository clientRegistrationRepository() {
return new InMemoryReactiveClientRegistrationRepository(this.clientRegistration);
}
}
@Configuration
static class OAuth2ClientCustomConfig {
ReactiveAuthenticationManager manager = mock(ReactiveAuthenticationManager.class);
ServerAuthenticationConverter authenticationConverter = mock(ServerAuthenticationConverter.class);
@Bean
public SecurityWebFilterChain springSecurityFilter(ServerHttpSecurity http) {
http
.oauth2Client()
.authenticationConverter(this.authenticationConverter)
.authenticationManager(this.manager);
return http.build();
}
}
}
@@ -17,20 +17,36 @@
package org.springframework.security.config.web.server;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.junit.Rule;
import org.junit.Test;
import org.openqa.selenium.WebDriver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.oauth2.client.CommonOAuth2Provider;
import org.springframework.security.config.test.SpringTestRule;
import org.springframework.security.htmlunit.server.WebTestClientHtmlUnitDriverBuilder;
import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.TestOAuth2AccessTokens;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationExchanges;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.oauth2.core.user.TestOAuth2Users;
import org.springframework.security.test.web.reactive.server.WebTestClientBuilder;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.WebFilterChainProxy;
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
@@ -115,6 +131,57 @@ public class OAuth2LoginTests {
}
}
@Test
public void oauth2LoginWhenCustomObjectsThenUsed() {
this.spring.register(OAuth2LoginWithSingleClientRegistrations.class,
OAuth2LoginMockAuthenticationManagerConfig.class).autowire();
WebTestClient webTestClient = WebTestClientBuilder
.bindToWebFilters(this.springSecurity)
.build();
OAuth2LoginMockAuthenticationManagerConfig config = this.spring.getContext()
.getBean(OAuth2LoginMockAuthenticationManagerConfig.class);
ServerAuthenticationConverter converter = config.authenticationConverter;
ReactiveAuthenticationManager manager = config.manager;
OAuth2AuthorizationExchange exchange = TestOAuth2AuthorizationExchanges.success();
OAuth2User user = TestOAuth2Users.create();
OAuth2AccessToken accessToken = TestOAuth2AccessTokens.noScopes();
OAuth2LoginAuthenticationToken result = new OAuth2LoginAuthenticationToken(github, exchange, user, user.getAuthorities(), accessToken);
when(converter.convert(any())).thenReturn(Mono.just(new TestingAuthenticationToken("a", "b", "c")));
when(manager.authenticate(any())).thenReturn(Mono.just(result));
webTestClient.get()
.uri("/login/oauth2/code/github")
.exchange()
.expectStatus().is3xxRedirection();
verify(converter).convert(any());
verify(manager).authenticate(any());
}
@Configuration
static class OAuth2LoginMockAuthenticationManagerConfig {
ReactiveAuthenticationManager manager = mock(ReactiveAuthenticationManager.class);
ServerAuthenticationConverter authenticationConverter = mock(ServerAuthenticationConverter.class);
@Bean
public SecurityWebFilterChain springSecurityFilter(ServerHttpSecurity http) {
http
.authorizeExchange()
.anyExchange().authenticated()
.and()
.oauth2Login()
.authenticationConverter(authenticationConverter)
.authenticationManager(manager);
return http.build();
}
}
static class GitHubWebFilter implements WebFilter {
@Override
@@ -23,7 +23,10 @@ import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPublicKeySpec;
import java.time.Instant;
import java.util.Collection;
import java.util.Collections;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.PreDestroy;
import okhttp3.mockwebserver.MockResponse;
@@ -39,15 +42,21 @@ import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.test.SpringTestRule;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.jose.jws.JwsAlgorithms;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.WebTestClient;
@@ -213,6 +222,16 @@ public class OAuth2ResourceServerSpecTests {
.expectStatus().isForbidden();
}
@Test
public void getWhenSignedAndCustomConverterThenConverts() {
this.spring.register(CustomJwtAuthenticationConverterConfig.class, RootController.class).autowire();
this.client.get()
.headers(headers -> headers.setBearerAuth(this.messageReadToken))
.exchange()
.expectStatus().isOk();
}
@Test
public void getJwtDecoderWhenBeanWiredAndDslWiredThenDslTakesPrecedence() {
GenericWebApplicationContext context = autowireWebServerGenericWebApplicationContext();
@@ -386,6 +405,41 @@ public class OAuth2ResourceServerSpecTests {
}
}
@EnableWebFlux
@EnableWebFluxSecurity
static class CustomJwtAuthenticationConverterConfig {
@Bean
SecurityWebFilterChain springSecurity(ServerHttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeExchange()
.anyExchange().hasAuthority("message:read")
.and()
.oauth2ResourceServer()
.jwt()
.jwtAuthenticationConverter(jwtAuthenticationConverter())
.publicKey(publicKey());
// @formatter:on
return http.build();
}
@Bean
Converter<Jwt, Mono<AbstractAuthenticationToken>> jwtAuthenticationConverter() {
JwtAuthenticationConverter converter = new JwtAuthenticationConverter() {
@Override
protected Collection<GrantedAuthority> extractAuthorities(Jwt jwt) {
String[] claims = ((String) jwt.getClaims().get("scope")).split(" ");
return Stream.of(claims).map(SimpleGrantedAuthority::new).collect(Collectors.toList());
}
};
return new ReactiveJwtAuthenticationConverterAdapter(converter);
}
}
@RestController
static class RootController {
@GetMapping
@@ -16,12 +16,27 @@
package org.springframework.security.config.web.server;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.when;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import org.apache.http.HttpHeaders;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import reactor.core.publisher.Mono;
import reactor.test.publisher.TestPublisher;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.config.annotation.web.reactive.ServerHttpSecurityConfigurationBuilder;
@@ -29,21 +44,23 @@ import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.test.web.reactive.server.WebTestClientBuilder;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.WebFilterChainProxy;
import org.springframework.security.web.server.authentication.logout.DelegatingServerLogoutHandler;
import org.springframework.security.web.server.authentication.logout.LogoutWebFilter;
import org.springframework.security.web.server.authentication.logout.SecurityContextServerLogoutHandler;
import org.springframework.security.web.server.authentication.logout.ServerLogoutHandler;
import org.springframework.security.web.server.context.ServerSecurityContextRepository;
import org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository;
import org.springframework.security.web.server.csrf.CsrfServerLogoutHandler;
import org.springframework.security.web.server.csrf.CsrfWebFilter;
import org.springframework.security.web.server.csrf.ServerCsrfTokenRepository;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.test.web.reactive.server.EntityExchangeResult;
import org.springframework.test.web.reactive.server.FluxExchangeResult;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import reactor.test.publisher.TestPublisher;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.when;
import org.springframework.web.server.WebFilter;
/**
* @author Rob Winch
@@ -55,6 +72,8 @@ public class ServerHttpSecurityTests {
private ServerSecurityContextRepository contextRepository;
@Mock
private ReactiveAuthenticationManager authenticationManager;
@Mock
private ServerCsrfTokenRepository csrfTokenRepository;
private ServerHttpSecurity http;
@@ -134,6 +153,51 @@ public class ServerHttpSecurityTests {
.expectBody(String.class).isEqualTo("/foo/bar");
}
@Test
public void csrfServerLogoutHandlerNotAppliedIfCsrfIsntEnabled() {
SecurityWebFilterChain securityWebFilterChain = this.http.csrf().disable().build();
assertThat(getWebFilter(securityWebFilterChain, CsrfWebFilter.class))
.isNotPresent();
Optional<ServerLogoutHandler> logoutHandler = getWebFilter(securityWebFilterChain, LogoutWebFilter.class)
.map(logoutWebFilter -> (ServerLogoutHandler) ReflectionTestUtils.getField(logoutWebFilter, LogoutWebFilter.class, "logoutHandler"));
assertThat(logoutHandler)
.get()
.isExactlyInstanceOf(SecurityContextServerLogoutHandler.class);
}
@Test
public void csrfServerLogoutHandlerAppliedIfCsrfIsEnabled() {
SecurityWebFilterChain securityWebFilterChain = this.http.csrf().csrfTokenRepository(this.csrfTokenRepository).and().build();
assertThat(getWebFilter(securityWebFilterChain, CsrfWebFilter.class))
.get()
.extracting(csrfWebFilter -> ReflectionTestUtils.getField(csrfWebFilter, "csrfTokenRepository"))
.isEqualTo(this.csrfTokenRepository);
Optional<ServerLogoutHandler> logoutHandler = getWebFilter(securityWebFilterChain, LogoutWebFilter.class)
.map(logoutWebFilter -> (ServerLogoutHandler) ReflectionTestUtils.getField(logoutWebFilter, LogoutWebFilter.class, "logoutHandler"));
assertThat(logoutHandler)
.get()
.isExactlyInstanceOf(DelegatingServerLogoutHandler.class)
.extracting(delegatingLogoutHandler ->
((List<ServerLogoutHandler>) ReflectionTestUtils.getField(delegatingLogoutHandler, DelegatingServerLogoutHandler.class, "delegates")).stream()
.map(ServerLogoutHandler::getClass)
.collect(Collectors.toList()))
.isEqualTo(Arrays.asList(SecurityContextServerLogoutHandler.class, CsrfServerLogoutHandler.class));
}
private <T extends WebFilter> Optional<T> getWebFilter(SecurityWebFilterChain filterChain, Class<T> filterClass) {
return (Optional<T>) filterChain.getWebFilters()
.filter(Objects::nonNull)
.filter(filter -> filter.getClass().isAssignableFrom(filterClass))
.singleOrEmpty()
.blockOptional();
}
private WebTestClient buildClient() {
WebFilterChainProxy springSecurityFilterChain = new WebFilterChainProxy(
this.http.build());
@@ -51,9 +51,9 @@ import java.lang.annotation.Target;
@Documented
public @interface Secured {
/**
* Returns the list of security configuration attributes (e.g. ROLE_USER, ROLE_ADMIN).
* Returns the list of security configuration attributes (e.g.&nbsp;ROLE_USER, ROLE_ADMIN).
*
* @return String[] The secure method attributes
*/
public String[]value();
public String[] value();
}
@@ -68,7 +68,6 @@ public class ConsensusBased extends AbstractAccessDecisionManager {
Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
int grant = 0;
int deny = 0;
int abstain = 0;
for (AccessDecisionVoter voter : getDecisionVoters()) {
int result = voter.vote(authentication, object, configAttributes);
@@ -89,8 +88,6 @@ public class ConsensusBased extends AbstractAccessDecisionManager {
break;
default:
abstain++;
break;
}
}
@@ -64,7 +64,6 @@ public class UnanimousBased extends AbstractAccessDecisionManager {
Collection<ConfigAttribute> attributes) throws AccessDeniedException {
int grant = 0;
int abstain = 0;
List<ConfigAttribute> singleAttributeList = new ArrayList<>(1);
singleAttributeList.add(null);
@@ -91,8 +90,6 @@ public class UnanimousBased extends AbstractAccessDecisionManager {
"Access is denied"));
default:
abstain++;
break;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
* Copyright 2002-2018 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.
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.authentication;
import java.util.Collections;
@@ -158,6 +157,7 @@ public class ProviderManager implements AuthenticationManager, MessageSourceAwar
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
Authentication result = null;
Authentication parentResult = null;
boolean debug = logger.isDebugEnabled();
for (AuthenticationProvider provider : getProviders()) {
@@ -196,7 +196,7 @@ public class ProviderManager implements AuthenticationManager, MessageSourceAwar
if (result == null && parent != null) {
// Allow the parent to try.
try {
result = parent.authenticate(authentication);
result = parentResult = parent.authenticate(authentication);
}
catch (ProviderNotFoundException e) {
// ignore as we will throw below if no other exception occurred prior to
@@ -217,7 +217,11 @@ public class ProviderManager implements AuthenticationManager, MessageSourceAwar
((CredentialsContainer) result).eraseCredentials();
}
eventPublisher.publishAuthenticationSuccess(result);
// If the parent AuthenticationManager was attempted and successful than it will publish an AuthenticationSuccessEvent
// This check prevents a duplicate AuthenticationSuccessEvent if the parent AuthenticationManager already published it
if (parentResult == null) {
eventPublisher.publishAuthenticationSuccess(result);
}
return result;
}
@@ -143,7 +143,7 @@ public class InMemoryUserDetailsManager implements UserDetailsManager,
@Override
public UserDetails updatePassword(UserDetails user, String newPassword) {
String username = user.getUsername();
MutableUserDetails mutableUser = this.users.get(username);
MutableUserDetails mutableUser = this.users.get(username.toLowerCase());
mutableUser.setPassword(newPassword);
return mutableUser;
}
@@ -76,7 +76,7 @@ public class JdbcUserDetailsManager extends JdbcDaoImpl implements UserDetailsMa
// GroupManager SQL
public static final String DEF_FIND_GROUPS_SQL = "select group_name from groups";
public static final String DEF_FIND_USERS_IN_GROUP_SQL = "select username from group_members gm, groups g "
+ "where gm.group_id = g.id" + " and g.group_name = ?";
+ "where gm.group_id = g.id and g.group_name = ?";
public static final String DEF_INSERT_GROUP_SQL = "insert into groups (group_name) values (?)";
public static final String DEF_FIND_GROUP_ID_SQL = "select id from groups where group_name = ?";
public static final String DEF_INSERT_GROUP_AUTHORITY_SQL = "insert into group_authorities (group_id, authority) values (?,?)";
@@ -18,6 +18,7 @@ package org.springframework.security.provisioning;
import org.junit.Test;
import org.springframework.security.core.userdetails.PasswordEncodedUser;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import static org.assertj.core.api.Assertions.*;
@@ -37,4 +38,15 @@ public class InMemoryUserDetailsManagerTests {
this.manager.updatePassword(this.user, newPassword);
assertThat(this.manager.loadUserByUsername(this.user.getUsername()).getPassword()).isEqualTo(newPassword);
}
@Test
public void changePasswordWhenUsernameIsNotInLowercase() {
UserDetails userNotLowerCase = User.withUserDetails(PasswordEncodedUser.user())
.username("User")
.build();
String newPassword = "newPassword";
this.manager.updatePassword(userNotLowerCase, newPassword);
assertThat(this.manager.loadUserByUsername(userNotLowerCase.getUsername()).getPassword()).isEqualTo(newPassword);
}
}
@@ -15,7 +15,7 @@
*/
package org.springframework.security.data.repository.query;
import org.springframework.data.repository.query.spi.EvaluationContextExtensionSupport;
import org.springframework.data.repository.query.spi.EvaluationContextExtension;
import org.springframework.security.access.expression.SecurityExpressionRoot;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
@@ -79,7 +79,7 @@ import org.springframework.security.core.context.SecurityContextHolder;
* @author Rob Winch
*/
public class SecurityEvaluationContextExtension
extends EvaluationContextExtensionSupport {
implements EvaluationContextExtension {
private Authentication authentication;
/**
@@ -117,4 +117,4 @@ public class SecurityEvaluationContextExtension
SecurityContext context = SecurityContextHolder.getContext();
return context.getAuthentication();
}
}
}
@@ -26,7 +26,7 @@ In order to use Spring Security you must add the necessary dependencies. For the
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId> <1>
<artifactId>thymeleaf-extras-springsecurity5</artifactId> <1>
<version>2.1.2.RELEASE</version>
</dependency>
</dependencies>
@@ -32,7 +32,7 @@ Now that we have authenticated, let's update the application to display the user
[source,html]
----
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
<title>Hello Spring Security</title>
<meta charset="utf-8" />
@@ -1,31 +1,40 @@
[[community]]
== Spring Security Community
[[jira]]
=== Issue Tracking
Spring Security uses JIRA to manage bug reports and enhancement requests.
If you find a bug, please log a report using JIRA.
Do not log it on the support forum, mailing list or by emailing the project's developers.
Such approaches are ad-hoc and we prefer to manage bugs using a more formal process.
If possible, in your issue report please provide a JUnit test that demonstrates any incorrect behaviour.
Or, better yet, provide a patch that corrects the issue.
Similarly, enhancements are welcome to be logged in the issue tracker, although we only accept enhancement requests if you include corresponding unit tests.
This is necessary to ensure project test coverage is adequately maintained.
You can access the issue tracker at https://github.com/spring-projects/spring-security/issues.
Welcome to the Spring Security Community!
This section discusses how you to make the most of our vast community.
[[becoming-involved]]
[[community-help]]
=== Getting Help
If you need help with Spring Security, we are here to help.
Below are some of the best steps to getting help:
* Read our reference documentation
* Try one of our many sample applications
// FIXME: Add a link to the samples section
* Ask a question on https://stackoverflow.com with the tag `spring-security`
* Report a bugs and enhancement requests at https://github.com/spring-projects/spring-security/issues
[[community-becoming-involved]]
=== Becoming Involved
We welcome your involvement in the Spring Security project.
There are many ways of contributing, including reading the forum and responding to questions from other people, writing new code, improving existing code, assisting with documentation, developing samples or tutorials, or simply making suggestions.
There are many ways of contributing, including answering questions on StackOverflow, writing new code, improving existing code, assisting with documentation, developing samples or tutorials, reporting bugs, or simply making suggestions.
[[community-source]]
=== Source Code
[[further-info]]
=== Further Information
Questions and comments on Spring Security are welcome.
You can use the Spring at Stack Overflow web site at http://spring.io/questions[http://spring.io/questions] to discuss Spring Security with other users of the framework.
Remember to use JIRA for bug reports, as explained above.
Spring Security's source code can be found on GitHub at https://github.com/spring-projects/spring-security/
[[community-license]]
=== Apache 2 License
Spring Security is Open Source software released under the http://www.apache.org/licenses/LICENSE-2.0.html[Apache 2.0 license].
=== Social Media
You may follow https://twitter.com/SpringSecurity[@SpringSecurity] and https://twitter.com/SpringSecurity/lists/team[Spring Security team] on Twitter to stay up to date with the latest news.
You can also follow https://twitter.com/SpringCentral[@SpringCentral] to keep up to date with the entire Spring portfolio.
// == Getting Started
// FIXME: Add links to samples
@@ -0,0 +1,310 @@
[[get-spring-security]]
= Getting Spring Security
This section discusses all you need to know about getting the Spring Security binaries.
Please refer to <<community-source>> for how to obtain the source code.
== Release Numbering
Spring Security versions are formatted as MAJOR.MINOR.PATCH such that
* MAJOR versions may contain breaking changes.
Typically these are done to provide improved security to match modern security practices.
* MINOR versions contain enhancements, but are considered passive updates
* PATCH level should be perfectly compatible, forwards and backwards, with the possible exception of changes which are to fix bugs
[[maven]]
== Usage with Maven
Like most open source projects, Spring Security deploys its dependencies as Maven artifacts.
The following sections provide details on how to consume Spring Security when using Maven.
=== Spring Boot with Maven
Spring Boot provides a spring-boot-starter-security starter which aggregates Spring Security related dependencies together.
The simplest and preferred method to leverage the starter is to use https://docs.spring.io/initializr/docs/current/reference/htmlsingle/[Spring Initializr] using an IDE integration (http://joshlong.com/jl/blogPost/tech_tip_geting_started_with_spring_boot.html[Eclipse], https://www.jetbrains.com/help/idea/spring-boot.html#d1489567e2[IntelliJ], https://github.com/AlexFalappa/nb-springboot/wiki/Quick-Tour[NetBeans]) or through https://start.spring.io.
Alternatively, the starter can be added manually:
.pom.xml
[source,xml]
[subs="verbatim,attributes"]
----
<dependencies>
<!-- ... other dependency elements ... -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
----
Since Spring Boot provides a Maven BOM to manage dependency versions, there is no need to specify a version.
If you wish to override the Spring Security version, you may do so by providing a Maven property:
.pom.xml
[source,xml]
[subs="verbatim,attributes"]
----
<properties>
<!-- ... -->
<spring-security.version>{spring-security-version}</spring-security.version>
</dependencies>
----
Since Spring Security only makes breaking changes in major releases, it is safe to use a newer version of Spring Security with Spring Boot.
However, at times it may be necessary to update the version of Spring Framework as well.
This can easily be done by adding a Maven property as well:
.pom.xml
[source,xml]
[subs="verbatim,attributes"]
----
<properties>
<!-- ... -->
<spring.version>{spring-version}</spring.version>
</dependencies>
----
If you are using additional features like LDAP, OpenID, etc. you will need to also include the appropriate <<modules>>.
=== Maven Without Spring Boot
When using Spring Security without Spring Boot, the preferred way is to leverage Spring Security's BOM to ensure a consistent version of Spring Security is used throughout the entire project.
.pom.xml
[source,xml]
[subs="verbatim,attributes"]
----
<dependencyManagement>
<dependencies>
<!-- ... other dependency elements ... -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-bom</artifactId>
<version>{spring-security-version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
----
A minimal Spring Security Maven set of dependencies typically looks like the following:
.pom.xml
[source,xml]
[subs="verbatim,attributes"]
----
<dependencies>
<!-- ... other dependency elements ... -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
</dependencies>
----
If you are using additional features like LDAP, OpenID, etc. you will need to also include the appropriate <<modules>>.
Spring Security builds against Spring Framework {spring-version}, but should generally work with any newer version of Spring Framework 5.x
The problem that many users will have is that Spring Security's transitive dependencies resolve Spring Framework {spring-version} which can cause strange classpath problems.
The easiest way to resolve this is to use the `spring-framework-bom` within your `<dependencyManagement>` section of your `pom.xml` as shown below:
.pom.xml
[source,xml]
[subs="verbatim,attributes"]
----
<dependencyManagement>
<dependencies>
<!-- ... other dependency elements ... -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>{spring-version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
----
This will ensure that all the transitive dependencies of Spring Security use the Spring {spring-version} modules.
NOTE: This approach uses Maven's "bill of materials" (BOM) concept and is only available in Maven 2.0.9+.
For additional details about how dependencies are resolved refer to http://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html[Maven's Introduction to the Dependency Mechanism documentation].
[[maven-repositories]]
=== Maven Repositories
All GA releases (i.e. versions ending in .RELEASE) are deployed to Maven Central, so no additional Maven repositories need to be declared in your pom.
If you are using a SNAPSHOT version, you will need to ensure you have the Spring Snapshot repository defined as shown below:
.pom.xml
[source,xml]
----
<repositories>
<!-- ... possibly other repository elements ... -->
<repository>
<id>spring-snapshot</id>
<name>Spring Snapshot Repository</name>
<url>https://repo.spring.io/snapshot</url>
</repository>
</repositories>
----
If you are using a milestone or release candidate version, you will need to ensure you have the Spring Milestone repository defined as shown below:
.pom.xml
[source,xml]
----
<repositories>
<!-- ... possibly other repository elements ... -->
<repository>
<id>spring-milestone</id>
<name>Spring Milestone Repository</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
----
[[gradle]]
== Gradle
Like most open source projects, Spring Security deploys its dependencies as Maven artifacts which allows for for first class Gradle support.
The following sections provide details on how to consume Spring Security when using Gradle.
=== Spring Boot with Gradle
Spring Boot provides a spring-boot-starter-security starter which aggregates Spring Security related dependencies together.
The simplest and preferred method to leverage the starter is to use https://docs.spring.io/initializr/docs/current/reference/htmlsingle/[Spring Initializr] using an IDE integration (http://joshlong.com/jl/blogPost/tech_tip_geting_started_with_spring_boot.html[Eclipse], https://www.jetbrains.com/help/idea/spring-boot.html#d1489567e2[IntelliJ], https://github.com/AlexFalappa/nb-springboot/wiki/Quick-Tour[NetBeans]) or through https://start.spring.io.
Alternatively, the starter can be added manually:
.build.gradle
[source,groovy]
[subs="verbatim,attributes"]
----
dependencies {
compile "org.springframework.boot:spring-boot-starter-security"
}
----
Since Spring Boot provides a Maven BOM to manage dependency versions, there is no need to specify a version.
If you wish to override the Spring Security version, you may do so by providing a Gradle property:
.build.gradle
[source,groovy]
[subs="verbatim,attributes"]
----
ext['spring-security.version']='{spring-security-version}'
----
Since Spring Security only makes breaking changes in major releases, it is safe to use a newer version of Spring Security with Spring Boot.
However, at times it may be necessary to update the version of Spring Framework as well.
This can easily be done by adding a Gradle property as well:
.build.gradle
[source,groovy]
[subs="verbatim,attributes"]
----
ext['spring.version']='{spring-version}'
----
If you are using additional features like LDAP, OpenID, etc. you will need to also include the appropriate <<modules>>.
=== Gradle Without Spring Boot
When using Spring Security without Spring Boot, the preferred way is to leverage Spring Security's BOM to ensure a consistent version of Spring Security is used throughout the entire project.
This can be done by using the https://github.com/spring-gradle-plugins/dependency-management-plugin[Dependency Management Plugin].
.build.gradle
[source,groovy]
[subs="verbatim,attributes"]
----
plugins {
id "io.spring.dependency-management" version "1.0.6.RELEASE"
}
dependencyManagement {
imports {
mavenBom 'org.springframework.security:spring-security-bom:{spring-security-version}'
}
}
----
A minimal Spring Security Maven set of dependencies typically looks like the following:
.build.gradle
[source,groovy]
[subs="verbatim,attributes"]
----
dependencies {
compile "org.springframework.security:spring-security-web"
compile "org.springframework.security:spring-security-config"
}
----
If you are using additional features like LDAP, OpenID, etc. you will need to also include the appropriate <<modules>>.
Spring Security builds against Spring Framework {spring-version}, but should generally work with any newer version of Spring Framework 5.x
The problem that many users will have is that Spring Security's transitive dependencies resolve Spring Framework {spring-version} which can cause strange classpath problems.
The easiest way to resolve this is to use the `spring-framework-bom` within your `<dependencyManagement>` section of your `pom.xml` as shown below:
This can be done by using the https://github.com/spring-gradle-plugins/dependency-management-plugin[Dependency Management Plugin].
.build.gradle
[source,groovy]
[subs="verbatim,attributes"]
----
plugins {
id "io.spring.dependency-management" version "1.0.6.RELEASE"
}
dependencyManagement {
imports {
mavenBom 'org.springframework:spring-framework-bom:{spring-version}'
}
}
----
This will ensure that all the transitive dependencies of Spring Security use the Spring {spring-version} modules.
[[gradle-repositories]]
=== Gradle Repositories
All GA releases (i.e. versions ending in .RELEASE) are deployed to Maven Central, so using the mavenCentral() repository is sufficient for GA releases.
.build.gradle
[source,groovy]
----
repositories {
mavenCentral()
}
----
If you are using a SNAPSHOT version, you will need to ensure you have the Spring Snapshot repository defined as shown below:
.build.gradle
[source,groovy]
----
repositories {
maven { url 'https://repo.spring.io/snapshot' }
}
----
If you are using a milestone or release candidate version, you will need to ensure you have the Spring Milestone repository defined as shown below:
.build.gradle
[source,groovy]
----
repositories {
maven { url 'https://repo.spring.io/milestone' }
}
----
@@ -1,10 +0,0 @@
[[getting-started]]
== Getting Started
The later parts of this guide provide an in-depth discussion of the framework architecture and implementation classes, which you need to understand if you want to do any serious customization.
In this part, we'll introduce Spring Security 5, give a brief overview of the project's history and take a slightly gentler look at how to get started using the framework.
In particular, we'll look at namespace configuration which provides a much simpler way of securing your application compared to the traditional Spring bean approach where you have to wire up all the implementation classes individually.
We'll also take a look at the sample applications that are available.
It's worth trying to run these and experimenting with them a bit even before you read the later sections - you can dip back into them as your understanding of the framework increases.
Please also check out the [http://spring.io/spring-security](http://spring.io/spring-security) as it has useful information on building the project, plus links to articles, videos and tutorials.
@@ -1,56 +1,13 @@
[[preface]]
= Preface
Spring Security provides a comprehensive security solution for Java EE-based enterprise software applications.
As you will discover as you venture through this reference guide, we have tried to provide you a useful and highly configurable security system.
Security is an ever-moving target, and it's important to pursue a comprehensive, system-wide approach.
In security circles we encourage you to adopt "layers of security", so that each layer tries to be as secure as possible in its own right, with successive layers providing additional security.
The "tighter" the security of each layer, the more robust and safe your application will be.
At the bottom level you'll need to deal with issues such as transport security and system identification, in order to mitigate man-in-the-middle attacks.
Next you'll generally utilise firewalls, perhaps with VPNs or IP security to ensure only authorised systems can attempt to connect.
In corporate environments you may deploy a DMZ to separate public-facing servers from backend database and application servers.
Your operating system will also play a critical part, addressing issues such as running processes as non-privileged users and maximising file system security.
An operating system will usually also be configured with its own firewall.
Hopefully somewhere along the way you'll be trying to prevent denial of service and brute force attacks against the system.
An intrusion detection system will also be especially useful for monitoring and responding to attacks, with such systems able to take protective action such as blocking offending TCP/IP addresses in real-time.
Moving to the higher layers, your Java Virtual Machine will hopefully be configured to minimize the permissions granted to different Java types, and then your application will add its own problem domain-specific security configuration.
Spring Security makes this latter area - application security - much easier.
This section discusses the logistics of Spring Security.
Of course, you will need to properly address all security layers mentioned above, together with managerial factors that encompass every layer.
A non-exhaustive list of such managerial factors would include security bulletin monitoring, patching, personnel vetting, audits, change control, engineering management systems, data backup, disaster recovery, performance benchmarking, load monitoring, centralised logging, incident response procedures etc.
With Spring Security being focused on helping you with the enterprise application security layer, you will find that there are as many different requirements as there are business problem domains.
A banking application has different needs from an ecommerce application.
An ecommerce application has different needs from a corporate sales force automation tool.
These custom requirements make application security interesting, challenging and rewarding.
Please read <<getting-started>>, in its entirety to begin with.
This will introduce you to the framework and the namespace-based configuration system with which you can get up and running quite quickly.
To get more of an understanding of how Spring Security works, and some of the classes you might need to use, you should then read <<overall-architecture>>.
The remaining parts of this guide are structured in a more traditional reference style, designed to be read on an as-required basis.
We'd also recommend that you read up as much as possible on application security issues in general.
Spring Security is not a panacea which will solve all security issues.
It is important that the application is designed with security in mind from the start.
Attempting to retrofit it is not a good idea.
In particular, if you are building a web application, you should be aware of the many potential vulnerabilities such as cross-site scripting, request-forgery and session-hijacking which you should be taking into account from the start.
The OWASP web site (http://www.owasp.org/) maintains a top ten list of web application vulnerabilities as well as a lot of useful reference information.
We hope that you find this reference guide useful, and we welcome your feedback and <<jira,suggestions>>.
Finally, welcome to the Spring Security <<community,community>>.
include::getting-started.adoc[]
include::introduction.adoc[]
include::community.adoc[]
include::whats-new.adoc[]
include::guides.adoc[]
include::getting-spring-security.adoc[leveloffset=+1]
include::java-configuration.adoc[]
include::namespace.adoc[]
include::modules.adoc[leveloffset=+1]
include::samples.adoc[]
include::community.adoc[]
@@ -1,446 +0,0 @@
[[introduction]]
== Introduction
[[what-is-acegi-security]]
=== What is Spring Security?
Spring Security provides comprehensive security services for Java EE-based enterprise software applications.
There is a particular emphasis on supporting projects built using The Spring Framework, which is the leading Java EE solution for enterprise software development.
If you're not using Spring for developing enterprise applications, we warmly encourage you to take a closer look at it.
Some familiarity with Spring - and in particular dependency injection principles - will help you get up to speed with Spring Security more easily.
People use Spring Security for many reasons, but most are drawn to the project after finding the security features of Java EE's Servlet Specification or EJB Specification lack the depth required for typical enterprise application scenarios.
Whilst mentioning these standards, it's important to recognise that they are not portable at a WAR or EAR level.
Therefore, if you switch server environments, it is typically a lot of work to reconfigure your application's security in the new target environment.
Using Spring Security overcomes these problems, and also brings you dozens of other useful, customisable security features.
As you probably know two major areas of application security are _authentication_ and _authorization_ (or _access-control_).
These are the two main areas that Spring Security targets.
"Authentication" is the process of establishing a principal is who they claim to be (a "principal" generally means a user, device or some other system which can perform an action in your application).
"Authorization" refers to the process of deciding whether a principal is allowed to perform an action within your application.
To arrive at the point where an authorization decision is needed, the identity of the principal has already been established by the authentication process.
These concepts are common, and not at all specific to Spring Security.
At an authentication level, Spring Security supports a wide range of authentication models.
Most of these authentication models are either provided by third parties, or are developed by relevant standards bodies such as the Internet Engineering Task Force.
In addition, Spring Security provides its own set of authentication features.
Specifically, Spring Security currently supports authentication integration with all of these technologies:
* HTTP BASIC authentication headers (an IETF RFC-based standard)
* HTTP Digest authentication headers (an IETF RFC-based standard)
* HTTP X.509 client certificate exchange (an IETF RFC-based standard)
* LDAP (a very common approach to cross-platform authentication needs, especially in large environments)
* Form-based authentication (for simple user interface needs)
* OpenID authentication
* Authentication based on pre-established request headers (such as Computer Associates Siteminder)
* Jasig Central Authentication Service (otherwise known as CAS, which is a popular open source single sign-on system)
* Transparent authentication context propagation for Remote Method Invocation (RMI) and HttpInvoker (a Spring remoting protocol)
* Automatic "remember-me" authentication (so you can tick a box to avoid re-authentication for a predetermined period of time)
* Anonymous authentication (allowing every unauthenticated call to automatically assume a particular security identity)
* Run-as authentication (which is useful if one call should proceed with a different security identity)
* Java Authentication and Authorization Service (JAAS)
* Java EE container authentication (so you can still use Container Managed Authentication if desired)
* Kerberos
* Java Open Source Single Sign-On (JOSSO) *
* OpenNMS Network Management Platform *
* AppFuse *
* AndroMDA *
* Mule ESB *
* Direct Web Request (DWR) *
* Grails *
* Tapestry *
* JTrac *
* Jasypt *
* Roller *
* Elastic Path *
* Atlassian Crowd *
* Your own authentication systems (see below)
(* Denotes provided by a third party)
Many independent software vendors (ISVs) adopt Spring Security because of this significant choice of flexible authentication models.
Doing so allows them to quickly integrate their solutions with whatever their end clients need, without undertaking a lot of engineering or requiring the client to change their environment.
If none of the above authentication mechanisms suit your needs, Spring Security is an open platform and it is quite simple to write your own authentication mechanism.
Many corporate users of Spring Security need to integrate with "legacy" systems that don't follow any particular security standards, and Spring Security is happy to "play nicely" with such systems.
Irrespective of the authentication mechanism, Spring Security provides a deep set of authorization capabilities.
There are three main areas of interest: authorizing web requests, authorizing whether methods can be invoked and authorizing access to individual domain object instances.
To help you understand the differences, consider the authorization capabilities found in the Servlet Specification web pattern security, EJB Container Managed Security and file system security respectively.
Spring Security provides deep capabilities in all of these important areas, which we'll explore later in this reference guide.
[[history]]
=== History
Spring Security began in late 2003 as "The Acegi Security System for Spring".
A question was posed on the Spring Developers' mailing list asking whether there had been any consideration given to a Spring-based security implementation.
At the time the Spring community was relatively small (especially compared with the size today!), and indeed Spring itself had only existed as a SourceForge project from early 2003.
The response to the question was that it was a worthwhile area, although a lack of time currently prevented its exploration.
With that in mind, a simple security implementation was built and not released.
A few weeks later another member of the Spring community inquired about security, and at the time this code was offered to them.
Several other requests followed, and by January 2004 around twenty people were using the code.
These pioneering users were joined by others who suggested a SourceForge project was in order, which was duly established in March 2004.
In those early days, the project didn't have any of its own authentication modules.
Container Managed Security was relied upon for the authentication process, with Acegi Security instead focusing on authorization.
This was suitable at first, but as more and more users requested additional container support, the fundamental limitation of container-specific authentication realm interfaces became clear.
There was also a related issue of adding new JARs to the container's classpath, which was a common source of end user confusion and misconfiguration.
Acegi Security-specific authentication services were subsequently introduced.
Around a year later, Acegi Security became an official Spring Framework subproject.
The 1.0.0 final release was published in May 2006 - after more than two and a half years of active use in numerous production software projects and many hundreds of improvements and community contributions.
Acegi Security became an official Spring Portfolio project towards the end of 2007 and was rebranded as _Spring Security_.
Today Spring Security enjoys a strong and active open source community.
There are thousands of messages about Spring Security on the support forums.
There is an active core of developers who work on the code itself and an active community which also regularly share patches and support their peers.
[[release-numbering]]
=== Release Numbering
It is useful to understand how Spring Security release numbers work, as it will help you identify the effort (or lack thereof) involved in migrating to future releases of the project.
Each release uses a standard triplet of integers: MAJOR.MINOR.PATCH.
The intent is that MAJOR versions are incompatible, large-scale upgrades of the API.
MINOR versions should largely retain source and binary compatibility with older minor versions, thought there may be some design changes and incompatible updates.
PATCH level should be perfectly compatible, forwards and backwards, with the possible exception of changes which are to fix bugs and defects.
The extent to which you are affected by changes will depend on how tightly integrated your code is.
If you are doing a lot of customization you are more likely to be affected than if you are using a simple namespace configuration.
You should always test your application thoroughly before rolling out a new version.
[[get-spring-security]]
=== Getting Spring Security
You can get hold of Spring Security in several ways.
You can download a packaged distribution from the main http://spring.
io/spring-security[Spring Security] page, download individual jars from the Maven Central repository (or a Spring Maven repository for snapshot and milestone releases) or, alternatively, you can build the project from source yourself.
[[maven]]
==== Usage with Maven
A minimal Spring Security Maven set of dependencies typically looks like the following:
.pom.xml
[source,xml]
[subs="verbatim,attributes"]
----
<dependencies>
<!-- ... other dependency elements ... -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>{spring-security-version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>{spring-security-version}</version>
</dependency>
</dependencies>
----
If you are using additional features like LDAP, OpenID, etc. you will need to also include the appropriate <<modules>>.
[[maven-repositories]]
===== Maven Repositories
All GA releases (i.e. versions ending in .RELEASE) are deployed to Maven Central, so no additional Maven repositories need to be declared in your pom.
If you are using a SNAPSHOT version, you will need to ensure you have the Spring Snapshot repository defined as shown below:
.pom.xml
[source,xml]
----
<repositories>
<!-- ... possibly other repository elements ... -->
<repository>
<id>spring-snapshot</id>
<name>Spring Snapshot Repository</name>
<url>http://repo.spring.io/snapshot</url>
</repository>
</repositories>
----
If you are using a milestone or release candidate version, you will need to ensure you have the Spring Milestone repository defined as shown below:
.pom.xml
[source,xml]
----
<repositories>
<!-- ... possibly other repository elements ... -->
<repository>
<id>spring-milestone</id>
<name>Spring Milestone Repository</name>
<url>http://repo.spring.io/milestone</url>
</repository>
</repositories>
----
[[maven-bom]]
===== Spring Framework BOM
Spring Security builds against Spring Framework {spring-version}, but should work with 5
The problem that many users will have is that Spring Security's transitive dependencies resolve Spring Framework {spring-version} which can cause strange classpath problems.
One (tedious) way to circumvent this issue would be to include all the Spring Framework modules in a http://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#Dependency_Management[<dependencyManagement>] section of your pom.
An alternative approach is to include the `spring-framework-bom` within your `<dependencyManagement>` section of your `pom.xml` as shown below:
.pom.xml
[source,xml]
[subs="verbatim,attributes"]
----
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>{spring-version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
----
This will ensure that all the transitive dependencies of Spring Security use the Spring {spring-version} modules.
NOTE: This approach uses Maven's "bill of materials" (BOM) concept and is only available in Maven 2.0.9+.
For additional details about how dependencies are resolved refer to http://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html[Maven's Introduction to the Dependency Mechanism documentation].
[[gradle]]
==== Gradle
A minimal Spring Security Gradle set of dependencies typically looks like the following:
.build.gradle
[source,groovy]
[subs="verbatim,attributes"]
----
dependencies {
compile 'org.springframework.security:spring-security-web:{spring-security-version}'
compile 'org.springframework.security:spring-security-config:{spring-security-version}'
}
----
If you are using additional features like LDAP, OpenID, etc. you will need to also include the appropriate <<modules>>.
[[gradle-repositories]]
===== Gradle Repositories
All GA releases (i.e. versions ending in .RELEASE) are deployed to Maven Central, so using the mavenCentral() repository is sufficient for GA releases.
.build.gradle
[source,groovy]
----
repositories {
mavenCentral()
}
----
If you are using a SNAPSHOT version, you will need to ensure you have the Spring Snapshot repository defined as shown below:
.build.gradle
[source,groovy]
----
repositories {
maven { url 'https://repo.spring.io/snapshot' }
}
----
If you are using a milestone or release candidate version, you will need to ensure you have the Spring Milestone repository defined as shown below:
.build.gradle
[source,groovy]
----
repositories {
maven { url 'https://repo.spring.io/milestone' }
}
----
[[gradle-resolutionStrategy]]
===== Using Spring 4.0.x and Gradle
By default Gradle will use the newest version when resolving transitive versions.
This means that often times no additional work is necessary when running Spring Security {spring-security-version} with Spring Framework {spring-version}.
However, at times there can be issues that come up so it is best to mitigate this using http://www.gradle.org/docs/current/dsl/org.gradle.api.artifacts.ResolutionStrategy.html[Gradle's ResolutionStrategy] as shown below:
.build.gradle
[source,groovy]
[subs="verbatim,attributes"]
----
configurations.all {
resolutionStrategy.eachDependency { DependencyResolveDetails details ->
if (details.requested.group == 'org.springframework') {
details.useVersion '{spring-version}'
}
}
}
----
This will ensure that all the transitive dependencies of Spring Security use the Spring {spring-version} modules.
NOTE: This example uses Gradle 1.9, but may need modifications to work in future versions of Gradle since this is an incubating feature within Gradle.
[[modules]]
==== Project Modules
In Spring Security 3.0, the codebase has been sub-divided into separate jars which more clearly separate different functionality areas and third-party dependencies.
If you are using Maven to build your project, then these are the modules you will add to your `pom.xml`.
Even if you're not using Maven, we'd recommend that you consult the `pom.xml` files to get an idea of third-party dependencies and versions.
Alternatively, a good idea is to examine the libraries that are included in the sample applications.
[[spring-security-core]]
===== Core - spring-security-core.jar
Contains core authentication and access-contol classes and interfaces, remoting support and basic provisioning APIs.
Required by any application which uses Spring Security.
Supports standalone applications, remote clients, method (service layer) security and JDBC user provisioning.
Contains the top-level packages:
* `org.springframework.security.core`
* `org.springframework.security.access`
* `org.springframework.security.authentication`
* `org.springframework.security.provisioning`
[[spring-security-remoting]]
===== Remoting - spring-security-remoting.jar
Provides intergration with Spring Remoting.
You don't need this unless you are writing a remote client which uses Spring Remoting.
The main package is `org.springframework.security.remoting`.
[[spring-security-web]]
===== Web - spring-security-web.jar
Contains filters and related web-security infrastructure code.
Anything with a servlet API dependency.
You'll need it if you require Spring Security web authentication services and URL-based access-control.
The main package is `org.springframework.security.web`.
[[spring-security-config]]
===== Config - spring-security-config.jar
Contains the security namespace parsing code & Java configuration code.
You need it if you are using the Spring Security XML namespace for configuration or Spring Security's Java Configuration support.
The main package is `org.springframework.security.config`.
None of the classes are intended for direct use in an application.
[[spring-security-ldap]]
===== LDAP - spring-security-ldap.jar
LDAP authentication and provisioning code.
Required if you need to use LDAP authentication or manage LDAP user entries.
The top-level package is `org.springframework.security.ldap`.
[[spring-security-oauth2-core]]
===== OAuth 2.0 Core - spring-security-oauth2-core.jar
`spring-security-oauth2-core.jar` contains core classes and interfaces that provide support for the _OAuth 2.0 Authorization Framework_ and for _OpenID Connect Core 1.0_.
It is required by applications that use _OAuth 2.0_ or _OpenID Connect Core 1.0_, such as Client, Resource Server, and Authorization Server.
The top-level package is `org.springframework.security.oauth2.core`.
[[spring-security-oauth2-client]]
===== OAuth 2.0 Client - spring-security-oauth2-client.jar
`spring-security-oauth2-client.jar` is Spring Security's client support for _OAuth 2.0 Authorization Framework_ and _OpenID Connect Core 1.0_.
Required by applications leveraging *OAuth 2.0 Login* and/or OAuth Client support.
The top-level package is `org.springframework.security.oauth2.client`.
[[spring-security-oauth2-jose]]
===== OAuth 2.0 JOSE - spring-security-oauth2-jose.jar
`spring-security-oauth2-jose.jar` contains Spring Security's support for the _JOSE_ (Javascript Object Signing and Encryption) framework.
The _JOSE_ framework is intended to provide a method to securely transfer claims between parties.
It is built from a collection of specifications:
* JSON Web Token (JWT)
* JSON Web Signature (JWS)
* JSON Web Encryption (JWE)
* JSON Web Key (JWK)
It contains the top-level packages:
* `org.springframework.security.oauth2.jwt`
* `org.springframework.security.oauth2.jose`
[[spring-security-acl]]
===== ACL - spring-security-acl.jar
Specialized domain object ACL implementation.
Used to apply security to specific domain object instances within your application.
The top-level package is `org.springframework.security.acls`.
[[spring-security-cas]]
===== CAS - spring-security-cas.jar
Spring Security's CAS client integration.
If you want to use Spring Security web authentication with a CAS single sign-on server.
The top-level package is `org.springframework.security.cas`.
[[spring-security-openid]]
===== OpenID - spring-security-openid.jar
OpenID web authentication support.
Used to authenticate users against an external OpenID server.
`org.springframework.security.openid`.
Requires OpenID4Java.
[[spring-security-test]]
===== Test - spring-security-test.jar
Support for testing with Spring Security.
[[get-source]]
==== Checking out the Source
Since Spring Security is an Open Source project, we'd strongly encourage you to check out the source code using git.
This will give you full access to all the sample applications and you can build the most up to date version of the project easily.
Having the source for a project is also a huge help in debugging.
Exception stack traces are no longer obscure black-box issues but you can get straight to the line that's causing the problem and work out what's happening.
The source is the ultimate documentation for a project and often the simplest place to find out how something actually works.
To obtain the source for the project, use the following git command:
[source,txt]
----
git clone https://github.com/spring-projects/spring-security.git
----
This will give you access to the entire project history (including all releases and branches) on your local machine.
@@ -0,0 +1,114 @@
[[modules]]
= Project Modules
In Spring Security 3.0, the codebase has been sub-divided into separate jars which more clearly separate different functionality areas and third-party dependencies.
If you are using Maven to build your project, then these are the modules you will add to your `pom.xml`.
Even if you're not using Maven, we'd recommend that you consult the `pom.xml` files to get an idea of third-party dependencies and versions.
Alternatively, a good idea is to examine the libraries that are included in the sample applications.
[[spring-security-core]]
== Core - spring-security-core.jar
Contains core authentication and access-contol classes and interfaces, remoting support and basic provisioning APIs.
Required by any application which uses Spring Security.
Supports standalone applications, remote clients, method (service layer) security and JDBC user provisioning.
Contains the top-level packages:
* `org.springframework.security.core`
* `org.springframework.security.access`
* `org.springframework.security.authentication`
* `org.springframework.security.provisioning`
[[spring-security-remoting]]
== Remoting - spring-security-remoting.jar
Provides intergration with Spring Remoting.
You don't need this unless you are writing a remote client which uses Spring Remoting.
The main package is `org.springframework.security.remoting`.
[[spring-security-web]]
== Web - spring-security-web.jar
Contains filters and related web-security infrastructure code.
Anything with a servlet API dependency.
You'll need it if you require Spring Security web authentication services and URL-based access-control.
The main package is `org.springframework.security.web`.
[[spring-security-config]]
== Config - spring-security-config.jar
Contains the security namespace parsing code & Java configuration code.
You need it if you are using the Spring Security XML namespace for configuration or Spring Security's Java Configuration support.
The main package is `org.springframework.security.config`.
None of the classes are intended for direct use in an application.
[[spring-security-ldap]]
== LDAP - spring-security-ldap.jar
LDAP authentication and provisioning code.
Required if you need to use LDAP authentication or manage LDAP user entries.
The top-level package is `org.springframework.security.ldap`.
[[spring-security-oauth2-core]]
== OAuth 2.0 Core - spring-security-oauth2-core.jar
`spring-security-oauth2-core.jar` contains core classes and interfaces that provide support for the _OAuth 2.0 Authorization Framework_ and for _OpenID Connect Core 1.0_.
It is required by applications that use _OAuth 2.0_ or _OpenID Connect Core 1.0_, such as Client, Resource Server, and Authorization Server.
The top-level package is `org.springframework.security.oauth2.core`.
[[spring-security-oauth2-client]]
== OAuth 2.0 Client - spring-security-oauth2-client.jar
`spring-security-oauth2-client.jar` is Spring Security's client support for _OAuth 2.0 Authorization Framework_ and _OpenID Connect Core 1.0_.
Required by applications leveraging *OAuth 2.0 Login* and/or OAuth Client support.
The top-level package is `org.springframework.security.oauth2.client`.
[[spring-security-oauth2-jose]]
== OAuth 2.0 JOSE - spring-security-oauth2-jose.jar
`spring-security-oauth2-jose.jar` contains Spring Security's support for the _JOSE_ (Javascript Object Signing and Encryption) framework.
The _JOSE_ framework is intended to provide a method to securely transfer claims between parties.
It is built from a collection of specifications:
* JSON Web Token (JWT)
* JSON Web Signature (JWS)
* JSON Web Encryption (JWE)
* JSON Web Key (JWK)
It contains the top-level packages:
* `org.springframework.security.oauth2.jwt`
* `org.springframework.security.oauth2.jose`
[[spring-security-acl]]
== ACL - spring-security-acl.jar
Specialized domain object ACL implementation.
Used to apply security to specific domain object instances within your application.
The top-level package is `org.springframework.security.acls`.
[[spring-security-cas]]
== CAS - spring-security-cas.jar
Spring Security's CAS client integration.
If you want to use Spring Security web authentication with a CAS single sign-on server.
The top-level package is `org.springframework.security.cas`.
[[spring-security-openid]]
== OpenID - spring-security-openid.jar
OpenID web authentication support.
Used to authenticate users against an external OpenID server.
`org.springframework.security.openid`.
Requires OpenID4Java.
[[spring-security-test]]
== Test - spring-security-test.jar
Support for testing with Spring Security.
@@ -4,13 +4,47 @@
Spring Security 5.1 provides a number of new features.
Below are the highlights of the release.
=== New Features
=== Servlet
* <<test-method>>
** Support for customizing when the `SecurityContext` is setup in the test
* Automatic password storage upgrades through {security-api-url}org/springframework/security/core/userdetails/UserDetailsPasswordService.html[UserDetailsPasswordService]
* {gh-samples-url}/boot/oauth2webclient[OAuth 2.0 Client]
** Customizable Authorize and Token requests
** `authorization_code` grant support
** `client_credentials` grant support
* OAuth 2.0 Resource Server - support for {gh-samples-url}/boot/oauth2resourceserver[JWT-encoded bearer tokens]
* Added OAuth2 <<servlet-webclient,WebClient>> integration
* <<request-matching,HTTP Firewall>> protects against HTTP Verb Tampering and Cross-site Tracing
* <<exception-translation-filter,ExceptionTranslationFilter>> support for selecting an `AccessDeniedHandler` by `RequestMatcher`
* <<csrf,CSRF>> support for excluding certain requests
* Added Support for <<headers-feature,Feature Policy>>
* Added {security-api-url}core/src/main/java/org/springframework/security/core/Transient.java[@Transient] authentication tokens
* A modern look-and-feel for the default log in page
=== WebFlux
* Automatic password storage upgrades through {security-api-url}org/springframework/security/core/userdetails/ReactiveUserDetailsPasswordService.html[ReactiveUserDetailsPasswordService]
* Added <<webflux-oauth2,OAuth2>> support
** Added <<webflux-oauth2-client,OAuth2 Client>> support
** Added <<webflux-oauth2-resource-server,OAuth2 Resource Server>> support
** Added OAuth2 <<webclient,WebClient>> integration
* `@WithUserDetails` <<test-method-withuserdetails,now works>> with `ReactiveUserDetailsService`
* Added <<webflux-cors,CORS>> support
* Added support for the following <<webflux-headers,HTTP headers>>
** <<webflux-headers-csp,Content Security Policy>>
** <<webflux-headers-feature,Feature Policy>>
** <<webflux-headers-referrer,Referrer Policy>>
* <<webflux-redirect-https,Redirect to HTTPS>>
* Improvements for {security-api-url}org/springframework/security/core/annotation/AuthenticationPrincipal.html[@AuthenticationPrincipal]
** Support for resolving beans
** Support for resolving `errorOnInvalidType`
=== Integrations
* <<jackson,Jackson Support>> works with `BadCredentialsException`
* `@WithMockUser` <<test-method-withmockuser,supports>> customizing when the `SecurityContext` is setup in the test.
For example, `@WithMockUser(setupBefore = TestExecutionEvent.TEST_EXECUTION)` will setup a user after JUnit's `@Before` and before the test executes.
** `@WithUserDetails` now works with `ReactiveUserDetailsService`
* <<jackson>> - added support for `BadCredentialsException`
* <<mvc-authentication-principal>>
** Supports resolving beans in WebFlux (was already supported in Spring MVC)
** Supports resolving `errorOnInvalidType` in WebFlux (was already supported in Spring MVC)
* <<ldap,LDAP Authentication>> can be configured with custom environment variables
* <<x509,X.509 Authentication>> supports deriving the principal as a strategy
@@ -0,0 +1,37 @@
[[webflux-cors]]
== CORS
Spring Framework provides https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-cors-intro[first class support for CORS].
CORS must be processed before Spring Security because the pre-flight request will not contain any cookies (i.e. the `JSESSIONID`).
If the request does not contain any cookies and Spring Security is first, the request will determine the user is not authenticated (since there are no cookies in the request) and reject it.
The easiest way to ensure that CORS is handled first is to use the `CorsWebFilter`.
Users can integrate the `CorsWebFilter` with Spring Security by providing a `CorsConfigurationSource`.
For example, the following will integrate CORS support within Spring Security:
[source,java]
----
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("https://example.com"));
configuration.setAllowedMethods(Arrays.asList("GET","POST"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
----
The following will disable the CORS integration within Spring Security:
[source,java]
----
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
// ...
.cors().disable();
return http.build();
}
----
@@ -0,0 +1,444 @@
[[webflux-headers]]
Security HTTP Response Headers
This section discusses Spring Security's support for adding various security headers to the response of WebFlux.
= Default Security Headers
Spring Security allows users to easily inject the default security headers to assist in protecting their application.
The default for Spring Security is to include the following headers:
[source,http]
----
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Content-Type-Options: nosniff
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
For additional details on each of these headers, refer to the corresponding sections:
* <<webflux-headers-cache-control,Cache Control>>
* <<webflux-headers-content-type-options,Content Type Options>>
* <<webflux-headers-hsts,HTTP Strict Transport Security>>
* <<webflux-headers-frame-options,X-Frame-Options>>
* <<webflux-headers-xss-protection,X-XSS-Protection>>
While each of these headers are considered best practice, it should be noted that not all clients utilize the headers, so additional testing is encouraged.
You can customize specific headers.
For example, assume that want your HTTP response headers to look like the following:
[source,http]
----
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
----
Specifically, you want all of the default headers with the following customizations:
* <<webflux-headers-frame-options,X-Frame-Options>> to allow any request from same domain
* <<webflux-headers-hsts,HTTP Strict Transport Security (HSTS)>> will not be added to the response
You can easily do this with the following Java Configuration:
[source,java]
----
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
// ...
.headers()
.hsts().disable()
.frameOptions().mode(Mode.SAMEORIGIN);
return http.build();
}
----
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 for both Java and XML based configuration is provided below:
If necessary, you can disable all of the HTTP Security response headers with the following Java Configuration:
[source,java]
----
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
// ...
.headers()
.disable();
return http.build();
}
----
[[webflux-headers-cache-control]]
== Cache Control
In the past Spring Security required you to provide your own cache control for your web application.
This seemed reasonable at the time, but browser caches have evolved to include caches for secure connections as well.
This means that a user may view an authenticated page, log out, and then a malicious user can use the browser history to view the cached page.
To help mitigate this Spring Security has added cache control support which will insert the following headers into you response by default.
[source]
----
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
----
If you actually want to cache specific responses, your application can selectively set the cache control headers to override the header set by Spring Security.
This is useful to ensure things like CSS, JavaScript, and images are properly cached.
You can also disable cache control using the following Java Configuration:
[source,java]
----
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
// ...
.headers()
.cache().disable();
return http.build();
}
----
[[webflux-headers-content-type-options]]
== Content Type Options
Historically browsers, including Internet Explorer, would try to guess the content type of a request using http://en.wikipedia.org/wiki/Content_sniffing[content sniffing].
This allowed browsers to improve the user experience by guessing the content type on resources that had not specified the content type.
For example, if a browser encountered a JavaScript file that did not have the content type specified, it would be able to guess the content type and then execute it.
[NOTE]
==
There are many additional things one should do (i.e. only display the document in a distinct domain, ensure Content-Type header is set, sanitize the document, etc) when allowing content to be uploaded.
However, these measures are out of the scope of what Spring Security provides.
It is also important to point out when disabling content sniffing, you must specify the content type in order for things to work properly.
==
The problem with content sniffing is that this allowed malicious users to use polyglots (i.e. a file that is valid as multiple content types) to execute XSS attacks.
For example, some sites may allow users to submit a valid postscript document to a website and view it.
A malicious user might create a http://webblaze.cs.berkeley.edu/papers/barth-caballero-song.pdf[postscript document that is also a valid JavaScript file] and execute a XSS attack with it.
Content sniffing can be disabled by adding the following header to our response:
[source]
----
X-Content-Type-Options: nosniff
----
Just as with the cache control element, the nosniff directive is added by default.
However, if need to disable the header, the following may be used:
[source,java]
----
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
// ...
.headers()
.contentTypeOptions().disable();
return http.build();
}
----
[[webflux-headers-hsts]]
== HTTP Strict Transport Security (HSTS)
When you type in your bank's website, do you enter mybank.example.com or do you enter https://mybank.example.com[]?
If you omit the https protocol, you are potentially vulnerable to http://en.wikipedia.org/wiki/Man-in-the-middle_attack[Man in the Middle attacks].
Even if the website performs a redirect to https://mybank.example.com a malicious user could intercept the initial HTTP request and manipulate the response (i.e. redirect to https://mibank.example.com and steal their credentials).
Many users omit the https protocol and this is why http://tools.ietf.org/html/rfc6797[HTTP Strict Transport Security (HSTS)] was created.
Once mybank.example.com is added as a http://tools.ietf.org/html/rfc6797#section-5.1[HSTS host], a browser can know ahead of time that any request to mybank.example.com should be interpreted as https://mybank.example.com.
This greatly reduces the possibility of a Man in the Middle attack occurring.
[NOTE]
==
In accordance with http://tools.ietf.org/html/rfc6797#section-7.2[RFC6797], the HSTS header is only injected into HTTPS responses.
In order for the browser to acknowledge the header, the browser must first trust the CA that signed the SSL certificate used to make the connection (not just the SSL certificate).
==
One way for a site to be marked as a HSTS host is to have the host preloaded into the browser.
Another is to add the "Strict-Transport-Security" header to the response.
For example the following would instruct the browser to treat the domain as an HSTS host for a year (there are approximately 31536000 seconds in a year):
[source]
----
Strict-Transport-Security: max-age=31536000 ; includeSubDomains
----
The optional includeSubDomains directive instructs Spring Security that subdomains (i.e. secure.mybank.example.com) should also be treated as an HSTS domain.
As with the other headers, Spring Security adds HSTS by default.
You can customize HSTS headers with Java Configuration:
[source,java]
----
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
// ...
.headers()
.hsts()
.includeSubdomains(true)
.maxAge(Duration.ofDays(365));
return http.build();
}
----
[[webflux-headers-frame-options]]
== X-Frame-Options
Allowing your website to be added to a frame can be a security issue.
For example, using clever CSS styling users could be tricked into clicking on something that they were not intending (http://www.youtube.com/watch?v=3mk0RySeNsU[video demo]).
For example, a user that is logged into their bank might click a button that grants access to other users.
This sort of attack is known as http://en.wikipedia.org/wiki/Clickjacking[Clickjacking].
[NOTE]
==
Another modern approach to dealing with clickjacking is to use <<webflux-headers-csp>>.
==
There are a number ways to mitigate clickjacking attacks.
For example, to protect legacy browsers from clickjacking attacks you can use https://www.owasp.org/index.php/Clickjacking_Defense_Cheat_Sheet#Best-for-now_Legacy_Browser_Frame_Breaking_Script[frame breaking code].
While not perfect, the frame breaking code is the best you can do for the legacy browsers.
A more modern approach to address clickjacking is to use https://developer.mozilla.org/en-US/docs/HTTP/X-Frame-Options[X-Frame-Options] header:
[source]
----
X-Frame-Options: DENY
----
The X-Frame-Options response header instructs the browser to prevent any site with this header in the response from being rendered within a frame.
By default, Spring Security disables rendering within an iframe.
You can customize X-Frame-Options with Java Configuration using the following:
[source,java]
----
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
// ...
.headers()
.frameOptions()
.mode(SAMEORIGIN);
return http.build();
}
----
[[webflux-headers-xss-protection]]
== X-XSS-Protection
Some browsers have built in support for filtering out https://www.owasp.org/index.php/Testing_for_Reflected_Cross_site_scripting_(OWASP-DV-001)[reflected XSS attacks].
This is by no means foolproof, but does assist in XSS protection.
The filtering is typically enabled by default, so adding the header typically just ensures it is enabled and instructs the browser what to do when a XSS attack is detected.
For example, the filter might try to change the content in the least invasive way to still render everything.
At times, this type of replacement can become a http://hackademix.net/2009/11/21/ies-xss-filter-creates-xss-vulnerabilities/[XSS vulnerability in itself].
Instead, it is best to block the content rather than attempt to fix it.
To do this we can add the following header:
[source]
----
X-XSS-Protection: 1; mode=block
----
This header is included by default.
However, we can customize with Java Configuration with the following:
[source,java]
----
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
// ...
.headers()
.xssProtection()
.disable();
return http.build();
}
----
[[webflux-headers-csp]]
== Content Security Policy (CSP)
https://www.w3.org/TR/CSP2/[Content Security Policy (CSP)] is a mechanism that web applications can leverage to mitigate content injection vulnerabilities, such as cross-site scripting (XSS).
CSP is a declarative policy that provides a facility for web application authors to declare and ultimately inform the client (user-agent) about the sources from which the web application expects to load resources.
[NOTE]
==
Content Security Policy is not intended to solve all content injection vulnerabilities.
Instead, CSP can be leveraged to help reduce the harm caused by content injection attacks.
As a first line of defense, web application authors should validate their input and encode their output.
==
A web application may employ the use of CSP by including one of the following HTTP headers in the response:
* *_Content-Security-Policy_*
* *_Content-Security-Policy-Report-Only_*
Each of these headers are used as a mechanism to deliver a *_security policy_* to the client.
A security policy contains a set of *_security policy directives_* (for example, _script-src_ and _object-src_), each responsible for declaring the restrictions for a particular resource representation.
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:
[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.
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.
[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.io/[*_REPORT-URI_*].
The *_Content-Security-Policy-Report-Only_* header provides the capability for web application authors and administrators to monitor security policies, rather than enforce them.
This header is typically used when experimenting and/or developing security policies for a site.
When a policy is deemed effective, it can be enforced by using the _Content-Security-Policy_ header field instead.
Given the following response header, the policy declares that scripts may be loaded from one of two possible sources.
[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.
[[webflux-headers-csp-configure]]
=== Configuring Content Security Policy
It's important to note that Spring Security *_does not add_* Content Security Policy by default.
The web application author must declare the security policy(s) to enforce and/or monitor for the protected resources.
For example, given the following security policy:
[source]
----
script-src 'self' https://trustedscripts.example.com; object-src https://trustedplugins.example.com; report-uri /csp-report-endpoint/
----
You can enable the CSP header using Java configuration as shown below:
[source,java]
----
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
// ...
.headers()
.contentSecurityPolicy("script-src 'self' https://trustedscripts.example.com; object-src https://trustedplugins.example.com; report-uri /csp-report-endpoint/");
return http.build();
}
----
To enable the CSP _'report-only'_ header, provide the following Java configuration:
[source,java]
----
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
// ...
.headers()
.contentSecurityPolicy("script-src 'self' https://trustedscripts.example.com; object-src https://trustedplugins.example.com; report-uri /csp-report-endpoint/")
.reportOnly();
return http.build();
}
----
[[webflux-headers-csp-links]]
=== Additional Resources
Applying Content Security Policy to a web application is often a non-trivial undertaking.
The following resources may provide further assistance in developing effective security policies for your site.
http://www.html5rocks.com/en/tutorials/security/content-security-policy/[An Introduction to Content Security Policy]
https://developer.mozilla.org/en-US/docs/Web/Security/CSP[CSP Guide - Mozilla Developer Network]
https://www.w3.org/TR/CSP2/[W3C Candidate Recommendation]
[[webflux-headers-referrer]]
== Referrer Policy
https://www.w3.org/TR/referrer-policy[Referrer Policy] is a mechanism that web applications can leverage to manage the referrer field, which contains the last 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]:
[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.
[[webflux-headers-referrer-configure]]
=== Configuring Referrer Policy
Spring Security *_doesn't add_* Referrer Policy header by default.
You can enable the Referrer-Policy header using Java configuration as shown below:
[source,java]
----
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
// ...
.headers()
.referrerPolicy(ReferrerPolicy.SAME_ORIGIN);
return http.build();
}
----
[[webflux-headers-feature]]
== Feature Policy
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.
[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.
[[webflux-headers-feature-configure]]
=== Configuring Feature Policy
Spring Security *_doesn't add_* Feature Policy header by default.
You can enable the Feature-Policy header using Java configuration as shown below:
[source,java]
----
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
// ...
.headers()
.featurePolicy("geolocation 'self'");
return http.build();
}
----
@@ -0,0 +1,17 @@
= Reactive Applications
include::webflux.adoc[leveloffset=+1]
include::headers.adoc[leveloffset=+1]
include::redirect-https.adoc[leveloffset=+1]
include::oauth2/index.adoc[leveloffset=+1]
include::registered-oauth2-authorized-client.adoc[leveloffset=+1]
include::webclient.adoc[leveloffset=+1]
include::method.adoc[leveloffset=+1]
include::test.adoc[leveloffset=+1]
@@ -0,0 +1,115 @@
[[jc-erms]]
= EnableReactiveMethodSecurity
Spring Security supports method security using https://projectreactor.io/docs/core/release/reference/#context[Reactor's Context] which is setup using `ReactiveSecurityContextHolder`.
For example, this demonstrates how to retrieve the currently logged in user's message.
[NOTE]
====
For this to work the return type of the method must be a `org.reactivestreams.Publisher` (i.e. `Mono`/`Flux`).
This is necessary to integrate with Reactor's `Context`.
====
[source,java]
----
Authentication authentication = new TestingAuthenticationToken("user", "password", "ROLE_USER");
Mono<String> messageByUsername = ReactiveSecurityContextHolder.getContext()
.map(SecurityContext::getAuthentication)
.map(Authentication::getName)
.flatMap(this::findMessageByUsername)
// In a WebFlux application the `subscriberContext` is automatically setup using `ReactorContextWebFilter`
.subscriberContext(ReactiveSecurityContextHolder.withAuthentication(authentication));
StepVerifier.create(messageByUsername)
.expectNext("Hi user")
.verifyComplete();
----
with `this::findMessageByUsername` defined as:
[source,java]
----
Mono<String> findMessageByUsername(String username) {
return Mono.just("Hi " + username);
}
----
Below is a minimal method security configuration when using method security in reactive applications.
[source,java]
----
@EnableReactiveMethodSecurity
public class SecurityConfig {
@Bean
public MapReactiveUserDetailsService userDetailsService() {
User.UserBuilder userBuilder = User.withDefaultPasswordEncoder();
UserDetails rob = userBuilder.username("rob")
.password("rob")
.roles("USER")
.build();
UserDetails admin = userBuilder.username("admin")
.password("admin")
.roles("USER","ADMIN")
.build();
return new MapReactiveUserDetailsService(rob, admin);
}
}
----
Consider the following class:
[source,java]
----
@Component
public class HelloWorldMessageService {
@PreAuthorize("hasRole('ADMIN')")
public Mono<String> findMessage() {
return Mono.just("Hello World!");
}
}
----
Combined with our configuration above, `@PreAuthorize("hasRole('ADMIN')")` will ensure that `findByMessage` is only invoked by a user with the role `ADMIN`.
It is important to note that any of the expressions in standard method security work for `@EnableReactiveMethodSecurity`.
However, at this time we only support return type of `Boolean` or `boolean` of the expression.
This means that the expression must not block.
When integrating with <<jc-webflux>>, the Reactor Context is automatically established by Spring Security according to the authenticated user.
[source,java]
----
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class SecurityConfig {
@Bean
SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) throws Exception {
return http
// Demonstrate that method security works
// Best practice to use both for defense in depth
.authorizeExchange()
.anyExchange().permitAll()
.and()
.httpBasic().and()
.build();
}
@Bean
MapReactiveUserDetailsService userDetailsService() {
User.UserBuilder userBuilder = User.withDefaultPasswordEncoder();
UserDetails rob = userBuilder.username("rob")
.password("rob")
.roles("USER")
.build();
UserDetails admin = userBuilder.username("admin")
.password("admin")
.roles("USER","ADMIN")
.build();
return new MapReactiveUserDetailsService(rob, admin);
}
}
----
You can find a complete sample in {gh-samples-url}/javaconfig/hellowebflux-method[hellowebflux-method]
@@ -0,0 +1,35 @@
[[webflux-oauth2-client]]
= OAuth2 Client
Spring Security's OAuth Support allows obtaining an access token without authenticating.
A basic configuration with Spring Boot can be seen below:
[source,yml]
----
spring:
security:
oauth2:
client:
registration:
github:
client-id: replace-with-client-id
client-secret: replace-with-client-secret
scopes: read:user,public_repo
----
You will need to replace the `client-id` and `client-secret` with values registered with GitHub.
The next step is to instruct Spring Security that you wish to act as an OAuth2 Client so that you can obtain an access token.
[source,java]
----
@Bean
SecurityWebFilterChain configure(ServerHttpSecurity http) throws Exception {
http
// ...
.oauth2Client();
return http.build();
}
----
You can now leverage Spring Security's <<webclient>> or <<webflux-roac,@RegisteredOAuth2AuthorizedClient>> support to obtain and use the access token.
@@ -0,0 +1,10 @@
[[webflux-oauth2]]
= OAuth2 WebFlux
Spring Security provides OAuth2 and WebFlux integration for reactive applications.
include::login.adoc[leveloffset=+1]
include::access-token.adoc[leveloffset=+1]
include::resource-server.adoc[leveloffset=+1]
@@ -0,0 +1,154 @@
[[webflux-oauth2-login]]
= OAuth 2.0 Login
The OAuth 2.0 Login feature provides an application with the capability to have users log in to the application by using their existing account at an OAuth 2.0 Provider (e.g.
GitHub) or OpenID Connect 1.0 Provider (such as Google).
OAuth 2.0 Login implements the use cases: "Login with Google" or "Login with GitHub".
NOTE: OAuth 2.0 Login is implemented by using the *Authorization Code Grant*, as specified in the https://tools.ietf.org/html/rfc6749#section-4.1[OAuth 2.0 Authorization Framework] and http://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth[OpenID Connect Core 1.0].
[[webflux-oauth2-login-sample]]
== Spring Boot 2.0 Sample
Spring Boot 2.0 brings full auto-configuration capabilities for OAuth 2.0 Login.
This section shows how to configure the {gh-samples-url}/boot/oauth2login-webflux[*OAuth 2.0 Login WebFlux sample*] using _Google_ as the _Authentication Provider_ and covers the following topics:
* <<webflux-oauth2-login-sample-setup,Initial setup>>
* <<webflux-oauth2-login-sample-redirect,Setting the redirect URI>>
* <<webflux-oauth2-login-sample-config,Configure `application.yml`>>
* <<webflux-oauth2-login-sample-start,Boot up the application>>
[[webflux-oauth2-login-sample-setup]]
=== Initial setup
To use Google's OAuth 2.0 authentication system for login, you must set up a project in the Google API Console to obtain OAuth 2.0 credentials.
NOTE: https://developers.google.com/identity/protocols/OpenIDConnect[Google's OAuth 2.0 implementation] for authentication conforms to the http://openid.net/connect/[OpenID Connect 1.0] specification and is http://openid.net/certification/[OpenID Certified].
Follow the instructions on the https://developers.google.com/identity/protocols/OpenIDConnect[OpenID Connect] page, starting in the section, "Setting up OAuth 2.0".
After completing the "Obtain OAuth 2.0 credentials" instructions, you should have a new OAuth Client with credentials consisting of a Client ID and a Client Secret.
[[webflux-oauth2-login-sample-redirect]]
=== Setting the redirect URI
The redirect URI is the path in the application that the end-user's user-agent is redirected back to after they have authenticated with Google and have granted access to the OAuth Client _(<<webflux-oauth2-login-sample-setup,created in the previous step>>)_ on the Consent page.
In the "Set a redirect URI" sub-section, ensure that the *Authorized redirect URIs* field is set to `http://localhost:8080/login/oauth2/code/google`.
TIP: The default redirect URI template is `{baseUrl}/login/oauth2/code/{registrationId}`.
The *_registrationId_* is a unique identifier for the <<jc-oauth2login-client-registration,ClientRegistration>>.
For our example, the `registrationId` is `google`.
[[webflux-oauth2-login-sample-config]]
=== Configure `application.yml`
Now that you have a new OAuth Client with Google, you need to configure the application to use the OAuth Client for the _authentication flow_.
To do so:
. Go to `application.yml` and set the following configuration:
+
[source,yaml]
----
spring:
security:
oauth2:
client:
registration: <1>
google: <2>
client-id: google-client-id
client-secret: google-client-secret
----
+
.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 <<jc-oauth2login-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.
[[webflux-oauth2-login-sample-start]]
=== Boot up the application
Launch the Spring Boot 2.0 sample and go to `http://localhost:8080`.
You are then redirected to the default _auto-generated_ login page, which displays a link for Google.
Click on the Google link, and you are then redirected to Google for authentication.
After authenticating with your Google account credentials, the next page presented to you is the Consent screen.
The Consent screen asks you to either allow or deny access to the OAuth Client you created earlier.
Click *Allow* to authorize the OAuth Client to access your email address and basic profile information.
At this point, the OAuth Client retrieves your email address and basic profile information from the http://openid.net/specs/openid-connect-core-1_0.html#UserInfo[UserInfo Endpoint] and establishes an authenticated session.
[[webflux-oauth2-login-openid-provider-configuration]]
== Using OpenID Provider Configuration
For well known providers, Spring Security provides the necessary defaults for the OAuth Authorization Provider's configuration.
If you are working with your own Authorization Provider that supports https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig[OpenID Provider Configuration], you may use the https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse[OpenID Provider Configuration Response] the issuer-uri can be used to configure the application.
[source,yml]
----
spring:
security:
oauth2:
client:
provider:
keycloak:
issuer-uri: https://idp.example.com/auth/realms/demo
registration:
keycloak:
client-id: spring-security
client-secret: 6cea952f-10d0-4d00-ac79-cc865820dc2c
----
The `issuer-uri` instructs Spring Security to leverage the endpoint at `https://idp.example.com/auth/realms/demo/.well-known/openid-configuration` to discover the configuration.
The `client-id` and `client-secret` are linked to the provider because `keycloak` is used for both the provider and the registration.
[[webflux-oauth2-login-explicit]]
== Explicit OAuth2 Login Configuration
A minimal OAuth2 Login configuration is shown below:
[source,java]
----
@Bean
ReactiveClientRegistrationRepository clientRegistrations() {
ClientRegistration clientRegistration = ClientRegistrations
.fromOidcIssuerLocation("https://idp.example.com/auth/realms/demo")
.clientId("spring-security")
.clientSecret("6cea952f-10d0-4d00-ac79-cc865820dc2c")
.build();
return new InMemoryReactiveClientRegistrationRepository(clientRegistration);
}
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
// ...
.oauth2Login();
return http.build();
}
----
Additional configuration options can be seen below:
[source,java]
----
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
// ...
.oauth2Login()
.authenticationConverter(converter)
.authenticationManager(manager)
.authorizedClientRepository(authorizedClients)
.clientRegistrationRepository(clientRegistrations);
return http.build();
}
----
@@ -0,0 +1,396 @@
[[webflux-oauth2-resource-server]]
= OAuth2 Resource Server
Spring Security supports protecting endpoints using https://tools.ietf.org/html/rfc7519[JWT]-encoded OAuth 2.0 https://tools.ietf.org/html/rfc6750.html[Bearer Tokens].
This is handy in circumstances where an application has federated its authority management out to an https://tools.ietf.org/html/rfc6749[authorization server] (for example, Okta or Ping Identity).
This authorization server can be consulted by Resource Servers to validate authority when serving requests.
[NOTE]
====
A complete working example can be found in {gh-samples-url}/boot/oauth2resourceserver-webflux[*OAuth 2.0 Resource Server WebFlux sample*].
====
== Dependencies
Most Resource Server support is collected into `spring-security-oauth2-resource-server`.
However, the support for decoding and verifying JWTs is in `spring-security-oauth2-jose`, meaning that both are necessary in order to have a working resource server that supports JWT-encoded Bearer Tokens.
[[webflux-oauth2-resource-server-minimal-configuration]]
== Minimal Configuration
When using https://spring.io/projects/spring-boot[Spring Boot], configuring an application as a resource server consists of two basic steps.
First, include the needed dependencies and second, indicate the location of the authorization server.
=== Specify the Authorization Server
In a Spring Boot application, to specify which authorization server to use, simply do:
[source,yml]
----
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://idp.example.com
----
Where `https://idp.example.com` is the value contained in the `iss` claim for JWT tokens that the authorization server will issue.
Resource Server will use this property to further self-configure, discover the authorization server's public keys, and subsequently validate incoming JWTs.
[NOTE]
To use the `issuer-uri` property, it must also be true that `https://idp.example.com/.well-known/openid-configuration` is a supported endpoint for the authorization server.
This endpoint is referred to as a https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig[Provider Configuration] endpoint.
And that's it!
=== Startup Expectations
When this property and these dependencies are used, Resource Server will automatically configure itself to validate JWT-encoded Bearer Tokens.
It achieves this through a deterministic startup process:
1. Hit the Provider Configuration endpoint, `https://the.issuer.location/.well-known/openid-configuration`, processing the response for the `jwks_url` property
2. Configure the validation strategy to query `jwks_url` for valid public keys
3. Configure the validation strategy to validate each JWTs `iss` claim against `https://idp.example.com`.
A consequence of this process is that the authorization server must be up and receiving requests in order for Resource Server to successfully start up.
[NOTE]
If the authorization server is down when Resource Server queries it (given appropriate timeouts), then startup will fail.
=== Runtime Expectations
Once the application is started up, Resource Server will attempt to process any request containing an `Authorization: Bearer` header:
[source,html]
----
GET / HTTP/1.1
Authorization: Bearer some-token-value # Resource Server will process this
----
So long as this scheme is indicated, Resource Server will attempt to process the request according to the Bearer Token specification.
Given a well-formed JWT token, Resource Server will:
1. Validate its signature against a public key obtained from the `jwks_url` endpoint during startup and matched against the JWTs header
2. Validate the JWTs `exp` and `nbf` timestamps and the JWTs `iss` claim, and
3. Map each scope to an authority with the prefix `SCOPE_`.
[NOTE]
As the authorization server makes available new keys, Spring Security will automatically rotate the keys used to validate the JWT tokens.
The resulting `Authentication#getPrincipal`, by default, is a Spring Security `Jwt` object, and `Authentication#getName` maps to the JWT's `sub` property, if one is present.
<<webflux-oauth2-resource-server-jwkseturi,How to Configure without Tying Resource Server startup to an authorization server's availability>>
<<webflux-oauth2-resource-server-sans-boot,How to Configure without Spring Boot>>
[[webflux-oauth2-resource-server-jwkseturi]]
=== Specifying the Authorization Server JWK Set Uri Directly
If the authorization server doesn't support the Provider Configuration endpoint, or if Resource Server must be able to start up independently from the authorization server, then `issuer-uri` can be exchanged for `jwk-set-uri`:
[source,yaml]
----
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: https://idp.example.com/.well-known/jwks.json
----
[NOTE]
The JWK Set uri is not standardized, but can typically be found in the authorization server's documentation
Consequently, Resource Server will not ping the authorization server at startup.
However, it will also no longer validate the `iss` claim in the JWT (since Resource Server no longer knows what the issuer value should be).
[NOTE]
This property can also be supplied directly on the <<webflux-oauth2-resource-server-jwkseturi-dsl,DSL>>.
[[webflux-oauth2-resource-server-sans-boot]]
=== Overriding or Replacing Boot Auto Configuration
There are two `@Bean` s that Spring Boot generates on Resource Server's behalf.
The first is a `SecurityWebFilterChain` that configures the app as a resource server:
[source,java]
----
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange()
.anyExchange().authenticated()
.and()
.oauth2ResourceServer()
.jwt();
return http.build();
}
----
If the application doesn't expose a `SecurityWebFilterChain` bean, then Spring Boot will expose the above default one.
Replacing this is as simple as exposing the bean within the application:
[source,java]
----
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange()
.pathMatchers("/message/**").hasAuthority("SCOPE_message:read")
.anyExchange().authenticated()
.and()
.oauth2ResourceServer()
.jwt();
return http.build();
}
----
The above requires the scope of `message:read` for any URL that starts with `/messages/`.
Methods on the `oauth2ResourceServer` DSL will also override or replace auto configuration.
For example, the second `@Bean` Spring Boot creates is a `ReactiveJwtDecoder`, which decodes `String` tokens into validated instances of `Jwt`:
[source,java]
----
@Bean
public ReactiveJwtDecoder jwtDecoder() {
return ReactiveJwtDecoders.fromOidcIssuerLocation(issuerUri);
}
----
If the application doesn't expose a `ReactiveJwtDecoder` bean, then Spring Boot will expose the above default one.
And its configuration can be overridden using `jwkSetUri()` or replaced using `decoder()`.
[[webflux-oauth2-resource-server-jwkseturi-dsl]]
==== Using `jwkSetUri()`
An authorization server's JWK Set Uri can be configured <<webflux-oauth2-resource-server-jwkseturi,as a configuration property>> or it can be supplied in the DSL:
[source,java]
----
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange()
.anyExchange().authenticated()
.and()
.oauth2ResourceServer()
.jwt()
.jwkSetUri("https://idp.example.com/.well-known/jwks.json");
return http.build();
}
----
Using `jwkSetUri()` takes precedence over any configuration property.
[[webflux-oauth2-resource-server-decoder-dsl]]
==== Using `decoder()`
More powerful than `jwkSetUri()` is `decoder()`, which will completely replace any Boot auto configuration of `JwtDecoder`:
[source,java]
----
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange()
.anyExchange().authenticated()
.and()
.oauth2ResourceServer()
.jwt()
.decoder(myCustomDecoder());
return http.build();
}
----
This is handy when deeper configuration, like <<webflux-oauth2-resource-server-validation,validation>>, is necessary.
[[webflux-oauth2-resource-server-decoder-bean]]
==== Exposing a `ReactiveJwtDecoder` `@Bean`
Or, exposing a `ReactiveJwtDecoder` `@Bean` has the same effect as `decoder()`:
[source,java]
----
@Bean
public JwtDecoder jwtDecoder() {
return new NimbusReactiveJwtDecoder(jwkSetUri);
}
----
[[webflux-oauth2-resource-server-authorization]]
=== Configuring Authorization
A JWT that is issued from an OAuth 2.0 Authorization Server will typically either have a `scope` or `scp` attribute, indicating the scopes (or authorities) it's been granted, for example:
`{ ..., "scope" : "messages contacts"}`
When this is the case, Resource Server will attempt to coerce these scopes into a list of granted authorities, prefixing each scope with the string "SCOPE_".
This means that to protect an endpoint or method with a scope derived from a JWT, the corresponding expressions should include this prefix:
[source,java]
----
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange()
.mvcMatchers("/contacts/**").hasAuthority("SCOPE_contacts")
.mvcMatchers("/messages/**").hasAuthority("SCOPE_messages")
.anyExchange().authenticated()
.and()
.oauth2ResourceServer()
.jwt();
return http.build();
}
----
Or similarly with method security:
[source,java]
----
@PreAuthorize("hasAuthority('SCOPE_messages')")
public List<Message> getMessages(...) {}
----
[[webflux-oauth2-resource-server-authorization-extraction]]
==== Extracting Authorities Manually
However, there are a number of circumstances where this default is insufficient.
For example, some authorization servers don't use the `scope` attribute, but instead have their own custom attribute.
Or, at other times, the resource server may need to adapt the attribute or a composition of attributes into internalized authorities.
To this end, the DSL exposes `jwtAuthenticationConverter()`:
[source,java]
----
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange()
.anyExchange().authenticated()
.and()
.oauth2ResourceServer()
.jwt()
.jwtAuthenticationConverter(grantedAuthoritiesExtractor());
return http.build();
}
Converter<Jwt, Mono<AbstractAuthenticationToken>> grantedAuthoritiesExtractor() {
GrantedAuthoritiesExtractor extractor = new GrantedAuthoritiesExtractor();
return new ReactiveJwtAuthenticationConverterAdapter(extractor);
}
----
which is responsible for converting a `Jwt` into an `Authentication`.
We can override this quite simply to alter the way granted authorities are derived:
[source,java]
----
static class GrantedAuthoritiesExtractor extends JwtAuthenticationConverter {
protected Collection<GrantedAuthorities> extractAuthorities(Jwt jwt) {
Collection<String> authorities = (Collection<String>)
jwt.getClaims().get("mycustomclaim");
return authorities.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
}
}
----
For more flexibility, the DSL supports entirely replacing the converter with any class that implements `Converter<Jwt, Mono<AbstractAuthenticationToken>>`:
[source,java]
----
static class CustomAuthenticationConverter implements Converter<Jwt, Mono<AbstractAuthenticationToken>> {
public AbstractAuthenticationToken convert(Jwt jwt) {
return Mono.just(jwt).map(this::doConversion);
}
}
----
[[webflux-oauth2-resource-server-validation]]
=== Configuring Validation
Using <<webflux-oauth2-resource-server-minimal-configuration,minimal Spring Boot configuration>>, indicating the authorization server's issuer uri, Resource Server will default to verifying the `iss` claim as well as the `exp` and `nbf` timestamp claims.
In circumstances where validation needs to be customized, Resource Server ships with two standard validators and also accepts custom `OAuth2TokenValidator` instances.
[[webflux-oauth2-resource-server-validation-clockskew]]
==== Customizing Timestamp Validation
JWT's typically have a window of validity, with the start of the window indicated in the `nbf` claim and the end indicated in the `exp` claim.
However, every server can experience clock drift, which can cause tokens to appear expired to one server, but not to another.
This can cause some implementation heartburn as the number of collaborating servers increases in a distributed system.
Resource Server uses `JwtTimestampValidator` to verify a token's validity window, and it can be configured with a `clockSkew` to alleviate the above problem:
[source,java]
----
@Bean
ReactiveJwtDecoder jwtDecoder() {
NimbusReactiveJwtDecoder jwtDecoder = (NimbusReactiveJwtDecoder)
ReactiveJwtDecoders.withOidcIssuerLocation(issuerUri);
OAuth2TokenValidator<Jwt> withClockSkew = new DelegatingOAuth2TokenValidator<>(
new JwtTimestampValidator(Duration.ofSeconds(60)),
new IssuerValidator(issuerUri));
jwtDecoder.setJwtValidator(withClockSkew);
return jwtDecoder;
}
----
[NOTE]
By default, Resource Server configures a clock skew of 30 seconds.
[[webflux-oauth2-resource-server-validation-custom]]
==== Configuring a Custom Validator
Adding a check for the `aud` claim is simple with the `OAuth2TokenValidator` API:
[source,java]
----
public class AudienceValidator implements OAuth2TokenValidator<Jwt> {
OAuth2Error error = new OAuth2Error("invalid_token", "The required audience is missing", null);
public OAuth2TokenValidatorResult validate(Jwt jwt) {
if (jwt.getAudience().contains("messaging")) {
return OAuth2TokenValidatorResult.success();
} else {
return OAuth2TokenValidatorResult.failure(error);
}
}
}
----
Then, to add into a resource server, it's a matter of specifying the `ReactiveJwtDecoder` instance:
[source,java]
----
@Bean
ReactiveJwtDecoder jwtDecoder() {
NimbusReactiveJwtDecoder jwtDecoder = (NimbusReactiveJwtDecoder)
ReactiveJwtDecoders.withOidcIssuerLocation(issuerUri);
OAuth2TokenValidator<Jwt> audienceValidator = new AudienceValidator();
OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer(issuerUri);
OAuth2TokenValidator<Jwt> withAudience = new DelegatingOAuth2TokenValidator<>(withIssuer, audienceValidator);
jwtDecoder.setJwtValidator(withAudience);
return jwtDecoder;
}
----
@@ -0,0 +1,32 @@
[[webflux-redirect-https]]
= Redirect to HTTPS
HTTPS is required to provide a secure application.
Spring Security can be configured to perform a redirect to https using the following Java Configuration:
[source,java]
----
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
// ...
.redirectToHttps();
return http.build();
}
----
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:
[source,java]
----
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
// ...
.redirectToHttps()
.httpsRedirectWhen(e -> e.getRequest().getHeaders().containsKey("X-Forwarded-Proto"));
return http.build();
}
----
@@ -0,0 +1,39 @@
[[webflux-roac]]
= @RegisteredOAuth2AuthorizedClient
Spring Security allows resolving an access token using `@RegisteredOAuth2AuthorizedClient`.
[NOTE]
====
A working example can be found in {gh-samples-url}/boot/oauth2webclient-webflux[*OAuth 2.0 WebClient WebFlux sample*].
====
After configuring Spring Security for <<webflux-oauth2-login,OAuth2 Login>> or as an <<webflux-oauth2-client,OAuth2 Client>>, an `OAuth2AuthorizedClient` can be resolved using the following:
[source,java]
----
@GetMapping("/explicit")
Mono<String> explicit(@RegisteredOAuth2AuthorizedClient("client-id") OAuth2AuthorizedClient authorizedClient) {
// ...
}
----
This integrates into Spring Security to provide the following features:
* Spring Security will automatically refresh expired tokens (if a refresh token is present)
* If an access token is requested and not present, Spring Security will automatically request the access token.
** For `authorization_code` this involves performing the redirect and then replaying the original request
** For `client_credentials` the token is simply requested and saved
If the user authenticated using `oauth2Login()`, then the `client-id` is optional.
For example, the following would work:
[source,java]
----
@GetMapping("/implicit")
Mono<String> implicit(@RegisteredOAuth2AuthorizedClient OAuth2AuthorizedClient authorizedClient) {
// ...
}
----
This is convenient if the user always authenticates with OAuth2 Login and an access token from the same authorization server is needed.
@@ -1,8 +1,8 @@
[[test-webflux]]
== WebFlux Support
= Reactive Test Support
[[test-erms]]
=== Reactive Method Security
== Testing Reactive Method Security
For example, we can test our example from <<jc-erms>> using the same setup and annotations we did in <<test-method>>.
Here is a minimal sample of what we can do:
@@ -41,7 +41,7 @@ public class HelloWorldMessageServiceTests {
----
[[test-webtestclient]]
=== WebTestClientSupport
== WebTestClientSupport
Spring Security provides integration with `WebTestClient`.
The basic setup looks like this:
@@ -70,7 +70,7 @@ public class HelloWebfluxMethodApplicationTests {
}
----
==== Authentication
=== Authentication
After applying the Spring Security support to `WebTestClient` we can use either annotations or `mutateWith` support.
For example:
@@ -134,7 +134,7 @@ public void messageWhenMutateWithMockAdminThenOk() throws Exception {
----
==== CSRF Support
=== CSRF Support
Spring Security also provides support for CSRF testing with `WebTestClient`.
For example:
@@ -0,0 +1,100 @@
= WebClient
[NOTE]
====
The following documentation is for use within Reactive environments.
For Servlet environments, refer to <<servlet-webclient, WebClient for Servlet>> environments.
====
Spring Framework has built in support for setting a Bearer token.
[source,java]
----
webClient.get()
.headers(h -> h.setBearerAuth(token))
...
----
Spring Security builds on this support to provide additional benefits:
* Spring Security will automatically refresh expired tokens (if a refresh token is present)
* If an access token is requested and not present, Spring Security will automatically request the access token.
** For authorization_code this involves performing the redirect and then replaying the original request
** For client_credentials the token is simply requested and saved
* Support for the ability to transparently include the current OAuth token or explicitly select which token should be used.
[[webclient-setup]]
== WebClient OAuth2 Setup
The first step is ensuring to setup the `WebClient` correctly.
An example of setting up `WebClient` in a fully reactive environment can be found below:
[source,java]
----
@Bean
WebClient webClient(ReactiveClientRegistrationRepository clientRegistrations,
ServerOAuth2AuthorizedClientRepository authorizedClients) {
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth =
new ServerOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrations, authorizedClients);
// (optional) explicitly opt into using the oauth2Login to provide an access token implicitly
// oauth.setDefaultOAuth2AuthorizedClient(true);
// (optional) set a default ClientRegistration.registrationId
// oauth.setDefaultClientRegistrationId("client-registration-id");
return WebClient.builder()
.filter(oauth)
.build();
}
----
[[webclient-implicit]]
== Implicit OAuth2AuthorizedClient
If we set `defaultOAuth2AuthorizedClient` to `true`in our setup and the user authenticated with oauth2Login (i.e. OIDC), then the current authentication is used to automatically provide the access token.
Alternatively, if we set `defaultClientRegistrationId` to a valid `ClientRegistration` id, that registration is used to provide the access token.
This is convenient, but in environments where not all endpoints should get the access token, it is dangerous (you might provide the wrong access token to an endpoint).
[source,java]
----
Mono<String> body = this.webClient
.get()
.uri(this.uri)
.retrieve()
.bodyToMono(String.class);
----
[[webclient-explicit]]
== Explicit OAuth2AuthorizedClient
The `OAuth2AuthorizedClient` can be explicitly provided by setting it on the requests attributes.
In the example below we resolve the `OAuth2AuthorizedClient` using Spring WebFlux or Spring MVC argument resolver support.
However, it does not matter how the `OAuth2AuthorizedClient` is resolved.
[source,java]
----
@GetMapping("/explicit")
Mono<String> explicit(@RegisteredOAuth2AuthorizedClient("client-id") OAuth2AuthorizedClient authorizedClient) {
return this.webClient
.get()
.uri(this.uri)
.attributes(oauth2AuthorizedClient(authorizedClient))
.retrieve()
.bodyToMono(String.class);
}
----
[[webclient-clientregistrationid]]
== clientRegistrationId
Alternatively, it is possible to specify the `clientRegistrationId` on the request attributes and the `WebClient` will attempt to lookup the `OAuth2AuthorizedClient`.
If it is not found, one will automatically be acquired.
[source,java]
----
Mono<String> body = this.webClient
.get()
.uri(this.uri)
.attributes(clientRegistrationId("client-id"))
.retrieve()
.bodyToMono(String.class);
----
@@ -0,0 +1,68 @@
[[jc-webflux]]
= WebFlux Security
Spring Security's WebFlux support relies on a `WebFilter` and works the same for Spring WebFlux and Spring WebFlux.Fn.
You can find a few sample applications that demonstrate the code below:
* Hello WebFlux {gh-samples-url}/javaconfig/hellowebflux[hellowebflux]
* Hello WebFlux.Fn {gh-samples-url}/javaconfig/hellowebfluxfn[hellowebfluxfn]
* Hello WebFlux Method {gh-samples-url}/javaconfig/hellowebflux-method[hellowebflux-method]
== Minimal WebFlux Security Configuration
You can find a minimal WebFlux Security configuration below:
[source,java]
-----
@EnableWebFluxSecurity
public class HelloWebfluxSecurityConfig {
@Bean
public MapReactiveUserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("user")
.roles("USER")
.build();
return new MapReactiveUserDetailsService(user);
}
}
-----
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.
== Explicit WebFlux Security Configuration
You can find an explicit version of the minimal WebFlux Security configuration below:
[source,java]
-----
@EnableWebFluxSecurity
public class HelloWebfluxSecurityConfig {
@Bean
public MapReactiveUserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("user")
.roles("USER")
.build();
return new MapReactiveUserDetailsService(user);
}
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange()
.anyExchange().authenticated()
.and()
.httpBasic().and()
.formLogin();
return http.build();
}
}
-----
This configuration explicitly sets up all the same things as our minimal configuration.
From here you can easily make the changes to the defaults.
@@ -11,6 +11,8 @@ include::ldap.adoc[]
include::oauth2.adoc[]
include::webclient.adoc[]
include::jsp-taglibs.adoc[]
include::jaas.adoc[]
@@ -1,13 +1,10 @@
[[oauth2login-advanced]]
== OAuth 2.0 Login -- Advanced Configuration
`HttpSecurity.oauth2Login()` provides a number of configuration options for customizing OAuth 2.0 Login.
The main configuration options are grouped into their protocol endpoint counterparts.
For example, `oauth2Login().authorizationEndpoint()` allows configuring the _Authorization Endpoint_,
whereas `oauth2Login().tokenEndpoint()` allows configuring the _Token Endpoint_.
For example, `oauth2Login().authorizationEndpoint()` allows configuring the _Authorization Endpoint_, whereas `oauth2Login().tokenEndpoint()` allows configuring the _Token Endpoint_.
The following code shows an example:
@@ -43,14 +40,12 @@ The authorization process utilizes two authorization server endpoints (HTTP reso
As well as one client endpoint:
* Redirection Endpoint: Used by the authorization server to return responses
containing authorization credentials to the client via the resource owner user-agent.
* Redirection Endpoint: Used by the authorization server to return responses containing authorization credentials to the client via the resource owner user-agent.
The OpenID Connect Core 1.0 specification defines the http://openid.net/specs/openid-connect-core-1_0.html#UserInfo[UserInfo Endpoint] as follows:
The UserInfo Endpoint is an OAuth 2.0 Protected Resource that returns claims about the authenticated end-user.
To obtain the requested claims about the end-user, the client makes a request to the UserInfo Endpoint
by using an access token obtained through OpenID Connect Authentication.
To obtain the requested claims about the end-user, the client makes a request to the UserInfo Endpoint by using an access token obtained through OpenID Connect Authentication.
These claims are normally represented by a JSON object that contains a collection of name-value pairs for the claims.
The following code shows the complete configuration options available for the `oauth2Login()` DSL:
@@ -65,11 +60,13 @@ public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {
http
.oauth2Login()
.clientRegistrationRepository(this.clientRegistrationRepository())
.authorizedClientRepository(this.authorizedClientRepository())
.authorizedClientService(this.authorizedClientService())
.loginPage("/login")
.authorizationEndpoint()
.baseUri(this.authorizationRequestBaseUri())
.authorizationRequestRepository(this.authorizationRequestRepository())
.authorizationRequestResolver(this.authorizationRequestResolver())
.and()
.redirectionEndpoint()
.baseUri(this.authorizationResponseBaseUri())
@@ -86,12 +83,10 @@ public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {
}
----
The sections to follow go into more detail on each of the configuration options available:
The following sections go into more detail on each of the configuration options available:
* <<oauth2login-advanced-login-page>>
* <<oauth2login-advanced-authorization-endpoint>>
* <<oauth2login-advanced-redirection-endpoint>>
* <<oauth2login-advanced-token-endpoint>>
* <<oauth2login-advanced-userinfo-endpoint>>
@@ -99,8 +94,7 @@ The sections to follow go into more detail on each of the configuration options
=== OAuth 2.0 Login Page
By default, the OAuth 2.0 Login Page is auto-generated by the `DefaultLoginPageGeneratingFilter`.
The default login page shows each configured OAuth Client with its `ClientRegistration.clientName`
as a link, which is capable of initiating the Authorization Request (or OAuth 2.0 Login).
The default login page shows each configured OAuth Client with its `ClientRegistration.clientName` as a link, which is capable of initiating the Authorization Request (or OAuth 2.0 Login).
The link's destination for each OAuth Client defaults to the following:
@@ -113,8 +107,7 @@ The following line shows an example:
<a href="/oauth2/authorization/google">Google</a>
----
To override the default login page,
configure `oauth2Login().loginPage()` and (optionally) `oauth2Login().authorizationEndpoint().baseUri()`.
To override the default login page, configure `oauth2Login().loginPage()` and (optionally) `oauth2Login().authorizationEndpoint().baseUri()`.
The following listing shows an example:
@@ -152,52 +145,11 @@ The following line shows an example:
----
====
[[oauth2login-advanced-authorization-endpoint]]
=== Authorization Endpoint
[[oauth2login-advanced-authorization-request-repository]]
==== `AuthorizationRequestRepository`
`AuthorizationRequestRepository` is responsible for the persistence of the `OAuth2AuthorizationRequest`
from the time the Authorization Request is initiated to the time the Authorization Response
is received (the callback).
[TIP]
The `OAuth2AuthorizationRequest` is used to correlate and validate the Authorization Response.
The default implementation of `AuthorizationRequestRepository` is `HttpSessionOAuth2AuthorizationRequestRepository`,
which stores the `OAuth2AuthorizationRequest` in the `HttpSession`.
If you would like to provide a custom implementation of `AuthorizationRequestRepository`
that stores the attributes of `OAuth2AuthorizationRequest` in a `Cookie`,
configure it as shown in the following example:
[source,java]
----
@EnableWebSecurity
public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.oauth2Login()
.authorizationEndpoint()
.authorizationRequestRepository(this.cookieAuthorizationRequestRepository())
...
}
private AuthorizationRequestRepository<OAuth2AuthorizationRequest> cookieAuthorizationRequestRepository() {
return new HttpCookieOAuth2AuthorizationRequestRepository();
}
}
----
[[oauth2login-advanced-redirection-endpoint]]
=== Redirection Endpoint
The Redirection Endpoint is used by the Authorization Server for returning the Authorization Response
(which contains the authorization credentials) to the client via the Resource Owner user-agent.
The Redirection Endpoint is used by the Authorization Server for returning the Authorization Response (which contains the authorization credentials) to the client via the Resource Owner user-agent.
[TIP]
OAuth 2.0 Login leverages the Authorization Code Grant.
@@ -239,74 +191,34 @@ return CommonOAuth2Provider.GOOGLE.getBuilder("google")
----
====
[[oauth2login-advanced-token-endpoint]]
=== Token Endpoint
[[oauth2login-advanced-token-client]]
==== OAuth2AccessTokenResponseClient
`OAuth2AccessTokenResponseClient` is responsible for exchanging an authorization grant credential
for an access token credential at the Authorization Server's Token Endpoint.
The default implementation of `OAuth2AccessTokenResponseClient` is `NimbusAuthorizationCodeTokenResponseClient`,
which exchanges an authorization code for an access token at the Token Endpoint.
[NOTE]
`NimbusAuthorizationCodeTokenResponseClient` uses the https://connect2id.com/products/nimbus-oauth-openid-connect-sdk[Nimbus OAuth 2.0 SDK] internally.
If you would like to provide a custom implementation of `OAuth2AccessTokenResponseClient`
that uses Spring Framework 5 reactive `WebClient` for initiating requests to the Token Endpoint,
configure it as shown in the following example:
[source,java]
----
@EnableWebSecurity
public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.oauth2Login()
.tokenEndpoint()
.accessTokenResponseClient(this.accessTokenResponseClient())
...
}
private OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient() {
return new SpringWebClientAuthorizationCodeTokenResponseClient();
}
}
----
[[oauth2login-advanced-userinfo-endpoint]]
=== UserInfo Endpoint
The UserInfo Endpoint includes a number of configuration options, as described in the following sub-sections:
* <<oauth2login-advanced-map-authorities>>
* <<oauth2login-advanced-custom-user>>
* <<oauth2login-advanced-oauth2-user-service>>
* <<oauth2login-advanced-oidc-user-service>>
* <<oauth2login-advanced-map-authorities, Mapping User Authorities>>
* <<oauth2login-advanced-custom-user, Configuring a Custom OAuth2User>>
* <<oauth2login-advanced-oauth2-user-service, OAuth 2.0 UserService>>
* <<oauth2login-advanced-oidc-user-service, OpenID Connect 1.0 UserService>>
[[oauth2login-advanced-map-authorities]]
==== Mapping User Authorities
After the user successfully authenticates with the OAuth 2.0 Provider,
the `OAuth2User.getAuthorities()` (or `OidcUser.getAuthorities()`) may be mapped to a new set of `GrantedAuthority` instances,
which will be supplied to `OAuth2AuthenticationToken` when completing the authentication.
After the user successfully authenticates with the OAuth 2.0 Provider, the `OAuth2User.getAuthorities()` (or `OidcUser.getAuthorities()`) may be mapped to a new set of `GrantedAuthority` instances, which will be supplied to `OAuth2AuthenticationToken` when completing the authentication.
[TIP]
`OAuth2AuthenticationToken.getAuthorities()` is used for authorizing requests, such as in `hasRole('USER')` or `hasRole('ADMIN')`.
There are a couple of options to choose from when mapping user authorities:
* <<oauth2login-advanced-map-authorities-grantedauthoritiesmapper,Using a `GrantedAuthoritiesMapper`>>
* <<oauth2login-advanced-map-authorities-oauth2userservice,Delegation-based strategy with `OAuth2UserService`>>
* <<oauth2login-advanced-map-authorities-grantedauthoritiesmapper, Using a GrantedAuthoritiesMapper>>
* <<oauth2login-advanced-map-authorities-oauth2userservice, Delegation-based strategy with OAuth2UserService>>
[[oauth2login-advanced-map-authorities-grantedauthoritiesmapper]]
===== Using a `GrantedAuthoritiesMapper`
===== Using a GrantedAuthoritiesMapper
Provide an implementation of `GrantedAuthoritiesMapper` and configure it as shown in the following example:
@@ -374,16 +286,13 @@ public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {
}
----
[[oauth2login-advanced-map-authorities-oauth2userservice]]
===== Delegation-based strategy with `OAuth2UserService`
===== Delegation-based strategy with OAuth2UserService
This strategy is advanced compared to using a `GrantedAuthoritiesMapper`, however, it's also more flexible
as it gives you access to the `OAuth2UserRequest` and `OAuth2User` (when using an OAuth 2.0 UserService)
or `OidcUserRequest` and `OidcUser` (when using an OpenID Connect 1.0 UserService).
This strategy is advanced compared to using a `GrantedAuthoritiesMapper`, however, it's also more flexible as it gives you access to the `OAuth2UserRequest` and `OAuth2User` (when using an OAuth 2.0 UserService) or `OidcUserRequest` and `OidcUser` (when using an OpenID Connect 1.0 UserService).
The `OAuth2UserRequest` (and `OidcUserRequest`) provides you access to the associated `OAuth2AccessToken`,
which is very useful in the cases where the _delegator_ needs to fetch authority information
from a protected resource before it can map the custom authorities for the user.
The `OAuth2UserRequest` (and `OidcUserRequest`) provides you access to the associated `OAuth2AccessToken`, which is very useful in the cases where the _delegator_ needs to fetch authority information from a protected resource before it can map the custom authorities for the user.
The following example shows how to implement and configure a delegation-based strategy using an OpenID Connect 1.0 UserService:
@@ -424,14 +333,13 @@ public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {
}
----
[[oauth2login-advanced-custom-user]]
==== Configuring a Custom OAuth2User
`CustomUserTypesOAuth2UserService` is an implementation of an `OAuth2UserService`
that provides support for custom `OAuth2User` types.
`CustomUserTypesOAuth2UserService` is an implementation of an `OAuth2UserService` that provides support for custom `OAuth2User` types.
If the default implementation (`DefaultOAuth2User`) does not suit your needs,
you can define your own implementation of `OAuth2User`.
If the default implementation (`DefaultOAuth2User`) does not suit your needs, you can define your own implementation of `OAuth2User`.
The following code demonstrates how you would register a custom `OAuth2User` type for GitHub:
@@ -521,22 +429,33 @@ public class GitHubOAuth2User implements OAuth2User {
For detailed information returned from the UserInfo Endpoint, see the API documentation
for https://developer.github.com/v3/users/#get-the-authenticated-user["Get the authenticated user"].
[[oauth2login-advanced-oauth2-user-service]]
==== OAuth 2.0 UserService
`DefaultOAuth2UserService` is an implementation of an `OAuth2UserService`
that supports standard OAuth 2.0 Provider's.
`DefaultOAuth2UserService` is an implementation of an `OAuth2UserService` that supports standard OAuth 2.0 Provider's.
[NOTE]
`OAuth2UserService` obtains the user attributes
of the end-user (the resource owner) from the UserInfo Endpoint (by using the
access token granted to the client during the authorization flow)
and returns an `AuthenticatedPrincipal` in the form of an `OAuth2User`.
`OAuth2UserService` obtains the user attributes of the end-user (the resource owner) from the UserInfo Endpoint (by using the access token granted to the client during the authorization flow) and returns an `AuthenticatedPrincipal` in the form of an `OAuth2User`.
If the default implementation does not suit your needs, you can define your own implementation of `OAuth2UserService`
for standard OAuth 2.0 Provider's.
`DefaultOAuth2UserService` uses a `RestOperations` when requesting the user attributes at the UserInfo Endpoint.
The following configuration demonstrates how to configure a custom `OAuth2UserService`:
If you need to customize the pre-processing of the UserInfo Request, you can provide `DefaultOAuth2UserService.setRequestEntityConverter()` with a custom `Converter<OAuth2UserRequest, RequestEntity<?>>`.
The default implementation `OAuth2UserRequestEntityConverter` builds a `RequestEntity` representation of a UserInfo Request that sets the `OAuth2AccessToken` in the `Authorization` header by default.
On the other end, if you need to customize the post-handling of the UserInfo Response, you will need to provide `DefaultOAuth2UserService.setRestOperations()` with a custom configured `RestOperations`.
The default `RestOperations` is configured as follows:
[source,java]
----
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
----
`OAuth2ErrorResponseErrorHandler` is a `ResponseErrorHandler` that can handle an OAuth 2.0 Error (400 Bad Request).
It uses an `OAuth2ErrorHttpMessageConverter` for converting the OAuth 2.0 Error parameters to an `OAuth2Error`.
Whether you customize `DefaultOAuth2UserService` or provide your own implementation of `OAuth2UserService`, you'll need to configure it as shown in the following example:
[source,java]
----
@@ -553,27 +472,22 @@ public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {
}
private OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService() {
return new CustomOAuth2UserService();
...
}
}
----
[[oauth2login-advanced-oidc-user-service]]
==== OpenID Connect 1.0 UserService
`OidcUserService` is an implementation of an `OAuth2UserService`
that supports OpenID Connect 1.0 Provider's.
`OidcUserService` is an implementation of an `OAuth2UserService` that supports OpenID Connect 1.0 Provider's.
[NOTE]
`OAuth2UserService` is responsible for obtaining the user attributes
of the end user (the resource owner) from the UserInfo Endpoint (by using the
access token granted to the client during the authorization flow)
and return an `AuthenticatedPrincipal` in the form of an `OidcUser`.
The `OidcUserService` leverages the `DefaultOAuth2UserService` when requesting the user attributes at the UserInfo Endpoint.
If the default implementation does not suit your needs, you can define your own implementation of `OAuth2UserService`
for OpenID Connect 1.0 Provider's.
If you need to customize the pre-processing of the UserInfo Request and/or the post-handling of the UserInfo Response, you will need to provide `OidcUserService.setOauth2UserService()` with a custom configured `DefaultOAuth2UserService`.
The following configuration demonstrates how to configure a custom OpenID Connect 1.0 `OAuth2UserService`:
Whether you customize `OidcUserService` or provide your own implementation of `OAuth2UserService` for OpenID Connect 1.0 Provider's, you'll need to configure it as shown in the following example:
[source,java]
----
@@ -590,7 +504,7 @@ public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {
}
private OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
return new CustomOidcUserService();
...
}
}
----
@@ -0,0 +1,102 @@
[[servlet-webclient]]
= WebClient for Servlet Environments
[NOTE]
====
The following documentation is for use within Servlet environments.
For all other environments, refer to <<webclient, WebClient for Reactive>> environments.
====
Spring Framework has built in support for setting a Bearer token.
[source,java]
----
webClient.get()
.headers(h -> h.setBearerAuth(token))
...
----
Spring Security builds on this support to provide additional benefits:
* Spring Security will automatically refresh expired tokens (if a refresh token is present)
* If an access token is requested and not present, Spring Security will automatically request the access token.
** For authorization_code this involves performing the redirect and then replaying the original request
** For client_credentials the token is simply requested and saved
* Support for the ability to transparently include the current OAuth token or explicitly select which token should be used.
[[servlet-webclient-setup]]
== WebClient OAuth2 Setup
The first step is ensuring to setup the `WebClient` correctly.
An example of setting up `WebClient` in a servlet environment can be found below:
[source,java]
----
@Bean
WebClient webClient(ClientRegistrationRepository clientRegistrations,
OAuth2AuthorizedClientRepository authorizedClients) {
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrations, authorizedClients);
// (optional) explicitly opt into using the oauth2Login to provide an access token implicitly
// oauth.setDefaultOAuth2AuthorizedClient(true);
// (optional) set a default ClientRegistration.registrationId
// oauth.setDefaultClientRegistrationId("client-registration-id");
return WebClient.builder()
.apply(oauth2.oauth2Configuration())
.build();
}
----
[[servlet-webclient-implicit]]
== Implicit OAuth2AuthorizedClient
If we set `defaultOAuth2AuthorizedClient` to `true` in our setup and the user authenticated with oauth2Login (i.e. OIDC), then the current authentication is used to automatically provide the access token.
Alternatively, if we set `defaultClientRegistrationId` to a valid `ClientRegistration` id, that registration is used to provide the access token.
This is convenient, but in environments where not all endpoints should get the access token, it is dangerous (you might provide the wrong access token to an endpoint).
[source,java]
----
Mono<String> body = this.webClient
.get()
.uri(this.uri)
.retrieve()
.bodyToMono(String.class);
----
[[servlet-webclient-explicit]]
== Explicit OAuth2AuthorizedClient
The `OAuth2AuthorizedClient` can be explicitly provided by setting it on the request attributes.
In the example below we resolve the `OAuth2AuthorizedClient` using Spring WebFlux or Spring MVC argument resolver support.
However, it does not matter how the `OAuth2AuthorizedClient` is resolved.
[source,java]
----
@GetMapping("/explicit")
Mono<String> explicit(@RegisteredOAuth2AuthorizedClient("client-id") OAuth2AuthorizedClient authorizedClient) {
return this.webClient
.get()
.uri(this.uri)
.attributes(oauth2AuthorizedClient(authorizedClient))
.retrieve()
.bodyToMono(String.class);
}
----
[[servlet-webclient-clientregistrationid]]
== clientRegistrationId
Alternatively, it is possible to specify the `clientRegistrationId` on the request attributes and the `WebClient` will attempt to lookup the `OAuth2AuthorizedClient`.
If it is not found, one will automatically be acquired.
[source,java]
----
Mono<String> body = this.webClient
.get()
.uri(this.uri)
.attributes(clientRegistrationId("client-id"))
.retrieve()
.bodyToMono(String.class);
----
@@ -4,7 +4,7 @@
Spring Security has added Jackson Support for persisting Spring Security related classes.
This can improve the performance of serializing Spring Security related classes when working with distributed sessions (i.e. session replication, Spring Session, etc).
To use it, register the `JacksonJacksonModules.getModules(ClassLoader)` as http://wiki.fasterxml.com/JacksonFeatureModules[Jackson Modules].
To use it, register the `SecurityJackson2Modules.getModules(ClassLoader)` as http://wiki.fasterxml.com/JacksonFeatureModules[Jackson Modules].
[source,java]
----
@@ -9,7 +9,7 @@ Now the recommended approach for method security is to use <<ns-method-security,
This way the method security infrastructure beans are configured automatically for you so you don't really need to know about the implementation classes.
We'll just provide a quick overview of the classes that are involved here.
Method security in enforced using a `MethodSecurityInterceptor`, which secures `MethodInvocation` s.
Method security is enforced using a `MethodSecurityInterceptor`, which secures `MethodInvocation` s.
Depending on the configuration approach, an interceptor may be specific to a single bean or shared between multiple beans.
The interceptor uses a `MethodSecurityMetadataSource` instance to obtain the configuration attributes that apply to a particular method invocation.
`MapBasedMethodSecurityMetadataSource` is used to store configuration attributes keyed by method names (which can be wildcarded) and will be used internally when the attributes are defined in the application context using the `<intercept-methods>` or `<protect-point>` elements.
@@ -0,0 +1,17 @@
= Servlet Applications
include::preface/index.adoc[leveloffset=+1]
include::architecture/index.adoc[leveloffset=+1]
include::test/index.adoc[leveloffset=+1]
include::web/index.adoc[leveloffset=+1]
include::authorization/index.adoc[leveloffset=+1]
include::additional-topics/index.adoc[leveloffset=+1]
include::data/index.adoc[leveloffset=+1]
include::appendix/index.adoc[leveloffset=+1]

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