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

Compare commits

...

183 Commits

Author SHA1 Message Date
Rob Winch a1c93b1cce Release 5.1.0.M1 2018-05-14 21:44:09 -05:00
Rob Winch 32e368d9b7 Single ClientRegistration redirects by default
Fixes: gh-5339
2018-05-14 16:38:13 -05:00
Rob Winch f29e4cf91f LoginPageGeneratingWebFilter conditionally renders formLogin
Issue: gh-4807
2018-05-14 16:38:13 -05:00
Rob Winch bc3467c118 Add oauth2login-webflux sample
Issue: gh-4807
2018-05-11 04:19:50 -05:00
Rob Winch 7013c6fd76 Add OAuth2LoginSpec
Issue: gh-4807
2018-05-11 04:19:50 -05:00
Rob Winch 23f4b9d3d1 Add OAuth2AuthorizationRequestRedirectWebFilter
Issue: gh-4807
2018-05-11 04:19:50 -05:00
Rob Winch de959dbff6 Add OAuth2ClientArgumentResolver
Issue: gh-4807
2018-05-11 04:19:50 -05:00
Rob Winch c1e9785a48 Add OAuth2LoginReactiveAuthenticationManager
Issue: gh-4807
2018-05-11 04:19:50 -05:00
Rob Winch 7401cb2b51 Add ServerOAuth2LoginAuthenticationTokenConverter
Issue: gh-4807
2018-05-11 04:19:50 -05:00
Rob Winch 3cd2ddf793 Add NimbusReactiveAuthorizationCodeTokenResponseClient
Issue: gh-4807
2018-05-11 04:19:50 -05:00
Rob Winch 3220e9560a Add DefaultReactiveOAuth2UserService
Issue: gh-4807
2018-05-11 04:19:50 -05:00
Rob Winch b613b2d253 Add WebSessionOAuth2ReactiveAuthorizationRequestRepository
Issue: gh-4807
2018-05-11 04:19:50 -05:00
Rob Winch 5e9c714ff0 Add InMemoryReactiveOAuth2AuthorizedClientService
Issue: gh-4807
2018-05-11 04:19:50 -05:00
Rob Winch a02b0c17f8 Add InMemoryReactiveClientRegistrationRepository
Issue: gh-4807
2018-05-11 04:19:50 -05:00
Rob Winch ca9cd20832 Add DelegatingServerAuthenticationSuccessHandler
Fixes: gh-5332
2018-05-11 04:19:50 -05:00
Rob Winch d874c4954e AuthenticationWebFilter handle empty Authentication
Fixes: gh-5333
2018-05-11 04:19:50 -05:00
Rob Winch c696640276 OAuth2AuthorizationResponseUtils uses MultiMap
Fixes: gh-5331
2018-05-11 04:19:50 -05:00
Rob Winch 8fa6dd0f5b Revert "Fix SecuredAnnotationSecurityMetadataSourceTests -> Related SPR-16677"
This reverts commit d4e459874a.
2018-05-11 04:19:50 -05:00
Rob Winch e78457d3a1 Fix checkstyle for CsrfServerLogoutHandlerTests
Issue: gh-4840
2018-05-11 04:16:48 -05:00
Eric Deandrea 26f53a20b3 Add CsrfServerLogoutHandler
Create a CsrfServerLogoutHandler which invalidates the current CsrfToken

Fixes gh-4840
2018-05-11 04:16:48 -05:00
Eric Deandrea 21750242cf Add HttpStatusReturningServerLogoutSuccessHandler
An HttpStatusReturningServerLogoutSuccessHandler is missing on the
reactive side - essentially the reactive equivalent of
HttpStatusReturningLogoutSuccessHandler.

Fixes gh-5081
2018-05-11 04:03:21 -05:00
Eric Deandrea bc9f8ec430 Add HttpStatusServerEntryPoint
An HttpStatusServerEntryPoint is missing on the
reactive side - essentially the reactive equivalent of
HttpStatusEntryPoint.

Fixes gh-5082
2018-05-11 04:00:49 -05:00
Rob Winch 53850978b2 Fixes: gh-5319 2018-05-08 08:54:57 -05:00
Rob Winch a7a8ac756a Update to Spring Data Kay SR7
Fixes: gh-5318
2018-05-08 08:54:47 -05:00
Rob Winch cf4e139aa0 Update to Spring 5.0.6
Fixes: gh-5290
2018-05-08 08:35:36 -05:00
Artyom Emelyanenko 902fc0f657 Fixed confused word in the class javadoc 2018-05-07 16:54:40 -05:00
Johnny Lim b91ebf7090 Fix @since for MockEventListener 2018-05-07 16:53:26 -05:00
이경욱 26bc6be850 Support whitespace characters using RoleHierarchyImpl 2018-05-07 16:51:41 -05:00
이경욱 6adbe8dae0 Support whitespace characters using RoleHierarchyImpl 2018-05-07 16:51:41 -05:00
Robert Roth 4bfce2a591 Fixed typo in acl database schema appendix.
Fixes gh-5204
2018-05-07 16:44:32 -05:00
Robert Roth fddc28ba3b Fixed typo in CSRF documentation.
Fixes gh-4792
2018-05-07 16:44:32 -05:00
Denys Ivano fed15f2b01 Add accessDeniedHandler method to ExceptionHandlingSpec
This allows to configure accessDeniedHandler in ExceptionTranslationWebFilter through ServerHttpSecurity.

Issue: gh-5257
2018-05-07 16:22:29 -05:00
Eric Deandrea b3c5bfe4db CookieServerCsrfTokenRepository fails when cookie is null/empty
The CookieServerCsrfTokenRepository fails with an IllegalArgumentException
 when a cookie is present but the value is null or empty.

Fixes gh-5315
2018-05-07 16:16:51 -05:00
Johnny Lim 9b42831c70 Suppress deprecation warnings in spring-security-crypto 2018-05-04 21:02:57 -05:00
Johnny Lim 2a0f529ee4 Use spring-projects for organization in GitHub URLs 2018-05-04 21:01:39 -05:00
Rob Winch 3ba15a16bf Polish CookieServerCsrfTokenRepository
- Only do work if subscribed to
- use test naming conventions
- Refactor tests to avoid extracting
  - Uses String for member names which are not type safe
  - Uses long argument list which makes assertions difficult to read

Issue: gh-5083
2018-05-04 16:54:48 -05:00
Rob Winch 37b1136c0c Remove CookieServerCsrfTokenRepository builder methods
This is inconsistent with the rest of the code base.

Issue: gh-5083
2018-05-04 16:54:48 -05:00
Eric Deandrea 1eaecc12ec Add CookieServerCsrfTokenRepository
A cookie implementation of ServerCsrfTokenRepository (like CookieCsrfTokenRepository)
is missing. In this implementation it would be nice to allow the setting of the domain as well.

Fixes: gh-5083
2018-05-04 16:54:48 -05:00
Alexander Münch 0570cebbce Avoid unnecessary grow of ArrayList
Adapted ArrayList size in CacheControlHeadersWriter::createHeaders()
2018-05-04 14:23:31 -05:00
XYUU 3740d33e64 The HttpHeader's ContentLength is a byte unit 2018-05-04 14:18:03 -05:00
XYUU 23dd136efb The HttpHeader's ContentLength is a byte unit 2018-05-04 14:18:03 -05:00
Johnny Lim 44b22e7208 Fix 'attributes' deprecation warning in spring-security-docs-guides 2018-05-04 14:12:14 -05:00
Kazuki Shimizu 8d716f75a4 Fix incorrect explanation for customizing query on JdbcDaoImpl 2018-05-04 10:49:25 -05:00
Joe Grandja fff64db0e2 Improve ClaimAccessor getClaimAsInstant
Fixes gh-5250
2018-05-03 21:03:45 -04:00
Joe Grandja 2356749cc3 Add test NimbusUserInfoResponseClient sets Accept header to JSON
Issue gh-5294
2018-05-03 20:18:41 -04:00
Joe Grandja b8f225c49e NimbusUserInfoResponseClient sets Accept header to JSON
Fixes gh-5294
2018-05-03 16:34:38 -04:00
Rob Winch 0a5da93640 Improve PasswordEncoder deprecated notices
Fixes: gh-5296
2018-05-03 15:13:06 -05:00
Rob Winch 948e650a0e Add update-dependencies.sh
Fixes: gh-5276
2018-05-03 14:59:16 -05:00
Rob Winch bc0c3a5b69 Update to Spring Framework 5.0.6.BUILD-SNAPSHOT
Issue: gh-5290

# Conflicts:
#	gradle/dependency-management.gradle
2018-05-03 14:10:09 -05:00
Rob Winch 618e0ab98d Update to selenium 3.11.0
Fixes: gh-5289
2018-05-03 14:09:15 -05:00
Rob Winch bc017a62ca Update to mockito-core:2.18.3
Fixes: gh-5284
2018-05-03 14:09:15 -05:00
Rob Winch e971490605 Update to htmlunit:2.30
Fixes: gh-5283
2018-05-03 14:09:15 -05:00
Rob Winch 58b02364c2 Update to aspectj 1.9.1
Fixes: gh-5282
2018-05-03 14:09:12 -05:00
Rob Winch ef9279dd7c Update to javax.servlet-api:4.0.1
Fixes: gh-5281
2018-05-03 14:08:25 -05:00
Rob Winch ba5986f9b1 Update to oauth2-oidc-sdk:5.61
Fixes: gh-5280
2018-05-03 14:08:25 -05:00
Rob Winch 332aee8dd5 Update to nimbus-jose-jwt:5.10
Fixes: gh-5279
2018-05-03 14:08:25 -05:00
Rob Winch c0813b7448 Update Spring Boot 2.0.1.RELEASE
Fixes: gh-5278

# Conflicts:
#	gradle.properties
2018-05-03 14:08:18 -05:00
Rob Winch dc75c3e011 Update GAE 1.9.63
Fixes: gh-5277
2018-05-03 14:06:52 -05:00
Joe Grandja 4cc5705ae5 HttpSessionOAuth2AuthorizationRequestRepository removes empty Map from session
Fixes gh-5263
2018-05-02 11:07:26 -04:00
Joe Grandja 49b63e260d OAuth2LoginAuthenticationFilter should handle null ClientRegistration
Fixes gh-5251
2018-05-02 09:16:42 -04:00
Josh Cummings 2273839aad FormLoginConfigTests groovy->java
Issue: gh-4939
2018-05-01 08:11:04 -06:00
Rob Winch 9bb841ac67 ExceptionTranslationFilter does not handle committed responses
Fixes: gh-5273
2018-04-30 16:49:51 -05:00
Rob Winch eb067bc3a1 DefaultWebSecurityExpressionHandler uses PermissionEvaluator Bean
The default instance of DefaultWebSecurityExpressionHandler uses the
PermissionEvaluator Bean by default.

Fixes: gh-5272
2018-04-30 12:15:50 -05:00
Josh Cummings 359a73eff2 Merge pull request #5260 from jzheaux/gh-4939-FormLoginBeanDefinitionParserTests
FormLoginBeanDefinitionParserTests groovy->java
2018-04-27 12:03:55 -06:00
Josh Cummings 52d95770ec Merge pull request #5240 from jzheaux/gh-4939-CsrfConfigTests
CsrfConfigTests groovy->java
2018-04-25 12:26:16 -06:00
Josh Cummings 3c1231efd3 CsrfConfigTests groovy->java
Issue: gh-4939
2018-04-25 11:41:32 -06:00
Josh Cummings 65326b1178 FormLoginBeanDefinitionParserTests groovy->java
Issue: gh-4939
2018-04-25 11:12:07 -06:00
Josh Cummings f9eea1a58d Merge pull request #5224 from jzheaux/gh-4939-AccessDeniedHandlerTests
AccessDeniedConfigTests groovy->java
2018-04-25 08:23:30 -06:00
Josh Cummings 9c0f2cc281 AccessDeniedConfigTests groovy->java
Issue: gh-4939
2018-04-24 08:11:47 -06:00
Josh Cummings 7daa27874e Merge pull request #5247 from jzheaux/gh-5242
Remove Default URLs and Parameters Commentary
2018-04-19 10:34:16 -06:00
Josh Cummings f8247fa346 Remove Default URLs and Parameters Commentary
This paragraph was originally there to explain why Spring Security was
moving from the old default URLs and parameters to new ones.

Now that the defaults are the same in XML and well as Java Config, the
explanation is now no longer relevant.

Fixes: gh-5242
2018-04-19 08:53:23 -06:00
Rob Winch afdefe7b13 Fixes: gh-5190 2018-04-16 17:52:27 -05:00
Joe Grandja 6095340e93 OAuth2AuthorizationRequestRedirectFilter -> Reuse code for baseUrl
Fixes gh-5153
2018-04-09 21:11:00 -04:00
Joe Grandja d8f91e4261 Fix NPE with exp claim in NimbusJwtDecoderJwkSupport
Fixes gh-5168
2018-04-04 07:58:32 -04:00
Joe Grandja 2bd31c96ed ClaimAccessor.getClaimAsInstant() converts Long or Date
Fixes gh-5191, Fixes gh-5192
2018-04-03 21:14:25 -04:00
Joe Grandja d4e459874a Fix SecuredAnnotationSecurityMetadataSourceTests -> Related SPR-16677 2018-04-03 11:38:37 -04:00
Joe Grandja 526e0fdd4f Add OAuth2 Client HandlerMethodArgumentResolver
Fixes gh-4651
2018-04-02 12:13:52 -04:00
Joe Grandja 982fc360b2 Add support for authorization_code grant
Fixes gh-4928
2018-04-02 12:13:06 -04:00
Rob Winch 8fbec3f0f1 Polish NegatedServerWebExchangeMatcher
Issue: gh-5170
2018-03-29 21:17:40 -05:00
Tao Qian d83b67e4cb Add NegatedServerWebExchangeMatcher
Fixes: gh-5170
2018-03-29 21:16:11 -05:00
Rob Winch 234c20eb30 Polish XsdDocumentedTests
- NicerNoce->XmlNode
- NicerXmlSupport->XmlSupport
- NicerXmlParser->XmlParser

Issue: gh-4939
2018-03-29 16:36:41 -05:00
Josh Cummings 0c0abea3ad XsdDocumentedTests groovy->java
Groovy has more extensive support for Xml parsing via XmlSlurper.
To replace it, this conversion also introduces a SAX wrapper,
NicerXmlParser, and a companion Node wrapper, NicerNode, that
allowed for less modification of the converted tests.

Issue: gh-4939
2018-03-29 16:36:41 -05:00
Rob Winch fb7394c1de Polish Javadoc
Fixes: gh-5186
2018-03-29 15:33:57 -05:00
Rob Winch 6e1e977778 Polish HeadersSpec
Fixes: gh-5187
2018-03-29 15:33:57 -05:00
Rob Winch 9b692b9616 Update to mockito 2.17.0
Fixes: gh-5181
2018-03-28 14:57:12 -05:00
Rob Winch ffbc1f1e93 Update to hibernate-validator:6.0.9.Final
Fixes: gh-5180
2018-03-28 14:57:12 -05:00
Rob Winch 6d9e9007f6 Update to hibernate-entitymanager:5.2.16.Final
Fixes: gh-5178
2018-03-28 14:57:12 -05:00
Rob Winch a006ac3bb0 Update to assertj 2.9.1
Fixes: gh-5177
2018-03-28 14:57:12 -05:00
Rob Winch 692f2d343b Update to thymeleaf-layout-dialect:2.3.0
Fixes: gh-5176
2018-03-28 14:57:12 -05:00
Rob Winch b48972cf79 Update to unbounded 4.0.5
Fixes: gh-5175
2018-03-28 14:57:10 -05:00
Rob Winch 2ad90b95e1 Update to oauth2-oidc-sdk:5.57
Fixes: gh-5179
2018-03-28 14:55:04 -05:00
Rob Winch 453fb24ef1 Update to nimbus-jose-jwt:5.9
Fixes: gh-5174
2018-03-28 14:55:04 -05:00
Rob Winch 2ab18baa09 Update to appengine 1.9.63
Fixes: gh-5173
2018-03-28 14:55:04 -05:00
Rob Winch c0f52f73bb Update to Jackson 2.9.5
Fixes: gh-5172
2018-03-28 14:55:04 -05:00
Rob Winch 512fcf6094 Update mockwebserver 3.10.0
Fixes: gh-5171
2018-03-28 14:55:04 -05:00
Rob Winch 7a204a5f58 Fixes for SPR-16624
Fixes: gh-5164
2018-03-27 22:35:08 -05:00
Joe Grandja ce2f669245 Remove state assertion when loading OAuth2AuthorizationRequest
Fixes gh-5163
2018-03-27 20:06:30 -04:00
Josh Cummings ec46b7dbe1 WebSocketMessageBrokerConfigTests groovy->java
Of note is that this commit unrolls three Spock @Unroll-parameterized
tests into a separate test for each parameter.

Issue: gh-4939
2018-03-27 12:38:06 -05:00
Mark Hobson 3c07d99b0a Close quoted expected path in log when matching 2018-03-27 11:14:14 -05:00
Johnny Lim d20ed9f5c9 Fix @since for StrictHttpFirewall 2018-03-27 11:01:26 -05:00
Christoph Dreis d07cfe655d Use Supplier variants of Assert methods 2018-03-27 10:58:55 -05:00
Rob Winch 7d4e7bf42d Add JDK 10 & 11 Builds
Issue: gh-5160
2018-03-27 09:38:04 -05:00
Rob Winch b1d013e8f0 Fix JDK 9
Issue: gh-5160
2018-03-27 09:30:56 -05:00
Rob Winch 6f6aadbcff Add JDK 9 Build
Issue: gh-5160
2018-03-26 16:50:46 -05:00
Alter Ego 0e37c0912e Update User.java
fixed a typo; replaced "User.witUsername("user")" with "User.withUsername("user")"
2018-03-22 08:19:44 -05:00
Joe Grandja bb15213091 Ensure consistency by using Collection<GrantedAuthority> type
Fixes gh-5143
2018-03-21 10:35:18 -04:00
Joe Grandja 90f9d728cd Allow extension for OAuth2Error
Fixes gh-5148
2018-03-21 10:04:57 -04:00
Rob Winch e4255c9793 Add Security Vulnerabilities 2018-03-20 22:42:46 -05:00
Rob Winch 332c395875 Add Security Vulnerabilities Location 2018-03-20 22:42:15 -05:00
Rob Winch bf41d48718 HttpSessionOAuth2AuthorizationRequestRepository support distributed HttpSession
Previously HttpSessionOAuth2AuthorizationRequestRepository
getAuthorizationRequest attempted to update the state of HttpSession as
well as getting the Map of OAuth2AuthorizationRequest. This had a few
problems

- First it was confusing that a get method updated state
- It worked when the session was in memory, but would not work when the
  HttpSesson was persisted to an external store (i.e. Spring Session) since
  after updating the Map, there was no invocation to update

This commit cleans up the logic and ensures that the values are explicitly
set in the HttpSession so it works with a session persisted in an external
store.

Fixes: gh-5146
2018-03-20 22:14:48 -05:00
Rob Winch 04e2e86e6e Polish HttpSessionOAuth2AuthorizationRequestRepositoryTests
Fixes: gh-5147
2018-03-20 22:14:48 -05:00
Joe Grandja 59cef7d339 HttpSessionOAuth2AuthorizationRequestRepository handle multiple OAuth2AuthorizationRequest per session
Fixes gh-5110
2018-03-20 22:14:48 -05:00
Rob Winch 7e6ed52603 CookieClearingLogoutHandler adds uses contextPath + "/"
Fixes: gh-2325
2018-03-19 16:51:22 -05:00
Rob Winch 018ab7d92c Fix Javadoc Typo uses->use
Issue: gh-5113
2018-03-19 15:36:31 -05:00
Rob Winch 01152ede41 Clarify HttpSecurity.registerFilterAt
Fixes: gh-5113
2018-03-19 14:41:03 -05:00
Rob Winch 1851aaa66d Fix ReactorContextTestExecutionListener with custom SecurityContext
Fixes: gh-5137
2018-03-19 09:29:27 -05:00
Oleh Dokuka 76e36bd06e fix Mock Authentication resolution 2018-03-19 09:16:55 -05:00
Vedran Pavic b640d84b12 Improve EditorConfig file 2018-03-16 15:50:34 -05:00
Rob Winch 3a740ad988 Add SNAPSHOTs tests to CI
Fixes: gh-5129
2018-03-16 14:19:04 -05:00
Rob Winch 67d793ae5f Delay lookup of managedVersions
Fixes: gh-5127
2018-03-16 13:55:17 -05:00
Rob Winch efaf2b080f Make MIN_SPRING_VERSION Dynamic
Fixes: gh-5065
2018-03-16 13:53:40 -05:00
Rob Winch e86becc151 Relax assertions in HeaderSpecTests
Fixes: gh-5116
2018-03-15 08:30:37 -05:00
Rob Winch 4f709d47b9 Fix @since on GlobalAuthenticationConfigurerAdapter
Fixes: gh-5106
2018-03-13 14:23:36 -05:00
Rob Winch 452d855396 Fix appendix tests 2018-03-09 16:34:49 -06:00
Rob Winch 6e5105f899 Extract appendix subsections
Issue: gh-2567
2018-03-09 16:34:46 -06:00
Rob Winch 40bb73124c Move data to data/index
Issue: gh-2567
2018-03-09 16:34:42 -06:00
Rob Winch 780e6aefd2 Extract additional-topics subsections
Issue: gh-2567
2018-03-09 16:34:38 -06:00
Rob Winch 35345fac70 Extract authorization subsections
Issue: gh-2567
2018-03-09 16:34:35 -06:00
Rob Winch 8cf51032e0 Extract Subsections of Web
Issue: gh-2567
2018-03-09 16:34:31 -06:00
Rob Winch ae9075c023 Extract test subsections
Issue: gh-2567
2018-03-09 16:34:30 -06:00
Rob Winch cf4272ff64 Extract architecture subsections
Issue: gh-2567
2018-03-09 16:34:24 -06:00
Rob Winch 4152530e69 Fix new lines
Issue: gh-2567
2018-03-09 16:34:20 -06:00
Rob Winch 73cec43842 Extract subsections for preface
Issue: gh-2567
2018-03-09 16:34:12 -06:00
Rob Winch 86465026a1 Extract top level section of reference
Issue: gh-2567
2018-03-09 16:33:54 -06:00
Rob Winch e799f13ae2 Consistent new lines in referenche
Issue: gh-2567
2018-03-09 16:33:54 -06:00
Josh Cummings 744bb1b1be Advisory to avoid markdown in commit messages
Today, @rwinch and I were discussing the merits of leaving commit messages free of formatting hints, like back-ticks. Adding this bullet-point brings things into line with expectations.
2018-03-09 14:20:43 -06:00
Josh Cummings 776b378a1d Authorities authenticate TestingAuthenticationToken
In other extensions of `AbstractAuthenticationToken`, the constructors
that include `authorities` call `setAuthenticated(true)`. This includes
`PreAuthenticated`-, `UsernamePassword`-, and
`RememberMeAuthenticationToken`.

This change brings `TestingAuthenticationToken` in line with that
convention.

Note that this was done once already to one of the constructors
(ee13be4) in `TestingAuthenticationToken` that takes an arity of
`authorities`. It was not propagated to the constructor that takes a
collection, which is what this commit remedies.

Fixes: gh-5073
2018-03-09 13:21:47 -06:00
Rob Winch d21338d212 Support errorOnInvalidType for Reactive AuthenticationPrincipal
Fixes: gh-5096
2018-03-09 12:05:55 -06:00
Rob Winch a2073b2b91 Support BeanResolver for Reactive AuthenticationPrincipal
Fixes: gh-4326
2018-03-09 12:05:55 -06:00
Rob Winch d816af2337 Add BadCredentials Jackson Support to What's New
Issue: gh-5087
2018-03-09 12:05:55 -06:00
Rob Winch 7fafd899ee Add Reactive WithUserDetails to What's new
Issue: gh-4888
2018-03-09 12:05:55 -06:00
Josh Cummings bc21f80ebe Update to Spring Boot 2.0.0.RELEASE
Fixes: gh-5061
2018-03-09 09:46:38 -06:00
Rob Winch 65193963ad Fix Imports
Issue: gh-4888
2018-03-09 09:15:39 -06:00
Rob Winch 2228485a40 WithUserDetails supports ReactiveUserDetailsService
Fixes: gh-4888
2018-03-08 23:13:19 -06:00
Rob Winch 949c7d68b8 Fix StrictHttpFirewall rules
Fixes: gh-5044
2018-03-08 21:30:23 -06:00
ylombardi 1d0e97880d Add the BadCredentialsExceptionMixin to help Jackson serialization of BadCredentialsException 2018-03-08 16:55:57 -06:00
Josh Cummings 3121f9c000 NamespaceGlobalMethodSecurity groovy->java
Note that the `WhenUsingAspectJ` tests are still simply verifying structure instead of behavior. This is because the project appearsto be misconfigured in some way such that AspectJ advice isn't getting woven in at runtime. The original Groovy tests also only verified structure and they may be that way for a similar reason.

Either way, I will open up a ticket so we can review why that is the case and if there is a good fix.

Issue: gh-4939
2018-03-08 16:53:54 -06:00
Josh Cummings c91ca0584c Sec2758Tests groovy->java
Note that the old groovy test used a configuration of

```
http
    .authorizeRequests()
        .anyRequest().hasAnyAuthority("USER")
```

However, as I read the issue, gh-2984, the problem this issue
identifies is the non-passive change of defaulting to prefix
ROLE_ with all role-based configuration methods. So, the test now
does the following:

```
http
    .authorizeRequests()
        .anyRequest().access("hasAnyRole('USER')")
```

which demonstrates, given the configuration in this test, that
ROLE_ is correctly not prefixed in this expression, even though
it is a role-based configuration.

Issue: gh-4939
2018-03-08 16:52:20 -06:00
Vedran Pavic 350fcd4277 Upgrade Nimbus JOSE + JWT to 5.6 2018-03-08 16:39:38 -06:00
Vedran Pavic 505aa8dd02 Upgrade Nimbus OAuth + OIDC SDK to 5.56 2018-03-08 16:37:01 -06:00
Rob Winch abae2f3e87 Allow WithSecurityContextTestExecutionListener to execute after @Before
Fixes: gh-2935
2018-03-08 14:13:07 -06:00
Rob Winch 055a2ca917 Polish Javadoc HttpStatusServerAccessDeniedHandler 2018-03-07 12:35:25 -06:00
Rob Winch 9f23212e43 HttpStatusServerAccessDeniedHandler use injected HttpStatus
Fixes: gh-5078
2018-03-07 12:35:25 -06:00
Josh Cummings ca93b34f56 Update to Gradle 4.6
Only thing of interest really is that the Jacoco Gradle plugin was bumped to 0.8.0, which requires org.ow2.asm:asm:6.0.

Issue: gh-5062
2018-03-06 20:23:13 -06:00
Joe Grandja a5bd76b6ed Revert authorization_code grant support
This reverts commit eae7afd9aa.
2018-03-06 16:16:45 -05:00
Joe Grandja c922fe3be1 WebSecurityConfigurationTests groovy->java
Issue: gh-4939
2018-03-06 09:24:52 -05:00
Joe Grandja b1f3d495d9 Sec2515Tests groovy->java
Issue: gh-4939
2018-03-05 15:16:52 -05:00
Joe Grandja 0aa87e8501 EnableWebSecurityTests groovy->java
Issue: gh-4939
2018-03-05 10:23:48 -05:00
Joe Grandja 5af1d1d936 Polish HttpConfigurationTests 2018-03-05 08:36:15 -05:00
Joe Grandja 2a678ebc6e Polish WebSecurityConfigurerAdapterTests 2018-03-05 06:20:27 -05:00
Joe Grandja 5b023d0abc Fix Security version tests -> 5.1 2018-03-02 16:29:22 -05:00
Rob Winch b8dfcd0d35 Update version to 5.1.0.BUILD-SNAPSHOT 2018-03-02 14:34:09 -06:00
Joe Grandja eae7afd9aa Add support for authorization_code grant
Fixes gh-4928
2018-03-02 14:30:49 -05:00
Johnny Lim d316803596 Polish DaoAuthenticationProviderTests 2018-03-02 08:55:37 -06:00
Josh Cummings 1ed51033cc Migrate config-debug groovy->java
All tests in `org.springframework.security.config.debug` are migrated.

Note that `SecurityDebugBeanFactoryPostProceessorTest` preserves the original structure-verifying strategy used in the Groovy test. Verifying debug behavior turns out to be fairly tricky since being behaviorally invisible is in its nature.

Issue: gh-4939
2018-03-02 08:55:07 -06:00
Vedran Pavic b8ae110b7b Upgrade Nimbus JOSE + JWT to 5.5 2018-03-02 08:48:16 -06:00
Rob Winch 458b571d02 Next Development Version 2018-02-28 08:36:16 -06:00
Rob Winch ce8bea69ae Release 5.0.3.RELEASE 2018-02-28 07:05:44 -06:00
Rob Winch ab5a760380 Update to Spring Data Kay SR5
Fixes: gh-5049
2018-02-28 07:05:08 -06:00
Rob Winch ec3534ac8d Update to Reactor Bismuth SR7
Fixes: gh-5050
2018-02-27 20:47:00 -06:00
Rob Winch 68afb2475a Update Selenium Dependencies
Fixes: gh-5056
2018-02-27 20:45:53 -06:00
Rob Winch 0e1060e736 Update to oauth2-oidc-sdk:5.54
Fixes: gh-5055
2018-02-27 20:45:43 -06:00
Rob Winch c9ef2549b2 Update to appengine-gradle-plugin:1.3.5
Fixes: gh-5054
2018-02-27 20:45:27 -06:00
Rob Winch 51ddd2b36d Update to GAE 1.9.62
Fixes: gh-5053
2018-02-27 20:45:09 -06:00
Rob Winch 822129cebd Update to Spring Boot 2.0.0.RC2
Fixes: gh-5052
2018-02-27 20:44:54 -06:00
Rob Winch 3205f68f29 Update to spring-build-conventions:0.0.15.RELEASE
Fixes: gh-5048
2018-02-27 16:23:49 -06:00
Josh Cummings 1b69c62d20 PortMapperConfigurerTests groovy->java
Issue: gh-4939
2018-02-27 11:44:21 -05:00
Josh Cummings e08d4cc90c AnonymousConfigurerTests groovy->java
This test now checks key and principal both, which differs from the original Groovy test

In order to keep from needing to execute logic internal to `AnonymousAuthenticationToken`, this test changed from the original Groovy test. In the Groovy test, `key` is tested; however in this new test, `principal` is tested instead.

A concern was raised that if `AnonymousAuthenticationProvider` were invoked in this test, then testing only `principal` would not confirm that `key` was correctly propagated to `AnonymousAuthenticationProvider`. So, the test now configures both `key` and `principal`. The former to confirm correct wiring of `AnonymousAuthenticationProvider` and the latter to confirm correct wiring of `AnonymousAuthenticationFilter`.

Issue: gh-4939
2018-02-27 11:30:02 -05:00
Rob Winch 8d75554b6b Lazily Create Throwables
Fixes: gh-5040
2018-02-26 16:24:40 -06:00
Josh Cummings bb59733736 Sec2377Tests groovy->java
Issue: gh-4939
2018-02-22 10:48:18 -05:00
Joe Grandja dc9248e73c NamespaceHttpTests groovy->java
Issue: gh-4939
2018-02-22 10:29:48 -05:00
Rob Winch c24490cbb8 Update to spring-build-conventions:0.0.13.RELEASE
Fixes: gh-5030
2018-02-19 23:59:03 -06:00
Rob Winch cc2f676c03 Next Development Version 2018-02-19 22:07:03 -06:00
463 changed files with 33679 additions and 17171 deletions
+14 -9
View File
@@ -1,17 +1,22 @@
# EditorConfig for Spring Security
# see https://github.com/spring-projects/spring-security/blob/master/CONTRIBUTING.md#mind-the-whitespace
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
charset = latin1
# 4 tabs indentation
indent_style = tab
tab_width = 4
trim_trailing_whitespace = true
insert_final_newline = true
max_line_length = 120
[*.java]
indent_style = tab
indent_size = 4
charset = latin1
continuation_indent_size = 8
[*.xml]
indent_style = tab
indent_size = 4
charset = latin1
continuation_indent_size = 8
+4
View File
@@ -1,3 +1,7 @@
<!--
For Security Vulnerabilities, please use https://pivotal.io/security#reporting
-->
### Summary
<!--
+4
View File
@@ -1,3 +1,7 @@
<!--
For Security Vulnerabilities, please use https://pivotal.io/security#reporting
-->
<!--
Thanks for contributing to Spring Security. Please provide a brief description of your pull-request and reference any related issue numbers (prefix references with #).
-->
+2 -1
View File
@@ -8,7 +8,7 @@ By participating, you are expected to uphold this code. Please report unaccepta
# Similar but different
Each Spring module is slightly different than another in terms of team size, number of issues, etc. Therefore each project is managed slightly different. You will notice that this document is very similar to the [Spring Framework Contributor guidelines](https://github.com/SpringSource/spring-framework/wiki/Contributor-guidelines). However, there are some subtle differences between the two documents, so please be sure to read this document thoroughly.
Each Spring module is slightly different than another in terms of team size, number of issues, etc. Therefore each project is managed slightly different. You will notice that this document is very similar to the [Spring Framework Contributor guidelines](https://github.com/spring-projects/spring-framework/wiki/Contributor-guidelines). However, there are some subtle differences between the two documents, so please be sure to read this document thoroughly.
# Importing into IDE
@@ -180,6 +180,7 @@ Fixes gh-123
2. Do not end the subject line with a period
3. In the body of the commit message, explain how things worked before this commit, what has changed, and how things work now
3. Include Fixes gh-<issue-number> at the end if this fixes a GitHub issue
5. Avoid markdown, including back-ticks identifying code
# Run all tests prior to submission
Vendored
+58
View File
@@ -53,6 +53,64 @@ try {
}
}
}
},
snapshots: {
stage('Snapshot Tests') {
node {
checkout scm
try {
sh "./gradlew clean test -PspringVersion='5.+' -PreactorVersion=Bismuth-BUILD-SNAPSHOT -PspringDataVersion=Kay-BUILD-SNAPSHOT --refresh-dependencies --no-daemon --stacktrace"
} catch(Exception e) {
currentBuild.result = 'FAILED: snapshots'
throw e
}
}
}
},
jdk9: {
stage('JDK 9') {
node {
checkout scm
try {
withEnv(["JAVA_HOME=${ tool 'jdk9' }"]) {
sh "./gradlew clean test --no-daemon --stacktrace"
}
} catch(Exception e) {
currentBuild.result = 'FAILED: jdk9'
throw e
}
}
}
},
jdk10: {
stage('JDK 10') {
node {
checkout scm
try {
withEnv(["JAVA_HOME=${ tool 'jdk10' }"]) {
sh "./gradlew clean test --no-daemon --stacktrace"
}
} catch(Exception e) {
currentBuild.result = 'FAILED: jdk10'
throw e
}
}
}
},
jdk11: {
stage('JDK 11') {
node {
checkout scm
try {
withEnv(["JAVA_HOME=${ tool 'jdk11' }"]) {
sh "./gradlew clean test --no-daemon --stacktrace"
}
} catch(Exception e) {
currentBuild.result = 'FAILED: jdk11'
throw e
}
}
}
}
if(currentBuild.result == 'SUCCESS') {
@@ -92,11 +92,11 @@ public abstract class AclFormattingUtils {
*/
public static String printBinary(int mask, char code) {
Assert.doesNotContain(Character.toString(code),
Character.toString(Permission.RESERVED_ON), Permission.RESERVED_ON
+ " is a reserved character code");
Character.toString(Permission.RESERVED_ON),
() -> Permission.RESERVED_ON + " is a reserved character code");
Assert.doesNotContain(Character.toString(code),
Character.toString(Permission.RESERVED_OFF), Permission.RESERVED_OFF
+ " is a reserved character code");
Character.toString(Permission.RESERVED_OFF),
() -> Permission.RESERVED_OFF + " is a reserved character code");
return printBinary(mask, Permission.RESERVED_ON, Permission.RESERVED_OFF)
.replace(Permission.RESERVED_ON, code);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 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.
@@ -104,9 +104,9 @@ public class DefaultPermissionFactory implements PermissionFactory {
// Ensure no existing Permission uses this integer or code
Assert.isTrue(!registeredPermissionsByInteger.containsKey(mask),
"An existing Permission already provides mask " + mask);
() -> "An existing Permission already provides mask " + mask);
Assert.isTrue(!registeredPermissionsByName.containsKey(permissionName),
"An existing Permission already provides name '" + permissionName + "'");
() -> "An existing Permission already provides name '" + permissionName + "'");
// Register the new Permission
registeredPermissionsByInteger.put(mask, perm);
@@ -111,7 +111,7 @@ public class JdbcAclService implements AclService {
throws NotFoundException {
Map<ObjectIdentity, Acl> map = readAclsById(Arrays.asList(object), sids);
Assert.isTrue(map.containsKey(object),
"There should have been an Acl entry for ObjectIdentity " + object);
() -> "There should have been an Acl entry for ObjectIdentity " + object);
return (Acl) map.get(object);
}
+9 -1
View File
@@ -1,6 +1,6 @@
buildscript {
dependencies {
classpath 'io.spring.gradle:spring-build-conventions:0.0.12.RELEASE'
classpath 'io.spring.gradle:spring-build-conventions:0.0.15.RELEASE'
classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion"
}
repositories {
@@ -30,3 +30,11 @@ gradle.taskGraph.whenReady { graph ->
}
}
}
subprojects {
plugins.withType(JavaPlugin) {
project.sourceCompatibility='1.8'
}
}
@@ -0,0 +1,49 @@
/*
* 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 versions
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
/**
* @author Rob Winch
*/
class VersionsResourceTasks extends DefaultTask {
@OutputFile
File versionsFile;
@Input
Closure<Map<String,String>> versions;
void setVersions(Map<String,String> versions) {
this.versions = { versions };
}
void setVersions(Closure<Map<String,String>> versions) {
this.versions = versions
}
@TaskAction
void generateVersions() {
versionsFile.parentFile.mkdirs()
versionsFile.createNewFile()
Properties versionsProperties = new Properties()
versionsProperties.putAll(versions.call())
versionsProperties.store(versionsFile.newWriter(), null)
}
}
@@ -137,7 +137,9 @@ public class CasAuthenticationTokenMixinTests {
assertThat(token.getUserDetails()).isNotNull().isInstanceOf(User.class);
assertThat(token.getAssertion()).isNotNull().isInstanceOf(AssertionImpl.class);
assertThat(token.getKeyHash()).isEqualTo(KEY.hashCode());
assertThat(token.getUserDetails().getAuthorities()).hasSize(1).contains(new SimpleGrantedAuthority("ROLE_USER"));
assertThat(token.getUserDetails().getAuthorities())
.extracting(GrantedAuthority::getAuthority)
.containsOnly("ROLE_USER");
assertThat(token.getAssertion().getAuthenticationDate()).isEqualTo(START_DATE);
assertThat(token.getAssertion().getValidFromDate()).isEqualTo(START_DATE);
assertThat(token.getAssertion().getValidUntilDate()).isEqualTo(END_DATE);
@@ -16,11 +16,6 @@
package org.springframework.security.cas.web;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import javax.servlet.FilterChain;
import org.jasig.cas.client.proxy.ProxyGrantingTicketStorage;
import org.junit.After;
import org.junit.Test;
@@ -35,9 +30,13 @@ import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import javax.servlet.FilterChain;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;
/**
* Tests {@link CasAuthenticationFilter}.
*
@@ -146,8 +145,7 @@ public class CasAuthenticationFilterTests {
.createAuthorityList("ROLE_ANONYMOUS")));
assertThat(filter.requiresAuthentication(request, response)).isTrue();
SecurityContextHolder.getContext().setAuthentication(
new TestingAuthenticationToken("un", "principal", AuthorityUtils
.createAuthorityList("ROLE_ANONYMOUS")));
new TestingAuthenticationToken("un", "principal"));
assertThat(filter.requiresAuthentication(request, response)).isTrue();
SecurityContextHolder.getContext().setAuthentication(
new TestingAuthenticationToken("un", "principal", "ROLE_ANONYMOUS"));
+2
View File
@@ -35,7 +35,9 @@ dependencies {
testCompile powerMock2Dependencies
testCompile spockDependencies
testCompile 'ch.qos.logback:logback-classic'
testCompile 'io.projectreactor.ipc:reactor-netty'
testCompile 'javax.annotation:jsr250-api:1.0'
testCompile 'javax.xml.bind:jaxb-api'
testCompile 'ldapsdk:ldapsdk:4.1'
testCompile('net.sourceforge.htmlunit:htmlunit') {
exclude group: 'commons-logging', module: 'commons-logging'
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 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.
@@ -152,7 +152,7 @@ public class AuthenticationConfiguration {
return null;
}
Assert.isTrue(beanNamesForType.length == 1,
"Expecting to only find a single bean for type " + interfaceName
() -> "Expecting to only find a single bean for type " + interfaceName
+ ", but found " + Arrays.asList(beanNamesForType));
lazyTargetSource.setTargetBeanName(beanNamesForType[0]);
lazyTargetSource.setBeanFactory(applicationContext);
@@ -27,7 +27,7 @@ import org.springframework.security.config.annotation.authentication.configurati
* {@link AuthenticationConfiguration} to configure the global
* {@link AuthenticationManagerBuilder}.
*
* @since 3.2.1
* @since 5.0
* @author Rob Winch
*/
@Order(100)
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 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.
@@ -457,7 +457,7 @@ public class GlobalMethodSecurityConfiguration
EnableGlobalMethodSecurity methodSecurityAnnotation = AnnotationUtils
.findAnnotation(getClass(), EnableGlobalMethodSecurity.class);
Assert.notNull(methodSecurityAnnotation,
EnableGlobalMethodSecurity.class.getName() + " is required");
() -> EnableGlobalMethodSecurity.class.getName() + " is required");
Map<String, Object> methodSecurityAttrs = AnnotationUtils
.getAnnotationAttributes(methodSecurityAnnotation);
this.enableMethodSecurity = AnnotationAttributes.fromMap(methodSecurityAttrs);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 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.
@@ -42,7 +42,7 @@ final class GlobalMethodSecuritySelector implements ImportSelector {
.getAnnotationAttributes(annoType.getName(), false);
AnnotationAttributes attributes = AnnotationAttributes
.fromMap(annotationAttributes);
Assert.notNull(attributes, String.format(
Assert.notNull(attributes, () -> String.format(
"@%s is not present on importing class '%s' as expected",
annoType.getSimpleName(), importingClassMetadata.getClassName()));
@@ -117,6 +117,10 @@ final class FilterComparator implements Comparator<Filter>, Serializable {
order += STEP;
put(AnonymousAuthenticationFilter.class, order);
order += STEP;
filterToOrder.put(
"org.springframework.security.oauth2.client.web.OAuth2AuthorizationCodeGrantFilter",
order);
order += STEP;
put(SessionManagementFilter.class, order);
order += STEP;
put(ExceptionTranslationFilter.class, order);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 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.
@@ -15,14 +15,6 @@
*/
package org.springframework.security.config.annotation.web.builders;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.servlet.Filter;
import javax.servlet.http.HttpServletRequest;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
@@ -56,12 +48,13 @@ import org.springframework.security.config.annotation.web.configurers.SecurityCo
import org.springframework.security.config.annotation.web.configurers.ServletApiConfigurer;
import org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer;
import org.springframework.security.config.annotation.web.configurers.X509Configurer;
import org.springframework.security.config.annotation.web.configurers.oauth2.OAuth2Configurer;
import org.springframework.security.config.annotation.web.configurers.oauth2.client.OAuth2LoginConfigurer;
import org.springframework.security.config.annotation.web.configurers.openid.OpenIDLoginConfigurer;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.config.annotation.web.configurers.oauth2.client.OAuth2LoginConfigurer;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.PortMapper;
import org.springframework.security.web.PortMapperImpl;
@@ -79,6 +72,13 @@ import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
import javax.servlet.Filter;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* A {@link HttpSecurity} is similar to Spring Security's XML &lt;http&gt; element in the
* namespace configuration. It allows configuring web based security for specific http
@@ -991,6 +991,18 @@ public final class HttpSecurity extends
return getOrApply(new OAuth2LoginConfigurer<>());
}
/**
* Configures support for the <a target="_blank" href="https://tools.ietf.org/html/rfc6749">OAuth 2.0 Authorization Framework</a>.
*
* @author Joe Grandja
* @since 5.1
* @return the {@link OAuth2Configurer} for further customizations
* @throws Exception
*/
public OAuth2Configurer oauth2() throws Exception {
return getOrApply(new OAuth2Configurer());
}
/**
* Configures channel security. In order for this configuration to be useful at least
* one mapping to a required channel must be provided.
@@ -1163,6 +1175,11 @@ public final class HttpSecurity extends
* addFilterAt(new CustomFilter(), UsernamePasswordAuthenticationFilter.class)
* </pre>
*
* Registration of multiple Filters in the same location means their ordering is not
* deterministic. More concretely, registering multiple Filters in the same location
* does not override existing Filters. Instead, do not register Filters you do not
* want to use.
*
* @param filter the Filter to register
* @param atFilter the location of another {@link Filter} that is already registered
* (i.e. known) with Spring Security.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 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.
@@ -29,6 +29,7 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.http.HttpMethod;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.access.expression.SecurityExpressionHandler;
import org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder;
import org.springframework.security.config.annotation.ObjectPostProcessor;
@@ -278,7 +279,9 @@ public final class WebSecurity extends
protected Filter performBuild() throws Exception {
Assert.state(
!securityFilterChainBuilders.isEmpty(),
"At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. Typically this done by adding a @Configuration that extends WebSecurityConfigurerAdapter. More advanced users can invoke "
() -> "At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. "
+ "Typically this done by adding a @Configuration that extends WebSecurityConfigurerAdapter. "
+ "More advanced users can invoke "
+ WebSecurity.class.getSimpleName()
+ ".addSecurityFilterChainBuilder directly");
int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size();
@@ -382,6 +385,11 @@ public final class WebSecurity extends
throws BeansException {
this.defaultWebSecurityExpressionHandler
.setApplicationContext(applicationContext);
try {
this.defaultWebSecurityExpressionHandler.setPermissionEvaluator(applicationContext.getBean(
PermissionEvaluator.class));
} catch(NoSuchBeanDefinitionException e) {}
this.ignoredRequestRegistry = new IgnoredRequestConfigurer(applicationContext);
try {
this.httpFirewall = applicationContext.getBean(HttpFirewall.class);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 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.
@@ -73,7 +73,8 @@ import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import({ WebSecurityConfiguration.class,
SpringWebMvcImportSelector.class })
SpringWebMvcImportSelector.class,
OAuth2ImportSelector.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {
@@ -83,4 +84,4 @@ public @interface EnableWebSecurity {
* @return if true, enables debug support with Spring Security
*/
boolean debug() default false;
}
}
@@ -0,0 +1,76 @@
/*
* 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.configuration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.method.annotation.OAuth2ClientArgumentResolver;
import org.springframework.util.ClassUtils;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
/**
* {@link Configuration} for OAuth 2.0 Client support.
*
* <p>
* This {@code Configuration} is conditionally imported by {@link OAuth2ImportSelector}
* when the {@code spring-security-oauth2-client} module is present on the classpath.
*
* @author Joe Grandja
* @since 5.1
* @see OAuth2ImportSelector
*/
@Import(OAuth2ClientConfiguration.OAuth2ClientWebMvcImportSelector.class)
final class OAuth2ClientConfiguration {
static class OAuth2ClientWebMvcImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
boolean webmvcPresent = ClassUtils.isPresent(
"org.springframework.web.servlet.DispatcherServlet", getClass().getClassLoader());
return webmvcPresent ?
new String[] { "org.springframework.security.config.annotation.web.configuration.OAuth2ClientConfiguration.OAuth2ClientWebMvcSecurityConfiguration" } :
new String[] {};
}
}
@Configuration
static class OAuth2ClientWebMvcSecurityConfiguration implements WebMvcConfigurer {
@Autowired(required = false)
private ClientRegistrationRepository clientRegistrationRepository;
@Autowired(required = false)
private OAuth2AuthorizedClientService authorizedClientService;
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
if (this.clientRegistrationRepository != null && this.authorizedClientService != null) {
OAuth2ClientArgumentResolver oauth2ClientArgumentResolver = new OAuth2ClientArgumentResolver(
this.clientRegistrationRepository, this.authorizedClientService);
argumentResolvers.add(oauth2ClientArgumentResolver);
}
}
}
}
@@ -0,0 +1,41 @@
/*
* 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.configuration;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.ClassUtils;
/**
* Used by {@link EnableWebSecurity} to conditionally import {@link OAuth2ClientConfiguration}
* when the {@code spring-security-oauth2-client} module is present on the classpath.
* @author Joe Grandja
* @since 5.1
* @see OAuth2ClientConfiguration
*/
final class OAuth2ImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
boolean oauth2ClientPresent = ClassUtils.isPresent(
"org.springframework.security.oauth2.client.registration.ClientRegistration", getClass().getClassLoader());
return oauth2ClientPresent ?
new String[] { "org.springframework.security.config.annotation.web.configuration.OAuth2ClientConfiguration" } :
new String[] {};
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 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.
@@ -220,7 +220,7 @@ public final class UrlAuthorizationConfigurer<H extends HttpSecurityBuilder<H>>
private static String hasRole(String role) {
Assert.isTrue(
!role.startsWith("ROLE_"),
role
() -> role
+ " should not start with ROLE_ since ROLE_ is automatically prepended when using hasRole. Consider using hasAuthority or access instead.");
return "ROLE_" + role;
}
@@ -0,0 +1,52 @@
/*
* 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.oauth2;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.oauth2.client.OAuth2ClientConfigurer;
/**
* An {@link AbstractHttpConfigurer} that provides support for the
* <a target="_blank" href="https://tools.ietf.org/html/rfc6749">OAuth 2.0 Authorization Framework</a>.
*
* @author Joe Grandja
* @since 5.1
* @see HttpSecurity#oauth2()
* @see OAuth2ClientConfigurer
* @see AbstractHttpConfigurer
*/
public final class OAuth2Configurer extends AbstractHttpConfigurer<OAuth2Configurer, HttpSecurity> {
/**
* Returns the {@link OAuth2ClientConfigurer} for configuring OAuth 2.0 Client support.
*
* @return the {@link OAuth2ClientConfigurer}
* @throws Exception
*/
public OAuth2ClientConfigurer<HttpSecurity> client() throws Exception {
return this.getOrApply(new OAuth2ClientConfigurer<>());
}
@SuppressWarnings("unchecked")
private <C extends AbstractHttpConfigurer<C, HttpSecurity>> C getOrApply(C configurer) throws Exception {
C existingConfigurer = (C) this.getBuilder().getConfigurer(configurer.getClass());
if (existingConfigurer != null) {
return existingConfigurer;
}
return this.getBuilder().apply(configurer);
}
}
@@ -15,7 +15,6 @@
*/
package org.springframework.security.config.annotation.web.configurers.oauth2.client;
import org.springframework.context.ApplicationContext;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
@@ -86,7 +85,7 @@ public final class ImplicitGrantConfigurer<B extends HttpSecurityBuilder<B>> ext
@Override
public void configure(B http) throws Exception {
OAuth2AuthorizationRequestRedirectFilter authorizationRequestFilter = new OAuth2AuthorizationRequestRedirectFilter(
this.getClientRegistrationRepository(), this.getAuthorizationRequestBaseUri());
OAuth2ClientConfigurerUtils.getClientRegistrationRepository(this.getBuilder()), this.getAuthorizationRequestBaseUri());
http.addFilter(this.postProcess(authorizationRequestFilter));
}
@@ -95,17 +94,4 @@ public final class ImplicitGrantConfigurer<B extends HttpSecurityBuilder<B>> ext
this.authorizationRequestBaseUri :
OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI;
}
private ClientRegistrationRepository getClientRegistrationRepository() {
ClientRegistrationRepository clientRegistrationRepository = this.getBuilder().getSharedObject(ClientRegistrationRepository.class);
if (clientRegistrationRepository == null) {
clientRegistrationRepository = this.getClientRegistrationRepositoryBean();
this.getBuilder().setSharedObject(ClientRegistrationRepository.class, clientRegistrationRepository);
}
return clientRegistrationRepository;
}
private ClientRegistrationRepository getClientRegistrationRepositoryBean() {
return this.getBuilder().getSharedObject(ApplicationContext.class).getBean(ClientRegistrationRepository.class);
}
}
@@ -0,0 +1,300 @@
/*
* 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.oauth2.client;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationProvider;
import org.springframework.security.oauth2.client.endpoint.NimbusAuthorizationCodeTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationCodeGrantFilter;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.util.Assert;
/**
* An {@link AbstractHttpConfigurer} for OAuth 2.0 Client support.
*
* <p>
* The following configuration options are available:
*
* <ul>
* <li>{@link #authorizationCodeGrant()} - enables the OAuth 2.0 Authorization Code Grant</li>
* </ul>
*
* <p>
* Defaults are provided for all configuration options with the only required configuration
* being {@link #clientRegistrationRepository(ClientRegistrationRepository)}.
* Alternatively, a {@link ClientRegistrationRepository} {@code @Bean} may be registered instead.
*
* <h2>Security Filters</h2>
*
* The following {@code Filter}'s are populated when {@link #authorizationCodeGrant()} is configured:
*
* <ul>
* <li>{@link OAuth2AuthorizationRequestRedirectFilter}</li>
* <li>{@link OAuth2AuthorizationCodeGrantFilter}</li>
* </ul>
*
* <h2>Shared Objects Created</h2>
*
* The following shared objects are populated:
*
* <ul>
* <li>{@link ClientRegistrationRepository} (required)</li>
* <li>{@link OAuth2AuthorizedClientService} (optional)</li>
* </ul>
*
* <h2>Shared Objects Used</h2>
*
* The following shared objects are used:
*
* <ul>
* <li>{@link ClientRegistrationRepository}</li>
* <li>{@link OAuth2AuthorizedClientService}</li>
* </ul>
*
* @author Joe Grandja
* @since 5.1
* @see OAuth2AuthorizationRequestRedirectFilter
* @see OAuth2AuthorizationCodeGrantFilter
* @see ClientRegistrationRepository
* @see OAuth2AuthorizedClientService
* @see AbstractHttpConfigurer
*/
public final class OAuth2ClientConfigurer<B extends HttpSecurityBuilder<B>> extends
AbstractHttpConfigurer<OAuth2ClientConfigurer<B>, B> {
private AuthorizationCodeGrantConfigurer authorizationCodeGrantConfigurer;
/**
* Sets the repository of client registrations.
*
* @param clientRegistrationRepository the repository of client registrations
* @return the {@link OAuth2ClientConfigurer} for further configuration
*/
public OAuth2ClientConfigurer<B> clientRegistrationRepository(ClientRegistrationRepository clientRegistrationRepository) {
Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null");
this.getBuilder().setSharedObject(ClientRegistrationRepository.class, clientRegistrationRepository);
return this;
}
/**
* Sets the service for authorized client(s).
*
* @param authorizedClientService the authorized client service
* @return the {@link OAuth2ClientConfigurer} for further configuration
*/
public OAuth2ClientConfigurer<B> authorizedClientService(OAuth2AuthorizedClientService authorizedClientService) {
Assert.notNull(authorizedClientService, "authorizedClientService cannot be null");
this.getBuilder().setSharedObject(OAuth2AuthorizedClientService.class, authorizedClientService);
return this;
}
/**
* Returns the {@link AuthorizationCodeGrantConfigurer} for configuring the OAuth 2.0 Authorization Code Grant.
*
* @return the {@link AuthorizationCodeGrantConfigurer}
*/
public AuthorizationCodeGrantConfigurer authorizationCodeGrant() {
if (this.authorizationCodeGrantConfigurer == null) {
this.authorizationCodeGrantConfigurer = new AuthorizationCodeGrantConfigurer();
}
return this.authorizationCodeGrantConfigurer;
}
/**
* Configuration options for the OAuth 2.0 Authorization Code Grant.
*/
public class AuthorizationCodeGrantConfigurer {
private final AuthorizationEndpointConfig authorizationEndpointConfig = new AuthorizationEndpointConfig();
private final TokenEndpointConfig tokenEndpointConfig = new TokenEndpointConfig();
private AuthorizationCodeGrantConfigurer() {
}
/**
* Returns the {@link AuthorizationEndpointConfig} for configuring the Authorization Server's Authorization Endpoint.
*
* @return the {@link AuthorizationEndpointConfig}
*/
public AuthorizationEndpointConfig authorizationEndpoint() {
return this.authorizationEndpointConfig;
}
/**
* Configuration options for the Authorization Server's Authorization Endpoint.
*/
public class AuthorizationEndpointConfig {
private String authorizationRequestBaseUri;
private AuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository;
private AuthorizationEndpointConfig() {
}
/**
* Sets the base {@code URI} used for authorization requests.
*
* @param authorizationRequestBaseUri the base {@code URI} used for authorization requests
* @return the {@link AuthorizationEndpointConfig} for further configuration
*/
public AuthorizationEndpointConfig baseUri(String authorizationRequestBaseUri) {
Assert.hasText(authorizationRequestBaseUri, "authorizationRequestBaseUri cannot be empty");
this.authorizationRequestBaseUri = authorizationRequestBaseUri;
return this;
}
/**
* Sets the repository used for storing {@link OAuth2AuthorizationRequest}'s.
*
* @param authorizationRequestRepository the repository used for storing {@link OAuth2AuthorizationRequest}'s
* @return the {@link AuthorizationEndpointConfig} for further configuration
*/
public AuthorizationEndpointConfig authorizationRequestRepository(
AuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository) {
Assert.notNull(authorizationRequestRepository, "authorizationRequestRepository cannot be null");
this.authorizationRequestRepository = authorizationRequestRepository;
return this;
}
/**
* Returns the {@link AuthorizationCodeGrantConfigurer} for further configuration.
*
* @return the {@link AuthorizationCodeGrantConfigurer}
*/
public AuthorizationCodeGrantConfigurer and() {
return AuthorizationCodeGrantConfigurer.this;
}
}
/**
* Returns the {@link TokenEndpointConfig} for configuring the Authorization Server's Token Endpoint.
*
* @return the {@link TokenEndpointConfig}
*/
public TokenEndpointConfig tokenEndpoint() {
return this.tokenEndpointConfig;
}
/**
* Configuration options for the Authorization Server's Token Endpoint.
*/
public class TokenEndpointConfig {
private OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient;
private TokenEndpointConfig() {
}
/**
* Sets the client used for requesting the access token credential from the Token Endpoint.
*
* @param accessTokenResponseClient the client used for requesting the access token credential from the Token Endpoint
* @return the {@link TokenEndpointConfig} for further configuration
*/
public TokenEndpointConfig accessTokenResponseClient(
OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient) {
Assert.notNull(accessTokenResponseClient, "accessTokenResponseClient cannot be null");
this.accessTokenResponseClient = accessTokenResponseClient;
return this;
}
/**
* Returns the {@link AuthorizationCodeGrantConfigurer} for further configuration.
*
* @return the {@link AuthorizationCodeGrantConfigurer}
*/
public AuthorizationCodeGrantConfigurer and() {
return AuthorizationCodeGrantConfigurer.this;
}
}
/**
* Returns the {@link OAuth2ClientConfigurer} for further configuration.
*
* @return the {@link OAuth2ClientConfigurer}
*/
public OAuth2ClientConfigurer<B> and() {
return OAuth2ClientConfigurer.this;
}
}
@Override
public void init(B builder) throws Exception {
if (this.authorizationCodeGrantConfigurer != null) {
this.init(builder, this.authorizationCodeGrantConfigurer);
}
}
@Override
public void configure(B builder) throws Exception {
if (this.authorizationCodeGrantConfigurer != null) {
this.configure(builder, this.authorizationCodeGrantConfigurer);
}
}
private void init(B builder, AuthorizationCodeGrantConfigurer authorizationCodeGrantConfigurer) throws Exception {
OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient =
authorizationCodeGrantConfigurer.tokenEndpointConfig.accessTokenResponseClient;
if (accessTokenResponseClient == null) {
accessTokenResponseClient = new NimbusAuthorizationCodeTokenResponseClient();
}
OAuth2AuthorizationCodeAuthenticationProvider authorizationCodeAuthenticationProvider =
new OAuth2AuthorizationCodeAuthenticationProvider(accessTokenResponseClient);
builder.authenticationProvider(this.postProcess(authorizationCodeAuthenticationProvider));
}
private void configure(B builder, AuthorizationCodeGrantConfigurer authorizationCodeGrantConfigurer) throws Exception {
String authorizationRequestBaseUri = authorizationCodeGrantConfigurer.authorizationEndpointConfig.authorizationRequestBaseUri;
if (authorizationRequestBaseUri == null) {
authorizationRequestBaseUri = OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI;
}
OAuth2AuthorizationRequestRedirectFilter authorizationRequestFilter = new OAuth2AuthorizationRequestRedirectFilter(
OAuth2ClientConfigurerUtils.getClientRegistrationRepository(builder), authorizationRequestBaseUri);
if (authorizationCodeGrantConfigurer.authorizationEndpointConfig.authorizationRequestRepository != null) {
authorizationRequestFilter.setAuthorizationRequestRepository(
authorizationCodeGrantConfigurer.authorizationEndpointConfig.authorizationRequestRepository);
}
RequestCache requestCache = builder.getSharedObject(RequestCache.class);
if (requestCache != null) {
authorizationRequestFilter.setRequestCache(requestCache);
}
builder.addFilter(this.postProcess(authorizationRequestFilter));
AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
OAuth2AuthorizationCodeGrantFilter authorizationCodeGrantFilter = new OAuth2AuthorizationCodeGrantFilter(
OAuth2ClientConfigurerUtils.getClientRegistrationRepository(builder),
OAuth2ClientConfigurerUtils.getAuthorizedClientService(builder),
authenticationManager);
if (authorizationCodeGrantConfigurer.authorizationEndpointConfig.authorizationRequestRepository != null) {
authorizationCodeGrantFilter.setAuthorizationRequestRepository(
authorizationCodeGrantConfigurer.authorizationEndpointConfig.authorizationRequestRepository);
}
builder.addFilter(this.postProcess(authorizationCodeGrantFilter));
}
}
@@ -0,0 +1,74 @@
/*
* 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.oauth2.client;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.oauth2.client.InMemoryOAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import java.util.Map;
/**
* Utility methods for the OAuth 2.0 Client {@link AbstractHttpConfigurer}'s.
*
* @author Joe Grandja
* @since 5.1
*/
final class OAuth2ClientConfigurerUtils {
private OAuth2ClientConfigurerUtils() {
}
static <B extends HttpSecurityBuilder<B>> ClientRegistrationRepository getClientRegistrationRepository(B builder) {
ClientRegistrationRepository clientRegistrationRepository = builder.getSharedObject(ClientRegistrationRepository.class);
if (clientRegistrationRepository == null) {
clientRegistrationRepository = getClientRegistrationRepositoryBean(builder);
builder.setSharedObject(ClientRegistrationRepository.class, clientRegistrationRepository);
}
return clientRegistrationRepository;
}
private static <B extends HttpSecurityBuilder<B>> ClientRegistrationRepository getClientRegistrationRepositoryBean(B builder) {
return builder.getSharedObject(ApplicationContext.class).getBean(ClientRegistrationRepository.class);
}
static <B extends HttpSecurityBuilder<B>> OAuth2AuthorizedClientService getAuthorizedClientService(B builder) {
OAuth2AuthorizedClientService authorizedClientService = builder.getSharedObject(OAuth2AuthorizedClientService.class);
if (authorizedClientService == null) {
authorizedClientService = getAuthorizedClientServiceBean(builder);
if (authorizedClientService == null) {
authorizedClientService = new InMemoryOAuth2AuthorizedClientService(getClientRegistrationRepository(builder));
}
builder.setSharedObject(OAuth2AuthorizedClientService.class, authorizedClientService);
}
return authorizedClientService;
}
private static <B extends HttpSecurityBuilder<B>> OAuth2AuthorizedClientService getAuthorizedClientServiceBean(B builder) {
Map<String, OAuth2AuthorizedClientService> authorizedClientServiceMap = BeanFactoryUtils.beansOfTypeIncludingAncestors(
builder.getSharedObject(ApplicationContext.class), OAuth2AuthorizedClientService.class);
if (authorizedClientServiceMap.size() > 1) {
throw new NoUniqueBeanDefinitionException(OAuth2AuthorizedClientService.class, authorizedClientServiceMap.size(),
"Only one matching @Bean of type " + OAuth2AuthorizedClientService.class.getName() + " should be registered.");
}
return (!authorizedClientServiceMap.isEmpty() ? authorizedClientServiceMap.values().iterator().next() : null);
}
}
@@ -26,7 +26,6 @@ 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.oauth2.client.InMemoryOAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationProvider;
import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken;
@@ -53,6 +52,7 @@ import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
@@ -376,8 +376,8 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>> exten
public void init(B http) throws Exception {
OAuth2LoginAuthenticationFilter authenticationFilter =
new OAuth2LoginAuthenticationFilter(
this.getClientRegistrationRepository(),
this.getAuthorizedClientService(),
OAuth2ClientConfigurerUtils.getClientRegistrationRepository(this.getBuilder()),
OAuth2ClientConfigurerUtils.getAuthorizedClientService(this.getBuilder()),
OAuth2LoginAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI);
this.setAuthenticationFilter(authenticationFilter);
this.loginProcessingUrl(OAuth2LoginAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI);
@@ -442,12 +442,16 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>> exten
}
OAuth2AuthorizationRequestRedirectFilter authorizationRequestFilter = new OAuth2AuthorizationRequestRedirectFilter(
this.getClientRegistrationRepository(), authorizationRequestBaseUri);
OAuth2ClientConfigurerUtils.getClientRegistrationRepository(this.getBuilder()), authorizationRequestBaseUri);
if (this.authorizationEndpointConfig.authorizationRequestRepository != null) {
authorizationRequestFilter.setAuthorizationRequestRepository(
this.authorizationEndpointConfig.authorizationRequestRepository);
}
RequestCache requestCache = http.getSharedObject(RequestCache.class);
if (requestCache != null) {
authorizationRequestFilter.setRequestCache(requestCache);
}
http.addFilter(this.postProcess(authorizationRequestFilter));
OAuth2LoginAuthenticationFilter authenticationFilter = this.getAuthenticationFilter();
@@ -466,41 +470,6 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>> exten
return new AntPathRequestMatcher(loginProcessingUrl);
}
private ClientRegistrationRepository getClientRegistrationRepository() {
ClientRegistrationRepository clientRegistrationRepository =
this.getBuilder().getSharedObject(ClientRegistrationRepository.class);
if (clientRegistrationRepository == null) {
clientRegistrationRepository = this.getClientRegistrationRepositoryBean();
this.getBuilder().setSharedObject(ClientRegistrationRepository.class, clientRegistrationRepository);
}
return clientRegistrationRepository;
}
private ClientRegistrationRepository getClientRegistrationRepositoryBean() {
return this.getBuilder().getSharedObject(ApplicationContext.class).getBean(ClientRegistrationRepository.class);
}
private OAuth2AuthorizedClientService getAuthorizedClientService() {
OAuth2AuthorizedClientService authorizedClientService =
this.getBuilder().getSharedObject(OAuth2AuthorizedClientService.class);
if (authorizedClientService == null) {
authorizedClientService = this.getAuthorizedClientServiceBean();
if (authorizedClientService == null) {
authorizedClientService = new InMemoryOAuth2AuthorizedClientService(this.getClientRegistrationRepository());
}
this.getBuilder().setSharedObject(OAuth2AuthorizedClientService.class, authorizedClientService);
}
return authorizedClientService;
}
private OAuth2AuthorizedClientService getAuthorizedClientServiceBean() {
Map<String, OAuth2AuthorizedClientService> authorizedClientServiceMap =
BeanFactoryUtils.beansOfTypeIncludingAncestors(
this.getBuilder().getSharedObject(ApplicationContext.class),
OAuth2AuthorizedClientService.class);
return (!authorizedClientServiceMap.isEmpty() ? authorizedClientServiceMap.values().iterator().next() : null);
}
private GrantedAuthoritiesMapper getGrantedAuthoritiesMapper() {
GrantedAuthoritiesMapper grantedAuthoritiesMapper =
this.getBuilder().getSharedObject(GrantedAuthoritiesMapper.class);
@@ -528,7 +497,8 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>> exten
}
Iterable<ClientRegistration> clientRegistrations = null;
ClientRegistrationRepository clientRegistrationRepository = this.getClientRegistrationRepository();
ClientRegistrationRepository clientRegistrationRepository =
OAuth2ClientConfigurerUtils.getClientRegistrationRepository(this.getBuilder());
ResolvableType type = ResolvableType.forInstance(clientRegistrationRepository).as(Iterable.class);
if (type != ResolvableType.NONE && ClientRegistration.class.isAssignableFrom(type.resolveGenerics()[0])) {
clientRegistrations = (Iterable<ClientRegistration>) clientRegistrationRepository;
@@ -580,5 +550,4 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>> exten
return OAuth2LoginAuthenticationToken.class.isAssignableFrom(authentication);
}
}
}
@@ -55,7 +55,7 @@ import java.lang.annotation.Target;
* &#064;EnableWebFluxSecurity
* public class MyExplicitSecurityConfiguration {
* &#064;Bean
* SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
* http
* .authorizeExchange()
* .anyExchange().authenticated()
@@ -82,7 +82,8 @@ import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import({ServerHttpSecurityConfiguration.class, WebFluxSecurityConfiguration.class})
@Import({ServerHttpSecurityConfiguration.class, WebFluxSecurityConfiguration.class,
ReactiveOAuth2ClientImportSelector.class})
@Configuration
public @interface EnableWebFluxSecurity {
}
@@ -0,0 +1,80 @@
/*
* 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.reactive;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.reactive.result.method.annotation.OAuth2ClientArgumentResolver;
import org.springframework.util.ClassUtils;
import org.springframework.web.reactive.config.WebFluxConfigurer;
import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer;
/**
* {@link Configuration} for OAuth 2.0 Client support.
*
* <p>
* This {@code Configuration} is imported by {@link EnableWebFluxSecurity}
*
* @author Rob Winch
* @since 5.1
*/
final class ReactiveOAuth2ClientImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
boolean oauth2ClientPresent = ClassUtils.isPresent(
"org.springframework.security.oauth2.client.registration.ClientRegistration", getClass().getClassLoader());
return oauth2ClientPresent ?
new String[] { "org.springframework.security.config.annotation.web.reactive.ReactiveOAuth2ClientImportSelector$OAuth2ClientWebFluxSecurityConfiguration" } :
new String[] {};
}
@Configuration
static class OAuth2ClientWebFluxSecurityConfiguration implements WebFluxConfigurer {
private ReactiveClientRegistrationRepository clientRegistrationRepository;
private ReactiveOAuth2AuthorizedClientService authorizedClientService;
@Override
public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) {
if (this.clientRegistrationRepository != null && this.authorizedClientService != null) {
configurer.addCustomResolver(new OAuth2ClientArgumentResolver(this.clientRegistrationRepository, this.authorizedClientService));
}
}
@Autowired(required = false)
public void setClientRegistrationRepository(List<ReactiveClientRegistrationRepository> clientRegistrationRepository) {
if (clientRegistrationRepository.size() == 1) {
this.clientRegistrationRepository = clientRegistrationRepository.get(0);
}
}
@Autowired(required = false)
public void setAuthorizedClientService(List<ReactiveOAuth2AuthorizedClientService> authorizedClientService) {
if (authorizedClientService.size() == 1) {
this.authorizedClientService = authorizedClientService.get(0);
}
}
}
}
@@ -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.
@@ -16,9 +16,14 @@
package org.springframework.security.config.annotation.web.reactive;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager;
@@ -29,8 +34,6 @@ import org.springframework.security.web.reactive.result.method.annotation.Authen
import org.springframework.web.reactive.config.WebFluxConfigurer;
import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer;
import static org.springframework.security.config.web.server.ServerHttpSecurity.http;
/**
* @author Rob Winch
* @since 5.0
@@ -51,6 +54,9 @@ class ServerHttpSecurityConfiguration implements WebFluxConfigurer {
@Autowired(required = false)
private PasswordEncoder passwordEncoder;
@Autowired(required = false)
private BeanFactory beanFactory;
@Override
public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) {
configurer.addCustomResolver(authenticationPrincipalArgumentResolver());
@@ -58,13 +64,19 @@ class ServerHttpSecurityConfiguration implements WebFluxConfigurer {
@Bean
public AuthenticationPrincipalArgumentResolver authenticationPrincipalArgumentResolver() {
return new AuthenticationPrincipalArgumentResolver(this.adapterRegistry);
AuthenticationPrincipalArgumentResolver resolver = new AuthenticationPrincipalArgumentResolver(
this.adapterRegistry);
if(this.beanFactory != null) {
resolver.setBeanResolver(new BeanFactoryResolver(this.beanFactory));
}
return resolver;
}
@Bean(HTTPSECURITY_BEAN_NAME)
@Scope("prototype")
public ServerHttpSecurity httpSecurity() {
return http()
ContextAwareServerHttpSecurity http = new ContextAwareServerHttpSecurity();
return http
.authenticationManager(authenticationManager())
.headers().and()
.logout().and();
@@ -84,4 +96,13 @@ class ServerHttpSecurityConfiguration implements WebFluxConfigurer {
}
return null;
}
private static class ContextAwareServerHttpSecurity extends ServerHttpSecurity implements
ApplicationContextAware {
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
super.setApplicationContext(applicationContext);
}
}
}
@@ -16,6 +16,9 @@
package org.springframework.security.config.annotation.web.reactive;
import java.util.Arrays;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
@@ -25,12 +28,10 @@ import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.reactive.result.view.CsrfRequestDataValueProcessor;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.WebFilterChainProxy;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.web.reactive.result.view.AbstractView;
import java.util.Arrays;
import java.util.List;
/**
* @author Rob Winch
* @since 5.0
@@ -43,6 +44,11 @@ class WebFluxSecurityConfiguration {
private static final String SPRING_SECURITY_WEBFILTERCHAINFILTER_BEAN_NAME = BEAN_NAME_PREFIX + "WebFilterChainFilter";
public static final String REACTIVE_CLIENT_REGISTRATION_REPOSITORY_CLASSNAME = "org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository";
private static final boolean isOAuth2Present = ClassUtils.isPresent(
REACTIVE_CLIENT_REGISTRATION_REPOSITORY_CLASSNAME, WebFluxSecurityConfiguration.class.getClassLoader());
@Autowired(required = false)
private List<SecurityWebFilterChain> securityWebFilterChains;
@@ -81,10 +87,29 @@ class WebFluxSecurityConfiguration {
private SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange()
.anyExchange().authenticated()
.and()
.httpBasic().and()
.formLogin();
return http.build();
.anyExchange().authenticated();
if (isOAuth2Present && OAuth2ClasspathGuard.shouldConfigure(this.context)) {
OAuth2ClasspathGuard.configure(this.context, http);
} else {
http
.httpBasic().and()
.formLogin();
}
SecurityWebFilterChain result = http.build();
return result;
}
private static class OAuth2ClasspathGuard {
static void configure(ApplicationContext context, ServerHttpSecurity http) {
http.oauth2Login();
}
static boolean shouldConfigure(ApplicationContext context) {
ClassLoader loader = context.getClassLoader();
Class<?> reactiveClientRegistrationRepositoryClass = ClassUtils.resolveClassName(REACTIVE_CLIENT_REGISTRATION_REPOSITORY_CLASSNAME, loader);
return context.getBeanNamesForType(reactiveClientRegistrationRepositoryClass).length == 1;
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 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.
@@ -103,7 +103,7 @@ public class AuthenticationManagerBeanDefinitionParser implements BeanDefinition
else {
BeanDefinition provider = resolver.resolve(
providerElt.getNamespaceURI()).parse(providerElt, pc);
Assert.notNull(provider, "Parser for " + providerElt.getNodeName()
Assert.notNull(provider, () -> "Parser for " + providerElt.getNodeName()
+ " returned a null bean definition");
String providerId = pc.getReaderContext().generateBeanName(provider);
pc.registerBeanComponent(new BeanComponentDefinition(provider,
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 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.
@@ -49,7 +49,7 @@ public class CachingUserDetailsService implements UserDetailsService {
user = delegate.loadUserByUsername(username);
}
Assert.notNull(user, "UserDetailsService " + delegate
Assert.notNull(user, () -> "UserDetailsService " + delegate
+ " returned null for username " + username + ". "
+ "This is an interface contract violation");
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 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.
@@ -512,7 +512,7 @@ public class GlobalMethodSecurityBeanDefinitionParser implements BeanDefinitionP
synchronized (delegateMonitor) {
if (delegate == null) {
Assert.state(beanFactory != null,
"BeanFactory must be set to resolve " + authMgrBean);
() -> "BeanFactory must be set to resolve " + authMgrBean);
try {
delegate = beanFactory.getBean(authMgrBean,
AuthenticationManager.class);
@@ -36,7 +36,7 @@ public enum CommonOAuth2Provider {
@Override
public Builder getBuilder(String registrationId) {
ClientRegistration.Builder builder = getBuilder(registrationId,
ClientAuthenticationMethod.BASIC, DEFAULT_LOGIN_REDIRECT_URL);
ClientAuthenticationMethod.BASIC, DEFAULT_REDIRECT_URL);
builder.scope("openid", "profile", "email");
builder.authorizationUri("https://accounts.google.com/o/oauth2/v2/auth");
builder.tokenUri("https://www.googleapis.com/oauth2/v4/token");
@@ -53,7 +53,7 @@ public enum CommonOAuth2Provider {
@Override
public Builder getBuilder(String registrationId) {
ClientRegistration.Builder builder = getBuilder(registrationId,
ClientAuthenticationMethod.BASIC, DEFAULT_LOGIN_REDIRECT_URL);
ClientAuthenticationMethod.BASIC, DEFAULT_REDIRECT_URL);
builder.scope("read:user");
builder.authorizationUri("https://github.com/login/oauth/authorize");
builder.tokenUri("https://github.com/login/oauth/access_token");
@@ -69,7 +69,7 @@ public enum CommonOAuth2Provider {
@Override
public Builder getBuilder(String registrationId) {
ClientRegistration.Builder builder = getBuilder(registrationId,
ClientAuthenticationMethod.POST, DEFAULT_LOGIN_REDIRECT_URL);
ClientAuthenticationMethod.POST, DEFAULT_REDIRECT_URL);
builder.scope("public_profile", "email");
builder.authorizationUri("https://www.facebook.com/v2.8/dialog/oauth");
builder.tokenUri("https://graph.facebook.com/v2.8/oauth/access_token");
@@ -85,7 +85,7 @@ public enum CommonOAuth2Provider {
@Override
public Builder getBuilder(String registrationId) {
ClientRegistration.Builder builder = getBuilder(registrationId,
ClientAuthenticationMethod.BASIC, DEFAULT_LOGIN_REDIRECT_URL);
ClientAuthenticationMethod.BASIC, DEFAULT_REDIRECT_URL);
builder.scope("openid", "profile", "email", "address", "phone");
builder.userNameAttributeName(IdTokenClaimNames.SUB);
builder.clientName("Okta");
@@ -93,7 +93,7 @@ public enum CommonOAuth2Provider {
}
};
private static final String DEFAULT_LOGIN_REDIRECT_URL = "{baseUrl}/login/oauth2/code/{registrationId}";
private static final String DEFAULT_REDIRECT_URL = "{baseUrl}/{action}/oauth2/code/{registrationId}";
protected final ClientRegistration.Builder getBuilder(String registrationId,
ClientAuthenticationMethod method, String redirectUri) {
@@ -1,92 +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.method.configuration
import static org.assertj.core.api.Assertions.assertThat
import static org.junit.Assert.fail
import java.io.Serializable;
import org.springframework.context.ConfigurableApplicationContext
import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.annotation.Configuration
import org.springframework.security.access.AccessDeniedException
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler
import org.springframework.security.authentication.TestingAuthenticationToken
import org.springframework.security.config.annotation.BaseSpringSpec
import org.springframework.security.config.annotation.method.configuration.NamespaceGlobalMethodSecurityTests.BaseMethodConfig;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder
/**
*
* @author Rob Winch
*/
public class NamespaceGlobalMethodSecurityExpressionHandlerTests extends BaseSpringSpec {
def setup() {
SecurityContextHolder.getContext().setAuthentication(
new TestingAuthenticationToken("user", "password","ROLE_USER"))
}
def "global-method-security/expression-handler @PreAuthorize"() {
setup:
context = new AnnotationConfigApplicationContext(BaseMethodConfig,CustomAccessDecisionManagerConfig)
MethodSecurityService service = context.getBean(MethodSecurityService)
when:
service.hasPermission("granted")
then:
noExceptionThrown()
when:
service.hasPermission("denied")
then:
thrown(AccessDeniedException)
}
def "global-method-security/expression-handler @PostAuthorize"() {
setup:
context = new AnnotationConfigApplicationContext(BaseMethodConfig,CustomAccessDecisionManagerConfig)
MethodSecurityService service = context.getBean(MethodSecurityService)
when:
service.postHasPermission("granted")
then:
noExceptionThrown()
when:
service.postHasPermission("denied")
then:
thrown(AccessDeniedException)
}
@EnableGlobalMethodSecurity(prePostEnabled = true)
public static class CustomAccessDecisionManagerConfig extends GlobalMethodSecurityConfiguration {
@Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler()
expressionHandler.permissionEvaluator = new PermissionEvaluator() {
boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
"granted" == targetDomainObject
}
boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
throw new UnsupportedOperationException()
}
}
return expressionHandler
}
}
}
@@ -1,444 +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.method.configuration
import org.springframework.security.access.annotation.Jsr250MethodSecurityMetadataSource
import org.springframework.security.access.intercept.aspectj.AspectJMethodSecurityInterceptor
import static org.assertj.core.api.Assertions.assertThat
import static org.junit.Assert.fail
import java.lang.reflect.Method
import org.springframework.security.access.intercept.aspectj.aspect.AnnotationSecurityAspect
import org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator
import org.springframework.beans.factory.BeanCreationException
import org.springframework.context.ConfigurableApplicationContext
import org.springframework.context.annotation.AdviceMode
import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Import
import org.springframework.core.Ordered
import org.springframework.security.access.AccessDecisionManager
import org.springframework.security.access.AccessDeniedException
import org.springframework.security.access.ConfigAttribute
import org.springframework.security.access.SecurityConfig
import org.springframework.security.access.intercept.AfterInvocationManager
import org.springframework.security.access.intercept.RunAsManager
import org.springframework.security.access.intercept.RunAsManagerImpl
import org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor
import org.springframework.security.access.intercept.aopalliance.MethodSecurityMetadataSourceAdvisor
import org.springframework.security.access.method.AbstractMethodSecurityMetadataSource
import org.springframework.security.access.method.MethodSecurityMetadataSource
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.TestingAuthenticationToken
import org.springframework.security.config.annotation.BaseSpringSpec
import org.springframework.security.config.annotation.authentication.BaseAuthenticationConfig;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration
import org.springframework.security.core.Authentication
import org.springframework.security.core.context.SecurityContextHolder
/**
*
* @author Rob Winch
*/
public class NamespaceGlobalMethodSecurityTests extends BaseSpringSpec {
def setup() {
SecurityContextHolder.getContext().setAuthentication(
new TestingAuthenticationToken("user", "password","ROLE_USER"))
}
// --- access-decision-manager-ref ---
def "custom AccessDecisionManager can be used"() {
setup: "Create an instance with an AccessDecisionManager that always denies access"
context = new AnnotationConfigApplicationContext(BaseMethodConfig,CustomAccessDecisionManagerConfig)
MethodSecurityService service = context.getBean(MethodSecurityService)
when:
service.preAuthorize()
then:
thrown(AccessDeniedException)
when:
service.secured()
then:
thrown(AccessDeniedException)
}
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public static class CustomAccessDecisionManagerConfig extends GlobalMethodSecurityConfiguration {
@Override
protected AccessDecisionManager accessDecisionManager() {
return new DenyAllAccessDecisionManager()
}
public static class DenyAllAccessDecisionManager implements AccessDecisionManager {
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) {
throw new AccessDeniedException("Always Denied")
}
public boolean supports(ConfigAttribute attribute) {
return true
}
public boolean supports(Class<?> clazz) {
return true
}
}
}
// --- authentication-manager-ref ---
def "custom AuthenticationManager can be used"() {
when:
context = new AnnotationConfigApplicationContext(CustomAuthenticationConfig)
MethodSecurityInterceptor interceptor = context.getBean(MethodSecurityInterceptor)
interceptor.authenticationManager.authenticate(SecurityContextHolder.context.authentication)
then:
thrown(UnsupportedOperationException)
}
@EnableGlobalMethodSecurity
public static class CustomAuthenticationConfig extends GlobalMethodSecurityConfiguration {
@Override
protected AuthenticationManager authenticationManager() {
return new AuthenticationManager() {
Authentication authenticate(Authentication authentication) {
throw new UnsupportedOperationException()
}
}
}
}
// --- jsr250-annotations ---
def "enable jsr250"() {
when:
context = new AnnotationConfigApplicationContext(Jsr250Config)
MethodSecurityService service = context.getBean(MethodSecurityService)
then: "@Secured and @PreAuthorize are ignored"
service.secured() == null
service.preAuthorize() == null
when: "@DenyAll method invoked"
service.jsr250()
then: "access is denied"
thrown(AccessDeniedException)
when: "@PermitAll method invoked"
String jsr250PermitAll = service.jsr250PermitAll()
then: "access is allowed"
jsr250PermitAll == null
}
@EnableGlobalMethodSecurity(jsr250Enabled = true)
@Configuration
public static class Jsr250Config extends BaseMethodConfig {
}
// --- metadata-source-ref ---
def "custom MethodSecurityMetadataSource can be used with higher priority than other sources"() {
setup:
context = new AnnotationConfigApplicationContext(BaseMethodConfig,CustomMethodSecurityMetadataSourceConfig)
MethodSecurityService service = context.getBean(MethodSecurityService)
when:
service.preAuthorize()
then:
thrown(AccessDeniedException)
when:
service.secured()
then:
thrown(AccessDeniedException)
when:
service.jsr250()
then:
thrown(AccessDeniedException)
}
@EnableGlobalMethodSecurity
public static class CustomMethodSecurityMetadataSourceConfig extends GlobalMethodSecurityConfiguration {
@Override
protected MethodSecurityMetadataSource customMethodSecurityMetadataSource() {
return new AbstractMethodSecurityMetadataSource() {
public Collection<ConfigAttribute> getAttributes(Method method, Class<?> targetClass) {
// require ROLE_NOBODY for any method on MethodSecurityService class
return MethodSecurityService.isAssignableFrom(targetClass) ? [new SecurityConfig("ROLE_NOBODY")] : []
}
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null
}
}
}
}
// --- mode ---
def "aspectj mode works"() {
when:
context = new AnnotationConfigApplicationContext(AspectJModeConfig)
then:
context.getBean(AnnotationSecurityAspect)
context.getBean(AspectJMethodSecurityInterceptor)
}
@EnableGlobalMethodSecurity(mode = AdviceMode.ASPECTJ, proxyTargetClass = true)
public static class AspectJModeConfig extends BaseMethodConfig {
}
def "aspectj mode works extending GlobalMethodSecurityConfiguration"() {
when:
context = new AnnotationConfigApplicationContext(BaseMethodConfig,AspectJModeExtendsGMSCConfig)
then:
context.getBean(AnnotationSecurityAspect)
context.getBean(AspectJMethodSecurityInterceptor)
}
@EnableGlobalMethodSecurity(mode = AdviceMode.ASPECTJ)
public static class AspectJModeExtendsGMSCConfig extends GlobalMethodSecurityConfiguration {
}
// --- order ---
def order() {
when:
context = new AnnotationConfigApplicationContext(CustomOrderConfig)
MethodSecurityMetadataSourceAdvisor advisor = context.getBean(MethodSecurityMetadataSourceAdvisor)
then:
advisor.order == 135
}
@EnableGlobalMethodSecurity(order = 135)
public static class CustomOrderConfig extends BaseMethodConfig {
}
def "order is defaulted to Ordered.LOWEST_PRECEDENCE when using @EnableGlobalMethodSecurity"() {
when:
context = new AnnotationConfigApplicationContext(DefaultOrderConfig)
MethodSecurityMetadataSourceAdvisor advisor = context.getBean(MethodSecurityMetadataSourceAdvisor)
then:
advisor.order == Ordered.LOWEST_PRECEDENCE
}
@EnableGlobalMethodSecurity
public static class DefaultOrderConfig extends BaseMethodConfig {
}
def "order is defaulted to Ordered.LOWEST_PRECEDENCE when extending GlobalMethodSecurityConfiguration"() {
when:
context = new AnnotationConfigApplicationContext(BaseMethodConfig,DefaultOrderExtendsMethodSecurityConfig)
MethodSecurityMetadataSourceAdvisor advisor = context.getBean(MethodSecurityMetadataSourceAdvisor)
then:
advisor.order == Ordered.LOWEST_PRECEDENCE
}
@EnableGlobalMethodSecurity
public static class DefaultOrderExtendsMethodSecurityConfig extends GlobalMethodSecurityConfiguration {
}
// --- pre-post-annotations ---
def preAuthorize() {
when:
context = new AnnotationConfigApplicationContext(PreAuthorizeConfig)
MethodSecurityService service = context.getBean(MethodSecurityService)
then:
service.secured() == null
service.jsr250() == null
when:
service.preAuthorize()
then:
thrown(AccessDeniedException)
}
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Configuration
public static class PreAuthorizeConfig extends BaseMethodConfig {
}
def "prePostEnabled extends GlobalMethodSecurityConfiguration"() {
when:
context = new AnnotationConfigApplicationContext(BaseMethodConfig,PreAuthorizeExtendsGMSCConfig)
MethodSecurityService service = context.getBean(MethodSecurityService)
then:
service.secured() == null
service.jsr250() == null
when:
service.preAuthorize()
then:
thrown(AccessDeniedException)
}
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Configuration
public static class PreAuthorizeExtendsGMSCConfig extends GlobalMethodSecurityConfiguration {
}
// --- proxy-target-class ---
def "proxying classes works"() {
when:
context = new AnnotationConfigApplicationContext(ProxyTargetClass)
MethodSecurityServiceImpl service = context.getBean(MethodSecurityServiceImpl)
then:
noExceptionThrown()
}
@EnableGlobalMethodSecurity(proxyTargetClass = true)
@Configuration
public static class ProxyTargetClass extends BaseMethodConfig {
}
def "proxying interfaces works"() {
when:
context = new AnnotationConfigApplicationContext(PreAuthorizeConfig)
MethodSecurityService service = context.getBean(MethodSecurityService)
then: "we get an instance of the interface"
noExceptionThrown()
when: "try to cast to the class"
MethodSecurityServiceImpl serviceImpl = service
then: "we get a class cast exception"
thrown(ClassCastException)
}
// --- run-as-manager-ref ---
def "custom RunAsManager"() {
when:
context = new AnnotationConfigApplicationContext(BaseMethodConfig,CustomRunAsManagerConfig)
MethodSecurityService service = context.getBean(MethodSecurityService)
then:
service.runAs().authorities.find { it.authority == "ROLE_RUN_AS_SUPER"}
}
@EnableGlobalMethodSecurity(securedEnabled = true)
public static class CustomRunAsManagerConfig extends GlobalMethodSecurityConfiguration {
@Override
protected RunAsManager runAsManager() {
RunAsManagerImpl runAsManager = new RunAsManagerImpl()
runAsManager.setKey("some key")
return runAsManager
}
}
// --- secured-annotation ---
def "secured enabled"() {
setup:
context = new AnnotationConfigApplicationContext(SecuredConfig)
MethodSecurityService service = context.getBean(MethodSecurityService)
when:
service.secured()
then:
thrown(AccessDeniedException)
and: "service with ROLE_USER allowed"
service.securedUser() == null
and:
service.preAuthorize() == null
service.jsr250() == null
}
@EnableGlobalMethodSecurity(securedEnabled = true)
@Configuration
public static class SecuredConfig extends BaseMethodConfig {
}
// --- after-invocation-provider
def "custom AfterInvocationManager"() {
setup:
context = new AnnotationConfigApplicationContext(BaseMethodConfig,CustomAfterInvocationManagerConfig)
MethodSecurityService service = context.getBean(MethodSecurityService)
when:
service.preAuthorizePermitAll()
then:
AccessDeniedException e = thrown()
e.message == "custom AfterInvocationManager"
}
@EnableGlobalMethodSecurity(prePostEnabled = true)
public static class CustomAfterInvocationManagerConfig extends GlobalMethodSecurityConfiguration {
@Override
protected AfterInvocationManager afterInvocationManager() {
return new AfterInvocationManagerStub()
}
public static class AfterInvocationManagerStub implements AfterInvocationManager {
Object decide(Authentication authentication, Object object, Collection<ConfigAttribute> attributes,
Object returnedObject) throws AccessDeniedException {
throw new AccessDeniedException("custom AfterInvocationManager")
}
boolean supports(ConfigAttribute attribute) {
return true
}
boolean supports(Class<?> clazz) {
return true
}
}
}
// --- misc ---
def "good error message when no Enable annotation"() {
when:
context = new AnnotationConfigApplicationContext(ExtendsNoEnableAnntotationConfig)
MethodSecurityInterceptor interceptor = context.getBean(MethodSecurityInterceptor)
interceptor.authenticationManager.authenticate(SecurityContextHolder.context.authentication)
then:
BeanCreationException e = thrown()
e.message.contains(EnableGlobalMethodSecurity.class.getName() + " is required")
}
@Configuration
public static class ExtendsNoEnableAnntotationConfig extends GlobalMethodSecurityConfiguration {
@Override
protected AuthenticationManager authenticationManager() {
return new AuthenticationManager() {
Authentication authenticate(Authentication authentication) {
throw new UnsupportedOperationException()
}
}
}
}
def "import subclass of GlobalMethodSecurityConfiguration"() {
when:
context = new AnnotationConfigApplicationContext(ImportSubclassGMSCConfig)
MethodSecurityService service = context.getBean(MethodSecurityService)
then:
service.secured() == null
service.jsr250() == null
when:
service.preAuthorize()
then:
thrown(AccessDeniedException)
}
@Configuration
@Import(PreAuthorizeExtendsGMSCConfig)
public static class ImportSubclassGMSCConfig extends BaseMethodConfig {
}
@Configuration
public static class BaseMethodConfig extends BaseAuthenticationConfig {
@Bean
public MethodSecurityService methodSecurityService() {
return new MethodSecurityServiceImpl()
}
}
}
@@ -1,143 +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.sec2758;
import javax.annotation.security.RolesAllowed;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.core.PriorityOrdered;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.access.annotation.Jsr250MethodSecurityMetadataSource;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authentication.TestingAuthenticationToken
import org.springframework.security.config.annotation.BaseSpringSpec
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.annotation.web.configuration.sec2377.a.*
import org.springframework.security.config.annotation.web.configuration.sec2377.b.*
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext
public class Sec2758Tests extends BaseSpringSpec {
def cleanup() {
SecurityContextHolder.clearContext()
}
def "SEC-2758: Verify Passivity Restored with Advice from JIRA"() {
setup:
SecurityContextHolder.context.authentication = new TestingAuthenticationToken("user", "pass", "USER")
loadConfig(SecurityConfig)
Service service = context.getBean(Service)
when:
findFilter(FilterSecurityInterceptor).doFilter(new MockHttpServletRequest(), new MockHttpServletResponse(), new MockFilterChain())
then:
noExceptionThrown()
when:
service.doPreAuthorize()
then:
noExceptionThrown()
when:
service.doJsr250()
then:
noExceptionThrown()
}
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
static class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().hasAnyAuthority("USER");
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
auth
.inMemoryAuthentication()
.withUser("user").password("password").authorities("USER")
}
@Bean
Service service() {
return new ServiceImpl()
}
@Bean
static DefaultRolesPrefixPostProcessor defaultRolesPrefixPostProcessor() {
new DefaultRolesPrefixPostProcessor()
}
}
interface Service {
void doPreAuthorize()
void doJsr250()
}
static class ServiceImpl implements Service {
@PreAuthorize("hasRole('USER')")
void doPreAuthorize() {}
@RolesAllowed("USER")
void doJsr250() {}
}
static class DefaultRolesPrefixPostProcessor implements BeanPostProcessor, PriorityOrdered {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
if(bean instanceof Jsr250MethodSecurityMetadataSource) {
((Jsr250MethodSecurityMetadataSource) bean).setDefaultRolePrefix(null);
}
if(bean instanceof DefaultMethodSecurityExpressionHandler) {
((DefaultMethodSecurityExpressionHandler) bean).setDefaultRolePrefix(null);
}
if(bean instanceof DefaultWebSecurityExpressionHandler) {
((DefaultWebSecurityExpressionHandler) bean).setDefaultRolePrefix(null);
}
return bean;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
@Override
public int getOrder() {
return PriorityOrdered.HIGHEST_PRECEDENCE;
}
}
}
@@ -1,34 +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.builders;
import org.springframework.security.config.annotation.web.configuration.BaseWebConfig;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.UrlAuthorizationConfigurer;
@EnableWebSecurity
public class DisableUseExpressionsConfig extends BaseWebConfig {
// @formatter:off
protected void configure(HttpSecurity http) throws Exception {
// This config is also on UrlAuthorizationConfigurer javadoc
http
.apply(new UrlAuthorizationConfigurer<>(getApplicationContext())).getRegistry()
.antMatchers("/users**", "/sessions/**").hasRole("USER")
.antMatchers("/signup").hasRole("ANONYMOUS")
.anyRequest().hasRole("USER");
}
// @formatter:on
}
@@ -1,499 +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.builders
import javax.servlet.http.HttpServletRequest
import org.springframework.context.annotation.Configuration
import org.springframework.security.access.AccessDecisionManager
import org.springframework.security.access.ConfigAttribute
import org.springframework.security.access.vote.AuthenticatedVoter
import org.springframework.security.access.vote.RoleVoter
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.BadCredentialsException
import org.springframework.security.config.annotation.BaseSpringSpec
import org.springframework.security.config.annotation.web.builders.NamespaceHttpTests.AuthenticationManagerRefConfig.CustomAuthenticationManager
import org.springframework.security.config.annotation.web.builders.NamespaceHttpTests.RequestMatcherRefConfig.MyRequestMatcher
import org.springframework.security.config.annotation.web.configuration.BaseWebConfig
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configurers.UrlAuthorizationConfigurer
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.Authentication
import org.springframework.security.core.AuthenticationException
import org.springframework.security.web.FilterInvocation
import org.springframework.security.web.access.ExceptionTranslationFilter
import org.springframework.security.web.access.expression.ExpressionBasedFilterInvocationSecurityMetadataSource
import org.springframework.security.web.access.expression.WebExpressionVoter
import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter
import org.springframework.security.web.context.HttpSessionSecurityContextRepository
import org.springframework.security.web.context.NullSecurityContextRepository
import org.springframework.security.web.context.SecurityContextPersistenceFilter
import org.springframework.security.web.jaasapi.JaasApiIntegrationFilter
import org.springframework.security.web.savedrequest.HttpSessionRequestCache
import org.springframework.security.web.savedrequest.NullRequestCache
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
import org.springframework.security.web.session.SessionManagementFilter
import org.springframework.security.web.util.matcher.RegexRequestMatcher
import org.springframework.security.web.util.matcher.RequestMatcher
/**
* Tests to verify that all the functionality of <http> attributes is present
*
* @author Rob Winch
*
*/
public class NamespaceHttpTests extends BaseSpringSpec {
def "http@access-decision-manager-ref"() {
setup:
AccessDecisionManagerRefConfig.ACCESS_DECISION_MGR = Mock(AccessDecisionManager)
AccessDecisionManagerRefConfig.ACCESS_DECISION_MGR.supports(FilterInvocation) >> true
AccessDecisionManagerRefConfig.ACCESS_DECISION_MGR.supports(_ as ConfigAttribute) >> true
when:
loadConfig(AccessDecisionManagerRefConfig)
then:
findFilter(FilterSecurityInterceptor).accessDecisionManager == AccessDecisionManagerRefConfig.ACCESS_DECISION_MGR
}
@Configuration
static class AccessDecisionManagerRefConfig extends BaseWebConfig {
static AccessDecisionManager ACCESS_DECISION_MGR
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().permitAll()
.accessDecisionManager(ACCESS_DECISION_MGR)
}
}
def "http@access-denied-page"() {
when:
loadConfig(AccessDeniedPageConfig)
then:
findFilter(ExceptionTranslationFilter).accessDeniedHandler.errorPage == "/AccessDeniedPageConfig"
}
@Configuration
static class AccessDeniedPageConfig extends BaseWebConfig {
protected void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling()
.accessDeniedPage("/AccessDeniedPageConfig")
}
}
def "http@authentication-manager-ref"() {
when: "Specify AuthenticationManager"
loadConfig(AuthenticationManagerRefConfig)
then: "Populates the AuthenticationManager"
findFilter(FilterSecurityInterceptor).authenticationManager.parent.class == CustomAuthenticationManager
}
@Configuration
static class AuthenticationManagerRefConfig extends BaseWebConfig {
// demo authentication-manager-ref (could be any value)
@Override
protected AuthenticationManager authenticationManager() throws Exception {
return new CustomAuthenticationManager();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().hasRole("USER");
}
static class CustomAuthenticationManager implements AuthenticationManager {
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
throw new BadCredentialsException("This always fails");
}
}
}
// Note: There is no http@auto-config equivalent in Java Config
def "http@create-session=always"() {
when:
loadConfig(IfRequiredConfig)
then:
findFilter(SecurityContextPersistenceFilter).forceEagerSessionCreation == false
findFilter(SecurityContextPersistenceFilter).repo.allowSessionCreation == true
findFilter(SessionManagementFilter).securityContextRepository.allowSessionCreation == true
findFilter(ExceptionTranslationFilter).requestCache.class == HttpSessionRequestCache
}
@Configuration
static class CreateSessionAlwaysConfig extends BaseWebConfig {
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.ALWAYS);
}
}
def "http@create-session=stateless"() {
when:
loadConfig(CreateSessionStatelessConfig)
then:
findFilter(SecurityContextPersistenceFilter).forceEagerSessionCreation == false
findFilter(SecurityContextPersistenceFilter).repo.class == NullSecurityContextRepository
findFilter(SessionManagementFilter).securityContextRepository.class == NullSecurityContextRepository
findFilter(ExceptionTranslationFilter).requestCache.class == NullRequestCache
findFilter(RequestCacheAwareFilter).requestCache.class == NullRequestCache
}
@Configuration
static class CreateSessionStatelessConfig extends BaseWebConfig {
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
def "http@create-session=ifRequired"() {
when:
loadConfig(IfRequiredConfig)
then:
findFilter(SecurityContextPersistenceFilter).forceEagerSessionCreation == false
findFilter(SecurityContextPersistenceFilter).repo.allowSessionCreation == true
findFilter(SessionManagementFilter).securityContextRepository.allowSessionCreation == true
}
@Configuration
static class IfRequiredConfig extends BaseWebConfig {
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
}
}
def "http@create-session defaults to ifRequired"() {
when:
loadConfig(IfRequiredConfig)
then:
findFilter(SecurityContextPersistenceFilter).forceEagerSessionCreation == false
findFilter(SecurityContextPersistenceFilter).repo.allowSessionCreation == true
findFilter(SessionManagementFilter).securityContextRepository.allowSessionCreation == true
}
def "http@create-session=never"() {
when:
loadConfig(CreateSessionNeverConfig)
then:
findFilter(SecurityContextPersistenceFilter).forceEagerSessionCreation == false
findFilter(SecurityContextPersistenceFilter).repo.allowSessionCreation == false
findFilter(SessionManagementFilter).securityContextRepository.allowSessionCreation == false
}
@Configuration
static class CreateSessionNeverConfig extends BaseWebConfig {
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.NEVER);
}
}
@Configuration
static class DefaultCreateSessionConfig extends BaseWebConfig {
protected void configure(HttpSecurity http) throws Exception {
}
}
def "http@disable-url-rewriting = true (default for Java Config)"() {
when:
loadConfig(DefaultUrlRewritingConfig)
then:
findFilter(SecurityContextPersistenceFilter).repo.disableUrlRewriting
}
@Configuration
static class DefaultUrlRewritingConfig extends BaseWebConfig {
protected void configure(HttpSecurity http) throws Exception {
}
}
// http@disable-url-rewriting is on by default to disable it create a custom HttpSecurityContextRepository and use security-context-repository-ref
def "http@disable-url-rewriting = false"() {
when:
loadConfig(EnableUrlRewritingConfig)
then:
findFilter(SecurityContextPersistenceFilter).repo.disableUrlRewriting == false
}
@Configuration
static class EnableUrlRewritingConfig extends BaseWebConfig {
protected void configure(HttpSecurity http) throws Exception {
HttpSessionSecurityContextRepository repository = new HttpSessionSecurityContextRepository()
repository.disableUrlRewriting = false // explicitly configured
http.
securityContext()
.securityContextRepository(repository)
}
}
def "http@entry-point-ref"() {
when:
loadConfig(EntryPointRefConfig)
then:
findFilter(ExceptionTranslationFilter).authenticationEntryPoint.loginFormUrl == "/EntryPointRefConfig"
}
@Configuration
static class EntryPointRefConfig extends BaseWebConfig {
protected void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling()
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/EntryPointRefConfig"))
}
}
def "http@jaas-api-provision"() {
when:
loadConfig(JaasApiProvisionConfig)
then:
findFilter(JaasApiIntegrationFilter)
}
@Configuration
static class JaasApiProvisionConfig extends BaseWebConfig {
protected void configure(HttpSecurity http) throws Exception {
http
.addFilter(new JaasApiIntegrationFilter())
}
}
// http@name is not available since it can be done w/ standard bean configuration easily
def "http@once-per-request=true"() {
when:
loadConfig(OncePerRequestConfig)
then:
findFilter(FilterSecurityInterceptor).observeOncePerRequest
}
@Configuration
static class OncePerRequestConfig extends BaseWebConfig {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().hasRole("USER");
}
}
def "http@once-per-request=false"() {
when:
loadConfig(OncePerRequestFalseConfig)
then:
!findFilter(FilterSecurityInterceptor).observeOncePerRequest
}
@Configuration
static class OncePerRequestFalseConfig extends BaseWebConfig {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.
authorizeRequests()
.filterSecurityInterceptorOncePerRequest(false)
.antMatchers("/users**","/sessions/**").hasRole("ADMIN")
.antMatchers("/signup").permitAll()
.anyRequest().hasRole("USER");
}
}
def "http@realm"() {
setup:
loadConfig(RealmConfig)
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.getHeader("WWW-Authenticate") == 'Basic realm="RealmConfig"'
}
@Configuration
static class RealmConfig extends BaseWebConfig {
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic().realmName("RealmConfig")
}
}
// http@request-matcher is not available (instead request securityMatcher instances are used)
def "http@request-matcher-ref ant"() {
when:
loadConfig(RequestMatcherAntConfig)
then:
filterChain(0).requestMatcher.pattern == "/api/**"
}
@Configuration
static class RequestMatcherAntConfig extends BaseWebConfig {
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/**")
}
}
def "http@request-matcher-ref regex"() {
when:
loadConfig(RequestMatcherRegexConfig)
then:
filterChain(0).requestMatcher.class == RegexRequestMatcher
filterChain(0).requestMatcher.pattern.matcher("/regex/a")
filterChain(0).requestMatcher.pattern.matcher("/regex/b")
!filterChain(0).requestMatcher.pattern.matcher("/regex1/b")
}
@Configuration
static class RequestMatcherRegexConfig extends BaseWebConfig {
protected void configure(HttpSecurity http) throws Exception {
http
.regexMatcher("/regex/.*")
}
}
def "http@request-matcher-ref"() {
when:
loadConfig(RequestMatcherRefConfig)
then:
filterChain(0).requestMatcher.class == MyRequestMatcher
}
@Configuration
static class RequestMatcherRefConfig extends BaseWebConfig {
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatcher(new MyRequestMatcher());
}
static class MyRequestMatcher implements RequestMatcher {
public boolean matches(HttpServletRequest request) {
return true;
}
}
}
def "http@security=none"() {
when:
loadConfig(SecurityNoneConfig)
then:
filterChain(0).requestMatcher.pattern == "/resources/**"
filterChain(0).filters.empty
filterChain(1).requestMatcher.pattern == "/public/**"
filterChain(1).filters.empty
}
@Configuration
static class SecurityNoneConfig extends BaseWebConfig {
@Override
public void configure(WebSecurity web)
throws Exception {
web
.ignoring()
.antMatchers("/resources/**","/public/**")
}
@Override
protected void configure(HttpSecurity http) throws Exception {}
}
def "http@security-context-repository-ref"() {
when:
loadConfig(SecurityContextRepoConfig)
then:
findFilter(SecurityContextPersistenceFilter).repo.class == NullSecurityContextRepository
}
@Configuration
static class SecurityContextRepoConfig extends BaseWebConfig {
protected void configure(HttpSecurity http) throws Exception {
http
.securityContext()
.securityContextRepository(new NullSecurityContextRepository()) // security-context-repository-ref
}
}
def "http@servlet-api-provision=false"() {
when:
loadConfig(ServletApiProvisionConfig)
then:
findFilter(SecurityContextHolderAwareRequestFilter) == null
}
@Configuration
static class ServletApiProvisionConfig extends BaseWebConfig {
protected void configure(HttpSecurity http) throws Exception {
http.servletApi().disable()
}
}
def "http@servlet-api-provision defaults to true"() {
when:
loadConfig(ServletApiProvisionDefaultsConfig)
then:
findFilter(SecurityContextHolderAwareRequestFilter) != null
}
@Configuration
static class ServletApiProvisionDefaultsConfig extends BaseWebConfig {
protected void configure(HttpSecurity http) throws Exception {
}
}
def "http@use-expressions=true"() {
when:
loadConfig(UseExpressionsConfig)
then:
findFilter(FilterSecurityInterceptor).securityMetadataSource.class == ExpressionBasedFilterInvocationSecurityMetadataSource
findFilter(FilterSecurityInterceptor).accessDecisionManager.decisionVoters.collect { it.class } == [WebExpressionVoter]
}
@EnableWebSecurity
static class UseExpressionsConfig extends BaseWebConfig {
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/users**","/sessions/**").hasRole("USER")
.antMatchers("/signup").permitAll()
.anyRequest().hasRole("USER")
}
}
def "http@use-expressions=false"() {
when:
loadConfig(DisableUseExpressionsConfig)
then:
findFilter(FilterSecurityInterceptor).securityMetadataSource.class == DefaultFilterInvocationSecurityMetadataSource
findFilter(FilterSecurityInterceptor).accessDecisionManager.decisionVoters.collect { it.class } == [RoleVoter, AuthenticatedVoter]
}
}
@@ -1,144 +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.configuration
import org.springframework.mock.web.MockServletContext
import org.springframework.security.authentication.TestingAuthenticationToken
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.security.core.context.SecurityContext
import org.springframework.security.core.context.SecurityContextImpl
import org.springframework.security.core.userdetails.PasswordEncodedUser
import org.springframework.security.core.userdetails.User
import org.springframework.security.web.context.HttpSessionSecurityContextRepository
import org.springframework.test.context.web.WebAppConfiguration
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.mock.web.MockHttpServletRequest
import org.springframework.security.authentication.AnonymousAuthenticationToken
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
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.web.authentication.AnonymousAuthenticationFilter
import org.springframework.security.web.debug.DebugFilter
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
class EnableWebSecurityTests extends BaseSpringSpec {
def "@Bean(BeanIds.AUTHENTICATION_MANAGER) includes HttpSecurity's AuthenticationManagerBuilder"() {
when:
loadConfig(SecurityConfig)
AuthenticationManager authenticationManager = context.getBean(AuthenticationManager)
AnonymousAuthenticationToken anonymousAuthToken = findFilter(AnonymousAuthenticationFilter).createAuthentication(new MockHttpServletRequest())
then:
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken("user", "password"))
authenticationManager.authenticate(anonymousAuthToken)
}
@EnableWebSecurity
static class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser(PasswordEncodedUser.user());
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean()
throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/*").hasRole("USER")
.and()
.formLogin();
}
}
def "@EnableWebSecurity on superclass"() {
when:
loadConfig(ChildSecurityConfig)
then:
context.getBean("springSecurityFilterChain", DebugFilter)
}
@Configuration
static class ChildSecurityConfig extends DebugSecurityConfig {
}
@EnableWebSecurity(debug=true)
static class DebugSecurityConfig extends WebSecurityConfigurerAdapter {
}
def "SEC-2942: EnableWebSecurity adds AuthenticationPrincipalArgumentResolver"() {
setup:
def username = "test"
context = new AnnotationConfigWebApplicationContext()
context.servletContext = new MockServletContext()
context.register(AuthenticationPrincipalConfig)
context.refresh()
SecurityContext securityContext = new SecurityContextImpl(authentication: new TestingAuthenticationToken(username, "pass", "ROLE_USER"))
MockMvc mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.addFilters(springSecurityFilterChain)
.build()
when:
String body = mockMvc
.perform(get("/").sessionAttr(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, securityContext))
.andReturn().response.contentAsString
then:
body == username
}
@EnableWebSecurity
@EnableWebMvc
@Configuration
static class AuthenticationPrincipalConfig {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
auth.inMemoryAuthentication()
}
@RestController
static class AuthController {
@RequestMapping("/")
String principal(@AuthenticationPrincipal String principal) {
principal
}
}
}
}
@@ -1,113 +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.configuration;
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.FatalBeanException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.TestingAuthenticationToken
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.config.annotation.BaseSpringSpec
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
public class Sec2515Tests extends BaseSpringSpec {
def "SEC-2515: Prevent StackOverflow with bean graph cycle"() {
when:
loadConfig(StackOverflowSecurityConfig)
then:
thrown(FatalBeanException)
}
@EnableWebSecurity
static class StackOverflowSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
@Bean
public AuthenticationManager authenticationManagerBean()
throws Exception {
return super.authenticationManagerBean();
}
}
def "Custom Name Prevent StackOverflow with bean graph cycle"() {
when:
loadConfig(StackOverflowSecurityConfig)
then:
thrown(FatalBeanException)
}
@EnableWebSecurity
static class CustomBeanNameStackOverflowSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
@Bean(name="custom")
public AuthenticationManager authenticationManagerBean()
throws Exception {
return super.authenticationManagerBean();
}
}
def "SEC-2549: Can load with child classloader"() {
setup:
CanLoadWithChildConfig.AM = Mock(AuthenticationManager)
context = new AnnotationConfigApplicationContext()
context.classLoader = new URLClassLoader(new URL[0], context.classLoader)
context.register(CanLoadWithChildConfig)
context.refresh()
when:
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken("user", "password"))
then:
noExceptionThrown()
1 * CanLoadWithChildConfig.AM.authenticate(_) >> new TestingAuthenticationToken("user","password","ROLE_USER")
}
@EnableWebSecurity
static class CanLoadWithChildConfig extends WebSecurityConfigurerAdapter {
static AuthenticationManager AM
@Bean
public AuthenticationManager am() {
AM
}
}
def "SEC-2515: @Bean still works when configure(AuthenticationManagerBuilder) used"() {
when:
loadConfig(SecurityConfig)
then:
noExceptionThrown();
}
@EnableWebSecurity
static class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
@Bean
public AuthenticationManager authenticationManagerBean()
throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.inMemoryAuthentication()
}
}
}
@@ -1,344 +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.configuration
import java.lang.reflect.Modifier
import static org.junit.Assert.*
import org.springframework.beans.factory.BeanCreationException
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.annotation.Order
import org.springframework.expression.ExpressionParser
import org.springframework.mock.web.MockHttpServletRequest
import org.springframework.security.access.expression.SecurityExpressionHandler
import org.springframework.security.authentication.AuthenticationManager
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.builders.WebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurationTests.DuplicateOrderConfig;
import org.springframework.security.web.FilterChainProxy
import org.springframework.security.web.SecurityFilterChain
import org.springframework.security.web.access.DefaultWebInvocationPrivilegeEvaluator
import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler
import org.springframework.security.web.util.matcher.AnyRequestMatcher
import org.springframework.test.util.ReflectionTestUtils
/**
* @author Rob Winch
*
*/
class WebSecurityConfigurationTests extends BaseSpringSpec {
def "WebSecurityConfigurers are sorted"() {
when:
loadConfig(SortedWebSecurityConfigurerAdaptersConfig);
List<SecurityFilterChain> filterChains = context.getBean(FilterChainProxy).filterChains
then:
filterChains[0].requestMatcher.pattern == "/ignore1"
filterChains[0].filters.empty
filterChains[1].requestMatcher.pattern == "/ignore2"
filterChains[1].filters.empty
filterChains[2].requestMatcher.pattern == "/role1/**"
filterChains[3].requestMatcher.pattern == "/role2/**"
filterChains[4].requestMatcher.pattern == "/role3/**"
filterChains[5].requestMatcher.class == AnyRequestMatcher
}
@EnableWebSecurity
static class SortedWebSecurityConfigurerAdaptersConfig {
public AuthenticationManager authenticationManager() throws Exception {
return new AuthenticationManagerBuilder()
.inMemoryAuthentication()
.withUser("marissa").password("koala").roles("USER").and()
.withUser("paul").password("emu").roles("USER").and()
.and()
.build();
}
@Configuration
@Order(1)
public static class WebConfigurer1 extends WebSecurityConfigurerAdapter {
@Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/ignore1","/ignore2");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/role1/**")
.authorizeRequests()
.anyRequest().hasRole("1");
}
}
@Configuration
@Order(2)
public static class WebConfigurer2 extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/role2/**")
.authorizeRequests()
.anyRequest().hasRole("2");
}
}
@Configuration
@Order(3)
public static class WebConfigurer3 extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/role3/**")
.authorizeRequests()
.anyRequest().hasRole("3");
}
}
@Configuration
public static class WebConfigurer4 extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().hasRole("4");
}
}
}
def "WebSecurityConfigurers fails with duplicate order"() {
when:
loadConfig(DuplicateOrderConfig);
then:
BeanCreationException e = thrown()
e.message.contains "@Order on WebSecurityConfigurers must be unique"
e.message.contains DuplicateOrderConfig.WebConfigurer1.class.name
e.message.contains DuplicateOrderConfig.WebConfigurer2.class.name
}
@EnableWebSecurity
static class DuplicateOrderConfig {
public AuthenticationManager authenticationManager() throws Exception {
return new AuthenticationManagerBuilder()
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER").and()
.and()
.build();
}
@Configuration
public static class WebConfigurer1 extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/role1/**")
.authorizeRequests()
.anyRequest().hasRole("1");
}
}
@Configuration
public static class WebConfigurer2 extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/role2/**")
.authorizeRequests()
.anyRequest().hasRole("2");
}
}
}
def "Override privilegeEvaluator"() {
setup:
WebInvocationPrivilegeEvaluator privilegeEvaluator = Mock()
PrivilegeEvaluatorConfigurerAdapterConfig.PE = privilegeEvaluator
when:
loadConfig(PrivilegeEvaluatorConfigurerAdapterConfig)
then:
context.getBean(WebInvocationPrivilegeEvaluator) == privilegeEvaluator
}
@EnableWebSecurity
static class PrivilegeEvaluatorConfigurerAdapterConfig extends WebSecurityConfigurerAdapter {
static WebInvocationPrivilegeEvaluator PE
@Override
public void configure(WebSecurity web) throws Exception {
web
.privilegeEvaluator(PE)
}
}
def "Override webSecurityExpressionHandler"() {
setup:
SecurityExpressionHandler expressionHandler = Mock()
ExpressionParser parser = Mock()
WebSecurityExpressionHandlerConfig.EH = expressionHandler
when:
loadConfig(WebSecurityExpressionHandlerConfig)
then:
context.getBean(SecurityExpressionHandler) == expressionHandler
1 * expressionHandler.getExpressionParser() >> parser
}
@EnableWebSecurity
static class WebSecurityExpressionHandlerConfig extends WebSecurityConfigurerAdapter {
static SecurityExpressionHandler EH
@Override
public void configure(WebSecurity web) throws Exception {
web
.expressionHandler(EH)
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.expressionHandler(EH)
.anyRequest().authenticated()
}
}
def "#138 webSecurityExpressionHandler defaults"() {
when:
loadConfig(WebSecurityExpressionHandlerDefaultsConfig)
then:
SecurityExpressionHandler wseh = context.getBean(SecurityExpressionHandler)
wseh instanceof DefaultWebSecurityExpressionHandler
}
@EnableWebSecurity
static class WebSecurityExpressionHandlerDefaultsConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
}
}
def "#138 WebInvocationPrivilegeEvaluator defaults"() {
when:
loadConfig(WebInvocationPrivilegeEvaluatorDefaultsConfig)
then:
WebInvocationPrivilegeEvaluator wipe = context.getBean(WebInvocationPrivilegeEvaluator)
wipe instanceof DefaultWebInvocationPrivilegeEvaluator
wipe.securityInterceptor != null
}
@EnableWebSecurity
static class WebInvocationPrivilegeEvaluatorDefaultsConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
}
}
def "SEC-2303: DefaultExpressionHandler has bean resolver set"() {
when:
loadConfig(DefaultExpressionHandlerSetsBeanResolverConfig)
then: "the exposed bean has a BeanResolver set"
ReflectionTestUtils.getField(context.getBean(SecurityExpressionHandler),"br")
when:
springSecurityFilterChain.doFilter(request, response, chain)
then: "we can use the BeanResolver with a grant"
noExceptionThrown()
when: "we can use the Beanresolver with a deny"
springSecurityFilterChain.doFilter(new MockHttpServletRequest(method:'POST'), response, chain)
then:
noExceptionThrown()
}
@EnableWebSecurity
static class DefaultExpressionHandlerSetsBeanResolverConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().access("request.method == 'GET' ? @b.grant() : @b.deny()")
}
@Bean
public MyBean b() {
new MyBean()
}
static class MyBean {
boolean deny() {
false
}
boolean grant() {
true
}
}
}
def "SEC-2461: Multiple WebSecurityConfiguration instances cause null springSecurityFilterChain"() {
setup:
def parent = loadConfig(ParentConfig)
def child = new AnnotationConfigApplicationContext()
child.register(ChildConfig)
child.parent = parent
when:
child.refresh()
then: "springSecurityFilterChain can be found in parent and child"
parent.getBean("springSecurityFilterChain")
child.getBean("springSecurityFilterChain")
and: "springSecurityFilterChain is defined in both parent and child (don't search parent)"
parent.containsBeanDefinition("springSecurityFilterChain")
child.containsBeanDefinition("springSecurityFilterChain")
cleanup:
child?.close()
// parent.close() is in superclass
}
@EnableWebSecurity
static class ParentConfig extends WebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
auth.inMemoryAuthentication()
}
}
@EnableWebSecurity
static class ChildConfig extends WebSecurityConfigurerAdapter { }
def "SEC-2773: delegatingApplicationListener is static method"() {
expect: 'delegatingApplicationListener to prevent premature instantiation of WebSecurityConfiguration'
Modifier.isStatic(WebSecurityConfiguration.metaClass.methods.find { it.name == 'delegatingApplicationListener'}.modifiers)
}
}
@@ -1,38 +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.configuration.sec2377;
import org.springframework.security.config.annotation.BaseSpringSpec
import org.springframework.security.config.annotation.web.configuration.sec2377.a.*
import org.springframework.security.config.annotation.web.configuration.sec2377.b.*
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext
public class Sec2377Tests extends BaseSpringSpec {
def "SEC-2377: Error reporting with multiple EnableWebSecurity from other packages"() {
when:
AnnotationConfigWebApplicationContext parent = new AnnotationConfigWebApplicationContext()
parent.register(Sec2377AConfig)
parent.refresh()
AnnotationConfigWebApplicationContext child = new AnnotationConfigWebApplicationContext()
child.register(Sec2377BConfig)
child.parent = parent
child.refresh()
then:
noExceptionThrown();
}
}
@@ -1,53 +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.context.annotation.Configuration
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.authentication.AnonymousAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter
/**
*
* @author Rob Winch
*/
class AnonymousConfigurerTests extends BaseSpringSpec {
def "invoke logout twice does not override"() {
when:
loadConfig(InvokeTwiceDoesNotOverride)
then:
findFilter(AnonymousAuthenticationFilter).key == "custom"
}
@EnableWebSecurity
static class InvokeTwiceDoesNotOverride extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.anonymous()
.key("custom")
.and()
.anonymous()
}
}
}
@@ -186,13 +186,14 @@ class LogoutConfigurerTests extends BaseSpringSpec {
}
}
def "LogoutConfigurer content negotiation default redirects"() {
def "LogoutConfigurer content negotiation text/html redirects"() {
setup:
loadConfig(LogoutHandlerContentNegotiation)
when:
login()
request.method = 'POST'
request.servletPath = '/logout'
request.addHeader('Accept', 'text/html')
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == 302
@@ -1,39 +0,0 @@
/*
* Copyright 2002-2011 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.debug
import org.springframework.security.config.BeanIds
import org.springframework.security.config.http.AbstractHttpConfigTests
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.debug.DebugFilter;
class SecurityDebugBeanFactoryPostProcessorTest extends AbstractHttpConfigTests {
// SEC-1885
def 'SEC-1885 - SecurityDebugBeanFactoryPostProcessor works when dependencies have Autowired constructor'() {
when: 'debug used and FilterChainProxy has dependency with @Autowired constructor'
xml.debug()
httpAutoConfig {}
xml.'authentication-manager'() {
'authentication-provider'('ref': 'authProvider')
}
xml.'context:component-scan'('base-package':'org.springframework.security.config.debug')
createAppContext('')
then: 'TestAuthenticationProvider.<init>() is not thrown'
appContext.getBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN) instanceof DebugFilter
appContext.getBean(BeanIds.FILTER_CHAIN_PROXY) instanceof FilterChainProxy
}
}
@@ -1,33 +0,0 @@
/*
* Copyright 2002-2011 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.doc
/**
* Represents a Spring Security XSD Attribute. It is created when parsing the current xsd to compare to the documented appendix.
*
* @author Rob Winch
* @see SpringSecurityXsdParser
* @see XsdDocumentedSpec
*/
class Attribute {
def name
def desc
def elmt
def getId() {
return "${elmt.id}-${name}".toString()
}
}
@@ -1,90 +0,0 @@
/*
* Copyright 2002-2011 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.doc
/**
* Represents a Spring Security XSD Element. It is created when parsing the current xsd to compare to the documented appendix.
*
* @author Rob Winch
* @see SpringSecurityXsdParser
* @see XsdDocumentedSpec
*/
class Element {
def name
def desc
def attrs
/**
* Contains the elements that extend this element (i.e. any-user-service contains ldap-user-service)
*/
def subGrps = []
def childElmts = [:]
def parentElmts = [:]
def getId() {
return "nsa-${name}".toString()
}
/**
* Gets all the ids related to this Element including attributes, parent elements, and child elements.
*
* <p>
* The expected ids to be found are documented below.
* <ul>
* <li>Elements - any xml element will have the nsa-&lt;element&gt;. For example the http element will have the id
* nsa-http</li>
* <li>Parent Section - Any element with a parent other than beans will have a section named
* nsa-&lt;element&gt;-parents. For example, authentication-provider would have a section id of
* nsa-authentication-provider-parents. The section would then contain a list of links pointing to the
* documentation for each parent element.</li>
* <li>Attributes Section - Any element with attributes will have a section with the id
* nsa-&lt;element&gt;-attributes. For example the http element would require a section with the id
* http-attributes.</li>
* <li>Attribute - Each attribute of an element would have an id of nsa-&lt;element&gt;-&lt;attributeName&gt;. For
* example the attribute create-session for the http attribute would have the id http-create-session.</li>
* <li>Child Section - Any element with a child element will have a section named nsa-&lt;element&gt;-children.
* For example, authentication-provider would have a section id of nsa-authentication-provider-children. The
* section would then contain a list of links pointing to the documentation for each child element.</li>
* </ul>
* @return
*/
def getIds() {
def ids = [id]
childElmts.values()*.ids.each { ids.addAll it }
attrs*.id.each { ids.add it }
if(childElmts) {
ids.add id+'-children'
}
if(attrs) {
ids.add id+'-attributes'
}
if(parentElmts) {
ids.add id+'-parents'
}
ids
}
def getAllChildElmts() {
def result = [:]
childElmts.values()*.subGrps*.each { elmt -> result.put(elmt.name,elmt) }
result + childElmts
}
def getAllParentElmts() {
def result = [:]
parentElmts.values()*.subGrps*.each { elmt -> result.put(elmt.name,elmt) }
result + parentElmts
}
}
@@ -1,177 +0,0 @@
/*
* Copyright 2002-2011 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.doc
import groovy.xml.Namespace
/**
* Parses the Spring Security Xsd Document
*
* @author Rob Winch
*/
class SpringSecurityXsdParser {
private def rootElement
private def xs = new Namespace("http://www.w3.org/2001/XMLSchema", 'xs')
private def attrElmts = [] as Set
private def elementNameToElement = [:] as Map
/**
* Returns a map of the element name to the {@link Element}.
* @return
*/
Map<String,Element> parse() {
elements(rootElement)
elementNameToElement
}
/**
* Creates a Map of the name to an Element object of all the children of element.
*
* @param element
* @return
*/
private def elements(element) {
def elementNameToElement = [:] as Map
element.children().each { c->
if(c.name() == 'element') {
def e = elmt(c)
elementNameToElement.put(e.name,e)
} else {
elementNameToElement.putAll(elements(c))
}
}
elementNameToElement
}
/**
* Any children that are attribute will be returned as an Attribute object.
* @param element
* @return a collection of Attribute objects that are children of element.
*/
private def attrs(element) {
def r = []
element.children().each { c->
if(c.name() == 'attribute') {
r.add(attr(c))
}else if(c.name() == 'element') {
}else {
r.addAll(attrs(c))
}
}
r
}
/**
* Any children will be searched for an attributeGroup, each of it's children will be returned as an Attribute
* @param element
* @return
*/
private def attrgrps(element) {
def r = []
element.children().each { c->
if(c.name() == 'element') {
}else if (c.name() == 'attributeGroup') {
if(c.attributes().get('name')) {
r.addAll(attrgrp(c))
} else {
def n = c.attributes().get('ref').split(':')[1]
def attrGrp = findNode(element,n)
r.addAll(attrgrp(attrGrp))
}
} else {
r.addAll(attrgrps(c))
}
}
r
}
private def findNode(c,name) {
def root = c
while(root.name() != 'schema') {
root = root.parent()
}
def result = root.breadthFirst().find { child-> name == child.@name?.text() }
assert result?.@name?.text() == name
result
}
/**
* Processes an individual attributeGroup by obtaining all the attributes and then looking for more attributeGroup elements and prcessing them.
* @param e
* @return all the attributes for a specific attributeGroup and any child attributeGroups
*/
private def attrgrp(e) {
def attrs = attrs(e)
attrs.addAll(attrgrps(e))
attrs
}
/**
* Obtains the description for a specific element
* @param element
* @return
*/
private def desc(element) {
return element['annotation']['documentation']
}
/**
* Given an element creates an attribute from it.
* @param n
* @return
*/
private def attr(n) {
new Attribute(desc: desc(n), name: n.@name.text())
}
/**
* Given an element creates an Element out of it by collecting all its attributes and child elements.
*
* @param n
* @return
*/
private def elmt(n) {
def name = n.@ref.text()
if(name) {
name = name.split(':')[1]
n = findNode(n,name)
} else {
name = n.@name.text()
}
if(elementNameToElement.containsKey(name)) {
return elementNameToElement.get(name)
}
attrElmts.add(name)
def e = new Element()
e.name = n.@name.text()
e.desc = desc(n)
e.childElmts = elements(n)
e.attrs = attrs(n)
e.attrs.addAll(attrgrps(n))
e.attrs*.elmt = e
e.childElmts.values()*.each { it.parentElmts.put(e.name,e) }
def subGrpName = n.@substitutionGroup.text()
if(subGrpName) {
def subGrp = elmt(findNode(n,subGrpName.split(":")[1]))
subGrp.subGrps.add(e)
}
elementNameToElement.put(name,e)
e
}
}
@@ -1,222 +0,0 @@
/*
* Copyright 2011-2016 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.doc
import groovy.util.slurpersupport.GPathResult
import spock.lang.*
import org.springframework.security.config.http.SecurityFilters
/**
* Tests to ensure that the xsd is properly documented.
*
* @author Rob Winch
*/
class XsdDocumentedTests extends Specification {
def ignoredIds = [
'nsa-any-user-service',
'nsa-any-user-service-parents',
'nsa-authentication',
'nsa-websocket-security',
'nsa-ldap',
'nsa-method-security',
'nsa-web'
]
@Shared def reference = new File('../docs/manual/src/docs/asciidoc/index.adoc')
@Shared File schema31xDocument = new File('src/main/resources/org/springframework/security/config/spring-security-3.1.xsd')
@Shared File schemaDocument = new File('src/main/resources/org/springframework/security/config/spring-security-5.0.xsd')
@Shared Map<String,Element> elementNameToElement
@Shared GPathResult schemaRootElement
def setupSpec() {
schemaRootElement = new XmlSlurper().parse(schemaDocument)
elementNameToElement = new SpringSecurityXsdParser(rootElement: schemaRootElement).parse()
}
def cleanupSpec() {
reference = null
schema31xDocument = null
schemaDocument = null
elementNameToElement = null
schemaRootElement = null
}
def 'SEC-2139: named-security-filter are all defined and ordered properly'() {
setup:
def expectedFilters = (EnumSet.allOf(SecurityFilters) as List).sort { it.order }
when:
def nsf = schemaRootElement.simpleType.find { it.@name == 'named-security-filter' }
def nsfValues = nsf.children().children().collect { c ->
Enum.valueOf(SecurityFilters, c.@value.toString())
}
then:
expectedFilters == nsfValues
}
def 'SEC-2139: 3.1.x named-security-filter are all defined and ordered properly'() {
setup:
def expectedFilters = [
"FIRST",
"CHANNEL_FILTER",
"SECURITY_CONTEXT_FILTER",
"CONCURRENT_SESSION_FILTER",
"LOGOUT_FILTER",
"X509_FILTER",
"PRE_AUTH_FILTER",
"CAS_FILTER",
"FORM_LOGIN_FILTER",
"OPENID_FILTER",
"LOGIN_PAGE_FILTER",
"DIGEST_AUTH_FILTER",
"BASIC_AUTH_FILTER",
"REQUEST_CACHE_FILTER",
"SERVLET_API_SUPPORT_FILTER",
"JAAS_API_SUPPORT_FILTER",
"REMEMBER_ME_FILTER",
"ANONYMOUS_FILTER",
"SESSION_MANAGEMENT_FILTER",
"EXCEPTION_TRANSLATION_FILTER",
"FILTER_SECURITY_INTERCEPTOR",
"SWITCH_USER_FILTER",
"LAST"
].collect {
Enum.valueOf(SecurityFilters, it)
}
def schema31xRootElement = new XmlSlurper().parse(schema31xDocument)
when:
def nsf = schema31xRootElement.simpleType.find { it.@name == 'named-security-filter' }
def nsfValues = nsf.children().children().collect { c ->
Enum.valueOf(SecurityFilters, c.@value.toString())
}
then:
expectedFilters == nsfValues
}
/**
* This will check to ensure that the expected number of xsd documents are found to ensure that we are validating
* against the current xsd document. If this test fails, all that is needed is to update the schemaDocument
* and the expected size for this test.
* @return
*/
def 'the latest schema is being validated'() {
when: 'all the schemas are found'
def schemas = schemaDocument.getParentFile().list().findAll { it.endsWith('.xsd') }
then: 'the count is equal to 12, if not then schemaDocument needs updated'
schemas.size() == 12
}
/**
* This uses a naming convention for the ids of the appendix to ensure that the entire appendix is documented.
* The naming convention for the ids is documented in {@link Element#getIds()}.
* @return
*/
def 'the entire schema is included in the appendix documentation'() {
setup: 'get all the documented ids and the expected ids'
def documentedIds = []
reference.eachLine { line ->
if(line.matches("\\[\\[(nsa-.*)\\]\\]")) {
documentedIds.add(line.substring(2,line.length() - 2))
}
}
when: 'the schema is compared to the appendix documentation'
def expectedIds = [] as Set
elementNameToElement*.value*.ids*.each { expectedIds.addAll it }
documentedIds.removeAll ignoredIds
expectedIds.removeAll ignoredIds
def undocumentedIds = (expectedIds - documentedIds)
def shouldNotBeDocumented = (documentedIds - expectedIds)
then: 'all the elements and attributes are documented'
shouldNotBeDocumented.empty
undocumentedIds.empty
}
/**
* This test ensures that any element that has children or parents contains a section that has links pointing to that
* documentation.
* @return
*/
def 'validate parents and children are linked in the appendix documentation'() {
when: "get all the links for each element's children and parents"
def docAttrNameToChildren = [:]
def docAttrNameToParents = [:]
def currentDocAttrNameToElmt
def docAttrName
reference.eachLine { line ->
if(line.matches('^\\[\\[.*\\]\\]$')) {
def id = line.substring(2,line.length() - 2)
if(id.endsWith("-children")) {
docAttrName = id.substring(0,id.length() - 9)
currentDocAttrNameToElmt = docAttrNameToChildren
} else if(id.endsWith("-parents")) {
docAttrName = id.substring(0,id.length() - 8)
currentDocAttrNameToElmt = docAttrNameToParents
} else if(docAttrName && !id.startsWith(docAttrName)) {
currentDocAttrNameToElmt = null
docAttrName = null
}
}
if(docAttrName) {
def expression = '^\\* <<(nsa-.*),.*>>$'
if(line.matches(expression)) {
String elmtId = line.replaceAll(expression, '$1')
currentDocAttrNameToElmt.get(docAttrName, []).add(elmtId)
}
}
}
def schemaAttrNameToParents = [:]
def schemaAttrNameToChildren = [:]
elementNameToElement.each { entry ->
def key = 'nsa-'+entry.key
if(ignoredIds.contains(key)) {
return
}
def parentIds = entry.value.allParentElmts.values()*.id.findAll { !ignoredIds.contains(it) }.sort()
if(parentIds) {
schemaAttrNameToParents.put(key,parentIds)
}
def childIds = entry.value.allChildElmts.values()*.id.findAll { !ignoredIds.contains(it) }.sort()
if(childIds) {
schemaAttrNameToChildren.put(key,childIds)
}
}
then: "the expected parents and children are all documented"
schemaAttrNameToChildren.sort() == docAttrNameToChildren.sort()
schemaAttrNameToParents.sort() == docAttrNameToParents.sort()
}
/**
* This test checks each xsd element and ensures there is documentation for it.
* @return
*/
def 'entire xsd is documented'() {
when: "validate that the entire xsd contains documentation"
def notDocElmtIds = elementNameToElement.values().findAll {
!it.desc.text() && !ignoredIds.contains(it.id)
}*.id.sort().join("\n")
def notDocAttrIds = elementNameToElement.values()*.attrs.flatten().findAll {
!it.desc.text() && !ignoredIds.contains(it.id)
}*.id.sort().join("\n")
then: "all the elements and attributes have some documentation"
!notDocElmtIds
!notDocAttrIds
}
}
@@ -1,47 +0,0 @@
package org.springframework.security.config.http
import org.springframework.beans.factory.BeanCreationException
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException
import org.springframework.security.web.access.AccessDeniedHandlerImpl
import org.springframework.security.web.access.ExceptionTranslationFilter
/**
*
* @author Luke Taylor
*/
class AccessDeniedConfigTests extends AbstractHttpConfigTests {
def invalidAccessDeniedUrlIsDetected() {
when:
httpAutoConfig() {
'access-denied-handler'('error-page':'noLeadingSlash')
}
createAppContext();
then:
thrown(BeanCreationException)
}
def accessDeniedHandlerIsSetCorectly() {
httpAutoConfig() {
'access-denied-handler'(ref: 'adh')
}
bean('adh', AccessDeniedHandlerImpl)
createAppContext();
def filter = getFilter(ExceptionTranslationFilter.class);
def adh = appContext.getBean("adh");
expect:
filter.accessDeniedHandler == adh
}
def void accessDeniedHandlerPageAndRefAreMutuallyExclusive() {
when:
httpAutoConfig {
'access-denied-handler'('error-page': '/go-away', ref: 'adh')
}
createAppContext();
bean('adh', AccessDeniedHandlerImpl)
then:
thrown(BeanDefinitionParsingException)
}
}
@@ -1,342 +0,0 @@
/*
* Copyright 2002-2012 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.http
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import spock.lang.Unroll
import org.springframework.mock.web.MockFilterChain
import org.springframework.mock.web.MockHttpServletRequest
import org.springframework.mock.web.MockHttpServletResponse
import org.springframework.security.access.AccessDeniedException
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.Authentication
import org.springframework.security.core.authority.AuthorityUtils
import org.springframework.security.core.context.SecurityContextImpl
import org.springframework.security.web.access.AccessDeniedHandler
import org.springframework.security.web.context.HttpRequestResponseHolder
import org.springframework.security.web.context.HttpSessionSecurityContextRepository
import org.springframework.security.web.csrf.CsrfFilter
import org.springframework.security.web.csrf.CsrfToken
import org.springframework.security.web.csrf.CsrfTokenRepository
import org.springframework.security.web.csrf.DefaultCsrfToken
import org.springframework.security.web.util.matcher.RequestMatcher
import org.springframework.web.servlet.support.RequestDataValueProcessor
import static org.mockito.Matchers.*
import static org.mockito.Mockito.*
/**
*
* @author Rob Winch
*/
class CsrfConfigTests extends AbstractHttpConfigTests {
MockHttpServletRequest request = new MockHttpServletRequest()
MockHttpServletResponse response = new MockHttpServletResponse()
MockFilterChain chain = new MockFilterChain()
@Unroll
def 'csrf is enabled by default'() {
setup:
httpAutoConfig {
}
createAppContext()
when:
request.method = httpMethod
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == httpStatus
where:
httpMethod | httpStatus
'POST' | HttpServletResponse.SC_FORBIDDEN
'PUT' | HttpServletResponse.SC_FORBIDDEN
'PATCH' | HttpServletResponse.SC_FORBIDDEN
'DELETE' | HttpServletResponse.SC_FORBIDDEN
'INVALID' | HttpServletResponse.SC_FORBIDDEN
'GET' | HttpServletResponse.SC_OK
'HEAD' | HttpServletResponse.SC_OK
'TRACE' | HttpServletResponse.SC_OK
'OPTIONS' | HttpServletResponse.SC_OK
}
def 'csrf disabled'() {
when:
httpAutoConfig { csrf(disabled:true) }
createAppContext()
then:
!getFilter(CsrfFilter)
}
@Unroll
def 'csrf defaults'() {
setup:
httpAutoConfig { 'csrf'() }
createAppContext()
when:
request.method = httpMethod
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == httpStatus
where:
httpMethod | httpStatus
'POST' | HttpServletResponse.SC_FORBIDDEN
'PUT' | HttpServletResponse.SC_FORBIDDEN
'PATCH' | HttpServletResponse.SC_FORBIDDEN
'DELETE' | HttpServletResponse.SC_FORBIDDEN
'INVALID' | HttpServletResponse.SC_FORBIDDEN
'GET' | HttpServletResponse.SC_OK
'HEAD' | HttpServletResponse.SC_OK
'TRACE' | HttpServletResponse.SC_OK
'OPTIONS' | HttpServletResponse.SC_OK
}
def 'csrf default creates CsrfRequestDataValueProcessor'() {
when:
httpAutoConfig { 'csrf'() }
createAppContext()
then:
appContext.getBean("requestDataValueProcessor",RequestDataValueProcessor)
}
def 'csrf custom AccessDeniedHandler'() {
setup:
httpAutoConfig {
'access-denied-handler'(ref:'adh')
'csrf'()
}
mockBean(AccessDeniedHandler,'adh')
createAppContext()
AccessDeniedHandler adh = appContext.getBean(AccessDeniedHandler)
request.method = "POST"
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
verify(adh).handle(any(HttpServletRequest),any(HttpServletResponse),any(AccessDeniedException))
response.status == HttpServletResponse.SC_OK // our mock doesn't do anything
}
def "csrf disables posts for RequestCache"() {
setup:
httpAutoConfig {
'csrf'('token-repository-ref':'repo')
'intercept-url'(pattern:"/**",access:'ROLE_USER')
}
mockBean(CsrfTokenRepository,'repo')
createAppContext()
CsrfTokenRepository repo = appContext.getBean("repo",CsrfTokenRepository)
CsrfToken token = new DefaultCsrfToken("X-CSRF-TOKEN","_csrf", "abc")
when(repo.loadToken(any(HttpServletRequest))).thenReturn(token)
when(repo.generateToken(any(HttpServletRequest))).thenReturn(token)
request.setParameter(token.parameterName,token.token)
request.servletPath = "/some-url"
request.requestURI = "/some-url"
request.method = "POST"
when: "CSRF passes and our session times out"
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"
response = new MockHttpServletResponse()
request = new MockHttpServletRequest(session: request.session)
request.servletPath = "/login"
request.setParameter(token.parameterName,token.token)
request.setParameter("username","user")
request.setParameter("password","password")
request.method = "POST"
springSecurityFilterChain.doFilter(request,response,chain)
then: "sent to default success because we don't want csrf attempts made prior to authentication to pass"
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
response.redirectedUrl == "/"
}
def "csrf enables gets for RequestCache"() {
setup:
httpAutoConfig {
'csrf'('token-repository-ref':'repo')
'intercept-url'(pattern:"/**",access:'ROLE_USER')
}
mockBean(CsrfTokenRepository,'repo')
createAppContext()
CsrfTokenRepository repo = appContext.getBean("repo",CsrfTokenRepository)
CsrfToken token = new DefaultCsrfToken("X-CSRF-TOKEN","_csrf", "abc")
when(repo.loadToken(any(HttpServletRequest))).thenReturn(token)
when(repo.generateToken(any(HttpServletRequest))).thenReturn(token)
request.setParameter(token.parameterName,token.token)
request.servletPath = "/some-url"
request.requestURI = "/some-url"
request.method = "GET"
when: "CSRF passes and our session times out"
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"
response = new MockHttpServletResponse()
request = new MockHttpServletRequest(session: request.session)
request.servletPath = "/login"
request.setParameter(token.parameterName,token.token)
request.setParameter("username","user")
request.setParameter("password","password")
request.method = "POST"
springSecurityFilterChain.doFilter(request,response,chain)
then: "sent to original URL since it was a GET"
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
response.redirectedUrl == "http://localhost/some-url"
}
def "SEC-2422: csrf expire CSRF token and session-management invalid-session-url"() {
setup:
httpAutoConfig {
'csrf'()
'session-management'('invalid-session-url': '/error/sessionError')
}
createAppContext()
request.setParameter("_csrf","abc")
request.method = "POST"
when: "No existing expected CsrfToken (session times out) and a POST"
springSecurityFilterChain.doFilter(request,response,chain)
then: "sent to the session timeout page page"
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
response.redirectedUrl == "/error/sessionError"
when: "Existing expected CsrfToken and a POST (invalid token provided)"
response = new MockHttpServletResponse()
request = new MockHttpServletRequest(session: request.session, method:'POST')
springSecurityFilterChain.doFilter(request,response,chain)
then: "Access Denied occurs"
response.status == HttpServletResponse.SC_FORBIDDEN
}
def "csrf requireCsrfProtectionMatcher"() {
setup:
httpAutoConfig { 'csrf'('request-matcher-ref':'matcher') }
mockBean(RequestMatcher,'matcher')
createAppContext()
request.method = 'POST'
RequestMatcher matcher = appContext.getBean("matcher",RequestMatcher)
when:
when(matcher.matches(any(HttpServletRequest))).thenReturn(false)
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == HttpServletResponse.SC_OK
when:
when(matcher.matches(any(HttpServletRequest))).thenReturn(true)
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == HttpServletResponse.SC_FORBIDDEN
}
def "csrf csrfTokenRepository default delays save"() {
setup:
httpAutoConfig {
}
createAppContext()
request.method = "GET"
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == HttpServletResponse.SC_OK
request.getSession(false) == null
}
def "csrf csrfTokenRepository"() {
setup:
httpAutoConfig { 'csrf'('token-repository-ref':'repo') }
mockBean(CsrfTokenRepository,'repo')
createAppContext()
CsrfTokenRepository repo = appContext.getBean("repo",CsrfTokenRepository)
CsrfToken token = new DefaultCsrfToken("X-CSRF-TOKEN","_csrf", "abc")
when(repo.loadToken(any(HttpServletRequest))).thenReturn(token)
request.setParameter(token.parameterName,token.token)
request.method = "POST"
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == HttpServletResponse.SC_OK
when:
request.setParameter(token.parameterName,token.token+"INVALID")
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == HttpServletResponse.SC_FORBIDDEN
}
def "csrf clears on login"() {
setup:
httpAutoConfig { 'csrf'('token-repository-ref':'repo') }
mockBean(CsrfTokenRepository,'repo')
createAppContext()
CsrfTokenRepository repo = appContext.getBean("repo",CsrfTokenRepository)
CsrfToken token = new DefaultCsrfToken("X-CSRF-TOKEN","_csrf", "abc")
when(repo.loadToken(any(HttpServletRequest))).thenReturn(token)
when(repo.generateToken(any(HttpServletRequest))).thenReturn(token)
request.setParameter(token.parameterName,token.token)
request.method = "POST"
request.setParameter("username","user")
request.setParameter("password","password")
request.servletPath = "/login"
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
verify(repo, atLeastOnce()).saveToken(eq(null),any(HttpServletRequest), any(HttpServletResponse))
}
def "csrf clears on logout"() {
setup:
httpAutoConfig { 'csrf'('token-repository-ref':'repo') }
mockBean(CsrfTokenRepository,'repo')
createAppContext()
CsrfTokenRepository repo = appContext.getBean("repo",CsrfTokenRepository)
CsrfToken token = new DefaultCsrfToken("X-CSRF-TOKEN","_csrf", "abc")
when(repo.loadToken(any(HttpServletRequest))).thenReturn(token)
request.setParameter(token.parameterName,token.token)
request.method = "POST"
request.servletPath = "/logout"
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
verify(repo).saveToken(eq(null),any(HttpServletRequest), any(HttpServletResponse))
}
def "SEC-2495: csrf disables logout on GET"() {
setup:
httpAutoConfig { 'csrf'() }
createAppContext()
login()
request.method = "GET"
request.requestURI = "/logout"
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
getAuthentication(request) != null
}
def login(String username="user", String role="ROLE_USER") {
login(new UsernamePasswordAuthenticationToken(username, null, AuthorityUtils.createAuthorityList(role)))
}
def login(Authentication auth) {
HttpSessionSecurityContextRepository repo = new HttpSessionSecurityContextRepository()
HttpRequestResponseHolder requestResponseHolder = new HttpRequestResponseHolder(request, response)
repo.loadContext(requestResponseHolder)
repo.saveContext(new SecurityContextImpl(authentication:auth), requestResponseHolder.request, requestResponseHolder.response)
}
def getAuthentication(HttpServletRequest request) {
HttpSessionSecurityContextRepository repo = new HttpSessionSecurityContextRepository()
HttpRequestResponseHolder requestResponseHolder = new HttpRequestResponseHolder(request, response)
repo.loadContext(requestResponseHolder)?.authentication
}
}
@@ -1,153 +0,0 @@
package org.springframework.security.config.http
import org.springframework.mock.web.MockFilterChain
import org.springframework.mock.web.MockHttpServletRequest
import org.springframework.mock.web.MockHttpServletResponse
import org.springframework.security.web.WebAttributes
/**
*
* @author Luke Taylor
*/
class FormLoginBeanDefinitionParserTests extends AbstractHttpConfigTests {
def 'form-login default login page'() {
setup:
MockHttpServletRequest request = new MockHttpServletRequest(method:'GET',requestURI:'/login')
MockHttpServletResponse response = new MockHttpServletResponse()
MockFilterChain chain = new MockFilterChain()
httpAutoConfig {
csrf(disabled:true)
}
createAppContext()
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.getContentAsString() == """<html><head><title>Login Page</title></head><body onload='document.f.username.focus();'>
<h3>Login with Username and Password</h3><form name='f' action='/login' method='POST'>
<table>
<tr><td>User:</td><td><input type='text' name='username' value=''></td></tr>
<tr><td>Password:</td><td><input type='password' name='password'/></td></tr>
<tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr>
</table>
</form></body></html>"""
}
def 'form-login default login page custom attributes'() {
setup:
MockHttpServletRequest request = new MockHttpServletRequest(method:'GET',requestURI:'/login')
MockHttpServletResponse response = new MockHttpServletResponse()
MockFilterChain chain = new MockFilterChain()
httpAutoConfig {
'form-login'('login-processing-url':'/login_custom','username-parameter':'custom_user','password-parameter':'custom_password')
csrf(disabled:true)
}
createAppContext()
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.getContentAsString() == """<html><head><title>Login Page</title></head><body onload='document.f.custom_user.focus();'>
<h3>Login with Username and Password</h3><form name='f' action='/login_custom' method='POST'>
<table>
<tr><td>User:</td><td><input type='text' name='custom_user' value=''></td></tr>
<tr><td>Password:</td><td><input type='password' name='custom_password'/></td></tr>
<tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr>
</table>
</form></body></html>"""
}
def 'openid-login default login page'() {
setup:
MockHttpServletRequest request = new MockHttpServletRequest(method:'GET',requestURI:'/login')
MockHttpServletResponse response = new MockHttpServletResponse()
MockFilterChain chain = new MockFilterChain()
httpAutoConfig {
'openid-login'()
csrf(disabled:true)
}
createAppContext()
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.getContentAsString() == """<html><head><title>Login Page</title></head><body onload='document.f.username.focus();'>
<h3>Login with Username and Password</h3><form name='f' action='/login' method='POST'>
<table>
<tr><td>User:</td><td><input type='text' name='username' value=''></td></tr>
<tr><td>Password:</td><td><input type='password' name='password'/></td></tr>
<tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr>
</table>
</form><h3>Login with OpenID Identity</h3><form name='oidf' action='/login/openid' method='POST'>
<table>
<tr><td>Identity:</td><td><input type='text' size='30' name='openid_identifier'/></td></tr>
<tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr>
</table>
</form></body></html>"""
}
def 'openid-login default login page custom attributes'() {
setup:
MockHttpServletRequest request = new MockHttpServletRequest(method:'GET',requestURI:'/login')
MockHttpServletResponse response = new MockHttpServletResponse()
MockFilterChain chain = new MockFilterChain()
httpAutoConfig {
'openid-login'('login-processing-url':'/login_custom')
csrf(disabled:true)
}
createAppContext()
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.getContentAsString() == """<html><head><title>Login Page</title></head><body onload='document.f.username.focus();'>
<h3>Login with Username and Password</h3><form name='f' action='/login' method='POST'>
<table>
<tr><td>User:</td><td><input type='text' name='username' value=''></td></tr>
<tr><td>Password:</td><td><input type='password' name='password'/></td></tr>
<tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr>
</table>
</form><h3>Login with OpenID Identity</h3><form name='oidf' action='/login_custom' method='POST'>
<table>
<tr><td>Identity:</td><td><input type='text' size='30' name='openid_identifier'/></td></tr>
<tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr>
</table>
</form></body></html>"""
}
def 'form-login forward authentication failure handler'() {
setup:
MockHttpServletRequest request = new MockHttpServletRequest(method:'POST',servletPath:'/login')
request.setParameter("username", "bob")
request.setParameter("password", "invalidpassword")
MockHttpServletResponse response = new MockHttpServletResponse()
MockFilterChain chain = new MockFilterChain()
httpAutoConfig {
'form-login'('authentication-failure-forward-url':'/failure_forward_url')
csrf(disabled:true)
}
createAppContext()
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.getStatus() == 200
response.forwardedUrl == "/failure_forward_url"
request.getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION) != null;
}
def 'form-login forward authentication success handler'() {
setup:
MockHttpServletRequest request = new MockHttpServletRequest(method:'POST',servletPath:'/login')
request.setParameter("username", "bob")
request.setParameter("password", "bobspassword")
MockHttpServletResponse response = new MockHttpServletResponse()
MockFilterChain chain = new MockFilterChain()
httpAutoConfig {
'form-login'('authentication-success-forward-url':'/success_forward_url')
csrf(disabled:true)
}
createAppContext()
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.getStatus() == 200
response.forwardedUrl == "/success_forward_url"
}
}
@@ -1,161 +0,0 @@
package org.springframework.security.config.http
import javax.servlet.http.HttpServletResponse
import org.springframework.beans.factory.BeanCreationException
import org.springframework.mock.web.MockFilterChain
import org.springframework.mock.web.MockHttpServletRequest
import org.springframework.mock.web.MockHttpServletResponse
import org.springframework.security.util.FieldUtils
import org.springframework.security.web.access.ExceptionTranslationFilter
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter
import spock.lang.Unroll;
/**
*
* @author Luke Taylor
*/
class FormLoginConfigTests extends AbstractHttpConfigTests {
def formLoginWithNoLoginPageAddsDefaultLoginPageFilter() {
httpAutoConfig('ant') {
form-login()
}
createAppContext()
filtersMatchExpectedAutoConfigList();
}
def 'Form login alwaysUseDefaultTarget sets correct property'() {
xml.http {
'form-login'('default-target-url':'/default', 'always-use-default-target': 'true')
}
createAppContext()
def filter = getFilter(UsernamePasswordAuthenticationFilter.class);
expect:
FieldUtils.getFieldValue(filter, 'successHandler.defaultTargetUrl') == '/default';
FieldUtils.getFieldValue(filter, 'successHandler.alwaysUseDefaultTargetUrl');
}
def 'form-login attributes support SpEL'() {
setup:
def spelUrl = '#{T(org.springframework.security.config.http.WebConfigUtilsTest).URL}'
def expectedUrl = WebConfigUtilsTest.URL
when:
xml.http {
'form-login'('default-target-url': spelUrl , 'authentication-failure-url': spelUrl, 'login-page': spelUrl)
}
createAppContext()
def unPwdFilter = getFilter(UsernamePasswordAuthenticationFilter)
def exTransFilter = getFilter(ExceptionTranslationFilter)
then:
unPwdFilter.successHandler.defaultTargetUrl == expectedUrl
unPwdFilter
FieldUtils.getFieldValue(unPwdFilter, 'successHandler.defaultTargetUrl') == expectedUrl
FieldUtils.getFieldValue(unPwdFilter, 'failureHandler.defaultFailureUrl') == expectedUrl
FieldUtils.getFieldValue(exTransFilter, 'authenticationEntryPoint.loginFormUrl') == expectedUrl
}
def invalidLoginPageIsDetected() {
when:
xml.http {
'form-login'('login-page': 'noLeadingSlash')
}
createAppContext()
then:
BeanCreationException e = thrown();
}
def invalidDefaultTargetUrlIsDetected() {
when:
xml.http {
'form-login'('default-target-url': 'noLeadingSlash')
}
createAppContext()
then:
BeanCreationException e = thrown();
}
def customSuccessAndFailureHandlersCanBeSetThroughTheNamespace() {
xml.http {
'form-login'('authentication-success-handler-ref': 'sh', 'authentication-failure-handler-ref':'fh')
}
bean('sh', SavedRequestAwareAuthenticationSuccessHandler.class.name)
bean('fh', SimpleUrlAuthenticationFailureHandler.class.name)
createAppContext()
def apf = getFilter(UsernamePasswordAuthenticationFilter.class);
expect:
FieldUtils.getFieldValue(apf, "successHandler") == appContext.getBean("sh");
FieldUtils.getFieldValue(apf, "failureHandler") == appContext.getBean("fh")
}
def usernameAndPasswordParametersCanBeSetThroughNamespace() {
xml.http {
'form-login'('username-parameter': 'xname', 'password-parameter':'xpass')
}
createAppContext()
def apf = getFilter(UsernamePasswordAuthenticationFilter.class);
expect:
apf.usernameParameter == 'xname';
apf.passwordParameter == 'xpass'
}
def 'SEC-2919: DefaultLoginGeneratingFilter should not be present if login-page="/login"'() {
when:
xml.http() {
'form-login'('login-page':'/login')
}
createAppContext()
then:
getFilter(DefaultLoginPageGeneratingFilter) == null
}
@Unroll
def 'Form Login requires CSRF Token #csrfDisabled'(int status, boolean csrfDisabled) {
setup:
MockHttpServletRequest request = new MockHttpServletRequest(method:'POST',servletPath:'/login')
request.setParameter('username','user')
request.setParameter('password','password')
MockHttpServletResponse response = new MockHttpServletResponse()
MockFilterChain chain = new MockFilterChain()
httpAutoConfig {
'form-login'()
csrf(disabled:csrfDisabled) {}
}
createAppContext()
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == status
where:
status | csrfDisabled
HttpServletResponse.SC_FORBIDDEN | false
HttpServletResponse.SC_MOVED_TEMPORARILY | true
}
def 'SEC-3147: authentication-failure-url should be contained "error" parameter if login-page="/login"'() {
xml.http {
'form-login'('login-page':'/login')
}
createAppContext()
def apf = getFilter(UsernamePasswordAuthenticationFilter.class);
expect:
apf.failureHandler.defaultFailureUrl == '/login?error'
}
}
@@ -131,6 +131,7 @@ class InterceptUrlConfigTests extends AbstractHttpConfigTests {
when: 'user cannot access otheruser'
request = new MockHttpServletRequest(method:'GET', servletPath : '/user/otheruser/abc')
login(request, 'user', 'password')
response = new MockHttpServletResponse()
chain.reset()
springSecurityFilterChain.doFilter(request,response,chain)
then: 'The response is OK'
@@ -138,6 +139,7 @@ class InterceptUrlConfigTests extends AbstractHttpConfigTests {
when: 'user can access case insensitive URL'
request = new MockHttpServletRequest(method:'GET', servletPath : '/USER/user/abc')
login(request, 'user', 'password')
response = new MockHttpServletResponse()
chain.reset()
springSecurityFilterChain.doFilter(request,response,chain)
then: 'The response is OK'
@@ -164,6 +166,7 @@ class InterceptUrlConfigTests extends AbstractHttpConfigTests {
when: 'user cannot access otheruser'
request = new MockHttpServletRequest(method:'GET', servletPath : '/user/otheruser/abc')
login(request, 'user', 'password')
response = new MockHttpServletResponse()
chain.reset()
springSecurityFilterChain.doFilter(request,response,chain)
then: 'The response is OK'
@@ -171,6 +174,7 @@ class InterceptUrlConfigTests extends AbstractHttpConfigTests {
when: 'user can access case insensitive URL'
request = new MockHttpServletRequest(method:'GET', servletPath : '/USER/user/abc')
login(request, 'user', 'password')
response = new MockHttpServletResponse()
chain.reset()
springSecurityFilterChain.doFilter(request,response,chain)
then: 'The response is OK'
@@ -1,560 +0,0 @@
package org.springframework.security.config.websocket
import static org.mockito.Mockito.*
import org.springframework.beans.BeansException
import org.springframework.beans.factory.config.BeanDefinition
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException
import org.springframework.beans.factory.support.BeanDefinitionRegistry
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor
import org.springframework.beans.factory.support.RootBeanDefinition
import org.springframework.core.MethodParameter
import org.springframework.core.task.SyncTaskExecutor
import org.springframework.http.server.ServerHttpRequest
import org.springframework.http.server.ServerHttpResponse
import org.springframework.messaging.Message
import org.springframework.messaging.MessageDeliveryException
import org.springframework.messaging.handler.annotation.MessageMapping
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver
import org.springframework.messaging.simp.SimpMessageHeaderAccessor
import org.springframework.messaging.simp.SimpMessageType
import org.springframework.messaging.simp.annotation.support.SimpAnnotationMethodMessageHandler
import org.springframework.messaging.support.ChannelInterceptor
import org.springframework.messaging.support.GenericMessage
import org.springframework.mock.web.MockHttpServletRequest
import org.springframework.mock.web.MockHttpServletResponse
import org.springframework.security.access.AccessDeniedException
import org.springframework.security.access.expression.SecurityExpressionOperations;
import org.springframework.security.authentication.TestingAuthenticationToken
import org.springframework.security.config.AbstractXmlConfigTests
import org.springframework.security.core.Authentication
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.messaging.access.expression.DefaultMessageSecurityExpressionHandler;
import org.springframework.security.messaging.access.expression.MessageSecurityExpressionRoot;
import org.springframework.security.web.csrf.CsrfToken
import org.springframework.security.web.csrf.DefaultCsrfToken
import org.springframework.security.web.csrf.InvalidCsrfTokenException
import org.springframework.stereotype.Controller
import org.springframework.util.AntPathMatcher
import org.springframework.web.servlet.HandlerMapping
import org.springframework.web.socket.WebSocketHandler
import org.springframework.web.socket.server.HandshakeFailureException
import org.springframework.web.socket.server.HandshakeHandler
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor
import org.springframework.web.socket.server.support.WebSocketHttpRequestHandler
import org.springframework.web.socket.sockjs.support.SockJsHttpRequestHandler
import org.springframework.web.socket.sockjs.transport.handler.SockJsWebSocketHandler
import spock.lang.Unroll
/**
*
* @author Rob Winch
*/
class WebSocketMessageBrokerConfigTests extends AbstractXmlConfigTests {
Authentication messageUser = new TestingAuthenticationToken('user','pass','ROLE_USER')
boolean useSockJS = false
CsrfToken csrfToken = new DefaultCsrfToken('headerName', 'paramName', 'token')
def cleanup() {
SecurityContextHolder.clearContext()
}
def 'websocket with no id automatically integrates with clientInboundChannel'() {
setup:
websocket {
'intercept-message'(pattern:'/permitAll',access:'permitAll')
'intercept-message'(pattern:'/denyAll',access:'denyAll')
}
when: 'message is sent to the denyAll endpoint'
clientInboundChannel.send(message('/denyAll'))
then: 'access is denied to the denyAll endpoint'
def e = thrown(MessageDeliveryException)
e.cause instanceof AccessDeniedException
and: 'access is granted to the permitAll endpoint'
clientInboundChannel.send(message('/permitAll'))
}
def 'anonymous authentication supported'() {
setup:
websocket {
'intercept-message'(pattern:'/permitAll',access:'permitAll')
'intercept-message'(pattern:'/denyAll',access:'denyAll')
}
messageUser = null
when: 'message is sent to the permitAll endpoint with no user'
clientInboundChannel.send(message('/permitAll'))
then: 'access is granted'
noExceptionThrown()
}
@Unroll
def "message type - #type"(SimpMessageType type) {
setup:
websocket {
'intercept-message'('type': type.toString(), access:'permitAll')
'intercept-message'(pattern:'/**', access:'denyAll')
}
messageUser = null
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(type)
if(SimpMessageType.CONNECT == type) {
headers.setNativeHeader(csrfToken.headerName, csrfToken.token)
}
Message message = message(headers, '/permitAll')
when: 'message is sent to the permitAll endpoint with no user'
clientInboundChannel.send(message)
then: 'access is granted'
noExceptionThrown()
where:
type << SimpMessageType.values()
}
@Unroll
def "pattern and message type - #type"(SimpMessageType type) {
setup:
websocket {
'intercept-message'(pattern: '/permitAll', 'type': type.toString(), access:'permitAll')
'intercept-message'(pattern:'/**', access:'denyAll')
}
when: 'message is sent to the permitAll endpoint with no user'
clientInboundChannel.send(message('/permitAll', type))
then: 'access is granted'
noExceptionThrown()
when: 'message sent to other message type'
clientInboundChannel.send(message('/permitAll', SimpMessageType.UNSUBSCRIBE))
then: 'does not match'
MessageDeliveryException e = thrown()
e.cause instanceof AccessDeniedException
when: 'message is sent to other pattern'
clientInboundChannel.send(message('/other', type))
then: 'does not match'
MessageDeliveryException eOther = thrown()
eOther.cause instanceof AccessDeniedException
where:
type << [SimpMessageType.MESSAGE, SimpMessageType.SUBSCRIBE]
}
@Unroll
def "intercept-message with invalid type and pattern - #type"(SimpMessageType type) {
when:
websocket {
'intercept-message'(pattern : '/**', 'type': type.toString(), access:'permitAll')
}
then:
thrown(BeanDefinitionParsingException)
where:
type << [SimpMessageType.CONNECT, SimpMessageType.CONNECT_ACK, SimpMessageType.DISCONNECT, SimpMessageType.DISCONNECT_ACK, SimpMessageType.HEARTBEAT, SimpMessageType.OTHER, SimpMessageType.UNSUBSCRIBE ]
}
def 'messages with no id automatically adds Authentication argument resolver'() {
setup:
def id = 'authenticationController'
bean(id,MyController)
bean('inPostProcessor',InboundExecutorPostProcessor)
websocket {
'intercept-message'(pattern:'/**',access:'permitAll')
}
when: 'message is sent to the authentication endpoint'
clientInboundChannel.send(message('/authentication'))
then: 'the AuthenticationPrincipal is resolved'
def controller = appContext.getBean(id)
controller.authenticationPrincipal == messageUser.name
}
def 'messages of type CONNECT use CsrfTokenHandshakeInterceptor'() {
setup:
def id = 'authenticationController'
bean(id,MyController)
bean('inPostProcessor',InboundExecutorPostProcessor)
websocket {
'intercept-message'(pattern:'/**',access:'permitAll')
}
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT)
Message<?> message = message(headers,'/authentication')
WebSocketHttpRequestHandler handler = appContext.getBean(WebSocketHttpRequestHandler)
MockHttpServletRequest request = new MockHttpServletRequest()
String sessionAttr = "sessionAttr"
request.getSession().setAttribute(sessionAttr,"sessionValue")
CsrfToken token = new DefaultCsrfToken("header", "param", "token")
request.setAttribute(CsrfToken.name, token)
when:
handler.handleRequest(request , new MockHttpServletResponse())
TestHandshakeHandler handshakeHandler = appContext.getBean(TestHandshakeHandler)
then: 'CsrfToken is populated'
handshakeHandler.attributes.get(CsrfToken.name) == token
and: 'Explicitly listed HandshakeInterceptor are not overridden'
handshakeHandler.attributes.get(sessionAttr) == request.getSession().getAttribute(sessionAttr)
}
def 'messages of type CONNECT use CsrfTokenHandshakeInterceptor with SockJS'() {
setup:
useSockJS = true
def id = 'authenticationController'
bean(id,MyController)
bean('inPostProcessor',InboundExecutorPostProcessor)
websocket {
'intercept-message'(pattern:'/**',access:'permitAll')
}
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT)
Message<?> message = message(headers,'/authentication')
SockJsHttpRequestHandler handler = appContext.getBean(SockJsHttpRequestHandler)
MockHttpServletRequest request = new MockHttpServletRequest()
String sessionAttr = "sessionAttr"
request.getSession().setAttribute(sessionAttr,"sessionValue")
CsrfToken token = new DefaultCsrfToken("header", "param", "token")
request.setAttribute(CsrfToken.name, token)
request.setMethod("GET")
request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "/289/tpyx6mde/websocket")
when:
handler.handleRequest(request , new MockHttpServletResponse())
TestHandshakeHandler handshakeHandler = appContext.getBean(TestHandshakeHandler)
then: 'CsrfToken is populated'
handshakeHandler.attributes?.get(CsrfToken.name) == token
and: 'Explicitly listed HandshakeInterceptor are not overridden'
handshakeHandler.attributes?.get(sessionAttr) == request.getSession().getAttribute(sessionAttr)
}
def 'messages of type CONNECT require valid CsrfToken'() {
setup:
def id = 'authenticationController'
bean(id,MyController)
bean('inPostProcessor',InboundExecutorPostProcessor)
websocket {
'intercept-message'(pattern:'/**',access:'permitAll')
}
when: 'websocket of type CONNECTION is sent without CsrfTOken'
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT)
Message<?> message = message(headers,'/authentication')
clientInboundChannel.send(message)
then: 'CSRF Protection blocks the Message'
MessageDeliveryException expected = thrown()
expected.cause instanceof InvalidCsrfTokenException
}
def 'messages of type CONNECT disabled valid CsrfToken'() {
setup:
def id = 'authenticationController'
bean(id,MyController)
bean('inPostProcessor',InboundExecutorPostProcessor)
websocket('same-origin-disabled':true) {
'intercept-message'(pattern:'/**',access:'permitAll')
}
when: 'websocket of type CONNECTION is sent without CsrfTOken'
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT)
Message<?> message = message(headers,'/authentication')
clientInboundChannel.send(message)
then: 'CSRF Protection blocks the Message'
noExceptionThrown()
}
def 'websocket with no id does not override customArgumentResolvers'() {
setup:
def id = 'authenticationController'
bean(id,MyController)
bean('inPostProcessor',InboundExecutorPostProcessor)
bean('mcar', MyCustomArgumentResolver)
xml.'websocket:message-broker' {
'websocket:transport' {}
'websocket:stomp-endpoint'(path:'/app') {
'websocket:handshake-handler'(ref:'testHandler') {}
}
'websocket:simple-broker'(prefix:"/queue, /topic"){}
'websocket:argument-resolvers' {
'b:ref'(bean:'mcar')
}
}
websocket {
'intercept-message'(pattern:'/**',access:'permitAll')
}
when: 'websocket is sent to the myCustom endpoint'
clientInboundChannel.send(message('/myCustom'))
then: 'myCustomArgument is resolved'
def controller = appContext.getBean(id)
controller.myCustomArgument!= null
}
def 'websocket defaults pathMatcher'() {
setup:
bean('pathMatcher',AntPathMatcher.name,['.'])
bean('testHandler', TestHandshakeHandler)
xml.'websocket:message-broker'('path-matcher':'pathMatcher') {
'websocket:transport' {}
'websocket:stomp-endpoint'(path:'/app') {
'websocket:handshake-handler'(ref:'testHandler') {}
}
'websocket:simple-broker'(prefix:"/queue, /topic"){}
}
xml.'websocket-message-broker' {
'intercept-message'(pattern:'/denyAll.*',access:'denyAll')
}
createAppContext()
when: 'sent to denyAll.a'
appContext.getBean(SimpAnnotationMethodMessageHandler)
clientInboundChannel.send(message('/denyAll.a'))
then: 'access is denied'
MessageDeliveryException expected = thrown()
expected.cause instanceof AccessDeniedException
when: 'sent to denyAll.a.b'
clientInboundChannel.send(message('/denyAll.a.b'))
then: 'access is allowed'
noExceptionThrown()
}
def 'websocket with id does not integrate with clientInboundChannel'() {
setup:
websocket([id:'inCsi']) {
'intercept-message'(pattern:'/**',access:'denyAll')
}
when:
def success = clientInboundChannel.send(message('/denyAll'))
then:
success
}
def 'websocket with id can be explicitly integrated with clientInboundChannel'() {
setup: 'websocket security explicitly setup'
xml.'websocket:message-broker' {
'websocket:transport' {}
'websocket:stomp-endpoint'(path:'/app') {
'websocket:sockjs' {}
}
'websocket:simple-broker'(prefix:"/queue, /topic"){}
'websocket:client-inbound-channel' {
'websocket:interceptors' {
'b:bean'(class:'org.springframework.security.messaging.context.SecurityContextChannelInterceptor'){}
'b:ref'(bean:'inCsi'){}
}
}
}
xml.'websocket-message-broker'(id:'inCsi') {
'intercept-message'(pattern:'/**',access:'denyAll')
}
createAppContext()
when:
clientInboundChannel.send(message('/denyAll'))
then:
def e = thrown(MessageDeliveryException)
e.cause instanceof AccessDeniedException
}
def 'automatic integration with clientInboundChannel does not override exisiting websocket:interceptors'() {
setup:
mockBean(ChannelInterceptor,'mci')
xml.'websocket:message-broker'('application-destination-prefix':'/app',
'user-destination-prefix':'/user') {
'websocket:transport' {}
'websocket:stomp-endpoint'(path:'/foo') {
'websocket:sockjs' {}
}
'websocket:simple-broker'(prefix:"/queue, /topic"){}
'websocket:client-inbound-channel' {
'websocket:interceptors' {
'b:ref'(bean:'mci'){}
}
}
}
xml.'websocket-message-broker' {
'intercept-message'(pattern:'/denyAll',access:'denyAll')
'intercept-message'(pattern:'/permitAll',access:'permitAll')
}
createAppContext()
ChannelInterceptor mci = appContext.getBean('mci')
when:
Message<?> message = message('/permitAll')
clientInboundChannel.send(message)
then:
verify(mci).preSend(message, clientInboundChannel) || true
}
def websocket(Map<String,Object> attrs=[:], Closure c) {
bean('testHandler', TestHandshakeHandler)
xml.'websocket:message-broker' {
'websocket:transport' {}
'websocket:stomp-endpoint'(path:'/app') {
'websocket:handshake-handler'(ref:'testHandler') {}
'websocket:handshake-interceptors' {
'b:bean'('class':HttpSessionHandshakeInterceptor.name) {}
}
if(useSockJS) {
'websocket:sockjs' {}
}
}
'websocket:simple-broker'(prefix:"/queue, /topic"){}
}
xml.'websocket-message-broker'(attrs, c)
createAppContext()
}
def 'custom expressions'() {
setup:
bean('expressionHandler', DenyRobMessageSecurityExpressionHandler)
websocket {
'expression-handler' (ref: 'expressionHandler') {}
'intercept-message'(pattern:'/**',access:'denyRob()')
}
when: 'message is sent with user'
clientInboundChannel.send(message('/message'))
then: 'access is allowed to custom expression'
noExceptionThrown()
when:
messageUser = new TestingAuthenticationToken('rob', 'pass', 'ROLE_USER')
clientInboundChannel.send(message('/message'))
then:
def e = thrown(MessageDeliveryException)
e.cause instanceof AccessDeniedException
}
def getClientInboundChannel() {
appContext.getBean("clientInboundChannel")
}
def message(String destination, SimpMessageType type=SimpMessageType.MESSAGE) {
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(type)
message(headers, destination)
}
def message(SimpMessageHeaderAccessor headers, String destination) {
headers.sessionId = '123'
headers.sessionAttributes = [:]
headers.destination = destination
if(messageUser != null) {
headers.user = messageUser
}
if(csrfToken != null) {
headers.sessionAttributes[CsrfToken.name] = csrfToken
}
new GenericMessage<String>("hi",headers.messageHeaders)
}
@Controller
static class MyController {
String authenticationPrincipal
MyCustomArgument myCustomArgument
@MessageMapping('/authentication')
public void authentication(@AuthenticationPrincipal String un) {
this.authenticationPrincipal = un
}
@MessageMapping('/myCustom')
public void myCustom(MyCustomArgument myCustomArgument) {
this.myCustomArgument = myCustomArgument
}
}
static class MyCustomArgument {
MyCustomArgument(String notDefaultConstr) {}
}
static class MyCustomArgumentResolver implements HandlerMethodArgumentResolver {
@Override
boolean supportsParameter(MethodParameter parameter) {
parameter.parameterType.isAssignableFrom(MyCustomArgument)
}
@Override
Object resolveArgument(MethodParameter parameter, Message<?> message) throws Exception {
new MyCustomArgument("")
}
}
static class TestHandshakeHandler implements HandshakeHandler {
Map<String, Object> attributes;
boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws HandshakeFailureException {
this.attributes = attributes
if(wsHandler instanceof SockJsWebSocketHandler) {
// work around SPR-12716
SockJsWebSocketHandler sockJs = (SockJsWebSocketHandler) wsHandler;
this.attributes = sockJs.sockJsSession.attributes
}
true
}
}
/**
* Changes the clientInboundChannel Executor to be synchronous
*/
static class InboundExecutorPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
BeanDefinition inbound = registry.getBeanDefinition("clientInboundChannel")
inbound.getConstructorArgumentValues().addIndexedArgumentValue(0, new RootBeanDefinition(SyncTaskExecutor));
}
@Override
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
}
static class DenyRobMessageSecurityExpressionHandler extends DefaultMessageSecurityExpressionHandler<Object> {
@Override
protected SecurityExpressionOperations createSecurityExpressionRoot(
Authentication authentication,
Message<Object> invocation) {
return new MessageSecurityExpressionRoot(authentication, invocation) {
public boolean denyRob() {
Authentication auth = getAuthentication();
return auth != null && !"rob".equals(auth.getName());
}
};
}
}
}
@@ -24,7 +24,7 @@ import java.util.List;
/**
* @author Rob Winch
* @since 5.0
* @since 5.0.2
*/
public class MockEventListener<T extends ApplicationEvent>
implements ApplicationListener<T> {
@@ -78,7 +78,7 @@ public class AuthenticationManagerBuilderTests {
verify(opp, never()).postProcess(provider);
}
// https://github.com/SpringSource/spring-security-javaconfig/issues/132
// https://github.com/spring-projects/spring-security-javaconfig/issues/132
@Test
public void customAuthenticationEventPublisherWithWeb() throws Exception {
ObjectPostProcessor<Object> opp = mock(ObjectPostProcessor.class);
@@ -62,7 +62,7 @@ public class Issue50Tests {
}
@Test
// https://github.com/SpringSource/spring-security-javaconfig/issues/50
// https://github.com/spring-projects/spring-security-javaconfig/issues/50
public void loadWhenGlobalMethodSecurityConfigurationThenAuthenticationManagerLazy() {
// no exception
}
@@ -0,0 +1,28 @@
/*
* 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.method.configuration;
import org.springframework.context.annotation.Bean;
/**
* @author Josh Cummings
*/
public class MethodSecurityServiceConfig {
@Bean
MethodSecurityService service() {
return new MethodSecurityServiceImpl();
}
}
@@ -0,0 +1,95 @@
/*
* 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.method.configuration;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.config.test.SpringTestRule;
import org.springframework.security.core.Authentication;
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.io.Serializable;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
*
* @author Rob Winch
* @author Josh Cummings
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SecurityTestExecutionListeners
public class NamespaceGlobalMethodSecurityExpressionHandlerTests {
@Rule
public final SpringTestRule spring = new SpringTestRule();
@Autowired(required = false)
private MethodSecurityService service;
@Test
@WithMockUser
public void methodSecurityWhenUsingCustomPermissionEvaluatorThenPreAuthorizesAccordingly() {
this.spring.register(CustomAccessDecisionManagerConfig.class, MethodSecurityServiceConfig.class).autowire();
assertThatCode(() -> this.service.hasPermission("granted"))
.doesNotThrowAnyException();
assertThatThrownBy(() -> this.service.hasPermission("denied"))
.isInstanceOf(AccessDeniedException.class);
}
@Test
@WithMockUser
public void methodSecurityWhenUsingCustomPermissionEvaluatorThenPostAuthorizesAccordingly() {
this.spring.register(CustomAccessDecisionManagerConfig.class, MethodSecurityServiceConfig.class).autowire();
assertThatCode(() -> this.service.postHasPermission("granted"))
.doesNotThrowAnyException();
assertThatThrownBy(() -> this.service.postHasPermission("denied"))
.isInstanceOf(AccessDeniedException.class);
}
@EnableGlobalMethodSecurity(prePostEnabled = true)
public static class CustomAccessDecisionManagerConfig extends GlobalMethodSecurityConfiguration {
@Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
expressionHandler.setPermissionEvaluator(new PermissionEvaluator() {
public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
return "granted".equals(targetDomainObject);
}
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
throw new UnsupportedOperationException();
}
});
return expressionHandler;
}
}
}
@@ -0,0 +1,514 @@
/*
* 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.method.configuration;
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.core.Ordered;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.access.intercept.AfterInvocationManager;
import org.springframework.security.access.intercept.RunAsManager;
import org.springframework.security.access.intercept.RunAsManagerImpl;
import org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor;
import org.springframework.security.access.intercept.aopalliance.MethodSecurityMetadataSourceAdvisor;
import org.springframework.security.access.intercept.aspectj.AspectJMethodSecurityInterceptor;
import org.springframework.security.access.method.AbstractMethodSecurityMetadataSource;
import org.springframework.security.access.method.MethodSecurityMetadataSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.test.SpringTestRule;
import org.springframework.security.core.Authentication;
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
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.*;
/**
*
* @author Rob Winch
* @author Josh Cummings
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SecurityTestExecutionListeners
public class NamespaceGlobalMethodSecurityTests {
@Rule
public final SpringTestRule spring = new SpringTestRule();
@Autowired(required = false)
private MethodSecurityService service;
// --- access-decision-manager-ref ---
@Test
@WithMockUser
public void methodSecurityWhenCustomAccessDecisionManagerThenAuthorizes() {
this.spring.register(CustomAccessDecisionManagerConfig.class, MethodSecurityServiceConfig.class).autowire();
assertThatThrownBy(() -> this.service.preAuthorize())
.isInstanceOf(AccessDeniedException.class);
assertThatThrownBy(() -> this.service.secured())
.isInstanceOf(AccessDeniedException.class);
}
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public static class CustomAccessDecisionManagerConfig extends GlobalMethodSecurityConfiguration {
@Override
protected AccessDecisionManager accessDecisionManager() {
return new DenyAllAccessDecisionManager();
}
public static class DenyAllAccessDecisionManager implements AccessDecisionManager {
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) {
throw new AccessDeniedException("Always Denied");
}
public boolean supports(ConfigAttribute attribute) {
return true;
}
public boolean supports(Class<?> clazz) {
return true;
}
}
}
// --- after-invocation-provider
@Test
@WithMockUser
public void methodSecurityWhenCustomAfterInvocationManagerThenAuthorizes() {
this.spring.register(CustomAfterInvocationManagerConfig.class, MethodSecurityServiceConfig.class).autowire();
assertThatThrownBy(() -> this.service.preAuthorizePermitAll())
.isInstanceOf(AccessDeniedException.class);
}
@EnableGlobalMethodSecurity(prePostEnabled = true)
public static class CustomAfterInvocationManagerConfig
extends GlobalMethodSecurityConfiguration {
@Override
protected AfterInvocationManager afterInvocationManager() {
return new AfterInvocationManagerStub();
}
public static class AfterInvocationManagerStub implements AfterInvocationManager {
public Object decide(Authentication authentication,
Object object,
Collection<ConfigAttribute> attributes,
Object returnedObject) throws AccessDeniedException {
throw new AccessDeniedException("custom AfterInvocationManager");
}
public boolean supports(ConfigAttribute attribute) {
return true;
}
public boolean supports(Class<?> clazz) {
return true;
}
}
}
// --- authentication-manager-ref ---
@Test
@WithMockUser
public void methodSecurityWhenCustomAuthenticationManagerThenAuthorizes() {
this.spring.register(CustomAuthenticationConfig.class, MethodSecurityServiceConfig.class).autowire();
assertThatThrownBy(() -> this.service.preAuthorize())
.isInstanceOf(UnsupportedOperationException.class);
}
@EnableGlobalMethodSecurity(prePostEnabled = true)
public static class CustomAuthenticationConfig extends GlobalMethodSecurityConfiguration {
@Override
public MethodInterceptor methodSecurityInterceptor() throws Exception {
MethodInterceptor interceptor = super.methodSecurityInterceptor();
((MethodSecurityInterceptor) interceptor).setAlwaysReauthenticate(true);
return interceptor;
}
@Override
protected AuthenticationManager authenticationManager() {
return (authentication) -> {
throw new UnsupportedOperationException();
};
}
}
// --- jsr250-annotations ---
@Test
@WithMockUser
public void methodSecurityWhenJsr250EnabledThenAuthorizes() {
this.spring.register(Jsr250Config.class, MethodSecurityServiceConfig.class).autowire();
assertThatCode(() -> this.service.preAuthorize())
.doesNotThrowAnyException();
assertThatCode(() -> this.service.secured())
.doesNotThrowAnyException();
assertThatThrownBy(() -> this.service.jsr250())
.isInstanceOf(AccessDeniedException.class);
assertThatCode(() -> this.service.jsr250PermitAll())
.doesNotThrowAnyException();
}
@EnableGlobalMethodSecurity(jsr250Enabled = true)
@Configuration
public static class Jsr250Config {
}
// --- metadata-source-ref ---
@Test
@WithMockUser
public void methodSecurityWhenCustomMethodSecurityMetadataSourceThenAuthorizes() {
this.spring.register(CustomMethodSecurityMetadataSourceConfig.class, MethodSecurityServiceConfig.class).autowire();
assertThatThrownBy(() -> this.service.preAuthorize())
.isInstanceOf(AccessDeniedException.class);
assertThatThrownBy(() -> this.service.secured())
.isInstanceOf(AccessDeniedException.class);
assertThatThrownBy(() -> this.service.jsr250())
.isInstanceOf(AccessDeniedException.class);
}
@EnableGlobalMethodSecurity
public static class CustomMethodSecurityMetadataSourceConfig extends GlobalMethodSecurityConfiguration {
@Override
protected MethodSecurityMetadataSource customMethodSecurityMetadataSource() {
return new AbstractMethodSecurityMetadataSource() {
public Collection<ConfigAttribute> getAttributes(Method method, Class<?> targetClass) {
// require ROLE_NOBODY for any method on MethodSecurityService interface
return MethodSecurityService.class.isAssignableFrom(targetClass) ?
Arrays.asList(new SecurityConfig("ROLE_NOBODY")) :
Collections.emptyList();
}
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
};
}
}
// --- mode ---
@Test
@WithMockUser
public void contextRefreshWhenUsingAspectJThenAutowire() throws Exception {
this.spring.register(AspectJModeConfig.class, MethodSecurityServiceConfig.class).autowire();
assertThat(this.spring.getContext().getBean(Class.forName("org.springframework.security.access.intercept.aspectj.aspect.AnnotationSecurityAspect"))).isNotNull();
assertThat(this.spring.getContext().getBean(AspectJMethodSecurityInterceptor.class)).isNotNull();
//TODO diagnose why aspectj isn't weaving method security advice around MethodSecurityServiceImpl
}
@EnableGlobalMethodSecurity(mode = AdviceMode.ASPECTJ, securedEnabled = true)
public static class AspectJModeConfig {
}
@Test
public void contextRefreshWhenUsingAspectJAndCustomGlobalMethodSecurityConfigurationThenAutowire()
throws Exception {
this.spring.register(AspectJModeExtendsGMSCConfig.class).autowire();
assertThat(this.spring.getContext().getBean(Class.forName("org.springframework.security.access.intercept.aspectj.aspect.AnnotationSecurityAspect"))).isNotNull();
assertThat(this.spring.getContext().getBean(AspectJMethodSecurityInterceptor.class)).isNotNull();
}
@EnableGlobalMethodSecurity(mode = AdviceMode.ASPECTJ)
public static class AspectJModeExtendsGMSCConfig extends GlobalMethodSecurityConfiguration {
}
// --- order ---
private static class AdvisorOrderConfig
implements ImportBeanDefinitionRegistrar {
private static class ExceptingInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) {
throw new UnsupportedOperationException("Deny All");
}
}
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
BeanDefinitionBuilder advice = BeanDefinitionBuilder
.rootBeanDefinition(ExceptingInterceptor.class);
registry.registerBeanDefinition("exceptingInterceptor",
advice.getBeanDefinition());
BeanDefinitionBuilder advisor = BeanDefinitionBuilder
.rootBeanDefinition(MethodSecurityMetadataSourceAdvisor.class);
advisor.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
advisor.addConstructorArgValue("exceptingInterceptor");
advisor.addConstructorArgReference("methodSecurityMetadataSource");
advisor.addConstructorArgValue("methodSecurityMetadataSource");
advisor.addPropertyValue("order", 0);
registry.registerBeanDefinition("exceptingAdvisor",
advisor.getBeanDefinition());
}
}
@Test
@WithMockUser
public void methodSecurityWhenOrderSpecifiedThenConfigured() {
this.spring.register(CustomOrderConfig.class, MethodSecurityServiceConfig.class).autowire();
assertThat(this.spring.getContext()
.getBean("metaDataSourceAdvisor", MethodSecurityMetadataSourceAdvisor.class)
.getOrder())
.isEqualTo(-135);
assertThatThrownBy(() -> this.service.jsr250())
.isInstanceOf(AccessDeniedException.class);
}
@EnableGlobalMethodSecurity(order = -135, jsr250Enabled = true)
@Import(AdvisorOrderConfig.class)
public static class CustomOrderConfig {
}
@Test
@WithMockUser
public void methodSecurityWhenOrderUnspecifiedThenConfiguredToLowestPrecedence() {
this.spring.register(DefaultOrderConfig.class, MethodSecurityServiceConfig.class).autowire();
assertThat(this.spring.getContext()
.getBean("metaDataSourceAdvisor", MethodSecurityMetadataSourceAdvisor.class)
.getOrder())
.isEqualTo(Ordered.LOWEST_PRECEDENCE);
assertThatThrownBy(() -> this.service.jsr250())
.isInstanceOf(UnsupportedOperationException.class);
}
@EnableGlobalMethodSecurity(jsr250Enabled = true)
@Import(AdvisorOrderConfig.class)
public static class DefaultOrderConfig {
}
@Test
@WithMockUser
public void methodSecurityWhenOrderUnspecifiedAndCustomGlobalMethodSecurityConfigurationThenConfiguredToLowestPrecedence() {
this.spring.register(DefaultOrderExtendsMethodSecurityConfig.class, MethodSecurityServiceConfig.class).autowire();
assertThat(this.spring.getContext()
.getBean("metaDataSourceAdvisor", MethodSecurityMetadataSourceAdvisor.class)
.getOrder())
.isEqualTo(Ordered.LOWEST_PRECEDENCE);
assertThatThrownBy(() -> this.service.jsr250())
.isInstanceOf(UnsupportedOperationException.class);
}
@EnableGlobalMethodSecurity(jsr250Enabled = true)
@Import(AdvisorOrderConfig.class)
public static class DefaultOrderExtendsMethodSecurityConfig extends GlobalMethodSecurityConfiguration {
}
// --- pre-post-annotations ---
@Test
@WithMockUser
public void methodSecurityWhenPrePostEnabledThenPreAuthorizes() {
this.spring.register(PreAuthorizeConfig.class, MethodSecurityServiceConfig.class).autowire();
assertThatCode(() -> this.service.secured())
.doesNotThrowAnyException();
assertThatCode(() -> this.service.jsr250())
.doesNotThrowAnyException();
assertThatThrownBy(() -> this.service.preAuthorize())
.isInstanceOf(AccessDeniedException.class);
}
@EnableGlobalMethodSecurity(prePostEnabled = true)
public static class PreAuthorizeConfig {
}
@Test
@WithMockUser
public void methodSecurityWhenPrePostEnabledAndCustomGlobalMethodSecurityConfigurationThenPreAuthorizes() {
this.spring.register(PreAuthorizeExtendsGMSCConfig.class, MethodSecurityServiceConfig.class).autowire();
assertThatCode(() -> this.service.secured())
.doesNotThrowAnyException();
assertThatCode(() -> this.service.jsr250())
.doesNotThrowAnyException();
assertThatThrownBy(() -> this.service.preAuthorize())
.isInstanceOf(AccessDeniedException.class);
}
@EnableGlobalMethodSecurity(prePostEnabled = true)
public static class PreAuthorizeExtendsGMSCConfig extends GlobalMethodSecurityConfiguration {
}
// --- proxy-target-class ---
@Test
@WithMockUser
public void methodSecurityWhenProxyTargetClassThenDoesNotWireToInterface() {
this.spring.register(ProxyTargetClassConfig.class, MethodSecurityServiceConfig.class).autowire();
// make sure service was actually proxied
assertThat(this.service.getClass().getInterfaces())
.doesNotContain(MethodSecurityService.class);
assertThatThrownBy(() -> this.service.preAuthorize())
.isInstanceOf(AccessDeniedException.class);
}
@EnableGlobalMethodSecurity(proxyTargetClass = true, prePostEnabled = true)
public static class ProxyTargetClassConfig {
}
@Test
@WithMockUser
public void methodSecurityWhenDefaultProxyThenWiresToInterface() {
this.spring.register(DefaultProxyConfig.class, MethodSecurityServiceConfig.class).autowire();
assertThat(this.service.getClass().getInterfaces())
.contains(MethodSecurityService.class);
assertThatThrownBy(() -> this.service.preAuthorize())
.isInstanceOf(AccessDeniedException.class);
}
@EnableGlobalMethodSecurity(prePostEnabled = true)
public static class DefaultProxyConfig {
}
// --- run-as-manager-ref ---
@Test
@WithMockUser
public void methodSecurityWhenCustomRunAsManagerThenRunAsWrapsAuthentication() {
this.spring.register(CustomRunAsManagerConfig.class, MethodSecurityServiceConfig.class).autowire();
assertThat(service.runAs().getAuthorities())
.anyMatch(authority -> "ROLE_RUN_AS_SUPER".equals(authority.getAuthority()));
}
@EnableGlobalMethodSecurity(securedEnabled = true)
public static class CustomRunAsManagerConfig extends GlobalMethodSecurityConfiguration {
@Override
protected RunAsManager runAsManager() {
RunAsManagerImpl runAsManager = new RunAsManagerImpl();
runAsManager.setKey("some key");
return runAsManager;
}
}
// --- secured-annotation ---
@Test
@WithMockUser
public void methodSecurityWhenSecuredEnabledThenSecures() {
this.spring.register(SecuredConfig.class, MethodSecurityServiceConfig.class).autowire();
assertThatThrownBy(() -> this.service.secured())
.isInstanceOf(AccessDeniedException.class);
assertThatCode(() -> this.service.securedUser())
.doesNotThrowAnyException();
assertThatCode(() -> this.service.preAuthorize())
.doesNotThrowAnyException();
assertThatCode(() -> this.service.jsr250())
.doesNotThrowAnyException();
}
@EnableGlobalMethodSecurity(securedEnabled = true)
public static class SecuredConfig {
}
// --- unsorted ---
@Test
@WithMockUser
public void methodSecurityWhenMissingEnableAnnotationThenShowsHelpfulError() {
assertThatThrownBy(() ->
this.spring.register(ExtendsNoEnableAnntotationConfig.class).autowire())
.hasStackTraceContaining(EnableGlobalMethodSecurity.class.getName() + " is required");
}
@Configuration
public static class ExtendsNoEnableAnntotationConfig
extends GlobalMethodSecurityConfiguration {
}
@Test
@WithMockUser
public void methodSecurityWhenImportingGlobalMethodSecurityConfigurationSubclassThenAuthorizes() {
this.spring.register(ImportSubclassGMSCConfig.class, MethodSecurityServiceConfig.class).autowire();
assertThatCode(() -> this.service.secured())
.doesNotThrowAnyException();
assertThatCode(() -> this.service.jsr250())
.doesNotThrowAnyException();
assertThatThrownBy(() -> this.service.preAuthorize())
.isInstanceOf(AccessDeniedException.class);
}
@Configuration
@Import(PreAuthorizeExtendsGMSCConfig.class)
public static class ImportSubclassGMSCConfig {
}
}
@@ -0,0 +1,150 @@
/*
* 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.sec2758;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.core.PriorityOrdered;
import org.springframework.security.access.annotation.Jsr250MethodSecurityMetadataSource;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.test.context.annotation.SecurityTestExecutionListeners;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.security.RolesAllowed;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* @author Josh Cummings
*
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SecurityTestExecutionListeners
public class Sec2758Tests {
@Rule
public final SpringTestRule spring = new SpringTestRule();
@Autowired
MockMvc mvc;
@Autowired(required = false)
Service service;
@WithMockUser(authorities = "CUSTOM")
@Test
public void requestWhenNullifyingRolePrefixThenPassivityRestored() throws Exception {
this.spring.register(SecurityConfig.class).autowire();
this.mvc.perform(get("/")).andExpect(status().isOk());
}
@WithMockUser(authorities = "CUSTOM")
@Test
public void methodSecurityWhenNullifyingRolePrefixThenPassivityRestored() {
this.spring.register(SecurityConfig.class).autowire();
assertThatCode(() -> service.doJsr250())
.doesNotThrowAnyException();
assertThatCode(() -> service.doPreAuthorize())
.doesNotThrowAnyException();
}
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true, jsr250Enabled = true)
static class SecurityConfig extends WebSecurityConfigurerAdapter {
@RestController
static class RootController {
@GetMapping("/")
public String ok() { return "ok"; }
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().access("hasAnyRole('CUSTOM')");
}
@Bean
public Service service() {
return new Service();
}
@Bean
static DefaultRolesPrefixPostProcessor defaultRolesPrefixPostProcessor() {
return new DefaultRolesPrefixPostProcessor();
}
}
static class Service {
@PreAuthorize("hasRole('CUSTOM')")
public void doPreAuthorize() {}
@RolesAllowed("CUSTOM")
public void doJsr250() {}
}
static class DefaultRolesPrefixPostProcessor implements BeanPostProcessor, PriorityOrdered {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if(bean instanceof Jsr250MethodSecurityMetadataSource) {
((Jsr250MethodSecurityMetadataSource) bean).setDefaultRolePrefix(null);
}
if(bean instanceof DefaultMethodSecurityExpressionHandler) {
((DefaultMethodSecurityExpressionHandler) bean).setDefaultRolePrefix(null);
}
if(bean instanceof DefaultWebSecurityExpressionHandler) {
((DefaultWebSecurityExpressionHandler) bean).setDefaultRolePrefix(null);
}
return bean;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public int getOrder() {
return PriorityOrdered.HIGHEST_PRECEDENCE;
}
}
}
@@ -17,6 +17,10 @@ package org.springframework.security.config.annotation.web;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
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;
@@ -34,15 +38,16 @@ import org.springframework.security.config.test.SpringTestRule;
import org.springframework.security.core.userdetails.PasswordEncodedUser;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;
import org.springframework.security.web.context.request.async.SecurityContextCallableProcessingInterceptor;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.accept.ContentNegotiationStrategy;
import org.springframework.web.accept.HeaderContentNegotiationStrategy;
import org.springframework.web.context.request.async.CallableProcessingInterceptor;
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.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
@@ -53,7 +58,8 @@ import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.ThrowableAssert.catchThrowable;
import static org.mockito.Mockito.mock;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
@@ -65,13 +71,12 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
* @author Rob Winch
* @author Joe Grandja
*/
@PrepareForTest({WebAsyncManager.class})
@RunWith(PowerMockRunner.class)
public class WebSecurityConfigurerAdapterTests {
@Rule
public final SpringTestRule spring = new SpringTestRule();
@Autowired
private FilterChainProxy springSecurityFilterChain;
@Autowired
private MockMvc mockMvc;
@@ -108,7 +113,21 @@ public class WebSecurityConfigurerAdapterTests {
public void loadConfigWhenDefaultConfigThenWebAsyncManagerIntegrationFilterAdded() throws Exception {
this.spring.register(WebAsyncPopulatedByDefaultConfig.class).autowire();
assertThat(this.findFilter(WebAsyncManagerIntegrationFilter.class, this.springSecurityFilterChain)).isNotNull();
WebAsyncManager webAsyncManager = mock(WebAsyncManager.class);
this.mockMvc.perform(get("/").requestAttr(WebAsyncUtils.WEB_ASYNC_MANAGER_ATTRIBUTE, webAsyncManager));
ArgumentCaptor<CallableProcessingInterceptor> callableProcessingInterceptorArgCaptor =
ArgumentCaptor.forClass(CallableProcessingInterceptor.class);
verify(webAsyncManager, atLeastOnce()).registerCallableInterceptor(any(), callableProcessingInterceptorArgCaptor.capture());
CallableProcessingInterceptor callableProcessingInterceptor =
callableProcessingInterceptorArgCaptor.getAllValues().stream()
.filter(e -> SecurityContextCallableProcessingInterceptor.class.isAssignableFrom(e.getClass()))
.findFirst()
.orElse(null);
assertThat(callableProcessingInterceptor).isNotNull();
}
@EnableWebSecurity
@@ -212,7 +231,7 @@ public class WebSecurityConfigurerAdapterTests {
public void loadConfigWhenUserDetailsServiceHasCircularReferenceThenStillLoads() throws Exception {
this.spring.register(RequiresUserDetailsServiceConfig.class, UserDetailsServiceConfig.class).autowire();
MyFilter myFilter = this.findFilter(MyFilter.class, this.springSecurityFilterChain);
MyFilter myFilter = this.spring.getContext().getBean(MyFilter.class);
Throwable thrown = catchThrowable(() -> myFilter.userDetailsService.loadUserByUsername("user") );
assertThat(thrown).isNull();
@@ -335,22 +354,4 @@ public class WebSecurityConfigurerAdapterTests {
@Order
static class LowestPriorityWebSecurityConfig extends WebSecurityConfigurerAdapter {
}
private <T extends Filter> T findFilter(Class<T> filterType, FilterChainProxy filterChainProxy) {
return this.findFilter(filterType, filterChainProxy, 0);
}
private <T extends Filter> T findFilter(Class<T> filterType, FilterChainProxy filterChainProxy, int filterChainIndex) {
if (filterChainIndex >= filterChainProxy.getFilterChains().size()) {
return null;
}
Filter filter = filterChainProxy.getFilterChains().get(filterChainIndex).getFilters()
.stream()
.filter(f -> f.getClass().isAssignableFrom(filterType))
.findFirst()
.orElse(null);
return (T) filter;
}
}
@@ -25,19 +25,20 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.test.SpringTestRule;
import org.springframework.security.core.userdetails.PasswordEncodedUser;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.ThrowableAssert.catchThrowable;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -51,9 +52,6 @@ public class HttpConfigurationTests {
@Rule
public final SpringTestRule spring = new SpringTestRule();
@Autowired
private FilterChainProxy springSecurityFilterChain;
@Autowired
private MockMvc mockMvc;
@@ -90,19 +88,25 @@ public class HttpConfigurationTests {
}
}
// https://github.com/SpringSource/spring-security-javaconfig/issues/104
// https://github.com/spring-projects/spring-security-javaconfig/issues/104
@Test
public void configureWhenAddFilterCasAuthenticationFilterThenFilterAdded() throws Exception {
CasAuthenticationFilterConfig.CAS_AUTHENTICATION_FILTER = spy(new CasAuthenticationFilter());
this.spring.register(CasAuthenticationFilterConfig.class).autowire();
assertThat(this.findFilter(CasAuthenticationFilter.class, this.springSecurityFilterChain)).isNotNull();
this.mockMvc.perform(get("/"));
verify(CasAuthenticationFilterConfig.CAS_AUTHENTICATION_FILTER).doFilter(
any(ServletRequest.class), any(ServletResponse.class), any(FilterChain.class));
}
@EnableWebSecurity
static class CasAuthenticationFilterConfig extends WebSecurityConfigurerAdapter {
static CasAuthenticationFilter CAS_AUTHENTICATION_FILTER;
protected void configure(HttpSecurity http) throws Exception {
http
.addFilter(new CasAuthenticationFilter());
.addFilter(CAS_AUTHENTICATION_FILTER);
}
}
@@ -131,22 +135,4 @@ public class HttpConfigurationTests {
.httpBasic();
}
}
private <T extends Filter> T findFilter(Class<T> filterType, FilterChainProxy filterChainProxy) {
return this.findFilter(filterType, filterChainProxy, 0);
}
private <T extends Filter> T findFilter(Class<T> filterType, FilterChainProxy filterChainProxy, int filterChainIndex) {
if (filterChainIndex >= filterChainProxy.getFilterChains().size()) {
return null;
}
Filter filter = filterChainProxy.getFilterChains().get(filterChainIndex).getFilters()
.stream()
.filter(f -> f.getClass().isAssignableFrom(filterType))
.findFirst()
.orElse(null);
return (T) filter;
}
}
@@ -0,0 +1,582 @@
/*
* 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.builders;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.jaas.JaasAuthenticationToken;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.UrlAuthorizationConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
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.DefaultSecurityFilterChain;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.expression.ExpressionBasedFilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.context.NullSecurityContextRepository;
import org.springframework.security.web.jaasapi.JaasApiIntegrationFilter;
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestWrapper;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RegexRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.web.bind.annotation.GetMapping;
import javax.security.auth.Subject;
import javax.security.auth.login.LoginContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
/**
* Tests to verify that all the functionality of <http> attributes are present in Java Config.
*
* @author Rob Winch
* @author Joe Grandja
*/
public class NamespaceHttpTests {
@Rule
public final SpringTestRule spring = new SpringTestRule();
@Autowired
private MockMvc mockMvc;
@Test // http@access-decision-manager-ref
public void configureWhenAccessDecisionManagerSetThenVerifyUse() throws Exception {
AccessDecisionManagerRefConfig.ACCESS_DECISION_MANAGER = mock(AccessDecisionManager.class);
when(AccessDecisionManagerRefConfig.ACCESS_DECISION_MANAGER.supports(FilterInvocation.class)).thenReturn(true);
when(AccessDecisionManagerRefConfig.ACCESS_DECISION_MANAGER.supports(any(ConfigAttribute.class))).thenReturn(true);
this.spring.register(AccessDecisionManagerRefConfig.class).autowire();
this.mockMvc.perform(get("/"));
verify(AccessDecisionManagerRefConfig.ACCESS_DECISION_MANAGER, times(1)).decide(any(Authentication.class), any(), anyCollection());
}
@EnableWebSecurity
static class AccessDecisionManagerRefConfig extends WebSecurityConfigurerAdapter {
static AccessDecisionManager ACCESS_DECISION_MANAGER;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().permitAll()
.accessDecisionManager(ACCESS_DECISION_MANAGER);
}
}
@Test // http@access-denied-page
public void configureWhenAccessDeniedPageSetAndRequestForbiddenThenForwardedToAccessDeniedPage() throws Exception {
this.spring.register(AccessDeniedPageConfig.class).autowire();
this.mockMvc.perform(get("/admin").with(user(PasswordEncodedUser.user())))
.andExpect(status().isForbidden())
.andExpect(forwardedUrl("/AccessDeniedPage"));
}
@EnableWebSecurity
static class AccessDeniedPageConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/admin").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.exceptionHandling()
.accessDeniedPage("/AccessDeniedPage");
}
}
@Test // http@authentication-manager-ref
public void configureWhenAuthenticationManagerProvidedThenVerifyUse() throws Exception {
AuthenticationManagerRefConfig.AUTHENTICATION_MANAGER = mock(AuthenticationManager.class);
this.spring.register(AuthenticationManagerRefConfig.class).autowire();
this.mockMvc.perform(formLogin());
verify(AuthenticationManagerRefConfig.AUTHENTICATION_MANAGER, times(1)).authenticate(any(Authentication.class));
}
@EnableWebSecurity
static class AuthenticationManagerRefConfig extends WebSecurityConfigurerAdapter {
static AuthenticationManager AUTHENTICATION_MANAGER;
@Override
protected AuthenticationManager authenticationManager() throws Exception {
return AUTHENTICATION_MANAGER;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin();
}
}
@Test // http@create-session=always
public void configureWhenSessionCreationPolicyAlwaysThenSessionCreatedOnRequest() throws Exception {
this.spring.register(CreateSessionAlwaysConfig.class).autowire();
MvcResult mvcResult = this.mockMvc.perform(get("/")).andReturn();
HttpSession session = mvcResult.getRequest().getSession(false);
assertThat(session).isNotNull();
assertThat(session.isNew()).isTrue();
}
@EnableWebSecurity
static class CreateSessionAlwaysConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().permitAll()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.ALWAYS);
}
}
@Test // http@create-session=stateless
public void configureWhenSessionCreationPolicyStatelessThenSessionNotCreatedOnRequest() throws Exception {
this.spring.register(CreateSessionStatelessConfig.class).autowire();
MvcResult mvcResult = this.mockMvc.perform(get("/")).andReturn();
HttpSession session = mvcResult.getRequest().getSession(false);
assertThat(session).isNull();
}
@EnableWebSecurity
static class CreateSessionStatelessConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().permitAll()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
@Test // http@create-session=ifRequired
public void configureWhenSessionCreationPolicyIfRequiredThenSessionCreatedWhenRequiredOnRequest() throws Exception {
this.spring.register(IfRequiredConfig.class).autowire();
MvcResult mvcResult = this.mockMvc.perform(get("/unsecure")).andReturn();
HttpSession session = mvcResult.getRequest().getSession(false);
assertThat(session).isNull();
mvcResult = this.mockMvc.perform(formLogin()).andReturn();
session = mvcResult.getRequest().getSession(false);
assertThat(session).isNotNull();
assertThat(session.isNew()).isTrue();
}
@EnableWebSecurity
static class IfRequiredConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/unsecure").permitAll()
.anyRequest().authenticated()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.formLogin();
}
}
@Test // http@create-session=never
public void configureWhenSessionCreationPolicyNeverThenSessionNotCreatedOnRequest() throws Exception {
this.spring.register(CreateSessionNeverConfig.class).autowire();
MvcResult mvcResult = this.mockMvc.perform(get("/")).andReturn();
HttpSession session = mvcResult.getRequest().getSession(false);
assertThat(session).isNull();
}
@EnableWebSecurity
static class CreateSessionNeverConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().anonymous()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.NEVER);
}
}
@Test // http@entry-point-ref
public void configureWhenAuthenticationEntryPointSetAndRequestUnauthorizedThenRedirectedToAuthenticationEntryPoint() throws Exception {
this.spring.register(EntryPointRefConfig.class).autowire();
this.mockMvc.perform(get("/"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrlPattern("**/entry-point"));
}
@EnableWebSecurity
static class EntryPointRefConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/entry-point"))
.and()
.formLogin();
}
}
@Test // http@jaas-api-provision
public void configureWhenJaasApiIntegrationFilterAddedThenJaasSubjectObtained() throws Exception {
LoginContext loginContext = mock(LoginContext.class);
when(loginContext.getSubject()).thenReturn(new Subject());
JaasAuthenticationToken authenticationToken = mock(JaasAuthenticationToken.class);
when(authenticationToken.isAuthenticated()).thenReturn(true);
when(authenticationToken.getLoginContext()).thenReturn(loginContext);
this.spring.register(JaasApiProvisionConfig.class).autowire();
this.mockMvc.perform(get("/").with(authentication(authenticationToken)));
verify(loginContext, times(1)).getSubject();
}
@EnableWebSecurity
static class JaasApiProvisionConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilter(new JaasApiIntegrationFilter());
}
}
@Test // http@realm
public void configureWhenHttpBasicAndRequestUnauthorizedThenReturnWWWAuthenticateWithRealm() throws Exception {
this.spring.register(RealmConfig.class).autowire();
this.mockMvc.perform(get("/"))
.andExpect(status().isUnauthorized())
.andExpect(header().string("WWW-Authenticate", "Basic realm=\"RealmConfig\""));
}
@EnableWebSecurity
static class RealmConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic()
.realmName("RealmConfig");
}
}
@Test // http@request-matcher-ref ant
public void configureWhenAntPatternMatchingThenAntPathRequestMatcherUsed() throws Exception {
this.spring.register(RequestMatcherAntConfig.class).autowire();
FilterChainProxy filterChainProxy = this.spring.getContext().getBean(FilterChainProxy.class);
assertThat(filterChainProxy.getFilterChains().get(0)).isInstanceOf(DefaultSecurityFilterChain.class);
DefaultSecurityFilterChain securityFilterChain = (DefaultSecurityFilterChain) filterChainProxy.getFilterChains().get(0);
assertThat(securityFilterChain.getRequestMatcher()).isInstanceOf(AntPathRequestMatcher.class);
}
@EnableWebSecurity
static class RequestMatcherAntConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/**");
}
}
@Test // http@request-matcher-ref regex
public void configureWhenRegexPatternMatchingThenRegexRequestMatcherUsed() throws Exception {
this.spring.register(RequestMatcherRegexConfig.class).autowire();
FilterChainProxy filterChainProxy = this.spring.getContext().getBean(FilterChainProxy.class);
assertThat(filterChainProxy.getFilterChains().get(0)).isInstanceOf(DefaultSecurityFilterChain.class);
DefaultSecurityFilterChain securityFilterChain = (DefaultSecurityFilterChain) filterChainProxy.getFilterChains().get(0);
assertThat(securityFilterChain.getRequestMatcher()).isInstanceOf(RegexRequestMatcher.class);
}
@EnableWebSecurity
static class RequestMatcherRegexConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.regexMatcher("/regex/.*");
}
}
@Test // http@request-matcher-ref
public void configureWhenRequestMatcherProvidedThenRequestMatcherUsed() throws Exception {
this.spring.register(RequestMatcherRefConfig.class).autowire();
FilterChainProxy filterChainProxy = this.spring.getContext().getBean(FilterChainProxy.class);
assertThat(filterChainProxy.getFilterChains().get(0)).isInstanceOf(DefaultSecurityFilterChain.class);
DefaultSecurityFilterChain securityFilterChain = (DefaultSecurityFilterChain) filterChainProxy.getFilterChains().get(0);
assertThat(securityFilterChain.getRequestMatcher()).isInstanceOf(RequestMatcherRefConfig.MyRequestMatcher.class);
}
@EnableWebSecurity
static class RequestMatcherRefConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatcher(new MyRequestMatcher());
}
static class MyRequestMatcher implements RequestMatcher {
public boolean matches(HttpServletRequest request) {
return true;
}
}
}
@Test // http@security=none
public void configureWhenIgnoredAntPatternsThenAntPathRequestMatcherUsedWithNoFilters() throws Exception {
this.spring.register(SecurityNoneConfig.class).autowire();
FilterChainProxy filterChainProxy = this.spring.getContext().getBean(FilterChainProxy.class);
assertThat(filterChainProxy.getFilterChains().get(0)).isInstanceOf(DefaultSecurityFilterChain.class);
DefaultSecurityFilterChain securityFilterChain = (DefaultSecurityFilterChain) filterChainProxy.getFilterChains().get(0);
assertThat(securityFilterChain.getRequestMatcher()).isInstanceOf(AntPathRequestMatcher.class);
assertThat(((AntPathRequestMatcher) securityFilterChain.getRequestMatcher()).getPattern()).isEqualTo("/resources/**");
assertThat(securityFilterChain.getFilters()).isEmpty();
assertThat(filterChainProxy.getFilterChains().get(1)).isInstanceOf(DefaultSecurityFilterChain.class);
securityFilterChain = (DefaultSecurityFilterChain) filterChainProxy.getFilterChains().get(1);
assertThat(securityFilterChain.getRequestMatcher()).isInstanceOf(AntPathRequestMatcher.class);
assertThat(((AntPathRequestMatcher) securityFilterChain.getRequestMatcher()).getPattern()).isEqualTo("/public/**");
assertThat(securityFilterChain.getFilters()).isEmpty();
}
@EnableWebSecurity
static class SecurityNoneConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/resources/**", "/public/**");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
}
}
@Test // http@security-context-repository-ref
public void configureWhenNullSecurityContextRepositoryThenSecurityContextNotSavedInSession() throws Exception {
this.spring.register(SecurityContextRepoConfig.class).autowire();
MvcResult mvcResult = this.mockMvc.perform(formLogin()).andReturn();
HttpSession session = mvcResult.getRequest().getSession(false);
assertThat(session).isNull();
}
@EnableWebSecurity
static class SecurityContextRepoConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.securityContext()
.securityContextRepository(new NullSecurityContextRepository())
.and()
.formLogin();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser(PasswordEncodedUser.user());
}
}
@Test // http@servlet-api-provision=false
public void configureWhenServletApiDisabledThenRequestNotServletApiWrapper() throws Exception {
this.spring.register(ServletApiProvisionConfig.class, MainController.class).autowire();
this.mockMvc.perform(get("/"));
assertThat(MainController.HTTP_SERVLET_REQUEST_TYPE).isNotInstanceOf(SecurityContextHolderAwareRequestWrapper.class);
}
@EnableWebSecurity
static class ServletApiProvisionConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().permitAll()
.and()
.servletApi()
.disable();
}
}
@Test // http@servlet-api-provision defaults to true
public void configureWhenServletApiDefaultThenRequestIsServletApiWrapper() throws Exception {
this.spring.register(ServletApiProvisionDefaultsConfig.class, MainController.class).autowire();
this.mockMvc.perform(get("/"));
assertThat(SecurityContextHolderAwareRequestWrapper.class).isAssignableFrom(MainController.HTTP_SERVLET_REQUEST_TYPE);
}
@EnableWebSecurity
static class ServletApiProvisionDefaultsConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().permitAll();
}
}
@Controller
static class MainController {
static Class<? extends HttpServletRequest> HTTP_SERVLET_REQUEST_TYPE;
@GetMapping("/")
public String index(HttpServletRequest request) {
HTTP_SERVLET_REQUEST_TYPE = request.getClass();
return "index";
}
}
@Test // http@use-expressions=true
public void configureWhenUseExpressionsEnabledThenExpressionBasedSecurityMetadataSource() throws Exception {
this.spring.register(UseExpressionsConfig.class).autowire();
UseExpressionsConfig config = this.spring.getContext().getBean(UseExpressionsConfig.class);
assertThat(ExpressionBasedFilterInvocationSecurityMetadataSource.class)
.isAssignableFrom(config.filterInvocationSecurityMetadataSourceType);
}
@EnableWebSecurity
static class UseExpressionsConfig extends WebSecurityConfigurerAdapter {
private Class<? extends FilterInvocationSecurityMetadataSource> filterInvocationSecurityMetadataSourceType;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/users**", "/sessions/**").hasRole("USER")
.antMatchers("/signup").permitAll()
.anyRequest().hasRole("USER");
}
@Override
public void init(final WebSecurity web) throws Exception {
super.init(web);
final HttpSecurity http = this.getHttp();
web.postBuildAction(() -> {
FilterSecurityInterceptor securityInterceptor = http.getSharedObject(FilterSecurityInterceptor.class);
UseExpressionsConfig.this.filterInvocationSecurityMetadataSourceType =
securityInterceptor.getSecurityMetadataSource().getClass();
});
}
}
@Test // http@use-expressions=false
public void configureWhenUseExpressionsDisabledThenDefaultSecurityMetadataSource() throws Exception {
this.spring.register(DisableUseExpressionsConfig.class).autowire();
DisableUseExpressionsConfig config = this.spring.getContext().getBean(DisableUseExpressionsConfig.class);
assertThat(DefaultFilterInvocationSecurityMetadataSource.class)
.isAssignableFrom(config.filterInvocationSecurityMetadataSourceType);
}
@EnableWebSecurity
static class DisableUseExpressionsConfig extends WebSecurityConfigurerAdapter {
private Class<? extends FilterInvocationSecurityMetadataSource> filterInvocationSecurityMetadataSourceType;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.apply(new UrlAuthorizationConfigurer<>(getApplicationContext())).getRegistry()
.antMatchers("/users**", "/sessions/**").hasRole("USER")
.antMatchers("/signup").hasRole("ANONYMOUS")
.anyRequest().hasRole("USER");
}
@Override
public void init(final WebSecurity web) throws Exception {
super.init(web);
final HttpSecurity http = this.getHttp();
web.postBuildAction(() -> {
FilterSecurityInterceptor securityInterceptor = http.getSharedObject(FilterSecurityInterceptor.class);
DisableUseExpressionsConfig.this.filterInvocationSecurityMetadataSourceType =
securityInterceptor.getSecurityMetadataSource().getClass();
});
}
}
}
@@ -0,0 +1,125 @@
/*
* 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.configuration;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.test.SpringTestRule;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.PasswordEncodedUser;
import org.springframework.security.web.debug.DebugFilter;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
/**
* @author Joe Grandja
*/
public class EnableWebSecurityTests {
@Rule
public final SpringTestRule spring = new SpringTestRule();
@Autowired
private MockMvc mockMvc;
@Test
public void configureWhenOverrideAuthenticationManagerBeanThenAuthenticationManagerBeanRegistered() throws Exception {
this.spring.register(SecurityConfig.class).autowire();
AuthenticationManager authenticationManager = this.spring.getContext().getBean(AuthenticationManager.class);
Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken("user", "password"));
assertThat(authentication.isAuthenticated()).isTrue();
}
@EnableWebSecurity
static class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser(PasswordEncodedUser.user());
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/*").hasRole("USER")
.and()
.formLogin();
}
}
@Test
public void loadConfigWhenChildConfigExtendsSecurityConfigThenSecurityConfigInherited() throws Exception {
this.spring.register(ChildSecurityConfig.class).autowire();
this.spring.getContext().getBean("springSecurityFilterChain", DebugFilter.class);
}
@Configuration
static class ChildSecurityConfig extends DebugSecurityConfig {
}
@EnableWebSecurity(debug=true)
static class DebugSecurityConfig extends WebSecurityConfigurerAdapter {
}
@Test
public void configureWhenEnableWebMvcThenAuthenticationPrincipalResolvable() throws Exception {
this.spring.register(AuthenticationPrincipalConfig.class).autowire();
this.mockMvc.perform(get("/").with(authentication(new TestingAuthenticationToken("user1", "password"))))
.andExpect(content().string("user1"));
}
@EnableWebSecurity
@EnableWebMvc
static class AuthenticationPrincipalConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
}
@RestController
static class AuthController {
@GetMapping("/")
String principal(@AuthenticationPrincipal String principal) {
return principal;
}
}
}
}
@@ -0,0 +1,136 @@
/*
* 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.configuration;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.test.SpringTestRule;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.annotation.OAuth2Client;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests for {@link OAuth2ClientConfiguration}.
*
* @author Joe Grandja
*/
public class OAuth2ClientConfigurationTests {
@Rule
public final SpringTestRule spring = new SpringTestRule();
@Autowired
private MockMvc mockMvc;
@Test
public void requestWhenAuthorizedClientFoundThenOAuth2ClientArgumentsResolved() throws Exception {
String clientRegistrationId = "client1";
String principalName = "user1";
ClientRegistrationRepository clientRegistrationRepository = mock(ClientRegistrationRepository.class);
ClientRegistration clientRegistration = ClientRegistration.withRegistrationId(clientRegistrationId)
.clientId("client-id")
.clientSecret("secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.redirectUriTemplate("{baseUrl}/client1")
.scope("scope1", "scope2")
.authorizationUri("https://provider.com/oauth2/auth")
.tokenUri("https://provider.com/oauth2/token")
.clientName("Client 1")
.build();
when(clientRegistrationRepository.findByRegistrationId(clientRegistrationId)).thenReturn(clientRegistration);
OAuth2AuthorizedClientService authorizedClientService = mock(OAuth2AuthorizedClientService.class);
OAuth2AuthorizedClient authorizedClient = mock(OAuth2AuthorizedClient.class);
when(authorizedClientService.loadAuthorizedClient(clientRegistrationId, principalName)).thenReturn(authorizedClient);
OAuth2AccessToken accessToken = mock(OAuth2AccessToken.class);
when(authorizedClient.getAccessToken()).thenReturn(accessToken);
OAuth2ClientArgumentResolverConfig.CLIENT_REGISTRATION_REPOSITORY = clientRegistrationRepository;
OAuth2ClientArgumentResolverConfig.AUTHORIZED_CLIENT_SERVICE = authorizedClientService;
this.spring.register(OAuth2ClientArgumentResolverConfig.class).autowire();
this.mockMvc.perform(get("/access-token").with(user(principalName)))
.andExpect(status().isOk())
.andExpect(content().string("resolved"));
this.mockMvc.perform(get("/authorized-client").with(user(principalName)))
.andExpect(status().isOk())
.andExpect(content().string("resolved"));
this.mockMvc.perform(get("/client-registration").with(user(principalName)))
.andExpect(status().isOk())
.andExpect(content().string("resolved"));
}
@EnableWebMvc
@EnableWebSecurity
static class OAuth2ClientArgumentResolverConfig extends WebSecurityConfigurerAdapter {
static ClientRegistrationRepository CLIENT_REGISTRATION_REPOSITORY;
static OAuth2AuthorizedClientService AUTHORIZED_CLIENT_SERVICE;
@Override
protected void configure(HttpSecurity http) throws Exception {
}
@RestController
public class Controller {
@GetMapping("/access-token")
public String accessToken(@OAuth2Client("client1") OAuth2AccessToken accessToken) {
return accessToken != null ? "resolved" : "not-resolved";
}
@GetMapping("/authorized-client")
public String authorizedClient(@OAuth2Client("client1") OAuth2AuthorizedClient authorizedClient) {
return authorizedClient != null ? "resolved" : "not-resolved";
}
@GetMapping("/client-registration")
public String clientRegistration(@OAuth2Client("client1") ClientRegistration clientRegistration) {
return clientRegistration != null ? "resolved" : "not-resolved";
}
}
@Bean
public ClientRegistrationRepository clientRegistrationRepository() {
return CLIENT_REGISTRATION_REPOSITORY;
}
@Bean
public OAuth2AuthorizedClientService authorizedClientService() {
return AUTHORIZED_CLIENT_SERVICE;
}
}
}
@@ -0,0 +1,113 @@
/*
* 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.configuration;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.beans.FatalBeanException;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.test.SpringTestRule;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import java.net.URL;
import java.net.URLClassLoader;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* @author Joe Grandja
*/
public class Sec2515Tests {
@Rule
public final SpringTestRule spring = new SpringTestRule();
// SEC-2515
@Test(expected = FatalBeanException.class)
public void loadConfigWhenAuthenticationManagerNotConfiguredAndRegisterBeanThenThrowFatalBeanException() throws Exception {
this.spring.register(StackOverflowSecurityConfig.class).autowire();
}
@EnableWebSecurity
static class StackOverflowSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
@Test(expected = FatalBeanException.class)
public void loadConfigWhenAuthenticationManagerNotConfiguredAndRegisterBeanCustomNameThenThrowFatalBeanException() throws Exception {
this.spring.register(CustomBeanNameStackOverflowSecurityConfig.class).autowire();
}
@EnableWebSecurity
static class CustomBeanNameStackOverflowSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
@Bean(name="custom")
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
// SEC-2549
@Test
public void loadConfigWhenChildClassLoaderSetThenContextLoads() throws Exception {
CanLoadWithChildConfig.AUTHENTICATION_MANAGER = mock(AuthenticationManager.class);
this.spring.register(CanLoadWithChildConfig.class);
AnnotationConfigWebApplicationContext context = (AnnotationConfigWebApplicationContext) this.spring.getContext();
context.setClassLoader(new URLClassLoader(new URL[0], context.getClassLoader()));
this.spring.autowire();
assertThat(this.spring.getContext().getBean(AuthenticationManager.class)).isNotNull();
}
@EnableWebSecurity
static class CanLoadWithChildConfig extends WebSecurityConfigurerAdapter {
static AuthenticationManager AUTHENTICATION_MANAGER;
@Bean
public AuthenticationManager authenticationManager() {
return AUTHENTICATION_MANAGER;
}
}
// SEC-2515
@Test
public void loadConfigWhenAuthenticationManagerConfiguredAndRegisterBeanThenContextLoads() throws Exception {
this.spring.register(SecurityConfig.class).autowire();
}
@EnableWebSecurity
static class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication();
}
}
}
@@ -0,0 +1,406 @@
/*
* 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.configuration;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.Order;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.access.expression.AbstractSecurityExpressionHandler;
import org.springframework.security.access.expression.SecurityExpressionHandler;
import org.springframework.security.authentication.TestingAuthenticationToken;
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.builders.WebSecurity;
import org.springframework.security.config.test.SpringTestRule;
import org.springframework.security.config.users.AuthenticationTestConfiguration;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.DefaultWebInvocationPrivilegeEvaluator;
import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.util.ClassUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.catchThrowable;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
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.status;
/**
* Tests for {@link WebSecurityConfiguration}.
*
* @author Rob Winch
* @author Joe Grandja
*/
public class WebSecurityConfigurationTests {
@Rule
public final SpringTestRule spring = new SpringTestRule();
@Autowired
private MockMvc mockMvc;
@Test
public void loadConfigWhenWebSecurityConfigurersHaveOrderThenFilterChainsOrdered() throws Exception {
this.spring.register(SortedWebSecurityConfigurerAdaptersConfig.class).autowire();
FilterChainProxy filterChainProxy = this.spring.getContext().getBean(FilterChainProxy.class);
List<SecurityFilterChain> filterChains = filterChainProxy.getFilterChains();
assertThat(filterChains).hasSize(6);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
request.setServletPath("/ignore1");
assertThat(filterChains.get(0).matches(request)).isTrue();
assertThat(filterChains.get(0).getFilters()).isEmpty();
request.setServletPath("/ignore2");
assertThat(filterChains.get(1).matches(request)).isTrue();
assertThat(filterChains.get(1).getFilters()).isEmpty();
request.setServletPath("/role1/**");
assertThat(filterChains.get(2).matches(request)).isTrue();
request.setServletPath("/role2/**");
assertThat(filterChains.get(3).matches(request)).isTrue();
request.setServletPath("/role3/**");
assertThat(filterChains.get(4).matches(request)).isTrue();
request.setServletPath("/**");
assertThat(filterChains.get(5).matches(request)).isTrue();
}
@EnableWebSecurity
@Import(AuthenticationTestConfiguration.class)
static class SortedWebSecurityConfigurerAdaptersConfig {
@Configuration
@Order(1)
static class WebConfigurer1 extends WebSecurityConfigurerAdapter {
@Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/ignore1", "/ignore2");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/role1/**")
.authorizeRequests()
.anyRequest().hasRole("1");
}
}
@Configuration
@Order(2)
static class WebConfigurer2 extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/role2/**")
.authorizeRequests()
.anyRequest().hasRole("2");
}
}
@Configuration
@Order(3)
static class WebConfigurer3 extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/role3/**")
.authorizeRequests()
.anyRequest().hasRole("3");
}
}
@Configuration
static class WebConfigurer4 extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().hasRole("4");
}
}
}
@Test
public void loadConfigWhenWebSecurityConfigurersHaveSameOrderThenThrowBeanCreationException() throws Exception {
Throwable thrown = catchThrowable(() -> this.spring.register(DuplicateOrderConfig.class).autowire());
assertThat(thrown).isInstanceOf(BeanCreationException.class)
.hasMessageContaining("@Order on WebSecurityConfigurers must be unique")
.hasMessageContaining(DuplicateOrderConfig.WebConfigurer1.class.getName())
.hasMessageContaining(DuplicateOrderConfig.WebConfigurer2.class.getName());
}
@EnableWebSecurity
@Import(AuthenticationTestConfiguration.class)
static class DuplicateOrderConfig {
@Configuration
static class WebConfigurer1 extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/role1/**")
.authorizeRequests()
.anyRequest().hasRole("1");
}
}
@Configuration
static class WebConfigurer2 extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/role2/**")
.authorizeRequests()
.anyRequest().hasRole("2");
}
}
}
@Test
public void loadConfigWhenWebInvocationPrivilegeEvaluatorSetThenIsRegistered() throws Exception {
PrivilegeEvaluatorConfigurerAdapterConfig.PRIVILEGE_EVALUATOR = mock(WebInvocationPrivilegeEvaluator.class);
this.spring.register(PrivilegeEvaluatorConfigurerAdapterConfig.class).autowire();
assertThat(this.spring.getContext().getBean(WebInvocationPrivilegeEvaluator.class))
.isSameAs(PrivilegeEvaluatorConfigurerAdapterConfig.PRIVILEGE_EVALUATOR);
}
@EnableWebSecurity
static class PrivilegeEvaluatorConfigurerAdapterConfig extends WebSecurityConfigurerAdapter {
static WebInvocationPrivilegeEvaluator PRIVILEGE_EVALUATOR;
@Override
public void configure(WebSecurity web) throws Exception {
web.privilegeEvaluator(PRIVILEGE_EVALUATOR);
}
}
@Test
public void loadConfigWhenSecurityExpressionHandlerSetThenIsRegistered() throws Exception {
WebSecurityExpressionHandlerConfig.EXPRESSION_HANDLER = mock(SecurityExpressionHandler.class);
when(WebSecurityExpressionHandlerConfig.EXPRESSION_HANDLER.getExpressionParser()).thenReturn(mock(ExpressionParser.class));
this.spring.register(WebSecurityExpressionHandlerConfig.class).autowire();
assertThat(this.spring.getContext().getBean(SecurityExpressionHandler.class))
.isSameAs(WebSecurityExpressionHandlerConfig.EXPRESSION_HANDLER);
}
@EnableWebSecurity
static class WebSecurityExpressionHandlerConfig extends WebSecurityConfigurerAdapter {
static SecurityExpressionHandler EXPRESSION_HANDLER;
@Override
public void configure(WebSecurity web) throws Exception {
web.expressionHandler(EXPRESSION_HANDLER);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.expressionHandler(EXPRESSION_HANDLER);
}
}
@Test
public void loadConfigWhenDefaultSecurityExpressionHandlerThenDefaultIsRegistered() throws Exception {
this.spring.register(WebSecurityExpressionHandlerDefaultsConfig.class).autowire();
assertThat(this.spring.getContext().getBean(SecurityExpressionHandler.class))
.isInstanceOf(DefaultWebSecurityExpressionHandler.class);
}
@EnableWebSecurity
static class WebSecurityExpressionHandlerDefaultsConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated();
}
}
@Test
public void securityExpressionHandlerWhenPermissionEvaluatorBeanThenPermissionEvaluatorUsed() throws Exception {
this.spring.register(WebSecurityExpressionHandlerPermissionEvaluatorBeanConfig.class).autowire();
TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", "notused");
FilterInvocation invocation = new FilterInvocation(new MockHttpServletRequest(), new MockHttpServletResponse(), new MockFilterChain());
AbstractSecurityExpressionHandler handler = this.spring.getContext().getBean(AbstractSecurityExpressionHandler.class);
EvaluationContext evaluationContext = handler.createEvaluationContext(authentication, invocation);
Expression expression = handler.getExpressionParser()
.parseExpression("hasPermission(#study,'DELETE')");
boolean granted = expression.getValue(evaluationContext, Boolean.class);
assertThat(granted).isTrue();
}
@EnableWebSecurity
static class WebSecurityExpressionHandlerPermissionEvaluatorBeanConfig extends WebSecurityConfigurerAdapter {
static final PermissionEvaluator PERMIT_ALL_PERMISSION_EVALUATOR = new PermissionEvaluator() {
@Override
public boolean hasPermission(Authentication authentication,
Object targetDomainObject, Object permission) {
return true;
}
@Override
public boolean hasPermission(Authentication authentication,
Serializable targetId, String targetType, Object permission) {
return true;
}
};
@Bean
public PermissionEvaluator permissionEvaluator() {
return PERMIT_ALL_PERMISSION_EVALUATOR;
}
}
@Test
public void loadConfigWhenDefaultWebInvocationPrivilegeEvaluatorThenDefaultIsRegistered() throws Exception {
this.spring.register(WebInvocationPrivilegeEvaluatorDefaultsConfig.class).autowire();
assertThat(this.spring.getContext().getBean(WebInvocationPrivilegeEvaluator.class))
.isInstanceOf(DefaultWebInvocationPrivilegeEvaluator.class);
}
@EnableWebSecurity
static class WebInvocationPrivilegeEvaluatorDefaultsConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated();
}
}
// SEC-2303
@Test
public void loadConfigWhenDefaultSecurityExpressionHandlerThenBeanResolverSet() throws Exception {
this.spring.register(DefaultExpressionHandlerSetsBeanResolverConfig.class).autowire();
this.mockMvc.perform(get("/")).andExpect(status().isOk());
this.mockMvc.perform(post("/")).andExpect(status().isForbidden());
}
@EnableWebSecurity
static class DefaultExpressionHandlerSetsBeanResolverConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().access("request.method == 'GET' ? @b.grant() : @b.deny()");
}
@RestController
public class HomeController {
@GetMapping("/")
public String home() {
return "home";
}
}
@Bean
public MyBean b() {
return new MyBean();
}
static class MyBean {
public boolean deny() {
return false;
}
public boolean grant() {
return true;
}
}
}
@Rule
public SpringTestRule child = new SpringTestRule();
// SEC-2461
@Test
public void loadConfigWhenMultipleWebSecurityConfigurationThenContextLoads() throws Exception {
this.spring.register(ParentConfig.class).autowire();
this.child.register(ChildConfig.class);
this.child.getContext().setParent(this.spring.getContext());
this.child.autowire();
assertThat(this.spring.getContext().getBean("springSecurityFilterChain")).isNotNull();
assertThat(this.child.getContext().getBean("springSecurityFilterChain")).isNotNull();
assertThat(this.spring.getContext().containsBean("springSecurityFilterChain")).isTrue();
assertThat(this.child.getContext().containsBean("springSecurityFilterChain")).isTrue();
}
@EnableWebSecurity
static class ParentConfig extends WebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication();
}
}
@EnableWebSecurity
static class ChildConfig extends WebSecurityConfigurerAdapter {
}
// SEC-2773
@Test
public void getMethodDelegatingApplicationListenerWhenWebSecurityConfigurationThenIsStatic() throws Exception {
Method method = ClassUtils.getMethod(WebSecurityConfiguration.class, "delegatingApplicationListener", null);
assertThat(Modifier.isStatic(method.getModifiers())).isTrue();
}
}
@@ -0,0 +1,47 @@
/*
* 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.configuration.sec2377;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.security.config.annotation.web.configuration.sec2377.a.Sec2377AConfig;
import org.springframework.security.config.annotation.web.configuration.sec2377.b.Sec2377BConfig;
import org.springframework.security.config.test.SpringTestRule;
/**
* @author Rob Winch
* @author Josh Cummings
*/
public class Sec2377Tests {
@Rule
public final SpringTestRule parent = new SpringTestRule();
@Rule
public final SpringTestRule child = new SpringTestRule();
@Test
public void refreshContextWhenParentAndChildRegisteredThenNoException() {
this.parent.register(Sec2377AConfig.class).autowire();
ConfigurableApplicationContext context =
this.child.register(Sec2377BConfig.class).getContext();
context.setParent(this.parent.getContext());
this.child.autowire();
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 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.
@@ -21,4 +21,4 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
@EnableWebSecurity
public class Sec2377BConfig extends WebSecurityConfigurerAdapter {
}
}
@@ -0,0 +1,75 @@
/*
* 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.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.annotation.AuthenticationPrincipal;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
/**
* @author Rob Winch
* @author Josh Cummings
*/
public class AnonymousConfigurerTests {
@Rule
public final SpringTestRule spring = new SpringTestRule();
@Autowired
private MockMvc mockMvc;
@Test
public void requestWhenAnonymousTwiceInvokedThenDoesNotOverride() throws Exception {
this.spring.register(InvokeTwiceDoesNotOverride.class).autowire();
this.mockMvc.perform(get("/"))
.andExpect(content().string("principal"));
}
@EnableWebSecurity
@EnableWebMvc
static class InvokeTwiceDoesNotOverride extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.anonymous()
.key("key")
.principal("principal")
.and()
.anonymous();
}
@RestController
static class PrincipalController {
@GetMapping("/")
String principal(@AuthenticationPrincipal String principal) {
return principal;
}
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 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.
@@ -13,41 +13,42 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.configurers
package org.springframework.security.config.annotation.web.configurers;
import org.springframework.context.annotation.Configuration
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.junit.Rule;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
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.access.ExceptionTranslationFilter
import org.springframework.security.web.context.NullSecurityContextRepository;
import org.springframework.security.web.context.SecurityContextPersistenceFilter
import org.springframework.security.web.context.SecurityContextRepository
import org.springframework.security.web.savedrequest.RequestCache
import org.springframework.security.web.session.ConcurrentSessionFilter
import org.springframework.security.web.session.SessionManagementFilter
import org.springframework.security.config.test.SpringTestRule;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
/**
*
* @author Rob Winch
* @author Josh Cummings
*/
class PortMapperConfigurerTests extends BaseSpringSpec {
public class PortMapperConfigurerTests {
@Rule
public final SpringTestRule spring = new SpringTestRule();
def "invoke portMapper twice does not override"() {
setup:
loadConfig(InvokeTwiceDoesNotOverride)
request.setServerPort(543)
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.redirectedUrl == "https://localhost:123"
@Autowired
private MockMvc mockMvc;
@Test
public void requestWhenPortMapperTwiceInvokedThenDoesNotOverride() throws Exception {
this.spring.register(InvokeTwiceDoesNotOverride.class).autowire();
this.mockMvc.perform(get("http://localhost:543"))
.andExpect(redirectedUrl("https://localhost:123"));
}
@EnableWebSecurity
static class InvokeTwiceDoesNotOverride extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
@@ -57,7 +58,7 @@ class PortMapperConfigurerTests extends BaseSpringSpec {
.portMapper()
.http(543).mapsTo(123)
.and()
.portMapper()
.portMapper();
}
}
}
@@ -0,0 +1,213 @@
/*
* 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.oauth2.client;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockHttpSession;
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.oauth2.client.InMemoryOAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.annotation.OAuth2Client;
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository;
import org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizationRequestRepository;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests for {@link OAuth2ClientConfigurer}.
*
* @author Joe Grandja
*/
public class OAuth2ClientConfigurerTests {
private static ClientRegistrationRepository clientRegistrationRepository;
private static OAuth2AuthorizedClientService authorizedClientService;
private static OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient;
private static RequestCache requestCache;
@Rule
public final SpringTestRule spring = new SpringTestRule();
@Autowired
private MockMvc mockMvc;
private ClientRegistration registration1;
@Before
public void setup() {
this.registration1 = ClientRegistration.withRegistrationId("registration-1")
.clientId("client-1")
.clientSecret("secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.redirectUriTemplate("{baseUrl}/client-1")
.scope("user")
.authorizationUri("https://provider.com/oauth2/authorize")
.tokenUri("https://provider.com/oauth2/token")
.userInfoUri("https://provider.com/oauth2/user")
.userNameAttributeName("id")
.clientName("client-1")
.build();
clientRegistrationRepository = new InMemoryClientRegistrationRepository(this.registration1);
authorizedClientService = new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository);
OAuth2AccessTokenResponse accessTokenResponse = OAuth2AccessTokenResponse.withToken("access-token-1234")
.tokenType(OAuth2AccessToken.TokenType.BEARER)
.expiresIn(300)
.build();
accessTokenResponseClient = mock(OAuth2AccessTokenResponseClient.class);
when(accessTokenResponseClient.getTokenResponse(any(OAuth2AuthorizationCodeGrantRequest.class))).thenReturn(accessTokenResponse);
requestCache = mock(RequestCache.class);
}
@Test
public void configureWhenAuthorizationCodeRequestThenRedirectForAuthorization() throws Exception {
this.spring.register(OAuth2ClientConfig.class).autowire();
MvcResult mvcResult = this.mockMvc.perform(get("/oauth2/authorization/registration-1"))
.andExpect(status().is3xxRedirection())
.andReturn();
assertThat(mvcResult.getResponse().getRedirectedUrl()).matches("https://provider.com/oauth2/authorize\\?response_type=code&client_id=client-1&scope=user&state=.{15,}&redirect_uri=http://localhost/client-1");
}
@Test
public void configureWhenAuthorizationCodeResponseSuccessThenAuthorizedClientSaved() throws Exception {
this.spring.register(OAuth2ClientConfig.class).autowire();
// Setup the Authorization Request in the session
Map<String, Object> additionalParameters = new HashMap<>();
additionalParameters.put(OAuth2ParameterNames.REGISTRATION_ID, this.registration1.getRegistrationId());
OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
.authorizationUri(this.registration1.getProviderDetails().getAuthorizationUri())
.clientId(this.registration1.getClientId())
.redirectUri("http://localhost/client-1")
.state("state")
.additionalParameters(additionalParameters)
.build();
AuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository =
new HttpSessionOAuth2AuthorizationRequestRepository();
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, request, response);
MockHttpSession session = (MockHttpSession) request.getSession();
String principalName = "user1";
this.mockMvc.perform(get("/client-1")
.param(OAuth2ParameterNames.CODE, "code")
.param(OAuth2ParameterNames.STATE, "state")
.with(user(principalName))
.session(session))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("http://localhost/client-1"));
OAuth2AuthorizedClient authorizedClient = authorizedClientService.loadAuthorizedClient(
this.registration1.getRegistrationId(), principalName);
assertThat(authorizedClient).isNotNull();
}
@Test
public void configureWhenRequestCacheProvidedAndClientAuthorizationRequiredExceptionThrownThenRequestCacheUsed() throws Exception {
this.spring.register(OAuth2ClientConfig.class).autowire();
MvcResult mvcResult = this.mockMvc.perform(get("/resource1").with(user("user1")))
.andExpect(status().is3xxRedirection())
.andReturn();
assertThat(mvcResult.getResponse().getRedirectedUrl()).matches("https://provider.com/oauth2/authorize\\?response_type=code&client_id=client-1&scope=user&state=.{15,}&redirect_uri=http://localhost/client-1");
verify(requestCache).saveRequest(any(HttpServletRequest.class), any(HttpServletResponse.class));
}
@EnableWebSecurity
@EnableWebMvc
static class OAuth2ClientConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.requestCache()
.requestCache(requestCache)
.and()
.oauth2()
.client()
.authorizationCodeGrant()
.tokenEndpoint()
.accessTokenResponseClient(accessTokenResponseClient);
}
@Bean
public ClientRegistrationRepository clientRegistrationRepository() {
return clientRegistrationRepository;
}
@Bean
public OAuth2AuthorizedClientService authorizedClientService() {
return authorizedClientService;
}
@RestController
public class ResourceController {
@GetMapping("/resource1")
public String resource1(@OAuth2Client("registration-1") OAuth2AuthorizedClient authorizedClient) {
return "resource1";
}
}
}
}
@@ -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.
@@ -16,8 +16,17 @@
package org.springframework.security.config.annotation.web.reactive;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.csrf;
import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication;
import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.Credentials.basicAuthenticationCredentials;
import java.nio.charset.StandardCharsets;
import java.security.Principal;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
@@ -32,40 +41,43 @@ import org.springframework.security.config.test.SpringTestRule;
import org.springframework.security.config.users.ReactiveAuthenticationTestConfiguration;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.security.test.web.reactive.server.WebTestClientBuilder;
import org.springframework.security.web.reactive.result.view.CsrfRequestDataValueProcessor;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.WebFilterChainProxy;
import org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository;
import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.FluxExchangeResult;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.config.EnableWebFlux;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.result.view.AbstractView;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
import java.security.Principal;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.csrf;
import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.Credentials.basicAuthenticationCredentials;
import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication;
/**
* @author Rob Winch
* @since 5.0
*/
@RunWith(SpringRunner.class)
@SecurityTestExecutionListeners
public class EnableWebFluxSecurityTests {
@Rule
public final SpringTestRule spring = new SpringTestRule();
@@ -288,6 +300,46 @@ public class EnableWebFluxSecurityTests {
}
}
@Test
@WithMockUser
public void authenticationPrincipalArgumentResolverWhenSpelThenWorks() {
this.spring.register(AuthenticationPrincipalConfig.class).autowire();
WebTestClient client = WebTestClient.bindToApplicationContext(this.spring.getContext()).build();
client.get()
.uri("/spel")
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("user");
}
@EnableWebFluxSecurity
@EnableWebFlux
@Import(ReactiveAuthenticationTestConfiguration.class)
static class AuthenticationPrincipalConfig {
@Bean
public PrincipalBean principalBean() {
return new PrincipalBean();
}
static class PrincipalBean {
public String username(UserDetails user) {
return user.getUsername();
}
}
@RestController
public static class AuthenticationPrincipalResolver {
@GetMapping("/spel")
String username(@AuthenticationPrincipal(expression = "@principalBean.username(#this)") String username) {
return username;
}
}
}
private static DataBuffer toDataBuffer(String body) {
DataBuffer buffer = new DefaultDataBufferFactory().allocateBuffer();
buffer.write(body.getBytes(StandardCharsets.UTF_8));
@@ -1,11 +1,11 @@
/*
* Copyright 2002-2011 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -13,9 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.debug
package org.springframework.security.config.debug;
import org.springframework.stereotype.Component
import org.springframework.stereotype.Component;
/**
* Fake depenency for {@link TestAuthenticationProvider}
@@ -0,0 +1,46 @@
/*
* 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.debug;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.security.config.test.SpringTestRule;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.debug.DebugFilter;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.security.config.BeanIds.FILTER_CHAIN_PROXY;
import static org.springframework.security.config.BeanIds.SPRING_SECURITY_FILTER_CHAIN;
/**
* @author Rob Winch
* @author Josh Cummings
*/
public class SecurityDebugBeanFactoryPostProcessorTests {
@Rule
public final SpringTestRule spring = new SpringTestRule();
@Test
public void contextRefreshWhenInDebugModeAndDependencyHasAutowiredConstructorThenDebugModeStillWorks() {
// SEC-1885
this.spring.configLocations("classpath:org/springframework/security/config/debug/SecurityDebugBeanFactoryPostProcessorTests-context.xml")
.autowire();
assertThat(this.spring.getContext().getBean(SPRING_SECURITY_FILTER_CHAIN)).isInstanceOf(DebugFilter.class);
assertThat(this.spring.getContext().getBean(FILTER_CHAIN_PROXY)).isInstanceOf(FilterChainProxy.class);
}
}
@@ -1,11 +1,11 @@
/*
* Copyright 2002-2011 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -15,11 +15,12 @@
*/
package org.springframework.security.config.debug;
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.authentication.AuthenticationProvider
import org.springframework.security.core.Authentication
import org.springframework.security.core.AuthenticationException
import org.springframework.stereotype.Service
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Service;
/**
* An {@link AuthenticationProvider} that has an {@link Autowired} constructor which is necessary to recreate SEC-1885.
@@ -0,0 +1,66 @@
/*
* 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.doc;
/**
* Represents a Spring Security XSD Attribute. It is created when parsing the current xsd to compare to the documented appendix.
*
* @author Rob Winch
* @author Josh Cummings
*
* @see SpringSecurityXsdParser
* @see XsdDocumentedTests
*/
public class Attribute {
private String name;
private String desc;
private Element elmt;
public Attribute(String desc, String name) {
this.desc = desc;
this.name = name;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getDesc() {
return this.desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public Element getElmt() {
return this.elmt;
}
public void setElmt(Element elmt) {
this.elmt = elmt;
}
public String getId() {
return String.format("%s-%s", this.elmt.getId(), this.name);
}
}
@@ -0,0 +1,169 @@
/*
* 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.doc;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/**
* Represents a Spring Security XSD Element. It is created when parsing
* the current xsd to compare to the documented appendix.
*
* @author Rob Winch
* @author Josh Cummings
*
* @see SpringSecurityXsdParser
* @see XsdDocumentedTests
*/
public class Element {
private String name;
private String desc;
private Collection<Attribute> attrs = new ArrayList<>();
/**
* Contains the elements that extend this element (i.e. any-user-service contains ldap-user-service)
*/
private Collection<Element> subGrps = new ArrayList<>();
private Map<String, Element> childElmts = new HashMap<>();
private Map<String, Element> parentElmts = new HashMap<>();
public String getId() {
return String.format("nsa-%s", this.name);
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getDesc() {
return this.desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public Collection<Attribute> getAttrs() {
return this.attrs;
}
public void setAttrs(Collection<Attribute> attrs) {
this.attrs = attrs;
}
public Collection<Element> getSubGrps() {
return this.subGrps;
}
public void setSubGrps(Collection<Element> subGrps) {
this.subGrps = subGrps;
}
public Map<String, Element> getChildElmts() {
return this.childElmts;
}
public void setChildElmts(Map<String, Element> childElmts) {
this.childElmts = childElmts;
}
public Map<String, Element> getParentElmts() {
return this.parentElmts;
}
public void setParentElmts(Map<String, Element> parentElmts) {
this.parentElmts = parentElmts;
}
/**
* Gets all the ids related to this Element including attributes, parent elements, and child elements.
*
* <p>
* The expected ids to be found are documented below.
* <ul>
* <li>Elements - any xml element will have the nsa-&lt;element&gt;. For example the http element will have the id
* nsa-http</li>
* <li>Parent Section - Any element with a parent other than beans will have a section named
* nsa-&lt;element&gt;-parents. For example, authentication-provider would have a section id of
* nsa-authentication-provider-parents. The section would then contain a list of links pointing to the
* documentation for each parent element.</li>
* <li>Attributes Section - Any element with attributes will have a section with the id
* nsa-&lt;element&gt;-attributes. For example the http element would require a section with the id
* http-attributes.</li>
* <li>Attribute - Each attribute of an element would have an id of nsa-&lt;element&gt;-&lt;attributeName&gt;. For
* example the attribute create-session for the http attribute would have the id http-create-session.</li>
* <li>Child Section - Any element with a child element will have a section named nsa-&lt;element&gt;-children.
* For example, authentication-provider would have a section id of nsa-authentication-provider-children. The
* section would then contain a list of links pointing to the documentation for each child element.</li>
* </ul>
* @return
*/
public Collection<String> getIds() {
Collection<String> ids = new ArrayList<>();
ids.add(getId());
this.childElmts.values()
.forEach(elmt -> ids.add(elmt.getId()));
this.attrs.forEach(attr -> ids.add(attr.getId()));
if ( !this.childElmts.isEmpty() ) {
ids.add(getId() + "-children");
}
if ( !this.attrs.isEmpty() ) {
ids.add(getId() + "-attributes");
}
if ( !this.parentElmts.isEmpty() ) {
ids.add(getId() + "-parents");
}
return ids;
}
public Map<String, Element> getAllChildElmts() {
Map<String, Element> result = new HashMap<>();
this.childElmts.values()
.forEach(elmt ->
elmt.subGrps.forEach(
subElmt -> result.put(subElmt.name, subElmt)));
result.putAll(this.childElmts);
return result;
}
public Map<String, Element> getAllParentElmts() {
Map<String, Element> result = new HashMap<>();
this.parentElmts.values()
.forEach(elmt ->
elmt.subGrps.forEach(
subElmt -> result.put(subElmt.name, subElmt)));
result.putAll(this.parentElmts);
return result;
}
}
@@ -0,0 +1,211 @@
/*
* 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.doc;
import org.springframework.util.StringUtils;
import java.util.*;
import java.util.stream.Stream;
/**
* Parses the Spring Security Xsd Document
*
* @author Rob Winch
* @author Josh Cummings
*/
public class SpringSecurityXsdParser {
private XmlNode rootElement;
private Set<String> attrElmts = new LinkedHashSet<>();
private Map<String, Element> elementNameToElement = new HashMap<>();
public SpringSecurityXsdParser(XmlNode rootElement) {
this.rootElement = rootElement;
}
/**
* Returns a map of the element name to the {@link Element}.
*
* @return
*/
public Map<String, Element> parse() {
elements(this.rootElement);
return this.elementNameToElement;
}
/**
* Creates a Map of the name to an Element object of all the children of element.
*
* @param node
* @return
*/
private Map<String, Element> elements(XmlNode node) {
Map<String, Element> elementNameToElement = new HashMap<>();
node.children().forEach(child -> {
if ("element".equals(child.simpleName())) {
Element e = elmt(child);
elementNameToElement.put(e.getName(), e);
} else {
elementNameToElement.putAll(elements(child));
}
});
return elementNameToElement;
}
/**
* Any children that are attribute will be returned as an Attribute object.
*
* @param element
* @return a collection of Attribute objects that are children of element.
*/
private Collection<Attribute> attrs(XmlNode element) {
Collection<Attribute> attrs = new ArrayList<>();
element.children().forEach(c -> {
String name = c.simpleName();
if ("attribute".equals(name)) {
attrs.add(attr(c));
} else if ("element".equals(name)) {
} else {
attrs.addAll(attrs(c));
}
});
return attrs;
}
/**
* Any children will be searched for an attributeGroup, each of its children will be returned as an Attribute
*
* @param element
* @return
*/
private Collection<Attribute> attrgrps(XmlNode element) {
Collection<Attribute> attrgrp = new ArrayList<>();
element.children().forEach(c -> {
if ("element".equals(c.simpleName())) {
} else if ("attributeGroup".equals(c.simpleName())) {
if (c.attribute("name") != null) {
attrgrp.addAll(attrgrp(c));
} else {
String name = c.attribute("ref").split(":")[1];
XmlNode attrGrp = findNode(element, name);
attrgrp.addAll(attrgrp(attrGrp));
}
} else {
attrgrp.addAll(attrgrps(c));
}
});
return attrgrp;
}
private XmlNode findNode(XmlNode c, String name) {
XmlNode root = c;
while (!"schema".equals(root.simpleName())) {
root = root.parent().get();
}
return expand(root)
.filter(node -> name.equals(node.attribute("name")))
.findFirst().orElseThrow(IllegalArgumentException::new);
}
private Stream<XmlNode> expand(XmlNode root) {
return Stream.concat(
Stream.of(root),
root.children().flatMap(this::expand));
}
/**
* Processes an individual attributeGroup by obtaining all the attributes and then looking for more attributeGroup elements and prcessing them.
*
* @param e
* @return all the attributes for a specific attributeGroup and any child attributeGroups
*/
private Collection<Attribute> attrgrp(XmlNode e) {
Collection<Attribute> attrs = attrs(e);
attrs.addAll(attrgrps(e));
return attrs;
}
/**
* Obtains the description for a specific element
*
* @param element
* @return
*/
private String desc(XmlNode element) {
return element.child("annotation")
.flatMap(annotation -> annotation.child("documentation"))
.map(documentation -> documentation.text())
.orElse(null);
}
/**
* Given an element creates an attribute from it.
*
* @param n
* @return
*/
private Attribute attr(XmlNode n) {
return new Attribute(desc(n), n.attribute("name"));
}
/**
* Given an element creates an Element out of it by collecting all its attributes and child elements.
*
* @param n
* @return
*/
private Element elmt(XmlNode n) {
String name = n.attribute("ref");
if (StringUtils.isEmpty(name)) {
name = n.attribute("name");
} else {
name = name.split(":")[1];
n = findNode(n, name);
}
if (this.elementNameToElement.containsKey(name)) {
return this.elementNameToElement.get(name);
}
this.attrElmts.add(name);
Element e = new Element();
e.setName(n.attribute("name"));
e.setDesc(desc(n));
e.setChildElmts(elements(n));
e.setAttrs(attrs(n));
e.getAttrs().addAll(attrgrps(n));
e.getAttrs().forEach(attr -> attr.setElmt(e));
e.getChildElmts().values().forEach(element ->
element.getParentElmts().put(e.getName(), e));
String subGrpName = n.attribute("substitutionGroup");
if (!StringUtils.isEmpty(subGrpName)) {
Element subGrp = elmt(findNode(n, subGrpName.split(":")[1]));
subGrp.getSubGrps().add(e);
}
this.elementNameToElement.put(name, e);
return e;
}
}
@@ -0,0 +1,73 @@
/*
* 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.doc;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import java.util.Optional;
import java.util.stream.IntStream;
import java.util.stream.Stream;
/**
* @author Josh Cummings
*/
public class XmlNode {
private final Node node;
public XmlNode(Node node) {
this.node = node;
}
public String simpleName() {
String[] parts = this.node.getNodeName().split(":");
return parts[parts.length-1];
}
public String text() {
return this.node.getTextContent();
}
public Stream<XmlNode> children() {
NodeList children = this.node.getChildNodes();
return IntStream.range(0, children.getLength())
.mapToObj(children::item)
.map(XmlNode::new);
}
public Optional<XmlNode> child(String name) {
return this.children()
.filter(child -> name.equals(child.simpleName()))
.findFirst();
}
public Optional<XmlNode> parent() {
return Optional.ofNullable(this.node.getParentNode())
.map(parent -> new XmlNode(parent));
}
public String attribute(String name) {
return Optional.ofNullable(this.node.getAttributes())
.map(attrs -> attrs.getNamedItem(name))
.map(attr -> attr.getTextContent())
.orElse(null);
}
public Node node() {
return this.node;
}
}
@@ -0,0 +1,51 @@
/*
* 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.doc;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;
/**
* @author Josh Cummings
*/
public class XmlParser implements AutoCloseable {
private InputStream xml;
public XmlParser(InputStream xml) {
this.xml = xml;
}
public XmlNode parse() {
try {
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
return new XmlNode(dBuilder.parse(this.xml));
} catch ( IOException | ParserConfigurationException | SAXException e ) {
throw new IllegalStateException(e);
}
}
@Override
public void close() throws IOException {
this.xml.close();
}
}
@@ -0,0 +1,48 @@
/*
* 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.doc;
import org.springframework.core.io.ClassPathResource;
import java.io.IOException;
import java.util.Map;
/**
* Support for ensuring preparing the givens in {@link XsdDocumentedTests}
*
* @author Josh Cummings
*/
public class XmlSupport {
private XmlParser parser;
public XmlNode parse(String location) throws IOException {
ClassPathResource resource = new ClassPathResource(location);
this.parser = new XmlParser(resource.getInputStream());
return this.parser.parse();
}
public Map<String, Element> elementsByElementName(String location) throws IOException {
XmlNode node = parse(location);
return new SpringSecurityXsdParser(node).parse();
}
public void close() throws IOException {
if ( this.parser != null ) {
this.parser.close();
}
}
}
@@ -0,0 +1,293 @@
/*
* 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.doc;
import org.apache.commons.lang.StringUtils;
import org.junit.After;
import org.junit.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.config.http.SecurityFiltersAssertions;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests to ensure that the xsd is properly documented.
*
* @author Rob Winch
* @author Josh Cummings
*/
public class XsdDocumentedTests {
Collection<String> ignoredIds = Arrays.asList(
"nsa-any-user-service",
"nsa-any-user-service-parents",
"nsa-authentication",
"nsa-websocket-security",
"nsa-ldap",
"nsa-method-security",
"nsa-web");
String referenceLocation = "../docs/manual/src/docs/asciidoc/_includes/appendix/namespace.adoc";
String schema31xDocumentLocation = "org/springframework/security/config/spring-security-3.1.xsd";
String schemaDocumentLocation = "org/springframework/security/config/spring-security-5.0.xsd";
XmlSupport xml = new XmlSupport();
@After
public void close() throws IOException {
this.xml.close();
}
@Test
public void parseWhenLatestXsdThenAllNamedSecurityFiltersAreDefinedAndOrderedProperly()
throws IOException {
XmlNode root = this.xml.parse(this.schemaDocumentLocation);
List<String> nodes =
root.child("schema")
.map(XmlNode::children)
.orElse(Stream.empty())
.filter(node ->
"simpleType".equals(node.simpleName()) &&
"named-security-filter".equals(node.attribute("name")))
.flatMap(XmlNode::children)
.flatMap(XmlNode::children)
.map(node -> node.attribute("value"))
.filter(StringUtils::isNotEmpty)
.collect(Collectors.toList());
SecurityFiltersAssertions.assertEquals(nodes);
}
@Test
public void parseWhen31XsdThenAllNamedSecurityFiltersAreDefinedAndOrderedProperly()
throws IOException {
List<String> expected = Arrays.asList(
"FIRST",
"CHANNEL_FILTER",
"SECURITY_CONTEXT_FILTER",
"CONCURRENT_SESSION_FILTER",
"LOGOUT_FILTER",
"X509_FILTER",
"PRE_AUTH_FILTER",
"CAS_FILTER",
"FORM_LOGIN_FILTER",
"OPENID_FILTER",
"LOGIN_PAGE_FILTER",
"DIGEST_AUTH_FILTER",
"BASIC_AUTH_FILTER",
"REQUEST_CACHE_FILTER",
"SERVLET_API_SUPPORT_FILTER",
"JAAS_API_SUPPORT_FILTER",
"REMEMBER_ME_FILTER",
"ANONYMOUS_FILTER",
"SESSION_MANAGEMENT_FILTER",
"EXCEPTION_TRANSLATION_FILTER",
"FILTER_SECURITY_INTERCEPTOR",
"SWITCH_USER_FILTER",
"LAST"
);
XmlNode root = this.xml.parse(this.schema31xDocumentLocation);
List<String> nodes =
root.child("schema")
.map(XmlNode::children)
.orElse(Stream.empty())
.filter(node ->
"simpleType".equals(node.simpleName()) &&
"named-security-filter".equals(node.attribute("name")))
.flatMap(XmlNode::children)
.flatMap(XmlNode::children)
.map(node -> node.attribute("value"))
.filter(StringUtils::isNotEmpty)
.collect(Collectors.toList());
assertThat(nodes).isEqualTo(expected);
}
/**
* This will check to ensure that the expected number of xsd documents are found to ensure that we are validating
* against the current xsd document. If this test fails, all that is needed is to update the schemaDocument
* and the expected size for this test.
* @return
*/
@Test
public void sizeWhenReadingFilesystemThenIsCorrectNumberOfSchemaFiles()
throws IOException {
ClassPathResource resource = new ClassPathResource(this.schemaDocumentLocation);
String[] schemas = resource.getFile().getParentFile().list((dir, name) -> name.endsWith(".xsd"));
assertThat(schemas.length).isEqualTo(12)
.withFailMessage("the count is equal to 12, if not then schemaDocument needs updating");
}
/**
* This uses a naming convention for the ids of the appendix to ensure that the entire appendix is documented.
* The naming convention for the ids is documented in {@link Element#getIds()}.
* @return
*/
@Test
public void countReferencesWhenReviewingDocumentationThenEntireSchemaIsIncluded()
throws IOException {
Map<String, Element> elementsByElementName =
this.xml.elementsByElementName(this.schemaDocumentLocation);
List<String> documentIds =
Files.lines(Paths.get(this.referenceLocation))
.filter(line -> line.matches("\\[\\[(nsa-.*)\\]\\]"))
.map(line -> line.substring(2, line.length() - 2))
.collect(Collectors.toList());
Set<String> expectedIds =
elementsByElementName.values().stream()
.flatMap(element -> element.getIds().stream())
.collect(Collectors.toSet());
documentIds.removeAll(this.ignoredIds);
expectedIds.removeAll(this.ignoredIds);
assertThat(documentIds).containsAll(expectedIds);
assertThat(expectedIds).containsAll(documentIds);
}
/**
* This test ensures that any element that has children or parents contains a section that has links pointing to that
* documentation.
* @return
*/
@Test
public void countLinksWhenReviewingDocumentationThenParentsAndChildrenAreCorrectlyLinked()
throws IOException {
Map<String, List<String>> docAttrNameToChildren = new HashMap<>();
Map<String, List<String>> docAttrNameToParents = new HashMap<>();
String docAttrName = null;
Map<String, List<String>> currentDocAttrNameToElmt = null;
List<String> lines = Files.readAllLines(Paths.get(this.referenceLocation));
for ( String line : lines ) {
if(line.matches("^\\[\\[.*\\]\\]$")) {
String id = line.substring(2, line.length() - 2);
if(id.endsWith("-children")) {
docAttrName = id.substring(0, id.length() - 9);
currentDocAttrNameToElmt = docAttrNameToChildren;
} else if(id.endsWith("-parents")) {
docAttrName = id.substring(0, id.length() - 8);
currentDocAttrNameToElmt = docAttrNameToParents;
} else if(docAttrName != null && !id.startsWith(docAttrName)) {
currentDocAttrNameToElmt = null;
docAttrName = null;
}
}
if(docAttrName != null && currentDocAttrNameToElmt != null) {
String expression = "^\\* <<(nsa-.*),.*>>$";
if(line.matches(expression)) {
String elmtId = line.replaceAll(expression, "$1");
currentDocAttrNameToElmt
.computeIfAbsent(docAttrName, key -> new ArrayList<>())
.add(elmtId);
}
}
}
Map<String, Element> elementNameToElement = this.xml.elementsByElementName(this.schemaDocumentLocation);
Map<String, List<String>> schemaAttrNameToChildren = new HashMap<>();
Map<String, List<String>> schemaAttrNameToParents = new HashMap<>();
elementNameToElement.entrySet().stream()
.forEach(entry -> {
String key = "nsa-" + entry.getKey();
if (this.ignoredIds.contains(key) ) {
return;
}
List<String> parentIds =
entry.getValue().getAllParentElmts().values().stream()
.filter(element -> !this.ignoredIds.contains(element.getId()))
.map(element -> element.getId())
.sorted()
.collect(Collectors.toList());
if ( !parentIds.isEmpty() ) {
schemaAttrNameToParents.put(key, parentIds);
}
List<String> childIds =
entry.getValue().getAllChildElmts().values().stream()
.filter(element -> !this.ignoredIds.contains(element.getId()))
.map(element -> element.getId())
.sorted()
.collect(Collectors.toList());
if ( !childIds.isEmpty() ) {
schemaAttrNameToChildren.put(key, childIds);
}
});
assertThat(docAttrNameToChildren).isEqualTo(schemaAttrNameToChildren);
assertThat(docAttrNameToParents).isEqualTo(schemaAttrNameToParents);
}
/**
* This test checks each xsd element and ensures there is documentation for it.
* @return
*/
@Test
public void countWhenReviewingDocumentationThenAllElementsDocumented()
throws IOException {
Map<String, Element> elementNameToElement =
this.xml.elementsByElementName(this.schemaDocumentLocation);
String notDocElmtIds =
elementNameToElement.values().stream()
.filter(element ->
StringUtils.isEmpty(element.getDesc()) &&
!this.ignoredIds.contains(element.getId()))
.map(element -> element.getId())
.sorted()
.collect(Collectors.joining("\n"));
String notDocAttrIds =
elementNameToElement.values().stream()
.flatMap(element -> element.getAttrs().stream())
.filter(element ->
StringUtils.isEmpty(element.getDesc()) &&
!this.ignoredIds.contains(element.getId()))
.map(element -> element.getId())
.sorted()
.collect(Collectors.joining("\n"));
assertThat(notDocElmtIds).isEmpty();
assertThat(notDocAttrIds).isEmpty();
}
}
@@ -0,0 +1,101 @@
/*
* 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.http;
import org.eclipse.jetty.http.HttpStatus;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestRule;
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.web.servlet.MockMvc;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
*
* @author Luke Taylor
* @author Josh Cummings
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SecurityTestExecutionListeners
public class AccessDeniedConfigTests {
private static final String CONFIG_LOCATION_PREFIX =
"classpath:org/springframework/security/config/http/AccessDeniedConfigTests";
@Autowired
MockMvc mvc;
@Rule
public final SpringTestRule spring = new SpringTestRule();
@Test
public void configureWhenAccessDeniedHandlerIsMissingLeadingSlashThenException() {
SpringTestContext context = this.spring.configLocations(this.xml("NoLeadingSlash"));
assertThatThrownBy(() -> context.autowire())
.isInstanceOf(BeanCreationException.class)
.hasMessageContaining("errorPage must begin with '/'");
}
@Test
@WithMockUser
public void configureWhenAccessDeniedHandlerRefThenAutowire()
throws Exception {
this.spring.configLocations(this.xml("AccessDeniedHandler")).autowire();
this.mvc.perform(get("/"))
.andExpect(status().is(HttpStatus.GONE_410));
}
@Test
public void configureWhenAccessDeniedHandlerUsesPathAndRefThenException() {
SpringTestContext context = this.spring.configLocations(this.xml("UsesPathAndRef"));
assertThatThrownBy(() -> context.autowire())
.isInstanceOf(BeanDefinitionParsingException.class)
.hasMessageContaining("attribute error-page cannot be used together with the 'ref' attribute");
}
private String xml(String configName) {
return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml";
}
public static class GoneAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request,
HttpServletResponse response,
AccessDeniedException accessDeniedException) {
response.setStatus(HttpStatus.GONE_410);
}
}
}
@@ -0,0 +1,646 @@
/*
* 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.http;
import org.eclipse.jetty.http.HttpStatus;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestRule;
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.security.test.web.support.WebTestUtils;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.stereotype.Controller;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultMatcher;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.support.RequestDataValueProcessor;
import javax.servlet.Filter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.net.URI;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.head;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.request;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.web.bind.annotation.RequestMethod.DELETE;
import static org.springframework.web.bind.annotation.RequestMethod.GET;
import static org.springframework.web.bind.annotation.RequestMethod.HEAD;
import static org.springframework.web.bind.annotation.RequestMethod.OPTIONS;
import static org.springframework.web.bind.annotation.RequestMethod.PATCH;
import static org.springframework.web.bind.annotation.RequestMethod.POST;
import static org.springframework.web.bind.annotation.RequestMethod.PUT;
import static org.springframework.web.bind.annotation.RequestMethod.TRACE;
/**
*
* @author Rob Winch
* @author Josh Cummings
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SecurityTestExecutionListeners
public class CsrfConfigTests {
private static final String CONFIG_LOCATION_PREFIX =
"classpath:org/springframework/security/config/http/CsrfConfigTests";
@Rule
public final SpringTestRule spring = new SpringTestRule();
@Autowired
MockMvc mvc;
@Test
public void postWhenDefaultConfigurationThenForbiddenSinceCsrfIsEnabled() throws Exception {
this.spring.configLocations(
this.xml("AutoConfig")
).autowire();
this.mvc.perform(post("/csrf"))
.andExpect(status().isForbidden())
.andExpect(csrfCreated());
}
@Test
public void putWhenDefaultConfigurationThenForbiddenSinceCsrfIsEnabled() throws Exception {
this.spring.configLocations(
this.xml("AutoConfig")
).autowire();
this.mvc.perform(put("/csrf"))
.andExpect(status().isForbidden())
.andExpect(csrfCreated());
}
@Test
public void patchWhenDefaultConfigurationThenForbiddenSinceCsrfIsEnabled() throws Exception {
this.spring.configLocations(
this.xml("AutoConfig")
).autowire();
this.mvc.perform(patch("/csrf"))
.andExpect(status().isForbidden())
.andExpect(csrfCreated());
}
@Test
public void deleteWhenDefaultConfigurationThenForbiddenSinceCsrfIsEnabled() throws Exception {
this.spring.configLocations(
this.xml("AutoConfig")
).autowire();
this.mvc.perform(delete("/csrf"))
.andExpect(status().isForbidden())
.andExpect(csrfCreated());
}
@Test
public void invalidWhenDefaultConfigurationThenForbiddenSinceCsrfIsEnabled() throws Exception {
this.spring.configLocations(
this.xml("AutoConfig")
).autowire();
this.mvc.perform(request("INVALID", new URI("/csrf")))
.andExpect(status().isForbidden())
.andExpect(csrfCreated());
}
@Test
public void getWhenDefaultConfigurationThenCsrfIsEnabled() throws Exception {
this.spring.configLocations(
this.xml("shared-controllers"),
this.xml("AutoConfig")
).autowire();
this.mvc.perform(get("/csrf"))
.andExpect(csrfInBody());
}
@Test
public void headWhenDefaultConfigurationThenCsrfIsEnabled() throws Exception {
this.spring.configLocations(
this.xml("shared-controllers"),
this.xml("AutoConfig")
).autowire();
this.mvc.perform(head("/csrf-in-header"))
.andExpect(csrfInHeader());
}
@Test
public void traceWhenDefaultConfigurationThenCsrfIsEnabled() throws Exception {
this.spring.configLocations(
this.xml("shared-controllers"),
this.xml("AutoConfig")
).autowire();
MockMvc traceEnabled = MockMvcBuilders
.webAppContextSetup((WebApplicationContext) this.spring.getContext())
.apply(springSecurity())
.addDispatcherServletCustomizer(dispatcherServlet -> dispatcherServlet.setDispatchTraceRequest(true))
.build();
traceEnabled.perform(request(HttpMethod.TRACE, "/csrf-in-header"))
.andExpect(csrfInHeader());
}
@Test
public void optionsWhenDefaultConfigurationThenCsrfIsEnabled() throws Exception {
this.spring.configLocations(
this.xml("shared-controllers"),
this.xml("AutoConfig")
).autowire();
this.mvc.perform(options("/csrf-in-header"))
.andExpect(csrfInHeader());
}
@Test
public void postWhenCsrfDisabledThenRequestAllowed() throws Exception {
this.spring.configLocations(
this.xml("shared-controllers"),
this.xml("CsrfDisabled")
).autowire();
this.mvc.perform(post("/ok"))
.andExpect(status().isOk());
assertThat(getFilter(this.spring, CsrfFilter.class)).isNull();
}
@Test
public void postWhenCsrfElementEnabledThenForbidden() throws Exception {
this.spring.configLocations(
this.xml("CsrfEnabled")
).autowire();
this.mvc.perform(post("/csrf"))
.andExpect(status().isForbidden())
.andExpect(csrfCreated());
}
@Test
public void putWhenCsrfElementEnabledThenForbidden() throws Exception {
this.spring.configLocations(
this.xml("CsrfEnabled")
).autowire();
this.mvc.perform(put("/csrf"))
.andExpect(status().isForbidden())
.andExpect(csrfCreated());
}
@Test
public void patchWhenCsrfElementEnabledThenForbidden() throws Exception {
this.spring.configLocations(
this.xml("CsrfEnabled")
).autowire();
this.mvc.perform(patch("/csrf"))
.andExpect(status().isForbidden())
.andExpect(csrfCreated());
}
@Test
public void deleteWhenCsrfElementEnabledThenForbidden() throws Exception {
this.spring.configLocations(
this.xml("CsrfEnabled")
).autowire();
this.mvc.perform(delete("/csrf"))
.andExpect(status().isForbidden())
.andExpect(csrfCreated());
}
@Test
public void invalidWhenCsrfElementEnabledThenForbidden() throws Exception {
this.spring.configLocations(
this.xml("CsrfEnabled")
).autowire();
this.mvc.perform(request("INVALID", new URI("/csrf")))
.andExpect(status().isForbidden())
.andExpect(csrfCreated());
}
@Test
public void getWhenCsrfElementEnabledThenOk() throws Exception {
this.spring.configLocations(
this.xml("shared-controllers"),
this.xml("CsrfEnabled")
).autowire();
this.mvc.perform(get("/csrf"))
.andExpect(csrfInBody());
}
@Test
public void headWhenCsrfElementEnabledThenOk() throws Exception {
this.spring.configLocations(
this.xml("shared-controllers"),
this.xml("CsrfEnabled")
).autowire();
this.mvc.perform(head("/csrf-in-header"))
.andExpect(csrfInHeader());
}
@Test
public void traceWhenCsrfElementEnabledThenOk() throws Exception {
this.spring.configLocations(
this.xml("shared-controllers"),
this.xml("CsrfEnabled")
).autowire();
MockMvc traceEnabled = MockMvcBuilders
.webAppContextSetup((WebApplicationContext) this.spring.getContext())
.apply(springSecurity())
.addDispatcherServletCustomizer(dispatcherServlet -> dispatcherServlet.setDispatchTraceRequest(true))
.build();
traceEnabled.perform(request(HttpMethod.TRACE, "/csrf-in-header"))
.andExpect(csrfInHeader());
}
@Test
public void optionsWhenCsrfElementEnabledThenOk() throws Exception {
this.spring.configLocations(
this.xml("shared-controllers"),
this.xml("CsrfEnabled")
).autowire();
this.mvc.perform(options("/csrf-in-header"))
.andExpect(csrfInHeader());
}
@Test
public void autowireWhenCsrfElementEnabledThenCreatesCsrfRequestDataValueProcessor() {
this.spring.configLocations(
this.xml("CsrfEnabled")
).autowire();
assertThat(this.spring.getContext().getBean(RequestDataValueProcessor.class)).isNotNull();
}
@Test
public void postWhenUsingCsrfAndCustomAccessDeniedHandlerThenTheHandlerIsAppropriatelyEngaged()
throws Exception {
this.spring.configLocations(
this.xml("WithAccessDeniedHandler"),
this.xml("shared-access-denied-handler")
).autowire();
this.mvc.perform(post("/ok"))
.andExpect(status().isIAmATeapot());
}
@Test
public void postWhenHasCsrfTokenButSessionExpiresThenRequestIsCancelledAfterSuccessfulAuthentication()
throws Exception {
this.spring.configLocations(
this.xml("CsrfEnabled")
).autowire();
// simulates a request that has no authentication (e.g. session time-out)
MvcResult result = this.mvc.perform(post("/authenticated")
.with(csrf()))
.andExpect(redirectedUrl("http://localhost/login"))
.andReturn();
MockHttpSession session = (MockHttpSession) result.getRequest().getSession();
// if the request cache is consulted, then it will redirect back to /some-url, which we don't want
this.mvc.perform(post("/login")
.param("username", "user")
.param("password", "password")
.session(session)
.with(csrf()))
.andExpect(redirectedUrl("/"));
}
@Test
public void getWhenHasCsrfTokenButSessionExpiresThenRequestIsRememeberedAfterSuccessfulAuthentication()
throws Exception {
this.spring.configLocations(
this.xml("CsrfEnabled")
).autowire();
// simulates a request that has no authentication (e.g. session time-out)
MvcResult result =
this.mvc.perform(get("/authenticated"))
.andExpect(redirectedUrl("http://localhost/login"))
.andReturn();
MockHttpSession session = (MockHttpSession) result.getRequest().getSession();
// if the request cache is consulted, then it will redirect back to /some-url, which we do want
this.mvc.perform(post("/login")
.param("username", "user")
.param("password", "password")
.session(session)
.with(csrf()))
.andExpect(redirectedUrl("http://localhost/authenticated"));
}
/**
* SEC-2422: csrf expire CSRF token and session-management invalid-session-url
*/
@Test
public void postWhenUsingCsrfAndCustomSessionManagementAndNoSessionThenStillRedirectsToInvalidSessionUrl()
throws Exception {
this.spring.configLocations(
this.xml("WithSessionManagement")
).autowire();
MvcResult result = this.mvc.perform(post("/ok").param("_csrf", "abc"))
.andExpect(redirectedUrl("/error/sessionError"))
.andReturn();
MockHttpSession session = (MockHttpSession) result.getRequest().getSession();
this.mvc.perform(post("/csrf")
.session(session))
.andExpect(status().isForbidden());
}
@Test
public void requestWhenUsingCustomRequestMatcherConfiguredThenAppliesAccordingly()
throws Exception {
SpringTestContext context =
this.spring.configLocations(
this.xml("shared-controllers"),
this.xml("WithRequestMatcher"),
this.xml("mock-request-matcher")
);
context.autowire();
RequestMatcher matcher = context.getContext().getBean(RequestMatcher.class);
when(matcher.matches(any(HttpServletRequest.class))).thenReturn(false);
this.mvc.perform(post("/ok")).andExpect(status().isOk());
when(matcher.matches(any(HttpServletRequest.class))).thenReturn(true);
this.mvc.perform(get("/ok")).andExpect(status().isForbidden());
}
@Test
public void getWhenDefaultConfigurationThenSessionNotImmediatelyCreated()
throws Exception {
this.spring.configLocations(
this.xml("shared-controllers"),
this.xml("AutoConfig")
).autowire();
MvcResult result = this.mvc.perform(get("/ok"))
.andExpect(status().isOk())
.andReturn();
assertThat(result.getRequest().getSession(false)).isNull();
}
@Test
@WithMockUser
public void postWhenCsrfMismatchesThenForbidden()
throws Exception {
this.spring.configLocations(
this.xml("shared-controllers"),
this.xml("AutoConfig")
).autowire();
MvcResult result = this.mvc.perform(get("/ok")).andReturn();
MockHttpSession session = (MockHttpSession) result.getRequest().getSession();
this.mvc.perform(post("/ok")
.session(session)
.with(csrf().useInvalidToken()))
.andExpect(status().isForbidden());
}
@Test
public void loginWhenDefaultConfigurationThenCsrfCleared()
throws Exception {
this.spring.configLocations(
this.xml("shared-controllers"),
this.xml("AutoConfig")
).autowire();
MvcResult result = this.mvc.perform(get("/csrf")).andReturn();
MockHttpSession session = (MockHttpSession) result.getRequest().getSession();
this.mvc.perform(post("/login")
.param("username", "user")
.param("password", "password")
.session(session)
.with(csrf()))
.andExpect(status().isFound());
this.mvc.perform(get("/csrf").session(session))
.andExpect(csrfChanged(result));
}
@Test
public void logoutWhenDefaultConfigurationThenCsrfCleared()
throws Exception {
this.spring.configLocations(
this.xml("shared-controllers"),
this.xml("AutoConfig")
).autowire();
MvcResult result = this.mvc.perform(get("/csrf")).andReturn();
MockHttpSession session = (MockHttpSession) result.getRequest().getSession();
this.mvc.perform(post("/logout").session(session)
.with(csrf()))
.andExpect(status().isFound());
this.mvc.perform(get("/csrf").session(session))
.andExpect(csrfChanged(result));
}
/**
* SEC-2495: csrf disables logout on GET
*/
@Test
@WithMockUser
public void logoutWhenDefaultConfigurationThenDisabled()
throws Exception {
this.spring.configLocations(
this.xml("shared-controllers"),
this.xml("CsrfEnabled")
).autowire();
this.mvc.perform(get("/logout")).andExpect(status().isNotFound());
// still logged in
this.mvc.perform(get("/authenticated")).andExpect(status().isOk());
}
private <T extends Filter> T getFilter(SpringTestContext context, Class<T> type) {
FilterChainProxy chain = context.getContext().getBean(FilterChainProxy.class);
List<Filter> filters = chain.getFilters("/any");
for ( Filter filter : filters ) {
if ( type.isAssignableFrom(filter.getClass()) ) {
return (T) filter;
}
}
return null;
}
private String xml(String configName) {
return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml";
}
@Controller
public static class RootController {
@RequestMapping(value = "/csrf-in-header", method = { HEAD, TRACE, OPTIONS })
@ResponseBody
String csrfInHeaderAndBody(CsrfToken token, HttpServletResponse response) {
response.setHeader(token.getHeaderName(), token.getToken());
return csrfInBody(token);
}
@RequestMapping(value = "/csrf", method = { POST, PUT, PATCH, DELETE, GET })
@ResponseBody
String csrfInBody(CsrfToken token) {
return token.getToken();
}
@RequestMapping(value = "/ok", method = { POST, GET })
@ResponseBody
String ok() {
return "ok";
}
@GetMapping("/authenticated")
@ResponseBody
String authenticated() {
return "authenticated";
}
}
private static class TeapotAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(
HttpServletRequest request,
HttpServletResponse response,
AccessDeniedException accessDeniedException) {
response.setStatus(HttpStatus.IM_A_TEAPOT_418);
}
}
ResultMatcher csrfChanged(MvcResult first) {
return (second) -> {
assertThat(first).isNotNull();
assertThat(second).isNotNull();
assertThat(first.getResponse().getContentAsString())
.isNotEqualTo(second.getResponse().getContentAsString());
};
}
ResultMatcher csrfCreated() {
return new CsrfCreatedResultMatcher();
}
ResultMatcher csrfInHeader() {
return new CsrfReturnedResultMatcher(result -> result.getResponse().getHeader("X-CSRF-TOKEN"));
}
ResultMatcher csrfInBody() {
return new CsrfReturnedResultMatcher(result -> result.getResponse().getContentAsString());
}
@FunctionalInterface
interface ExceptionalFunction<IN, OUT> {
OUT apply(IN in) throws Exception;
}
static class CsrfCreatedResultMatcher implements ResultMatcher {
@Override
public void match(MvcResult result) throws Exception {
MockHttpServletRequest request = result.getRequest();
CsrfToken token = WebTestUtils.getCsrfTokenRepository(request).loadToken(request);
assertThat(token).isNotNull();
}
}
static class CsrfReturnedResultMatcher implements ResultMatcher {
ExceptionalFunction<MvcResult, String> token;
public CsrfReturnedResultMatcher(ExceptionalFunction<MvcResult, String> token) {
this.token = token;
}
@Override
public void match(MvcResult result) throws Exception {
MockHttpServletRequest request = result.getRequest();
CsrfToken token = WebTestUtils.getCsrfTokenRepository(request).loadToken(request);
assertThat(token).isNotNull();
assertThat(token.getToken()).isEqualTo(this.token.apply(result));
}
}
}
@@ -0,0 +1,166 @@
/*
* 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.http;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.test.SpringTestRule;
import org.springframework.security.web.WebAttributes;
import org.springframework.test.web.servlet.MockMvc;
import static org.hamcrest.core.IsNot.not;
import static org.hamcrest.core.IsNull.nullValue;
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.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
*
* @author Luke Taylor
* @author Josh Cummings
*/
public class FormLoginBeanDefinitionParserTests {
private static final String CONFIG_LOCATION_PREFIX =
"classpath:org/springframework/security/config/http/FormLoginBeanDefinitionParserTests";
@Rule
public final SpringTestRule spring = new SpringTestRule();
@Autowired
MockMvc mvc;
@Test
public void getLoginWhenAutoConfigThenShowsDefaultLoginPage()
throws Exception {
this.spring.configLocations(this.xml("Simple")).autowire();
String expectedContent =
"<html><head><title>Login Page</title></head><body onload='document.f.username.focus();'>\n" +
"<h3>Login with Username and Password</h3><form name='f' action='/login' method='POST'>\n" +
"<table>\n" +
" <tr><td>User:</td><td><input type='text' name='username' value=''></td></tr>\n" +
" <tr><td>Password:</td><td><input type='password' name='password'/></td></tr>\n" +
" <tr><td colspan='2'><input name=\"submit\" type=\"submit\" value=\"Login\"/></td></tr>\n" +
"</table>\n" +
"</form></body></html>";
this.mvc.perform(get("/login")).andExpect(content().string(expectedContent));
}
@Test
public void getLoginWhenConfiguredWithCustomAttributesThenLoginPageReflects()
throws Exception {
this.spring.configLocations(this.xml("WithCustomAttributes")).autowire();
String expectedContent =
"<html><head><title>Login Page</title></head><body onload='document.f.custom_user.focus();'>\n" +
"<h3>Login with Username and Password</h3><form name='f' action='/signin' method='POST'>\n" +
"<table>\n" +
" <tr><td>User:</td><td><input type='text' name='custom_user' value=''></td></tr>\n" +
" <tr><td>Password:</td><td><input type='password' name='custom_pass'/></td></tr>\n" +
" <tr><td colspan='2'><input name=\"submit\" type=\"submit\" value=\"Login\"/></td></tr>\n" +
"</table>\n" +
"</form></body></html>";
this.mvc.perform(get("/login")).andExpect(content().string(expectedContent));
}
@Test
public void getLoginWhenConfiguredForOpenIdThenLoginPageReflects()
throws Exception {
this.spring.configLocations(this.xml("WithOpenId")).autowire();
String expectedContent =
"<html><head><title>Login Page</title></head><body onload='document.f.username.focus();'>\n" +
"<h3>Login with Username and Password</h3><form name='f' action='/login' method='POST'>\n" +
"<table>\n" +
" <tr><td>User:</td><td><input type='text' name='username' value=''></td></tr>\n" +
" <tr><td>Password:</td><td><input type='password' name='password'/></td></tr>\n" +
" <tr><td colspan='2'><input name=\"submit\" type=\"submit\" value=\"Login\"/></td></tr>\n" +
"</table>\n" +
"</form><h3>Login with OpenID Identity</h3><form name='oidf' action='/login/openid' method='POST'>\n" +
"<table>\n" +
" <tr><td>Identity:</td><td><input type='text' size='30' name='openid_identifier'/></td></tr>\n" +
" <tr><td colspan='2'><input name=\"submit\" type=\"submit\" value=\"Login\"/></td></tr>\n" +
"</table>\n" +
"</form></body></html>";
this.mvc.perform(get("/login")).andExpect(content().string(expectedContent));
}
@Test
public void getLoginWhenConfiguredForOpenIdWithCustomAttributesThenLoginPageReflects()
throws Exception {
this.spring.configLocations(this.xml("WithOpenIdCustomAttributes")).autowire();
String expectedContent =
"<html><head><title>Login Page</title></head><body onload='document.f.username.focus();'>\n" +
"<h3>Login with Username and Password</h3><form name='f' action='/login' method='POST'>\n" +
"<table>\n" +
" <tr><td>User:</td><td><input type='text' name='username' value=''></td></tr>\n" +
" <tr><td>Password:</td><td><input type='password' name='password'/></td></tr>\n" +
" <tr><td colspan='2'><input name=\"submit\" type=\"submit\" value=\"Login\"/></td></tr>\n" +
"</table>\n" +
"</form><h3>Login with OpenID Identity</h3><form name='oidf' action='/signin' method='POST'>\n" +
"<table>\n" +
" <tr><td>Identity:</td><td><input type='text' size='30' name='openid_identifier'/></td></tr>\n" +
" <tr><td colspan='2'><input name=\"submit\" type=\"submit\" value=\"Login\"/></td></tr>\n" +
"</table>\n" +
"</form></body></html>";
this.mvc.perform(get("/login")).andExpect(content().string(expectedContent));
}
@Test
public void failedLoginWhenConfiguredWithCustomAuthenticationFailureThenForwardsAccordingly()
throws Exception {
this.spring.configLocations(this.xml("WithAuthenticationFailureForwardUrl")).autowire();
this.mvc.perform(post("/login")
.param("username", "bob")
.param("password", "invalidpassword"))
.andExpect(status().isOk())
.andExpect(forwardedUrl("/failure_forward_url"))
.andExpect(request().attribute(WebAttributes.AUTHENTICATION_EXCEPTION, not(nullValue())));
}
@Test
public void successfulLoginWhenConfiguredWithCustomAuthenticationSuccessThenForwardsAccordingly()
throws Exception {
this.spring.configLocations(this.xml("WithAuthenticationSuccessForwardUrl")).autowire();
this.mvc.perform(post("/login")
.param("username", "user")
.param("password", "password"))
.andExpect(status().isOk())
.andExpect(forwardedUrl("/success_forward_url"));
}
private String xml(String configName) {
return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml";
}
}
@@ -0,0 +1,262 @@
/*
* 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.http;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.test.SpringTestRule;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.Filter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
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.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
*
* @author Luke Taylor
* @author Josh Cummings
*/
public class FormLoginConfigTests {
private static final String CONFIG_LOCATION_PREFIX =
"classpath:org/springframework/security/config/http/FormLoginConfigTests";
@Rule
public final SpringTestRule spring = new SpringTestRule();
@Autowired
MockMvc mvc;
@Test
public void getProtectedPageWhenFormLoginConfiguredThenRedirectsToDefaultLoginPage()
throws Exception {
this.spring.configLocations(this.xml("WithAntRequestMatcher")).autowire();
this.mvc.perform(get("/"))
.andExpect(redirectedUrl("http://localhost/login"));
}
@Test
public void authenticateWhenDefaultTargetUrlConfiguredThenRedirectsAccordingly()
throws Exception {
this.spring.configLocations(this.xml("WithDefaultTargetUrl")).autowire();
this.mvc.perform(post("/login")
.param("username", "user")
.param("password", "password")
.with(csrf()))
.andExpect(redirectedUrl("/default"));
}
@Test
public void authenticateWhenConfiguredWithSpelThenRedirectsAccordingly()
throws Exception {
this.spring.configLocations(this.xml("UsingSpel")).autowire();
this.mvc.perform(post("/login")
.param("username", "user")
.param("password", "password")
.with(csrf()))
.andExpect(redirectedUrl(WebConfigUtilsTest.URL + "/default"));
this.mvc.perform(post("/login")
.param("username", "user")
.param("password", "wrong")
.with(csrf()))
.andExpect(redirectedUrl(WebConfigUtilsTest.URL + "/failure"));
this.mvc.perform(get("/"))
.andExpect(redirectedUrl("http://localhost" + WebConfigUtilsTest.URL + "/login"));
}
@Test
public void autowireWhenLoginPageIsMisconfiguredThenDetects() {
assertThatThrownBy(() -> this.spring.configLocations(this.xml("NoLeadingSlashLoginPage")).autowire())
.isInstanceOf(BeanCreationException.class);
}
@Test
public void autowireWhenDefaultTargetUrlIsMisconfiguredThenDetects() {
assertThatThrownBy(() -> this.spring.configLocations(this.xml("NoLeadingSlashDefaultTargetUrl")).autowire())
.isInstanceOf(BeanCreationException.class);
}
@Test
public void authenticateWhenCustomHandlerBeansConfiguredThenInvokesAccordingly()
throws Exception {
this.spring.configLocations(this.xml("WithSuccessAndFailureHandlers")).autowire();
this.mvc.perform(post("/login")
.param("username", "user")
.param("password", "password")
.with(csrf()))
.andExpect(status().isIAmATeapot());
this.mvc.perform(post("/login")
.param("username", "user")
.param("password", "wrong")
.with(csrf()))
.andExpect(status().isIAmATeapot());
}
@Test
public void authenticateWhenCustomUsernameAndPasswordParametersThenSucceeds()
throws Exception {
this.spring.configLocations(this.xml("WithUsernameAndPasswordParameters")).autowire();
this.mvc.perform(post("/login")
.param("xname", "user")
.param("xpass", "password")
.with(csrf()))
.andExpect(redirectedUrl("/"));
}
/**
* SEC-2919 - DefaultLoginGeneratingFilter incorrectly used if login-url="/login"
*/
@Test
public void autowireWhenCustomLoginPageIsSlashLoginThenNoDefaultLoginPageGeneratingFilterIsWired()
throws Exception {
this.spring.configLocations(this.xml("ForSec2919")).autowire();
this.mvc.perform(get("/login"))
.andExpect(content().string("teapot"));
assertThat(getFilter(this.spring.getContext(), DefaultLoginPageGeneratingFilter.class)).isNull();
}
@Test
public void authenticateWhenCsrfIsEnabledThenRequiresToken()
throws Exception {
this.spring.configLocations(this.xml("WithCsrfEnabled")).autowire();
this.mvc.perform(post("/login")
.param("username", "user")
.param("password", "password"))
.andExpect(status().isForbidden());
}
@Test
public void authenticateWhenCsrfIsDisabledThenDoesNotRequireToken()
throws Exception {
this.spring.configLocations(this.xml("WithCsrfDisabled")).autowire();
this.mvc.perform(post("/login")
.param("username", "user")
.param("password", "password"))
.andExpect(status().isFound());
}
/**
* SEC-3147: authentication-failure-url should be contained "error" parameter if login-page="/login"
*/
@Test
public void authenticateWhenLoginPageIsSlashLoginAndAuthenticationFailsThenRedirectContainsErrorParameter()
throws Exception {
this.spring.configLocations(this.xml("ForSec3147")).autowire();
this.mvc.perform(post("/login")
.param("username", "user")
.param("password", "wrong")
.with(csrf()))
.andExpect(redirectedUrl("/login?error"));
}
@RestController
public static class LoginController {
@GetMapping("/login")
public String ok() {
return "teapot";
}
}
public static class TeapotAuthenticationHandler implements
AuthenticationSuccessHandler,
AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
response.setStatus(HttpStatus.I_AM_A_TEAPOT.value());
}
@Override
public void onAuthenticationSuccess(
HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
response.setStatus(HttpStatus.I_AM_A_TEAPOT.value());
}
}
private Filter getFilter(ApplicationContext context, Class<? extends Filter> filterClass) {
FilterChainProxy filterChain = context.getBean(BeanIds.FILTER_CHAIN_PROXY, FilterChainProxy.class);
List<Filter> filters = filterChain.getFilters("/any");
for ( Filter filter : filters ) {
if ( filter.getClass() == filterClass ) {
return filter;
}
}
return null;
}
private String xml(String configName) {
return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml";
}
}
@@ -0,0 +1,40 @@
/*
* 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.http;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Assertions for tests that rely on confirming behavior of the package-private SecurityFilters enum
*
* @author Josh Cummings
*/
public class SecurityFiltersAssertions {
private static Collection<SecurityFilters> ordered = Arrays.asList(SecurityFilters.values());
public static void assertEquals(List<String> filters) {
List<String> expected = ordered.stream()
.map(SecurityFilters::name)
.collect(Collectors.toList());
assertThat(filters).isEqualTo(expected);
}
}
@@ -31,7 +31,7 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
public class CommonOAuth2ProviderTests {
private static final String DEFAULT_LOGIN_REDIRECT_URL = "{baseUrl}/login/oauth2/code/{registrationId}";
private static final String DEFAULT_REDIRECT_URL = "{baseUrl}/{action}/oauth2/code/{registrationId}";
@Test
public void getBuilderWhenGoogleShouldHaveGoogleSettings() throws Exception {
@@ -51,7 +51,7 @@ public class CommonOAuth2ProviderTests {
.isEqualTo(ClientAuthenticationMethod.BASIC);
assertThat(registration.getAuthorizationGrantType())
.isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE);
assertThat(registration.getRedirectUriTemplate()).isEqualTo(DEFAULT_LOGIN_REDIRECT_URL);
assertThat(registration.getRedirectUriTemplate()).isEqualTo(DEFAULT_REDIRECT_URL);
assertThat(registration.getScopes()).containsOnly("openid", "profile", "email");
assertThat(registration.getClientName()).isEqualTo("Google");
assertThat(registration.getRegistrationId()).isEqualTo("123");
@@ -74,7 +74,7 @@ public class CommonOAuth2ProviderTests {
.isEqualTo(ClientAuthenticationMethod.BASIC);
assertThat(registration.getAuthorizationGrantType())
.isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE);
assertThat(registration.getRedirectUriTemplate()).isEqualTo(DEFAULT_LOGIN_REDIRECT_URL);
assertThat(registration.getRedirectUriTemplate()).isEqualTo(DEFAULT_REDIRECT_URL);
assertThat(registration.getScopes()).containsOnly("read:user");
assertThat(registration.getClientName()).isEqualTo("GitHub");
assertThat(registration.getRegistrationId()).isEqualTo("123");
@@ -97,7 +97,7 @@ public class CommonOAuth2ProviderTests {
.isEqualTo(ClientAuthenticationMethod.POST);
assertThat(registration.getAuthorizationGrantType())
.isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE);
assertThat(registration.getRedirectUriTemplate()).isEqualTo(DEFAULT_LOGIN_REDIRECT_URL);
assertThat(registration.getRedirectUriTemplate()).isEqualTo(DEFAULT_REDIRECT_URL);
assertThat(registration.getScopes()).containsOnly("public_profile", "email");
assertThat(registration.getClientName()).isEqualTo("Facebook");
assertThat(registration.getRegistrationId()).isEqualTo("123");
@@ -122,7 +122,7 @@ public class CommonOAuth2ProviderTests {
.isEqualTo(ClientAuthenticationMethod.BASIC);
assertThat(registration.getAuthorizationGrantType())
.isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE);
assertThat(registration.getRedirectUriTemplate()).isEqualTo(DEFAULT_LOGIN_REDIRECT_URL);
assertThat(registration.getRedirectUriTemplate()).isEqualTo(DEFAULT_REDIRECT_URL);
assertThat(registration.getScopes()).containsOnly("openid", "profile", "email",
"address", "phone");
assertThat(registration.getClientName()).isEqualTo("Okta");
@@ -0,0 +1,143 @@
/*
* 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.web.server;
import org.junit.Test;
import org.springframework.http.HttpStatus;
import org.springframework.security.config.annotation.web.reactive.ServerHttpSecurityConfigurationBuilder;
import org.springframework.security.test.web.reactive.server.WebTestClientBuilder;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationEntryPoint;
import org.springframework.security.web.server.authorization.HttpStatusServerAccessDeniedHandler;
import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler;
import org.springframework.test.web.reactive.server.WebTestClient;
import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.Credentials.basicAuthenticationCredentials;
import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication;
/**
* @author Denys Ivano
* @since 5.0.5
*/
public class ExceptionHandlingSpecTests {
private ServerHttpSecurity http = ServerHttpSecurityConfigurationBuilder.httpWithDefaultAuthentication();
@Test
public void defaultAuthenticationEntryPoint() {
SecurityWebFilterChain securityWebFilter = this.http
.csrf().disable()
.authorizeExchange()
.anyExchange().authenticated()
.and()
.exceptionHandling()
.and()
.build();
WebTestClient client = WebTestClientBuilder
.bindToWebFilters(securityWebFilter)
.build();
client
.get()
.uri("/test")
.exchange()
.expectStatus().isUnauthorized()
.expectHeader().valueMatches("WWW-Authenticate", "Basic.*");
}
@Test
public void customAuthenticationEntryPoint() {
SecurityWebFilterChain securityWebFilter = this.http
.csrf().disable()
.authorizeExchange()
.anyExchange().authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(redirectServerAuthenticationEntryPoint("/auth"))
.and()
.build();
WebTestClient client = WebTestClientBuilder
.bindToWebFilters(securityWebFilter)
.build();
client
.get()
.uri("/test")
.exchange()
.expectStatus().isFound()
.expectHeader().valueMatches("Location", ".*");
}
@Test
public void defaultAccessDeniedHandler() {
SecurityWebFilterChain securityWebFilter = this.http
.csrf().disable()
.httpBasic().and()
.authorizeExchange()
.anyExchange().hasRole("ADMIN")
.and()
.exceptionHandling()
.and()
.build();
WebTestClient client = WebTestClientBuilder
.bindToWebFilters(securityWebFilter)
.filter(basicAuthentication())
.build();
client
.get()
.uri("/admin")
.attributes(basicAuthenticationCredentials("user", "password"))
.exchange()
.expectStatus().isForbidden();
}
@Test
public void customAccessDeniedHandler() {
SecurityWebFilterChain securityWebFilter = this.http
.csrf().disable()
.httpBasic().and()
.authorizeExchange()
.anyExchange().hasRole("ADMIN")
.and()
.exceptionHandling()
.accessDeniedHandler(httpStatusServerAccessDeniedHandler(HttpStatus.BAD_REQUEST))
.and()
.build();
WebTestClient client = WebTestClientBuilder
.bindToWebFilters(securityWebFilter)
.filter(basicAuthentication())
.build();
client
.get()
.uri("/admin")
.attributes(basicAuthenticationCredentials("user", "password"))
.exchange()
.expectStatus().isBadRequest();
}
private ServerAuthenticationEntryPoint redirectServerAuthenticationEntryPoint(String location) {
return new RedirectServerAuthenticationEntryPoint(location);
}
private ServerAccessDeniedHandler httpStatusServerAccessDeniedHandler(HttpStatus httpStatus) {
return new HttpStatusServerAccessDeniedHandler(httpStatus);
}
}
@@ -17,6 +17,8 @@
package org.springframework.security.config.web.server;
import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
@@ -36,6 +38,8 @@ import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* @author Rob Winch
@@ -204,9 +208,10 @@ public class FormLoginTests {
private LoginForm loginForm;
private OAuth2Login oauth2Login = new OAuth2Login();
public DefaultLoginPage(WebDriver webDriver) {
this.driver = webDriver;
this.loginForm = PageFactory.initElements(webDriver, LoginForm.class);
}
static DefaultLoginPage create(WebDriver driver) {
@@ -228,10 +233,23 @@ public class FormLoginTests {
return this;
}
public DefaultLoginPage assertLoginFormNotPresent() {
assertThatThrownBy(() -> loginForm().username(""))
.isInstanceOf(NoSuchElementException.class);
return this;
}
public LoginForm loginForm() {
if (this.loginForm == null) {
this.loginForm = PageFactory.initElements(this.driver, LoginForm.class);
}
return this.loginForm;
}
public OAuth2Login oauth2Login() {
return this.oauth2Login;
}
static DefaultLoginPage to(WebDriver driver) {
driver.get("http://localhost/login");
return PageFactory.initElements(driver, DefaultLoginPage.class);
@@ -263,6 +281,22 @@ public class FormLoginTests {
return PageFactory.initElements(this.driver, page);
}
}
public class OAuth2Login {
public WebElement findClientRegistrationByName(String clientName) {
return DefaultLoginPage.this.driver.findElement(By.linkText(clientName));
}
public OAuth2Login assertClientRegistrationByName(String clientName) {
assertThatCode(() -> findClientRegistrationByName(clientName))
.doesNotThrowAnyException();
return this;
}
public DefaultLoginPage and() {
return DefaultLoginPage.this;
}
}
}
public static class DefaultLogoutPage {
@@ -28,7 +28,7 @@ import org.springframework.test.web.reactive.server.FluxExchangeResult;
import org.springframework.test.web.reactive.server.WebTestClient;
import java.time.Duration;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -45,7 +45,7 @@ public class HeaderSpecTests {
HttpHeaders expectedHeaders = new HttpHeaders();
Set<String> ignoredHeaderNames = Collections.singleton(HttpHeaders.CONTENT_TYPE);
Set<String> headerNamesNotPresent = new HashSet<>();
@Before
public void setup() {
@@ -60,6 +60,23 @@ public class HeaderSpecTests {
.add(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "1 ; mode=block");
}
@Test
public void headersWhenDisableThenNoSecurityHeaders() {
new HashSet<>(this.expectedHeaders.keySet()).forEach(this::expectHeaderNamesNotPresent);
this.headers.disable();
assertHeaders();
}
@Test
public void headersWhenDisableAndInvokedExplicitlyThenDefautsUsed() {
this.headers.disable()
.headers();
assertHeaders();
}
@Test
public void headersWhenDefaultsThenAllDefaultsWritten() {
assertHeaders();
@@ -67,9 +84,7 @@ public class HeaderSpecTests {
@Test
public void headersWhenCacheDisableThenCacheNotWritten() {
this.expectedHeaders.remove(HttpHeaders.CACHE_CONTROL);
this.expectedHeaders.remove(HttpHeaders.PRAGMA);
this.expectedHeaders.remove(HttpHeaders.EXPIRES);
expectHeaderNamesNotPresent(HttpHeaders.CACHE_CONTROL, HttpHeaders.PRAGMA, HttpHeaders.EXPIRES);
this.headers.cache().disable();
assertHeaders();
@@ -77,7 +92,7 @@ public class HeaderSpecTests {
@Test
public void headersWhenContentOptionsDisableThenContentTypeOptionsNotWritten() {
this.expectedHeaders.remove(ContentTypeOptionsServerHttpHeadersWriter.X_CONTENT_OPTIONS);
expectHeaderNamesNotPresent(ContentTypeOptionsServerHttpHeadersWriter.X_CONTENT_OPTIONS);
this.headers.contentTypeOptions().disable();
assertHeaders();
@@ -85,7 +100,7 @@ public class HeaderSpecTests {
@Test
public void headersWhenHstsDisableThenHstsNotWritten() {
this.expectedHeaders.remove(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY);
expectHeaderNamesNotPresent(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY);
this.headers.hsts().disable();
assertHeaders();
@@ -103,7 +118,7 @@ public class HeaderSpecTests {
@Test
public void headersWhenFrameOptionsDisableThenFrameOptionsNotWritten() {
this.expectedHeaders.remove(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS);
expectHeaderNamesNotPresent(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS);
this.headers.frameOptions().disable();
assertHeaders();
@@ -111,22 +126,29 @@ public class HeaderSpecTests {
@Test
public void headersWhenFrameOptionsModeThenFrameOptionsCustomMode() {
this.expectedHeaders.remove(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS);
this.expectedHeaders
.add(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS, "SAMEORIGIN");
this.headers.frameOptions().mode(XFrameOptionsServerHttpHeadersWriter.Mode.SAMEORIGIN);
this.expectedHeaders.set(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS, "SAMEORIGIN");
this.headers
.frameOptions()
.mode(XFrameOptionsServerHttpHeadersWriter.Mode.SAMEORIGIN);
assertHeaders();
}
@Test
public void headersWhenXssProtectionDisableThenXssProtectionNotWritten() {
this.expectedHeaders.remove("X-Xss-Protection");
expectHeaderNamesNotPresent("X-Xss-Protection");
this.headers.xssProtection().disable();
assertHeaders();
}
private void expectHeaderNamesNotPresent(String... headerNames) {
for(String headerName : headerNames) {
this.expectedHeaders.remove(headerName);
this.headerNamesNotPresent.add(headerName);
}
}
private void assertHeaders() {
WebTestClient client = buildClient();
FluxExchangeResult<String> response = client.get()
@@ -135,10 +157,14 @@ public class HeaderSpecTests {
.returnResult(String.class);
Map<String, List<String>> responseHeaders = response.getResponseHeaders();
this.ignoredHeaderNames.stream().forEach(responseHeaders::remove);
assertThat(responseHeaders).describedAs(response.toString()).isEqualTo(
this.expectedHeaders);
if (!this.expectedHeaders.isEmpty()) {
assertThat(responseHeaders).describedAs(response.toString())
.containsAllEntriesOf(this.expectedHeaders);
}
if (!this.headerNamesNotPresent.isEmpty()) {
assertThat(responseHeaders.keySet()).doesNotContainAnyElementsOf(this.headerNamesNotPresent);
}
}
private WebTestClient buildClient() {

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