1
0
mirror of synced 2026-07-15 23:55:09 +00:00

Compare commits

...

165 Commits

Author SHA1 Message Date
Spring Buildmaster 63c4fec733 Release version 3.2.0.RC1 2013-08-19 15:05:55 -07:00
Rob Winch 110e769bd4 SEC-2257: Remove HttpSecurityBuilder#getAuthenticationManager()
Removed in favor of using shared object.
2013-08-19 15:22:04 -05:00
Hans-Joachim Kliemeck 8b1ab4e85f SEC-2260 - update pom/gradle to use current cas client library 2013-08-19 15:22:04 -05:00
Rob Winch 658a93178c SEC-2252: Add custom form guide 2013-08-19 15:22:04 -05:00
Rob Winch 51b9c4a19a Hide logout in main.jsp if not logged in 2013-08-17 14:38:39 -05:00
Rob Winch 5fe32bb3c8 SEC-2216: Add withObjectPostProcessor 2013-08-16 15:38:58 -05:00
Rob Winch d62c2e0835 SEC-2244: Defaults based on loginPage are now updated when loginPage changes 2013-08-16 14:48:45 -05:00
Rob Winch e0cad0d684 SEC-2230: Fix Header tests 2013-08-15 16:52:58 -05:00
Rob Winch 2e852f4613 SEC-2230: Remove stray import 2013-08-15 16:34:31 -05:00
Rob Winch a469f26b10 SEC-2230: Polish Headers JavaConfig 2013-08-15 16:31:43 -05:00
Rob Winch e9bb9e766e SEC-1574: Add CSRF Support 2013-08-15 14:49:21 -05:00
beamerblvd 5f35d9e3ec SEC-2135: Document HttpServletRequest.changeSessionId() support 2013-08-15 13:59:16 -05:00
Rob Winch 797df51264 SEC-2135: Support HttpServletRequest#changeSessionId() 2013-08-15 13:59:16 -05:00
Rob Winch 75fb971d23 SEC-2221: Fix the ignored media types to use includes instead of equals 2013-08-15 13:59:15 -05:00
Rob Winch 54c2166567 SEC-2194: Remove unnecessary MessageSecurityWebApplicationInitializer from helloworld 2013-08-15 12:50:41 -05:00
Rob Winch fea4d01aad SEC-2194: hello samples displays username and logout properly 2013-08-15 12:50:41 -05:00
Rob Winch b5ecaf61ed SEC-2194: Remove samples errors/tabs folders 2013-08-15 12:50:41 -05:00
Rob Winch f036970f8b SEC-2194: Add margin to links in header of samples 2013-08-15 12:50:41 -05:00
Rob Winch 2feded5fc5 SEC-2194: Update samples to have jsp-api 2013-08-15 12:50:40 -05:00
Rob Winch 485676be8c SEC-2251: Polish Hello World guides
* Correct how to add username and logout to mvc
* Externalize :revnumber:
2013-08-15 12:50:40 -05:00
Rob Winch 22e4d1646a SEC-2194: Remove login page from hellomvc and insecuremvc 2013-08-15 12:50:40 -05:00
Rob Winch 13da42ca1b SEC-2137: Allow disabling session fixation and enable concurrency control 2013-08-15 12:50:40 -05:00
Rob Winch 867f02e8ac SEC-2249: AbstractSecurityWebApplicationInitializer does not delegate WebApplicationInitializer
Previously AbstractSecurityWebApplicationInitializer delegated to a
WebApplicationInitializer, but it caused issues in some instances where
a container would pass the annonymous inner class to
SpringServletContainerInitializer which caused errors on startup.

Now AbstractSecurityWebApplicationInitializer registers the
ContextLoaderListener on its own instead of delegating.
2013-08-15 12:49:44 -05:00
Rob Winch e1dfa81a0f GRADLE-1116: Add back workaround for depending on test sources 2013-08-13 09:13:02 -05:00
Rob Winch 337c3cf96c SEC-2255: Update to Gradle 1.7 2013-08-08 16:31:36 -05:00
Rob Winch 54bf6c846b SEC-2097: Remove configure() blocks from gradle/*.gradle 2013-08-08 16:29:43 -05:00
Rob Winch e0cb931f69 SEC-2251: Create Hello World Java Configuration guides 2013-08-08 14:34:50 -05:00
Rob Winch e8278f3b9b SEC-2249: AbstractSecurityWebApplicationInitializer allows register config 2013-08-08 14:33:54 -05:00
Rob Winch 976d9a9016 SEC-2194: Polish java config sample apps 2013-08-08 14:33:54 -05:00
Guillaume Smet d20a8e0373 SEC-2245: Cast to interface instead of implementation
Makes our life easier when we want to override the
MethodSecurityExpressionRoot.
2013-08-05 17:07:12 -05:00
Rob Winch 1f86d5dad9 SEC-2097: Add Tomcat Gradle plugin 2013-08-05 16:49:34 -05:00
Rob Winch 343a76de13 Use eclipse-wtp instead of eclipse in java projects 2013-08-05 16:49:34 -05:00
Rob Winch 6a1a6b080f No longer using Eclipse classpath container, so remove workarounds for it 2013-08-05 16:49:34 -05:00
Rob Winch ab2b461075 Gradle workaround for test source being deployed 2013-08-05 16:49:34 -05:00
Rob Winch 9d58317731 Use included configuration instead of sourceSets.main.output
Previously the crypto module was not exported in Eclipse because it was
listed as a test dependency to have it added as a dependency. Note that
this was all to work around GRADLE-1116

Now we add an included configuration that is exported for Eclipse, but not
added to the Maven pom (since all the crypto module is included in core).
2013-08-05 16:49:34 -05:00
Rob Winch 51a48fa9fa Allow import without Eclipse dependency management
This adds a .classpath file that contains no source folders to ensure
that the projects that are not java projects work. The projects still
require a java project nature to add the gradle project nature.
2013-08-05 16:49:34 -05:00
Rob Winch fdb73fac23 Remove @Override from interface define methods 2013-08-05 16:49:33 -05:00
Rob Winch b13b87a1e7 Remove @Override from methods that override interfaces
Ensure JDK5 compatibility
2013-08-05 16:49:33 -05:00
Rob Winch 388a4dd9db SEC-2194: Add Java Config samples 2013-08-05 16:49:33 -05:00
Rob Winch 36418b964d Remove samples/runall.sh 2013-08-01 13:19:21 -05:00
Asaf David 333a7291a4 SEC-2242: Fixed typo in technical overview
Changed "source source" to "source"
2013-08-01 13:02:56 -05:00
Rob Winch 2266f0ca3f SEC-2238: Polish 2013-08-01 11:57:32 -05:00
Rob Winch 2fef79f3d2 SEC-2238: WebAsyncManagerIntegrationFilter Java Config 2013-08-01 11:40:34 -05:00
Rob Winch e242aeff3e SEC-2230: Polish and clickjacking demo 2013-08-01 10:19:36 -05:00
Rob Winch 8c3ac719bb SEC-2230: Added testing certificates 2013-08-01 09:48:09 -05:00
Rob Winch 0bc08f8a23 SEC-2230: Update contacts sample to use <headers> with no child elements 2013-08-01 09:47:57 -05:00
Rob Winch 283c906215 SEC-2230: Fix reference PDF 2013-07-31 12:22:41 -05:00
Rob Winch 94a73fee37 SEC-2230: Polish scoping and finals 2013-07-31 11:34:35 -05:00
Rob Winch a1bf28a697 SEC-2239: Remove duplicate SessionCreationPolicy 2013-07-31 10:44:22 -05:00
Rob Winch 606bddf598 SEC-2230: Add Header JavaConfig
Added JavaConfig for Headers. In the process, more HeaderWriter instances
were added so that we can reuse logic between the XML and JavaConfig. This
also prompted repackaging the writers.
2013-07-31 10:39:52 -05:00
Rob Winch 988e97e366 SEC-2230: Polish headers reference 2013-07-31 10:39:52 -05:00
Rob Winch bc8ff9590c SEC-2230: Defaults when using only <headers/>
Previously an error occurred when no child elements were specified with
<headers/>.

Now all the explicitly supported header elements are added with their
default settings.
2013-07-31 10:39:52 -05:00
Rob Winch c85328c5d1 SEC-2230: HTTP Strict Transport Security (HSTS)Add support for Strict
This is a distinct filter as apposed to reusing StaticHeaderWriter
since the specification specifies that the "Strict-Transport-Security"
header should only be set on secure requests. It would not make sense to
require DelegatingRequestMatcherHeaderWriter since this requirement is
in the specification.
2013-07-31 10:39:52 -05:00
Rob Winch 8013cd54d6 SEC-2230: Added Cache Control support 2013-07-31 10:39:45 -05:00
Rob Winch 7b164bb5e1 SEC-2230: Polish pull request 2013-07-26 14:19:53 -05:00
Rob Winch 8acd205486 SEC-2232: HeaderFactory to HeaderWriter 2013-07-26 09:01:12 -05:00
Rob Winch fd754c5cab SEC-2098, SEC-2099: Fix build
- hf.doFilter is missing FilterChain argument
  - response.headers does not contain the exact values for the headers so
    should not be used for comparison (note it is a private member so this
    is acceptable)
  - hf does not need non-null check when hf.doFilter is invoked
  - some of the configurations are no longer valid (i.e. ALLOW-FROM
    requires strategy)
  - Some error messages needed updated (some could still use improvement)
  - No validation for missing header name or value
  - rebased off master / merged
  - nsa=frame-options-strategy id should use - not =
  - FramewOptionsHeaderFactory did not produce "ALLOW-FROM " prefix of origin
  - remove @Override on interface overrides to work with JDK5
2013-07-25 16:23:25 -05:00
Marten Deinum d0b40cd2ae - Created HeaderFactory abstraction
- Implemented different ALLOW-FROM strategies as specified in the proposal.

Conflicts:
	config/src/main/java/org/springframework/security/config/http/HeadersBeanDefinitionParser.java
	config/src/test/groovy/org/springframework/security/config/http/HttpHeadersConfigTests.groovy
2013-07-25 16:22:43 -05:00
Rob Winch a63baa8391 SEC-2098, SEC-2099: Polishing 2013-07-25 16:22:43 -05:00
Marten Deinum 0adf5aea91 SEC-2098, SEC-2099: Created HeadersFilter
Created HeadersFilter for setting security headers added including a
bean definition parser for easy configuration of the headers. Enables
easy configuration for the X-Frame-Options, X-XSS-Protection and
X-Content-Type-Options headers. Also allows for additional headers to
be added.
2013-07-25 16:22:43 -05:00
Rob Winch f5a30e55a3 SEC-2042: AbstractAuthenticationProcessingFilter supports RequestMatcher 2013-07-23 13:06:51 -05:00
Rob Winch f34b459c80 SEC-2205: Create UserDetailsServiceDelegator
Ensure that the UserDetailsService is created lazily.
2013-07-22 16:38:09 -05:00
Rob Winch a39ff1b041 SEC-2202: http.authorizeUrls() to http.authorizeRequests()
This change is more meaningful since the requests can be matched on
anything not just the URL
2013-07-22 11:54:10 -05:00
Rob Winch e1d8db4e95 SEC-2197: Allow multiple invocations on HttpSecurity
Previously invoking methods like HttpSecurity#authorizeUrls() multiple
times would override one another. This has now changed to be more
intuitive. Initially this was required for the way that defaults were
provided so that they could be overriden, but this is no longer the case.
2013-07-21 22:56:23 -05:00
Rob Winch 686a7a8d62 SEC-2223: Correct FirewalledRequest#reset() javadoc 2013-07-21 14:30:20 -05:00
Rob Winch 04b7d5ca08 SEC-2156: Only configures COOKIE instead of SSL
Configuring SSL is only allowed for SSL enabled applications and should
be configured on its own (not in conjuction with other modes).
2013-07-20 10:29:54 -05:00
Rob Winch cf0fdc2d66 SEC-2222: Use auth parameter name instead of registry 2013-07-20 07:49:07 -05:00
Rob Winch ac053dbda7 SEC-2156: AbstractSecurityWebApplicationInitializer configures SessionTrackingMode
It also allows customization by overriding a method.
2013-07-19 17:09:58 -05:00
Rob Winch 90bd241ce2 SEC-2199: Support multiple AuthenticationEntryPoint defaults 2013-07-19 17:09:58 -05:00
Rob Winch 87c9a14bff SEC-2198: http.httpBasic() defaults AuthenticationEntryPoint 2013-07-19 17:09:58 -05:00
Rob Winch 4411ae3ff6 SEC-2221: Add MediaTypeRequestMatcher 2013-07-19 17:09:31 -05:00
Rob Winch eb15b19e50 SEC-2195: Update Groovy version on web module 2013-07-16 22:44:51 -05:00
Rob Winch 0f281f9575 SEC-2215: ServletApiConfigurer populates properties on SecurityContextHolderAwareRequestFilter
Previously ServletApiConfigurer left the following properties null:
authenticationManager, logoutHandlers, and authenticationEntryPoint
2013-07-16 22:43:53 -05:00
Rob Winch 59e8551279 Fix package tangles 2013-07-16 17:08:03 -05:00
Rob Winch 5e6ca12b01 SEC-2097: Update integrationTestCompile to use optional and provided
Also update slf4j version and remove explicit commons-logging from pom generation
2013-07-16 15:59:06 -05:00
Rob Winch 955a60cf49 SEC-2208: Use std docbook plugin and workspace cleanup 2013-07-16 15:15:47 -05:00
Rob Winch 7d9f0d688a SEC-2207: Workaround for GRADLE-1116 2013-07-16 15:15:47 -05:00
Rob Winch 00ed77af20 SEC-2207: Trick Gradle Eclipse to import project and not the build folders 2013-07-16 15:15:47 -05:00
Rob Winch 41b64e05d0 SEC-2211: check.dependsOn integrationTest 2013-07-16 15:15:47 -05:00
Rob Winch 02551e1b7a SEC-2214: Update Spring Version 2013-07-16 15:15:47 -05:00
Rob Winch faa8b354b7 SEC-2209: add pom.xml 2013-07-16 15:15:47 -05:00
Rob Winch e5fc063680 SEC-2206: Gradle Propdeps 2013-07-16 15:15:42 -05:00
Rob Winch 01230c76ef SEC-2217: Sonar Runner 2013-07-16 15:13:22 -05:00
Rob Winch 3c9a80433f SEC-2207: AspectJPlugin uses compileAspect and compileTestAspect 2013-07-16 15:02:39 -05:00
Rob Winch 07c3fdf8a7 SEC-2195: Update Groovy, Geb, Spock, httpcomponents, and Jetty 2013-07-16 15:02:39 -05:00
Rob Winch 1705c5d796 SEC-2207: Update Gradle to 1.6 2013-07-16 15:02:39 -05:00
Rob Winch fb45db11e9 SEC-2191: Remove AuthenticationManagerBuilder default constructor
This ensures that users must choose what ObjectPostProcessor is being used
with AuthenticationManagerBuilder. To make things easier for users, we now
automatically add an AuthenticationManagerBuilder object that can be used
for creating an AuthenticationManager with @Autowired.
2013-07-05 12:10:03 -05:00
Rob Winch e88800cd9b SEC-2187: Polish
Create private utf8UrlEncode method to improve readability
2013-07-05 10:24:10 -05:00
Tom Boettcher 54c1c20c69 SEC-2187: Encode query parameter names and values in return_to URL 2013-07-05 09:47:18 -05:00
Rob Winch e5c450a14c Merge in AbstractSecurityWebApplicationInitializerTests.groovy 2013-07-05 09:42:19 -05:00
Keesun Baik cf80cc88b5 SEC-2192: Create DEFAULT_FILTER_NAME 2013-07-05 09:41:53 -05:00
Rob Winch cd34c47643 Next development version 2013-07-01 14:05:22 -05:00
Spring Buildmaster b45f7b9585 [artifactory-release] Release version 3.2.0.M2 2013-07-01 11:42:45 -07:00
Rob Winch 3c178a339e remove snapshot repo references 2013-07-01 13:00:57 -05:00
Rob Winch 70b3a330ef #137 WebSecurityConfigurerAdapter no longer uses getClass() for logger
Previously it was difficult to change log levels due to CGLIB proxying of
the class which impacted the logger name.
2013-07-01 10:07:38 -05:00
Rob Winch 17bef05c3c #138 WebInvocationPrivilegeEvaluator has default value 2013-07-01 08:46:57 -05:00
Rob Winch d8ed429370 #138 Tests for WebSecurityExpressionHandler bean existing 2013-07-01 08:37:12 -05:00
Rob Winch 4d282cbe0d SEC-1953: Polish 2013-06-30 21:51:25 -05:00
Rob Winch 7412fe0748 SEC-1953: Polish bundlor warnings 2013-06-30 21:45:45 -05:00
Rob Winch d0c4e6ca72 SEC-1953: Spring Security Java Config support
This is the initial migration of Spring Security Java Config from the
external project at
https://github.com/SpringSource/spring-security-javaconfig
2013-06-30 17:28:33 -05:00
Luke Taylor fba4fec84b SEC-2175: Correct XSD docs on auto-config. 2013-06-09 14:51:58 +01:00
Rob Winch 7bc87cf13b SEC-2002: Polishing 2013-06-06 15:05:00 -05:00
Nicholas Williams d89ace26ab SEC-2002: Added events to notify of session ID change
Session fixation protection, whether by clean new session or
migrated session, now publishes an event when a session is
migrated or its ID is changed. This enables application developers
to keep track of the session ID of a particular authentication
from the time the authentication is successful until the time
of logout. Previously this was not possible since session
migration changed the session ID and there was no way to
reliably detect that.

Revised changes per Rob Winch's suggestions.
2013-06-05 14:44:17 -05:00
Luke Taylor 743960d2d8 SEC-2122: Fix broken integration tests.
Modified BCryptPasswordEncoder to no longer throw an
IllegalArgumentException when the encoded password is empty or
the incorrect format for bcrypt. Instead it now logs a warning
that non bcrypt data was found.

The Dms integration tests were failing after being changed to
use bcrypt and this fixes the issue.
2013-05-21 23:13:08 +01:00
Luke Taylor d8727638ab SEC-1785: Remove auto-config from manual.
Changed the namespace doc to use an explicit form-login
and logout element and avoid mention of auto-config or its
effects. This makes the intro shorter and simpler.
2013-05-18 21:25:11 +01:00
Luke Taylor ebba8ac514 SEC-2122: Update namespace to support bcrypt.
password-encoder now supports hash='bcrypt'.
2013-05-17 19:17:18 +01:00
Luke Taylor 896339087f SEC-2122: Update samples to use bcrypt.
Data sources modified to store bcrypt hashes and configs now
use BCryptPassworEncoder.
2013-05-17 18:44:30 +01:00
Luke Taylor d6524feb62 SEC-2122: Change doc to prioritize bcrypt use 2013-05-17 18:42:47 +01:00
Rob Winch 34893cd53a Remove ApacheDSContainerTests successfulStartupAndShutdown since it was commented out 2013-04-25 11:21:23 -05:00
Rob Winch 407b08956b SEC-2161: <ldap-server> creates unique dir for embedded LDAP 2013-04-25 11:21:21 -05:00
Rob Winch dd554e1842 SEC-2162: ApacheDSContainer throws RuntimeException on failure to start 2013-04-25 11:21:19 -05:00
Rob Winch c0921b9ede SEC-2133: Update doc from ChannelAuthenticationFilter to ChannelProcessingFilter 2013-04-25 08:56:47 -05:00
Rob Winch e469c93f9d SEC-2147: Deprecate .encoding.PasswordEncoding 2013-04-25 08:56:47 -05:00
Rob Winch f594ed76db SEC-2087: GlobalMethodSecurityBeanDefinitionParser uses AuthenticationManager to create AuthenticationManagerDelegator 2013-04-25 08:56:46 -05:00
Luke Taylor 6ebb9abfb7 Fix HttpSessionEventPublisher package name in FAQ. 2013-04-06 14:53:53 +01:00
Rob Winch 66357a2077 SEC-2143: Update XSD version mismatch error message 2013-03-06 10:57:41 -06:00
Oliver Becker 5eb5c91d86 SEC-2119: Rename rememberme-parameter to remember-me-parameter
This change extends pull request https://github.com/SpringSource/spring-security/pull/26
and its subsequent changes by renaming the attribute name 'rememberme-parameter' to
'remember-me-parameter'.

The spelling including the additional hyphen in 'remember-me-parameter' is more consistent
with the default spelling of the 'remember-me' functionality.
2013-03-05 14:47:25 -06:00
Rob Winch b014020955 SEC-2119: Polish remember-me@rememberme-parameter
- Change form-parameter to rememerme-parameter
  - Use rnc file for generating the xsd
  - Add test for deafult value of rememberme parameter
2013-03-01 17:03:09 -06:00
Oliver Becker 9eb34fe51c SEC-2119: Add a 'form-parameter' attribute to <remember-me>
This change extends the namespace configuration of <remember-me>
with a 'form-parameter' attribute. The introduced attribute sets
the 'parameter' property of  AbstractRememberMeServices.

This enables overriding the default value of
'_spring_security_remember_me' using the namespace configuration.
2013-03-01 17:03:02 -06:00
Rob Winch e8661913d1 SEC-2119: Update to 3.2 schema and use default schema version when available 2013-03-01 16:29:27 -06:00
Mike Noordermeer f8ed3791f9 SEC-2142: Schema documentation states anonymous and remember-me ke defaults to SecureRandom 2013-03-01 12:23:36 -06:00
Rob Winch 2a86c72436 Update XsdDocumentedTests to make easier to understand problems 2013-02-28 17:08:51 -06:00
Raghuram Devarakonda 047448464b SEC-2140: Correct javadoc order of security checkes for AclAuthorizationStrategyImpl 2013-02-28 11:56:55 -06:00
Rob Winch 914ec45e43 SEC-2136: Lazy load MethodSecurityExpressionHandler & MethodSecurityExpressionHandler.expressionParser
Previously wiring dependencies created with a FactoryBean into
MethodSecurityExpressionHandler &
MethodSecurityExpressionHandler.expressionParser and  would cause
NoSuchBeanDefinitionException's to occur. These changes make it easier
(but not impossible) to avoid such errors.

The following changes were made:

    - ExpressionBasedAnnotationAttributeFactory delays the invocation of
      MethodSecurityExpressionHandler.getExpressionParser()
    - MethodSecurityExpressionHandler is automatically wrapped in a
      LazyInitTargetSource and marked as lazyInit=true
2013-02-28 10:26:12 -06:00
@fbiville 83f1d76c16 SEC-2138: Fix code snippet in Hierarchical Roles section
The bean definition of RoleHierarchyVoter was syntactically incorrect.
2013-02-26 09:48:59 -06:00
Rob Winch 0722c8c4eb Update CONTRIBUTING.md
Updated link to github pull requests
2013-02-25 18:15:23 -06:00
Rob Winch 960564ef50 Add CONTRIBUTING.md 2013-02-25 17:13:12 -06:00
Rob Winch 3656dff720 SEC-2118: Include missing Bundlor packages 2013-02-25 17:07:09 -06:00
Jean-Pierre Bergamin c02a1486c0 SEC-2118: Fixing spring and aspectj OSGi version ranges [3.2, 3.2) -> [3.2, 3.3) 2013-02-25 17:05:29 -06:00
Rob Winch 5f9dfb73be SEC-2111: Disable auto save of SecurityContext when response committed after startAsync invoked
Previously Spring Security would disable automatically saving the
SecurityContext when the Thread was different than the Thread that
created the SaveContextOnUpdateOrErrorResponseWrapper. This worked for
many cases, but could cause issues when a timeout occurred. The problem
is that a Thread can be reused to process the timeout since the Threads
are pooled. This means that a timeout of a request trigger an apparent
logout as described in the following workflow:

  - The SecurityContext was established on the SecurityContextHolder
  - An Async request was made
  - The SecurityContextHolder would be cleared out
  - The Async request times out
  - The Async request would be dispatched back to the container upon
    timing out. If the container reused the same Thread to process the
    timeout as the original request, Spring Security would attempt to
    save the SecurityContext when the response was committed. Since the
    SecurityContextHolder was still cleared out it removes the
    SecurityContext from the HttpSession

Spring Security will now prevent the SecurityContext from automatically
being saved when the response is committed as soon as
HttpServletRequest#startAsync() or
ServletRequest#startAsync(ServletRequest,ServletResponse) is called.
2013-01-10 13:26:43 -06:00
Georges-Etienne Legendre 66d13642b7 SEC-2115: Improve French translation for "credentials"
"Créances" is not the right translation. "Identifications" is a lot better in this case.
2013-01-04 14:31:57 -06:00
Rob Winch 6b81f97081 SEC-2114: Polishing Spring Based Cache 2013-01-04 11:33:46 -06:00
Marten Deinum 01ea39ce35 SEC-2114: Provide Spring Cache Abstraction based cache implementations
As of Spring 3.1 spring has its own cache abstraction. This commit adds cache
imlpementations based on that abstraction.
2013-01-04 11:33:27 -06:00
Rob Winch 89c63fd752 Add spring-security-3.2.rnc 2013-01-03 18:32:33 -06:00
Rob Winch 036e0505b3 Make rnc transform part of Gradle build 2013-01-03 18:32:32 -06:00
Rob Winch d06eae9967 Merge pull request #22 from zagyi/SEC-2107
SEC-2107: Fix Javadoc on methods of AbstractAuthenticationProcessingFilter
2012-12-28 14:50:36 -08:00
Balazs Zagyvai 73ea8b5c05 SEC-2107: Fix Javadoc on methods of AbstractAuthenticationProcessingFilter
Both overloads of
AbstractAuthenticationProcessingFilter.successfulAuthentication()
claimed to invoke SessionAuthenticationStrategy, which is not true, as
the invokation happens earlier in doFilter(). The Javadoc on these
methods are updated to reflect the actual code.
2012-12-28 22:59:38 +01:00
Rob Winch 7edb1089a8 SEC-2096: Added release-checks 2012-12-18 15:15:46 -06:00
Rob Winch ebb82e1aa9 SEC-2096: Update to Spring 3.2.0.RELEASE 2012-12-18 15:15:46 -06:00
Rob Winch 2e8a61660d Disable artifactoryPublish for projects without artifacts 2012-12-12 18:04:24 -06:00
Rob Winch 22e333b9c6 SEC-2092: Add servlet api example 2012-12-11 17:44:57 -06:00
Rob Winch 9c4563285e SEC-1998: Async tests with SecurityContextHolderAwareReqeustFilter 2012-12-11 17:26:31 -06:00
Rob Winch c8d45397fe SEC-2079: Add Servlet 3 Authentication methods
Add support for HttpServletRequest's login(String,String), logout(),
and authenticate(HttpServletResponse).
2012-12-11 17:26:31 -06:00
Rob Winch d04cf5ea68 Remove unused FilterInvocation.DummyResponse 2012-12-11 14:21:03 -06:00
Rob Winch 1a650acbcc SEC-1998: DummyRequest extend HttpServletRequestWrapper
Previously DummyRequest implemented HttpServletRequest which caused complications
since Servlet 2.5 and Servlet 3 had non passive changes. While we were "safe" if the
Servlet 3 methods were never invoked reflective access of the methods would also
problems. We could prevent users from accessing the methods of DummyRequest by
returning new HttpServletRequestWrapper(DummyRequest), but a debugger could
potentially try to iterate over the methods triggering a NoClassDefFoundError.

DummyRequest now extends HttpServletRequestWrapper which will be dynamically
linked to the proper version of HttpServletRequest. We use a Dynamic Proxy that
throws UnsupportedOperationException to implement any methods we are not
interested in.
2012-12-11 14:21:03 -06:00
Rob Winch 3437ef714a SEC-1998: SecurityContextCallableProcessingInterceptor uses postProcess
Previously SecurityContextCallableProcessingInterceptor used afterCompletion
to clear the SecurityContextHolder. This does not work since afterCompletion
is invoked on the Servlet Container thread.

Now SecurityContextCallableProcessingInterceptor clears the
SecurityContextHolder on postProcess which is invoked on the same thread
that the Callable is processed on.
2012-12-11 14:21:03 -06:00
Rob Winch 796de42105 Revert "SEC-2078: AbstractPreAuthenticatedProcessingFilter requriesAuthentication support for non-String Principals"
This reverts commit 3fe7791266.
2012-12-11 14:21:02 -06:00
Rob Winch 70849aa8d2 Revert "SEC-2078: Updated Javadoc to reflect that updates to Principal will also trigger reauthentication"
This reverts commit ece4a0f067.
2012-12-11 14:21:02 -06:00
Rob Winch ece4a0f067 SEC-2078: Updated Javadoc to reflect that updates to Principal will also trigger reauthentication 2012-12-06 09:04:14 -06:00
Rob Winch 3fe7791266 SEC-2078: AbstractPreAuthenticatedProcessingFilter requriesAuthentication support for non-String Principals
Previously, if the Principal returned by getPreAuthenticatedPrincipal was not a String,
it prevented requiresAuthentication from detecting when the Principal was the same.
This caused the need to authenticate the user for every request even when the Principal
did not change.

Now requiresAuthentication will check to see if the result of
getPreAuthenticatedPrincipal is equal to the current Authentication.getPrincipal().
2012-12-04 10:54:29 -06:00
Rob Winch 5ba31dfd56 Use AspectJMethodSecurityInterceptor in reference
Change reference to use AspectJMethodSecurityInterceptor instead of
undefined AspectJSecurityInterceptor.
2012-12-04 10:06:27 -06:00
Rob Winch 373fe3a9f1 SEC-2074: Update reference to use <method-security-metadata-source> 2012-12-04 10:05:22 -06:00
Rob Winch 6e47834d77 SEC-2084: AntPathRequestMatcher and RegexpRequestMatcher support request.getMethod()
Previously a NullPointerException would occur if an HttpServletRequest.getMethod()
returned null.

Now AntPathRequestMatcher and RegexpRequestMatcher will handle if the
HttpServletRequest.getMethod() returns null. While under normal circumstances,
it is unlikely for the method to be null this can occur when using
DefaultWebInvocationPrivilegeEvaluator.isAllowed(String, Authentication).
2012-12-03 15:07:18 -06:00
Rob Winch eaf4843474 Update to Gradle 1.3 2012-11-30 16:33:44 -06:00
Rob Winch d40ecba9e0 SEC-1998: SaveContextOnUpdateOrErrorResponseWrapper only saves SecurityContext on original Thread
Previously SaveContextOnUpdateOrErrorResponseWrapper would save the SecurityContext on a different
Threads than the one it was created on. This causes issues with Async Web requests which may write
to the response on a new Thread.

Now SaveContextOnUpdateOrErrorResponseWrapper will not save the SecurityContext when a different
Thread invokes any of the methods that commit the response. This prevents issues with Async
processing. However, explicit calls to SecurityContextRepository.save will still save the
SecurityContext since it invokes the saveRequest method rather than private doSave method within
the SaveContextOnUpdateOrErrorResponseWrapper which contains the logic to prevent saving from
another Thread.
2012-11-30 14:27:02 -06:00
Rob Winch 593e512558 SEC-1998: SecurityContext integration with AsyncContext.start 2012-11-28 17:57:41 -06:00
Rob Winch 1ed643ca1f SEC-1998: Provide integration with WebAsyncManager#startCallableProcessing
Support integration of the Spring SecurityContext on Callable's used with
WebAsyncManager by registering SecurityContextCallableProcessingInterceptor.
2012-11-28 17:56:03 -06:00
Rob Winch 1a7aaa85c4 SEC-2066: ProtectPointcutPostProcessor is now ThreadSafe
Previously a ConcurrentModificationException could occur when
PointcutExpression.matchesMethodExecution was performed in multiple threads. Another
issue was that beans may get processed multiple times.

Now a lock is performed to ensure that only a single thread has access to
PointcutExpression.matchesMethodExecution and that each bean only gets processed once.
2012-11-09 14:34:00 -06:00
Rob Winch 51fd83060e SEC-2077: Concurrency support
Provide abstractions for transferring a SecurityContext across threads.

The main concepts are the DelegatingSecurityContextCallable and the
DelegatingSecurityContextRunnable which contain a SecurityContext to establish before
delegating to a Callable or Runnable.

There are also wrapper implementations for each of the key java.util.concurrent and
spring task interfaces to make using the DelegatingSecurityContextCallable and
DelegatingSecurityContextRunnable transparent to users. For example a
DelegatingSecurityContextTaskExecutor which can be injected with a specific
SecurityContext or use the SecurityContext from the SecurityContextHolder at the time the
task is submitted. There are similar  implementations for each of the key
java.util.concurrent and spring task interfaces.

Note that in order to get DelegatingSecurityContextExecutorService to compile with
JDK 5 or JDK 6 we could not use type safe methods. See
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6267833 for details.
2012-11-08 22:49:21 -06:00
Rob Winch 30780baf24 Externalize powermock dependencies for reuse 2012-11-08 22:49:20 -06:00
Rob Winch f61fab3509 Add fest-assert as default test dependency 2012-11-08 22:49:20 -06:00
Rob Winch ea6b444770 update to spring snapshot dependencies 2012-11-08 22:49:20 -06:00
Rob Winch 4c50d1f5de SEC-2072: <security:anonymous> granted-authority supports multiple authorities again 2012-11-02 16:24:14 -05:00
Rob Winch 091549779c Update SpringSecurityCoreVersion 2012-11-02 11:03:36 -05:00
Rob Winch ace6b804f7 Update to 3.2.x and added eclipse suffix to eclipse projects 2012-11-02 10:48:50 -05:00
872 changed files with 148639 additions and 3490 deletions
+186
View File
@@ -0,0 +1,186 @@
_Have something you'd like to contribute to the framework? We welcome pull requests, but ask that you carefully read this document first to understand how best to submit them; what kind of changes are likely to be accepted; and what to expect from the Spring Security team when evaluating your submission._
_Please refer back to this document as a checklist before issuing any pull request; this will save time for everyone!_
# 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.
# Understand the basics
Not sure what a pull request is, or how to submit one? Take a look at GitHub's excellent [help documentation first](https://help.github.com/articles/using-pull-requests).
# Search JIRA first; create an issue if necessary
Is there already an issue that addresses your concern? Do a bit of searching in our [JIRA issue tracker](https://jira.springsource.org/browse/SEC) to see if you can find something similar. If not, please create a new issue before submitting a pull request unless the change is not a user facing issue.
# Discuss non-trivial contribution ideas with committers
If you're considering anything more than correcting a typo or fixing a minor bug , please discuss it on the [Spring Security forums](http://forum.springsource.org/forumdisplay.php?33-Security) before submitting a pull request. We're happy to provide guidance but please spend an hour or two researching the subject on your own including searching the forums for prior discussions.
# Sign the Contributor License Agreement
If you have not previously done so, please fill out and submit the SpringSource CLA form. You'll receive a token when this process is complete. Keep track of this, you may be asked for it later!
* For **Project** select _Spring Security_
* For **Project Lead** enter _Rob Winch_
* Note that emailing/postal mailing a signed copy is not necessary. Submission of the web form is all that is required.
When you've completed the web form, simply add the following in a comment on your pull request:
> I have signed and agree to the terms of the SpringSource Individual Contributor License Agreement.
You do not need to include your token/id. Please add the statement above to all future pull requests as well, simply so the Spring Security team knows immediately that this process is complete.
# Create your branch from master
Create your topic branch to be submitted as a pull request from master. The Spring team will consider your pull request for backporting on a case-by-case basis; you don't need to worry about submitting anything for backporting.
# Use short branch names
Branches used when submitting pull requests should preferably be named according to JIRA issues, e.g. 'SEC-1234'. Otherwise, use succinct, lower-case, dash (-) delimited names, such as 'fix-warnings', 'fix-typo', etc. This is important, because branch names show up in the merge commits that result from accepting pull requests, and should be as expressive and concise as possible.
#Keep commits focused
Remember each JIRA should be focused on a single item of interest since the JIRA tickets are used to produce the changelog. Since each commit should be tied to a JIRA, ensure that your commits are focused. For example, do not include an update to a transitive library in your commit unless the JIRA is to update the library. Reviewing your commits is essential before sending a pull request.
# Mind the whitespace
Please carefully follow the whitespace and formatting conventions already present in the framework.
1. Spaces, not tabs
1. Unix (LF), not dos (CRLF) line endings
1. Eliminate all trailing whitespace
1. Aim to wrap code at 120 characters, but favor readability over wrapping
1. Preserve existing formatting; i.e. do not reformat code for its own sake
1. Search the codebase using git grep and other tools to discover common naming conventions, etc.
1. Latin-1 (ISO-8859-1) encoding for Java sources; use native2ascii to convert if necessary
Whitespace management tips
1. You can use the [AnyEdit Eclipse plugin](http://marketplace.eclipse.org/content/anyedit-tools) to ensure spaces are used and to clean up trailing whitespaces.
1. Use git's pre-commit.sample hook to prevent invalid whitespace from being pushed out. You can enable it by moving ~/spring-security/.git/hooks/pre-commit.sample to ~/spring-security/.git/hooks/pre-commit and ensuring it is executable. For more information on hooks refer to [Pro Git's Pre-Commit Hook's section](http://git-scm.com/book/cs/ch7-3.html)
# Add Apache license header to all new classes
<pre>
/*
* 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 ...;
</pre>
# Update Apache license header to modified files as necessary
Always check the date range in the license header. For example, if you've modified a file in 2012 whose header still reads
<pre>
* Copyright 2002-2011 the original author or authors.
</pre>
then be sure to update it to 2012 appropriately
<pre>
* Copyright 2002-2012 the original author or authors.
</pre>
# Use @since tags for newly-added public API types and methods
e.g.
<pre>
/**
* ...
*
* @author First Last
* @since 3.2
* @see ...
*/
</pre>
#Submit JUnit test cases for all behavior changes
Search the codebase to find related unit tests and add additional `@Test` methods within.
1. Any new tests should end in the name Tests (note this is plural). For example, a valid name would be `FilterChainProxyTests`. An invalid name would be `FilterChainProxyTest`.
2. New test methods should not start with test. This is an old JUnit3 convention and is not necessary since the method is annotated with @Test.
# Squash commits
Use git rebase --interactive, git add --patch and other tools to "squash" multiple commits into atomic changes. In addition to the man pages for git, there are many resources online to help you understand how these tools work. Here is one: http://book.git-scm.com/4_interactive_rebasing.html.
# Use real name in git commits
Please configure git to use your real first and last name for any commits you intend to submit as pull requests. For example, this is not acceptable:
<pre>
Author: Nickname &lt;user@mail.com&gt;
</pre>
Rather, please include your first and last name, properly capitalized, as submitted against the SpringSource contributor license agreement:
<pre>
Author: First Last &lt;user@mail.com&gt;
</pre>
This helps ensure traceability against the CLA, and also goes a long way to ensuring useful output from tools like git shortlog and others.
You can configure this globally via the account admin area GitHub (useful for fork-and-edit cases); globally with
<pre>
git config --global user.name "First Last"
git config --global user.email user@mail.com
</pre>
or locally for the spring-security repository only by omitting the '--global' flag:
<pre>
cd spring-security
git config user.name "First Last"
git config user.email user@mail.com
</pre>
# Format commit messages
<pre>
SEC-1234: Short (50 chars or less) summary of changes
More detailed explanatory text, if necessary. Wrap it to about 72
characters or so. In some contexts, the first line is treated as the
subject of an email and the rest of the text as the body. The blank
line separating the summary from the body is critical (unless you omit
the body entirely); tools like rebase can get confused if you run the
two together.
Further paragraphs come after blank lines.
- Bullet points are okay, too
- Typically a hyphen or asterisk is used for the bullet, preceded by a
single space, with blank lines in between, but conventions vary here
</pre>
1. The commit subject should start with the associated jira issue followed by a : as shown in the example above
2. Keep the subject line to 50 characters or less if possible
3. Do not end the subject line with a period
4. In the body of the commit message, explain how things worked before this commit, what has changed, and how things work now
# Run all tests prior to submission
<pre>
cd spring-security
./gradlew clean build integrationTest
</pre>
# Submit your pull request
Subject line:
Follow the same conventions for pull request subject lines as mentioned above for commit message subject lines.
In the body:
1. Explain your use case. What led you to submit this change? Why were existing mechanisms in the framework insufficient? Make a case that this is a general-purpose problem and that yours is a general-purpose solution, etc
2. Add any additional information and ask questions; start a conversation, or continue one from JIRA
3. Mention the JIRA issue ID
4. Also mention that you have submitted the CLA as described above
Note that for pull requests containing a single commit, GitHub will default the subject line and body of the pull request to match the subject line and body of the commit message. This is fine, but please also include the items above in the body of the request.
# Mention your pull request on the associated JIRA issue
Add a comment to the associated JIRA issue(s) linking to your new pull request.
# Expect discussion and rework
The Spring team takes a very conservative approach to accepting contributions to the framework. This is to keep code quality and stability as high as possible, and to keep complexity at a minimum. Your changes, if accepted, may be heavily modified prior to merging. You will retain "Author:" attribution for your Git commits granted that the bulk of your changes remain intact. You may be asked to rework the submission for style (as explained above) and/or substance. Again, we strongly recommend discussing any serious submissions with the Spring Framework team prior to engaging in serious development work.
Note that you can always force push (git push -f) reworked / rebased commits against the branch used to submit your pull request. i.e. you do not need to issue a new pull request when asked to make changes.
+2 -1
View File
@@ -3,12 +3,13 @@
dependencies {
compile project(':spring-security-core'),
'aopalliance:aopalliance:1.0',
"net.sf.ehcache:ehcache:$ehcacheVersion",
"org.springframework:spring-aop:$springVersion",
"org.springframework:spring-context:$springVersion",
"org.springframework:spring-tx:$springVersion",
"org.springframework:spring-jdbc:$springVersion"
optional "net.sf.ehcache:ehcache:$ehcacheVersion"
testCompile "org.springframework:spring-beans:$springVersion",
"org.springframework:spring-context-support:$springVersion",
"org.springframework:spring-test:$springVersion"
+169
View File
@@ -0,0 +1,169 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-acl</artifactId>
<version>3.2.0.RC1</version>
<name>spring-security-acl</name>
<description>spring-security-acl</description>
<url>http://springsource.org/spring-security</url>
<organization>
<name>SpringSource</name>
<url>http://springsource.org/</url>
</organization>
<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<developers>
<developer>
<id>rwinch</id>
<name>Rob Winch</name>
<email>rwinch@vmware.com</email>
</developer>
</developers>
<scm>
<connection>scm:git:git://github.com/SpringSource/spring-security</connection>
<developerConnection>scm:git:git://github.com/SpringSource/spring-security</developerConnection>
<url>https://github.com/SpringSource/spring-security</url>
</scm>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-snasphot</id>
<url>http://repo.springsource.org/libs-snapshot</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>3.2.0.RC1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>3.2.3.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.2.3.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>3.2.3.RELEASE</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>3.2.3.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>3.2.3.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>1.6.2</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>0.9.29</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>1.8.0.10</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.7</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.easytesting</groupId>
<artifactId>fest-assert</artifactId>
<version>1.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.8.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>3.2.3.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>3.2.3.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>3.2.3.RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -31,10 +31,14 @@ import org.springframework.util.Assert;
/**
* Default implementation of {@link AclAuthorizationStrategy}.
* <p>
* Permission will be granted provided the current principal is either the owner (as defined by the ACL), has
* {@link BasePermission#ADMINISTRATION} (as defined by the ACL and via a {@link Sid} retrieved for the current
* principal via {@link #sidRetrievalStrategy}), or if the current principal holds the relevant system-wide
* {@link GrantedAuthority} and injected into the constructor.
* Permission will be granted if at least one of the following conditions is true for the current
* principal.
* <ul>
* <li> is the owner (as defined by the ACL). </li>
* <li> holds the relevant system-wide {@link GrantedAuthority} injected into the
* constructor. </li>
* <li> has {@link BasePermission#ADMINISTRATION} permission (as defined by the ACL). </li>
* </ul>
*
* @author Ben Alex
*/
@@ -0,0 +1,130 @@
/*
* 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.acls.domain;
import org.springframework.cache.Cache;
import org.springframework.security.acls.model.AclCache;
import org.springframework.security.acls.model.MutableAcl;
import org.springframework.security.acls.model.ObjectIdentity;
import org.springframework.security.acls.model.PermissionGrantingStrategy;
import org.springframework.security.util.FieldUtils;
import org.springframework.util.Assert;
import java.io.Serializable;
/**
* Simple implementation of {@link org.springframework.security.acls.model.AclCache} that delegates to {@link Cache} implementation.
* <p>
* Designed to handle the transient fields in {@link org.springframework.security.acls.domain.AclImpl}. Note that this implementation assumes all
* {@link org.springframework.security.acls.domain.AclImpl} instances share the same {@link org.springframework.security.acls.model.PermissionGrantingStrategy} and {@link org.springframework.security.acls.domain.AclAuthorizationStrategy}
* instances.
*
* @author Marten Deinum
* @since 3.2
*/
public class SpringCacheBasedAclCache implements AclCache {
//~ Instance fields ================================================================================================
private final Cache cache;
private PermissionGrantingStrategy permissionGrantingStrategy;
private AclAuthorizationStrategy aclAuthorizationStrategy;
//~ Constructors ===================================================================================================
public SpringCacheBasedAclCache(Cache cache, PermissionGrantingStrategy permissionGrantingStrategy,
AclAuthorizationStrategy aclAuthorizationStrategy) {
Assert.notNull(cache, "Cache required");
Assert.notNull(permissionGrantingStrategy, "PermissionGrantingStrategy required");
Assert.notNull(aclAuthorizationStrategy, "AclAuthorizationStrategy required");
this.cache = cache;
this.permissionGrantingStrategy = permissionGrantingStrategy;
this.aclAuthorizationStrategy = aclAuthorizationStrategy;
}
//~ Methods ========================================================================================================
public void evictFromCache(Serializable pk) {
Assert.notNull(pk, "Primary key (identifier) required");
MutableAcl acl = getFromCache(pk);
if (acl != null) {
cache.evict(acl.getId());
cache.evict(acl.getObjectIdentity());
}
}
public void evictFromCache(ObjectIdentity objectIdentity) {
Assert.notNull(objectIdentity, "ObjectIdentity required");
MutableAcl acl = getFromCache(objectIdentity);
if (acl != null) {
cache.evict(acl.getId());
cache.evict(acl.getObjectIdentity());
}
}
public MutableAcl getFromCache(ObjectIdentity objectIdentity) {
Assert.notNull(objectIdentity, "ObjectIdentity required");
return getFromCache((Object)objectIdentity);
}
public MutableAcl getFromCache(Serializable pk) {
Assert.notNull(pk, "Primary key (identifier) required");
return getFromCache((Object)pk);
}
public void putInCache(MutableAcl acl) {
Assert.notNull(acl, "Acl required");
Assert.notNull(acl.getObjectIdentity(), "ObjectIdentity required");
Assert.notNull(acl.getId(), "ID required");
if ((acl.getParentAcl() != null) && (acl.getParentAcl() instanceof MutableAcl)) {
putInCache((MutableAcl) acl.getParentAcl());
}
cache.put(acl.getObjectIdentity(), acl);
cache.put(acl.getId(), acl);
}
private MutableAcl getFromCache(Object key) {
Cache.ValueWrapper element = cache.get(key);
if (element == null) {
return null;
}
return initializeTransientFields((MutableAcl) element.get());
}
private MutableAcl initializeTransientFields(MutableAcl value) {
if (value instanceof AclImpl) {
FieldUtils.setProtectedFieldValue("aclAuthorizationStrategy", value, this.aclAuthorizationStrategy);
FieldUtils.setProtectedFieldValue("permissionGrantingStrategy", value, this.permissionGrantingStrategy);
}
if (value.getParentAcl() != null) {
initializeTransientFields((MutableAcl) value.getParentAcl());
}
return value;
}
public void clearCache() {
cache.clear();
}
}
@@ -0,0 +1,142 @@
package org.springframework.security.acls.jdbc;
import org.junit.After;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.security.acls.domain.*;
import org.springframework.security.acls.model.MutableAcl;
import org.springframework.security.acls.model.ObjectIdentity;
import org.springframework.security.acls.model.PermissionGrantingStrategy;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.util.FieldUtils;
import java.util.Map;
import static org.junit.Assert.*;
/**
* Tests {@link org.springframework.security.acls.domain.SpringCacheBasedAclCache}
*
* @author Marten Deinum
*/
public class SpringCacheBasedAclCacheTests {
private static final String TARGET_CLASS = "org.springframework.security.acls.TargetObject";
private static CacheManager cacheManager;
@BeforeClass
public static void initCacheManaer() {
cacheManager = new ConcurrentMapCacheManager();
// Use disk caching immediately (to test for serialization issue reported in SEC-527)
cacheManager.getCache("springcasebasedacltests");
}
@After
public void clearContext() {
SecurityContextHolder.clearContext();
}
private Cache getCache() {
Cache cache = cacheManager.getCache("springcasebasedacltests");
cache.clear();
return cache;
}
@Test(expected=IllegalArgumentException.class)
public void constructorRejectsNullParameters() throws Exception {
new SpringCacheBasedAclCache(null, null, null);
}
@SuppressWarnings("rawtypes")
@Test
public void cacheOperationsAclWithoutParent() throws Exception {
Cache cache = getCache();
Map realCache = (Map) cache.getNativeCache();
ObjectIdentity identity = new ObjectIdentityImpl(TARGET_CLASS, Long.valueOf(100));
AclAuthorizationStrategy aclAuthorizationStrategy = new AclAuthorizationStrategyImpl(
new SimpleGrantedAuthority("ROLE_OWNERSHIP"), new SimpleGrantedAuthority("ROLE_AUDITING"),
new SimpleGrantedAuthority("ROLE_GENERAL"));
AuditLogger auditLogger = new ConsoleAuditLogger();
PermissionGrantingStrategy permissionGrantingStrategy = new DefaultPermissionGrantingStrategy(auditLogger);
SpringCacheBasedAclCache myCache = new SpringCacheBasedAclCache(cache, permissionGrantingStrategy, aclAuthorizationStrategy);
MutableAcl acl = new AclImpl(identity, Long.valueOf(1), aclAuthorizationStrategy, auditLogger);
assertEquals(0, realCache.size());
myCache.putInCache(acl);
// Check we can get from cache the same objects we put in
assertEquals(myCache.getFromCache(Long.valueOf(1)), acl);
assertEquals(myCache.getFromCache(identity), acl);
// Put another object in cache
ObjectIdentity identity2 = new ObjectIdentityImpl(TARGET_CLASS, Long.valueOf(101));
MutableAcl acl2 = new AclImpl(identity2, Long.valueOf(2), aclAuthorizationStrategy, new ConsoleAuditLogger());
myCache.putInCache(acl2);
// Try to evict an entry that doesn't exist
myCache.evictFromCache(Long.valueOf(3));
myCache.evictFromCache(new ObjectIdentityImpl(TARGET_CLASS, Long.valueOf(102)));
assertEquals(realCache.size(), 4);
myCache.evictFromCache(Long.valueOf(1));
assertEquals(realCache.size(), 2);
// Check the second object inserted
assertEquals(myCache.getFromCache(Long.valueOf(2)), acl2);
assertEquals(myCache.getFromCache(identity2), acl2);
myCache.evictFromCache(identity2);
assertEquals(realCache.size(), 0);
}
@SuppressWarnings("rawtypes")
@Test
public void cacheOperationsAclWithParent() throws Exception {
Cache cache = getCache();
Map realCache = (Map) cache.getNativeCache();
Authentication auth = new TestingAuthenticationToken("user", "password", "ROLE_GENERAL");
auth.setAuthenticated(true);
SecurityContextHolder.getContext().setAuthentication(auth);
ObjectIdentity identity = new ObjectIdentityImpl(TARGET_CLASS, Long.valueOf(1));
ObjectIdentity identityParent = new ObjectIdentityImpl(TARGET_CLASS, Long.valueOf(2));
AclAuthorizationStrategy aclAuthorizationStrategy = new AclAuthorizationStrategyImpl(
new SimpleGrantedAuthority("ROLE_OWNERSHIP"), new SimpleGrantedAuthority("ROLE_AUDITING"),
new SimpleGrantedAuthority("ROLE_GENERAL"));
AuditLogger auditLogger = new ConsoleAuditLogger();
PermissionGrantingStrategy permissionGrantingStrategy = new DefaultPermissionGrantingStrategy(auditLogger);
SpringCacheBasedAclCache myCache = new SpringCacheBasedAclCache(cache, permissionGrantingStrategy, aclAuthorizationStrategy);
MutableAcl acl = new AclImpl(identity, Long.valueOf(1), aclAuthorizationStrategy, auditLogger);
MutableAcl parentAcl = new AclImpl(identityParent, Long.valueOf(2), aclAuthorizationStrategy, auditLogger);
acl.setParent(parentAcl);
assertEquals(0, realCache.size());
myCache.putInCache(acl);
assertEquals(realCache.size(), 4);
// Check we can get from cache the same objects we put in
AclImpl aclFromCache = (AclImpl) myCache.getFromCache(Long.valueOf(1));
assertEquals(acl, aclFromCache);
// SEC-951 check transient fields are set on parent
assertNotNull(FieldUtils.getFieldValue(aclFromCache.getParentAcl(), "aclAuthorizationStrategy"));
assertNotNull(FieldUtils.getFieldValue(aclFromCache.getParentAcl(), "permissionGrantingStrategy"));
assertEquals(acl, myCache.getFromCache(identity));
assertNotNull(FieldUtils.getFieldValue(aclFromCache, "aclAuthorizationStrategy"));
AclImpl parentAclFromCache = (AclImpl) myCache.getFromCache(Long.valueOf(2));
assertEquals(parentAcl, parentAclFromCache);
assertNotNull(FieldUtils.getFieldValue(parentAclFromCache, "aclAuthorizationStrategy"));
assertEquals(parentAcl, myCache.getFromCache(identityParent));
}
}
+1
View File
@@ -11,6 +11,7 @@ Ignored-Existing-Headers:
Import-Template:
org.aopalliance.*;version="${aopAllianceRange}",
org.apache.commons.logging.*;version="${cloggingRange}",
org.springframework.cache.*;version="${springRange}";resolution:=optional,
org.springframework.security.core.*;version="${secRange}",
org.springframework.security.access.*;version="${secRange}",
org.springframework.security.util.*;version="${secRange}",
+2 -1
View File
@@ -4,5 +4,6 @@ dependencies {
"org.springframework:spring-beans:$springVersion",
"org.springframework:spring-context:$springVersion"
testCompile 'aopalliance:aopalliance:1.0'
testCompile 'aopalliance:aopalliance:1.0',
"org.springframework:spring-aop:$springVersion"
}
+145
View File
@@ -0,0 +1,145 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-aspects</artifactId>
<version>3.2.0.RC1</version>
<name>spring-security-aspects</name>
<description>spring-security-aspects</description>
<url>http://springsource.org/spring-security</url>
<organization>
<name>SpringSource</name>
<url>http://springsource.org/</url>
</organization>
<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<developers>
<developer>
<id>rwinch</id>
<name>Rob Winch</name>
<email>rwinch@vmware.com</email>
</developer>
</developers>
<scm>
<connection>scm:git:git://github.com/SpringSource/spring-security</connection>
<developerConnection>scm:git:git://github.com/SpringSource/spring-security</developerConnection>
<url>https://github.com/SpringSource/spring-security</url>
</scm>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-snasphot</id>
<url>http://repo.springsource.org/libs-snapshot</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>3.2.0.RC1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>3.2.3.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.2.3.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>3.2.3.RELEASE</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.6.10</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>0.9.29</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.7</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.easytesting</groupId>
<artifactId>fest-assert</artifactId>
<version>1.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.8.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>3.2.3.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>3.2.3.RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
+105 -17
View File
@@ -1,53 +1,136 @@
import groovy.text.SimpleTemplateEngine
buildscript {
repositories {
maven { url "http://repo.springsource.org/plugins-release" }
maven { url "http://dl.bintray.com/content/aalmiray/asciidoctor" }
maven { url "http://jcenter.bintray.com"}
}
dependencies {
classpath("org.springframework.build.gradle:propdeps-plugin:0.0.3")
classpath("org.springframework.build.gradle:docbook-reference-plugin:0.2.7")
classpath("org.springframework.build.gradle:bundlor-plugin:0.1.2")
classpath("org.gradle.api.plugins:gradle-tomcat-plugin:0.9.8")
classpath("org.asciidoctor:asciidoctor-gradle-plugin:0.4.1")
}
}
apply plugin: 'sonar-runner'
apply plugin: 'base'
description = 'Spring Security'
allprojects {
apply plugin: 'idea'
apply plugin: 'eclipse'
ext.releaseBuild = version.endsWith('RELEASE')
ext.snapshotBuild = version.endsWith('SNAPSHOT')
group = 'org.springframework.security'
repositories {
mavenCentral()
maven { url "http://repo.springsource.org/libs-release" }
maven { url "http://repo.springsource.org/plugins-release" }
}
eclipse.project.name = "${project.name}-3.2.x"
}
sonarRunner {
sonarProperties {
property "sonar.java.coveragePlugin", "jacoco"
property "sonar.projectName", "Spring Security"
property "sonar.jacoco.reportPath", "${buildDir.name}/jacoco.exec"
property "sonar.links.homepage", 'https://www.springsource.org/spring-security'
property "sonar.links.ci", 'https://build.springsource.org/browse/SEC-B32X'
property "sonar.links.issue", 'https://jira.springsource.org/browse/SEC'
property "sonar.links.scm", 'https://github.com/SpringSource/spring-security'
property "sonar.links.scm_dev", 'https://github.com/SpringSource/spring-security.git'
property "sonar.java.coveragePlugin", "jacoco"
}
}
// Set up different subproject lists for individual configuration
ext.javaProjects = subprojects.findAll { project -> project.name != 'docs' && project.name != 'faq' && project.name != 'manual' }
ext.javaProjects = subprojects.findAll { project -> project.name != 'docs' && project.name != 'faq' && project.name != 'manual' && project.name != 'guides' }
ext.sampleProjects = subprojects.findAll { project -> project.name.startsWith('spring-security-samples') }
ext.itestProjects = subprojects.findAll { project -> project.name.startsWith('itest') }
ext.coreModuleProjects = javaProjects - sampleProjects - itestProjects
ext.aspectjProjects = [project(':spring-security-aspects'), project(':spring-security-samples-aspectj')]
ext.aspectjProjects = [project(':spring-security-aspects'), project(':spring-security-samples-aspectj-xml')]
configure(allprojects - javaProjects) {
task afterEclipseImport {
ext.srcFile = file('.classpath')
inputs.file srcFile
outputs.dir srcFile
onlyIf { !srcFile.exists() }
doLast {
srcFile << """<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="output" path="bin"/>
</classpath>
"""
}
}
}
configure(subprojects - coreModuleProjects) {
tasks.findByPath("artifactoryPublish")?.enabled = false
sonarRunner {
skipProject = true
}
}
configure(javaProjects) {
ext.TOMCAT_GRADLE = "$rootDir/gradle/tomcat.gradle"
ext.WAR_SAMPLE_GRADLE = "$rootDir/gradle/war-sample.gradle"
apply from: "$rootDir/gradle/javaprojects.gradle"
apply from: "$rootDir/gradle/release-checks.gradle"
apply from: "$rootDir/gradle/maven-deployment.gradle"
}
configure(coreModuleProjects) {
// Gives better names in structure101 jar diagram
sourceSets.main.output.classesDir = new File(buildDir, "classes/" + project.name.substring("spring-security".length() + 1))
apply plugin: 'bundlor'
bundlor.expansions = bundlorProperties
apply from: "$rootDir/gradle/maven-deployment.gradle"
apply plugin: 'emma'
bundlor.doFirst {
def templateText = file("template.mf").text
bundlor.manifestTemplate = new SimpleTemplateEngine().createTemplate(templateText).make(bundlorProperties).toString()
}
configurations {
jacoco //Configuration Group used by Sonar to provide Code Coverage using JaCoCo
}
dependencies {
jacoco "org.jacoco:org.jacoco.agent:0.6.2.201302030002:runtime"
}
test {
jvmArgs "-javaagent:${configurations.jacoco.asPath}=destfile=${buildDir}/jacoco.exec,includes=${project.group}.*"
}
integrationTest {
jvmArgs "-javaagent:${configurations.jacoco.asPath}=destfile=${buildDir}/jacoco.exec,includes=${project.group}.*"
}
}
configure (aspectjProjects) {
apply plugin: 'java'
apply plugin: 'aspectj'
}
task coreBuild {
dependsOn coreModuleProjects*.tasks*.matching { task -> task.name == 'build' }
}
configure (aspectjProjects) {
apply plugin: 'aspectj'
}
// Task for creating the distro zip
task dist(type: Zip) {
dependsOn subprojects*.tasks*.matching { task -> task.name == 'assemble' || task.name.endsWith('Zip') }
dependsOn subprojects*.tasks*.matching { task -> task.name == 'assemble' || task.name.endsWith('Zip') || task.name.endsWith('generatePom') }
classifier = 'dist'
evaluationDependsOn(':docs')
evaluationDependsOn(':docs:manual')
def zipRootDir = "${project.name}-$version"
into(zipRootDir) {
@@ -57,11 +140,18 @@ task dist(type: Zip) {
into('docs') {
with(project(':docs').apiSpec)
with(project(':docs:manual').spec)
with(project(':docs:guides').spec)
}
into('dist') {
from coreModuleProjects.collect {project -> project.libsDir }
from project(':spring-security-samples-tutorial').libsDir
from project(':spring-security-samples-contacts').libsDir
}
sampleProjects.each { project->
into("$zipRootDir/samples/$project.name") {
from(project.projectDir) {
include "src/main/**"
include "pom.xml"
}
}
}
}
}
@@ -72,8 +162,6 @@ artifacts {
archives project(':docs').schemaZip
}
apply from: "$rootDir/gradle/ide-integration.gradle"
task wrapper(type: Wrapper) {
gradleVersion = '1.2'
gradleVersion = '1.7'
}
+3 -25
View File
@@ -12,28 +12,6 @@ repositories {
}
}
// Docbook Plugin
dependencies {
def fopDeps = [ 'org.apache.xmlgraphics:fop:0.95-1@jar',
'org.apache.xmlgraphics:xmlgraphics-commons:1.3',
'org.apache.xmlgraphics:batik-bridge:1.7@jar',
'org.apache.xmlgraphics:batik-util:1.7@jar',
'org.apache.xmlgraphics:batik-css:1.7@jar',
'org.apache.xmlgraphics:batik-dom:1.7',
'org.apache.xmlgraphics:batik-svg-dom:1.7@jar',
'org.apache.avalon.framework:avalon-framework-api:4.3.1']
groovy localGroovy()
compile gradleApi(),
'xml-resolver:xml-resolver:1.2',
'xerces:xercesImpl:2.9.1',
'saxon:saxon:6.5.3',
'net.java.dev.jets3t:jets3t:0.6.1',
fopDeps
runtime 'net.sf.xslthl:xslthl:2.0.1',
'net.sf.docbook:docbook-xsl:1.75.2:ns-resources@zip'
}
// GAE
dependencies {
compile 'com.google.appengine:appengine-tools-sdk:1.4.2'
@@ -43,10 +21,10 @@ dependencies{
compile "emma:emma:2.0.5312"
}
// Bundlor
// Trang
dependencies {
compile 'com.springsource.bundlor:com.springsource.bundlor:1.0.0.RELEASE',
'com.springsource.bundlor:com.springsource.bundlor.blint:1.0.0.RELEASE'
compile 'com.thaiopensource:trang:20091111',
'net.sourceforge.saxon:saxon:9.1.0.8'
}
task ide(type: Copy) {
@@ -9,8 +9,10 @@ import org.gradle.api.tasks.SourceSet
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.plugins.JavaPlugin
import org.gradle.plugins.ide.eclipse.GenerateEclipseProject
import org.gradle.plugins.ide.eclipse.GenerateEclipseClasspath
import org.gradle.plugins.ide.eclipse.EclipsePlugin
import org.gradle.plugins.ide.eclipse.model.BuildCommand
import org.gradle.plugins.ide.eclipse.model.ProjectDependency
@@ -21,6 +23,8 @@ import org.gradle.plugins.ide.eclipse.model.ProjectDependency
class AspectJPlugin implements Plugin<Project> {
void apply(Project project) {
project.plugins.apply(JavaPlugin)
if (!project.hasProperty('aspectjVersion')) {
throw new GradleException("You must set the property 'aspectjVersion' before applying the aspectj plugin")
}
@@ -29,7 +33,7 @@ class AspectJPlugin implements Plugin<Project> {
project.configurations.add('ajtools')
project.dependencies {
ajtools "org.aspectj:aspectjtools:${project.aspectjVersion}"
compile "org.aspectj:aspectjrt:${project.aspectjVersion}"
optional "org.aspectj:aspectjrt:${project.aspectjVersion}"
}
}
@@ -37,21 +41,28 @@ class AspectJPlugin implements Plugin<Project> {
project.configurations.add('aspectpath')
}
project.tasks.add(name: 'compileJava', overwrite: true, description: 'Compiles AspectJ Source', type: Ajc) {
project.tasks.add(name: 'compileAspect', overwrite: true, description: 'Compiles AspectJ Source', type: Ajc) {
dependsOn project.configurations*.getTaskDependencyFromProjectDependency(true, "compileJava")
dependsOn project.processResources
sourceSet = project.sourceSets.main
inputs.files(sourceSet.java.srcDirs)
inputs.files(sourceSet.allSource)
outputs.dir(sourceSet.output.classesDir)
aspectPath = project.configurations.aspectpath
}
project.tasks.compileJava.deleteAllActions()
project.tasks.compileJava.dependsOn project.tasks.compileAspect
project.tasks.add(name: 'compileTestJava', overwrite: true, description: 'Compiles AspectJ Test Source', type: Ajc) {
project.tasks.add(name: 'compileTestAspect', overwrite: true, description: 'Compiles AspectJ Test Source', type: Ajc) {
dependsOn project.processTestResources, project.compileJava, project.jar
sourceSet = project.sourceSets.test
inputs.files(sourceSet.java.srcDirs)
inputs.files(sourceSet.allSource)
outputs.dir(sourceSet.output.classesDir)
aspectPath = project.files(project.configurations.aspectpath, project.jar.archivePath)
}
project.tasks.compileTestJava.deleteAllActions()
project.tasks.compileTestJava.dependsOn project.tasks.compileTestAspect
project.tasks.withType(GenerateEclipseProject) {
project.eclipse.project.file.whenMerged { p ->
@@ -63,8 +74,13 @@ class AspectJPlugin implements Plugin<Project> {
project.tasks.withType(GenerateEclipseClasspath) {
project.eclipse.classpath.file.whenMerged { classpath ->
def entries = classpath.entries.findAll { it instanceof ProjectDependency}.findAll { entry ->
def projectPath = entry.path.replaceAll('/',':')
project.rootProject.findProject(projectPath).plugins.findPlugin(AspectJPlugin)
def projectPath = entry.path.replaceAll('/','')
project.rootProject.allprojects.find{ p->
if(p.plugins.findPlugin(EclipsePlugin)) {
return p.eclipse.project.name == projectPath && p.plugins.findPlugin(AspectJPlugin)
}
false
}
}
entries.each { entry->
entry.entryAttributes.put('org.eclipse.ajdt.aspectpath','org.eclipse.ajdt.aspectpath')
@@ -84,7 +100,11 @@ class Ajc extends DefaultTask {
@TaskAction
def compile() {
logger.info("="*30)
logger.info("="*30)
logger.info("Running ajc ...")
logger.info("classpath: ${sourceSet.compileClasspath.asPath}")
logger.info("srcDirs $sourceSet.java.srcDirs")
ant.taskdef(resource: "org/aspectj/tools/ant/taskdefs/aspectjTaskdefs.properties", classpath: project.configurations.ajtools.asPath)
ant.iajc(classpath: sourceSet.compileClasspath.asPath, fork: 'true', destDir: sourceSet.output.classesDir.absolutePath,
source: project.convention.plugins.java.sourceCompatibility,
@@ -92,6 +112,7 @@ class Ajc extends DefaultTask {
aspectPath: aspectPath.asPath, sourceRootCopyFilter: '**/*.java', showWeaveInfo: 'true') {
sourceroots {
sourceSet.java.srcDirs.each {
logger.info(" sourceRoot $it")
pathelement(location: it.absolutePath)
}
}
@@ -1,150 +0,0 @@
package bundlor
import com.springsource.bundlor.ClassPath
import com.springsource.bundlor.ManifestGenerator
import com.springsource.bundlor.ManifestWriter
import com.springsource.bundlor.blint.ManifestValidator
import com.springsource.bundlor.blint.support.DefaultManifestValidatorContributorsFactory
import com.springsource.bundlor.blint.support.StandardManifestValidator
import com.springsource.bundlor.support.DefaultManifestGeneratorContributorsFactory
import com.springsource.bundlor.support.StandardManifestGenerator
import com.springsource.bundlor.support.classpath.FileSystemClassPath
import com.springsource.bundlor.support.manifestwriter.FileSystemManifestWriter
import com.springsource.bundlor.support.properties.EmptyPropertiesSource
import com.springsource.bundlor.support.properties.FileSystemPropertiesSource
import com.springsource.bundlor.support.properties.PropertiesPropertiesSource
import com.springsource.bundlor.support.properties.PropertiesSource
import com.springsource.bundlor.util.BundleManifestUtils
import com.springsource.util.parser.manifest.ManifestContents
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.file.FileCollection
import org.gradle.api.logging.LogLevel
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
/**
* @author Luke Taylor
*/
class BundlorPlugin implements Plugin<Project> {
void apply(Project project) {
Task bundlor = project.tasks.add('bundlor', Bundlor.class)
bundlor.setDescription('Generates OSGi manifest using bundlor tool')
bundlor.dependsOn(project.classes)
project.jar.dependsOn bundlor
}
}
public class Bundlor extends DefaultTask {
@InputFile
@Optional
File manifestTemplate
@OutputDirectory
File bundlorDir = new File("${project.buildDir}/bundlor")
@OutputFile
File manifest = project.file("${bundlorDir}/META-INF/MANIFEST.MF")
@Input
Map<String,String> expansions = [:]
@InputFile
@Optional
File osgiProfile
@InputFiles
@Optional
FileCollection inputPaths
@Input
boolean failOnWarnings = false
Bundlor() {
manifestTemplate = new File(project.projectDir, 'template.mf')
if (!manifestTemplate.exists()) {
logger.info("No bundlor template for project " + project.name)
manifestTemplate = null
}
inputPaths = project.files(project.sourceSets.main.output.classesDir)
if (manifestTemplate != null) {
project.jar.manifest.from manifest
project.jar.inputs.files manifest
}
}
@TaskAction
void createManifest() {
if (manifestTemplate == null) {
return;
}
logging.captureStandardOutput(LogLevel.INFO)
project.mkdir(bundlorDir)
//String inputPath = project.sourceSets.main.classesDir
List<ClassPath> inputClassPath = [] as List;
ManifestWriter manifestWriter = new FileSystemManifestWriter(project.file(bundlorDir.absolutePath));
ManifestContents mfTemplate = BundleManifestUtils.getManifest(manifestTemplate);
inputPaths.each {f ->
inputClassPath.add(new FileSystemClassPath(f))
}
// Must be a better way of doing this...
Properties p = new Properties()
expansions.each {entry ->
p.setProperty(entry.key, entry.value as String)
}
PropertiesSource expansionProps = new PropertiesPropertiesSource(p)
PropertiesSource osgiProfileProps = osgiProfile == null ? new EmptyPropertiesSource() :
new FileSystemPropertiesSource(osgiProfile);
ManifestGenerator manifestGenerator = new StandardManifestGenerator(
DefaultManifestGeneratorContributorsFactory.create(expansionProps, osgiProfileProps));
ManifestContents mf = manifestGenerator.generate(mfTemplate, inputClassPath.toArray(new ClassPath[inputClassPath.size()]));
try {
manifestWriter.write(mf);
} finally {
manifestWriter.close();
}
ManifestValidator manifestValidator = new StandardManifestValidator(DefaultManifestValidatorContributorsFactory.create());
List<String> warnings = manifestValidator.validate(mf);
if (warnings.isEmpty()) {
return
}
logger.warn("Bundlor Warnings:");
for (String warning : warnings) {
logger.warn(" " + warning);
}
if (failOnWarnings) {
throw new GradleException("Bundlor returned warnings. Please fix manifest template at " + manifestTemplate.absolutePath + " and try again.")
}
}
def inputPath(FileCollection paths) {
inputPaths = project.files(inputPaths, paths)
}
}
@@ -1,301 +0,0 @@
package docbook;
import org.gradle.api.Plugin;
import org.gradle.api.GradleException;
import org.gradle.api.DefaultTask;
import org.gradle.api.Task;
import org.gradle.api.Project;
import org.gradle.api.Action;
import org.gradle.api.tasks.*;
import org.gradle.api.file.FileCollection;
import org.xml.sax.XMLReader;
import org.xml.sax.InputSource;
import org.apache.xml.resolver.CatalogManager;
import org.apache.xml.resolver.tools.CatalogResolver;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.*;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import java.io.*;
import java.util.*;
import java.util.zip.*;
import java.net.*;
import org.apache.fop.apps.*;
import com.icl.saxon.TransformerFactoryImpl;
/**
* Gradle Docbook plugin implementation.
* <p>
* Creates three tasks: docbookHtml, docbookHtmlSingle and docbookPdf. Each task takes a single File on
* which it operates.
*/
class DocbookPlugin implements Plugin<Project> {
public void apply(Project project) {
// Add the plugin tasks to the project
Task docbookHtml = project.tasks.add('docbookHtml', DocbookHtml.class);
docbookHtml.setDescription('Generates chunked docbook html output');
Task docbookHtmlSingle = project.tasks.add('docbookHtmlSingle', Docbook.class);
docbookHtmlSingle.setDescription('Generates single page docbook html output')
docbookHtmlSingle.suffix = '-single'
Task docbookFoPdf = project.tasks.add("docbookFoPdf", DocbookFoPdf.class);
docbookFoPdf.setDescription('Generates PDF output');
docbookFoPdf.extension = 'fo'
Task docbook = project.tasks.add("docbook", DefaultTask.class);
docbook.dependsOn (docbookHtml, docbookHtmlSingle, docbookFoPdf)
}
}
/**
*/
public class Docbook extends DefaultTask {
@Input
String extension = 'html';
@Input
String suffix = '';
@Input
boolean XIncludeAware = true;
@Input
boolean highlightingEnabled = true;
String admonGraphicsPath;
String imgSrcPath;
@InputDirectory
File sourceDirectory = new File(project.getProjectDir(), "src/docbook");
@Input
String sourceFileName;
@InputFile
File stylesheet;
@OutputDirectory
File docsDir = new File(project.getBuildDir(), "docs");
@TaskAction
public final void transform() {
SAXParserFactory factory = new org.apache.xerces.jaxp.SAXParserFactoryImpl();
factory.setXIncludeAware(XIncludeAware);
docsDir.mkdirs();
File srcFile = new File(filterDocbookSources(sourceDirectory), sourceFileName);
String outputFilename = srcFile.getName().substring(0, srcFile.getName().length() - 4) + suffix + '.' + extension;
File outputFile = new File(getDocsDir(), outputFilename);
Result result = new StreamResult(outputFile.getAbsolutePath());
CatalogResolver resolver = new CatalogResolver(createCatalogManager());
InputSource inputSource = new InputSource(srcFile.getAbsolutePath());
XMLReader reader = factory.newSAXParser().getXMLReader();
reader.setEntityResolver(resolver);
TransformerFactory transformerFactory = new TransformerFactoryImpl();
transformerFactory.setURIResolver(resolver);
URL url = stylesheet.toURL();
Source source = new StreamSource(url.openStream(), url.toExternalForm());
Transformer transformer = transformerFactory.newTransformer(source);
if (highlightingEnabled) {
File highlightingDir = new File(getProject().getBuildDir(), "highlighting");
if (!highlightingDir.exists()) {
highlightingDir.mkdirs();
extractHighlightFiles(highlightingDir);
}
transformer.setParameter("highlight.xslthl.config", new File(highlightingDir, "xslthl-config.xml").toURI().toURL());
}
if (admonGraphicsPath != null) {
transformer.setParameter("admon.graphics", "1");
transformer.setParameter("admon.graphics.path", admonGraphicsPath);
}
if (imgSrcPath != null) {
transformer.setParameter("img.src.path", imgSrcPath);
}
preTransform(transformer, srcFile, outputFile);
transformer.transform(new SAXSource(reader, inputSource), result);
postTransform(outputFile);
}
/**
* @param sourceDir directory of unfiltered sources
* @return directory of filtered sources
* @author Chris Beams
*/
private File filterDocbookSources(File sourceDir) {
def docbookWorkDir = new File("${project.buildDir}/reference-work")
docbookWorkDir.mkdirs()
// copy everything but springsecurity.xml
project.copy {
into(docbookWorkDir)
from(sourceDir) { exclude '**/springsecurity.xml' }
}
// copy index.xml and expand ${...} variables along the way
// e.g.: ${version} needs to be replaced in the header
project.copy {
into(docbookWorkDir)
from(sourceDir) { include '**/springsecurity.xml' }
expand(version: "${project.version}")
}
return docbookWorkDir
}
private void extractHighlightFiles(File toDir) {
URLClassLoader cl = (URLClassLoader) getClass().getClassLoader();
URL[] urls = cl.getURLs();
URL docbookZip = null;
for (URL url : urls) {
if (url.toString().contains("docbook-xsl-")) {
docbookZip = url;
break;
}
}
if (docbookZip == null) {
throw new GradleException("Docbook zip file not found");
}
ZipFile zipFile = new ZipFile(new File(docbookZip.toURI()));
Enumeration e = zipFile.entries();
while (e.hasMoreElements()) {
ZipEntry ze = (ZipEntry) e.nextElement();
if (ze.getName().matches(".*/highlighting/.*\\.xml")) {
String filename = ze.getName().substring(ze.getName().lastIndexOf("/highlighting/") + 14);
copyFile(zipFile.getInputStream(ze), new File(toDir, filename));
}
}
}
private void copyFile(InputStream source, File destFile) {
destFile.createNewFile();
FileOutputStream to = null;
try {
to = new FileOutputStream(destFile);
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = source.read(buffer)) > 0) {
to.write(buffer, 0, bytesRead);
}
} finally {
if (source != null) {
source.close();
}
if (to != null) {
to.close();
}
}
}
protected void preTransform(Transformer transformer, File sourceFile, File outputFile) {
}
protected void postTransform(File outputFile) {
}
private CatalogManager createCatalogManager() {
CatalogManager manager = new CatalogManager();
manager.setIgnoreMissingProperties(true);
ClassLoader classLoader = this.getClass().getClassLoader();
StringBuilder builder = new StringBuilder();
String docbookCatalogName = "docbook/catalog.xml";
URL docbookCatalog = classLoader.getResource(docbookCatalogName);
if (docbookCatalog == null) {
throw new IllegalStateException("Docbook catalog " + docbookCatalogName + " could not be found in " + classLoader);
}
builder.append(docbookCatalog.toExternalForm());
Enumeration enumeration = classLoader.getResources("/catalog.xml");
while (enumeration.hasMoreElements()) {
builder.append(';');
URL resource = (URL) enumeration.nextElement();
builder.append(resource.toExternalForm());
}
String catalogFiles = builder.toString();
manager.setCatalogFiles(catalogFiles);
return manager;
}
}
/**
*/
class DocbookHtml extends Docbook {
@Override
protected void preTransform(Transformer transformer, File sourceFile, File outputFile) {
String rootFilename = outputFile.getName();
rootFilename = rootFilename.substring(0, rootFilename.lastIndexOf('.'));
transformer.setParameter("root.filename", rootFilename);
transformer.setParameter("base.dir", outputFile.getParent() + File.separator);
}
}
/**
*/
class DocbookFoPdf extends Docbook {
/**
* <a href="http://xmlgraphics.apache.org/fop/0.95/embedding.html#render">From the FOP usage guide</a>
*/
@Override
protected void postTransform(File foFile) {
FopFactory fopFactory = FopFactory.newInstance();
OutputStream out = null;
final File pdfFile = getPdfOutputFile(foFile);
logger.debug("Transforming 'fo' file "+ foFile + " to PDF: " + pdfFile);
try {
out = new BufferedOutputStream(new FileOutputStream(pdfFile));
Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, out);
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer();
Source src = new StreamSource(foFile);
Result res = new SAXResult(fop.getDefaultHandler());
transformer.transform(src, res);
} finally {
if (out != null) {
out.close();
}
}
/* if (!foFile.delete()) {
logger.warn("Failed to delete 'fo' file " + foFile);
}*/
}
private File getPdfOutputFile(File foFile) {
String name = foFile.getAbsolutePath();
return new File(name.substring(0, name.length() - 2) + "pdf");
}
}
@@ -0,0 +1,59 @@
package trang;
import com.thaiopensource.relaxng.translate.Driver
import javax.xml.transform.Transformer
import javax.xml.transform.TransformerFactory
import javax.xml.transform.stream.StreamSource
import javax.xml.transform.stream.StreamResult
import org.gradle.api.*;
import org.gradle.api.tasks.*
import org.gradle.api.file.FileCollection
/**
* Used for converting .rnc files to .xsd files.
* @author Rob Winch
*/
class TrangPlugin implements Plugin<Project> {
public void apply(Project project) {
Task rncToXsd = project.tasks.add('rncToXsd', RncToXsd.class)
rncToXsd.description = 'Converts .rnc to .xsd'
rncToXsd.group = 'Build'
}
}
/**
* Converts .rnc files to .xsd files using trang and then applies an xsl file to cleanup the results.
*/
public class RncToXsd extends DefaultTask {
@InputDirectory
File rncDir
@InputFile
File xslFile
@OutputDirectory
File xsdDir
@TaskAction
public final void transform() {
String xslPath = xslFile.absolutePath
rncDir.listFiles( { dir, file -> file.endsWith('.rnc')} as FilenameFilter).each { rncFile ->
File xsdFile = new File(xsdDir, rncFile.name.replace('.rnc', '.xsd'))
String xsdOutputPath = xsdFile.absolutePath
new Driver().run([rncFile.absolutePath, xsdOutputPath] as String[]);
TransformerFactory tFactory = new net.sf.saxon.TransformerFactoryImpl()
Transformer transformer =
tFactory.newTransformer(new StreamSource(xslPath))
File temp = File.createTempFile("gradle-trang-" + xsdFile.name, ".xsd")
xsdFile.withInputStream { is ->
temp << is
}
StreamSource xmlSource = new StreamSource(temp)
transformer.transform(xmlSource, new StreamResult(xsdFile))
temp.delete()
}
}
}
@@ -1 +0,0 @@
implementation-class=bundlor.BundlorPlugin
@@ -1 +0,0 @@
implementation-class=docbook.DocbookPlugin
@@ -0,0 +1 @@
implementation-class=trang.TrangPlugin
+5 -4
View File
@@ -5,8 +5,9 @@ dependencies {
"org.springframework:spring-context:$springVersion",
"org.springframework:spring-beans:$springVersion",
"org.springframework:spring-web:$springVersion",
"org.jasig.cas.client:cas-client-core:3.1.12",
"net.sf.ehcache:ehcache:$ehcacheVersion"
"org.jasig.cas.client:cas-client-core:3.2.1"
provided 'javax.servlet:servlet-api:2.5'
}
optional "net.sf.ehcache:ehcache:$ehcacheVersion"
provided "org.apache.tomcat:tomcat-servlet-api:$servletApiVersion"
}
+157
View File
@@ -0,0 +1,157 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-cas</artifactId>
<version>3.2.0.RC1</version>
<name>spring-security-cas</name>
<description>spring-security-cas</description>
<url>http://springsource.org/spring-security</url>
<organization>
<name>SpringSource</name>
<url>http://springsource.org/</url>
</organization>
<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<developers>
<developer>
<id>rwinch</id>
<name>Rob Winch</name>
<email>rwinch@vmware.com</email>
</developer>
</developers>
<scm>
<connection>scm:git:git://github.com/SpringSource/spring-security</connection>
<developerConnection>scm:git:git://github.com/SpringSource/spring-security</developerConnection>
<url>https://github.com/SpringSource/spring-security</url>
</scm>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-snasphot</id>
<url>http://repo.springsource.org/libs-snapshot</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.jasig.cas.client</groupId>
<artifactId>cas-client-core</artifactId>
<version>3.2.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>3.2.0.RC1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>3.2.0.RC1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>3.2.3.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.2.3.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>3.2.3.RELEASE</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>3.2.3.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>1.6.2</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-servlet-api</artifactId>
<version>7.0.33</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>0.9.29</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.7</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.easytesting</groupId>
<artifactId>fest-assert</artifactId>
<version>1.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.8.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>3.2.3.RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,80 @@
/*
* 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.cas.authentication;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cache.Cache;
import org.springframework.util.Assert;
/**
* Caches tickets using a Spring IoC defined {@link Cache}.
*
* @author Marten Deinum
* @since 3.2
*
*/
public class SpringCacheBasedTicketCache implements StatelessTicketCache {
//~ Static fields/initializers =====================================================================================
private static final Log logger = LogFactory.getLog(SpringCacheBasedTicketCache.class);
//~ Instance fields ================================================================================================
private final Cache cache;
//~ Constructors ===================================================================================================
public SpringCacheBasedTicketCache(Cache cache) throws Exception {
Assert.notNull(cache, "cache mandatory");
this.cache = cache;
}
//~ Methods ========================================================================================================
public CasAuthenticationToken getByTicketId(final String serviceTicket) {
final Cache.ValueWrapper element = serviceTicket != null ? cache.get(serviceTicket) : null;
if (logger.isDebugEnabled()) {
logger.debug("Cache hit: " + (element != null) + "; service ticket: " + serviceTicket);
}
return element == null ? null : (CasAuthenticationToken) element.get();
}
public void putTicketInCache(final CasAuthenticationToken token) {
String key = token.getCredentials().toString();
if (logger.isDebugEnabled()) {
logger.debug("Cache put: " + key);
}
cache.put(key, token);
}
public void removeTicketFromCache(final CasAuthenticationToken token) {
if (logger.isDebugEnabled()) {
logger.debug("Cache remove: " + token.getCredentials().toString());
}
this.removeTicketFromCache(token.getCredentials().toString());
}
public void removeTicketFromCache(final String serviceTicket) {
cache.evict(serviceTicket);
}
}
@@ -0,0 +1,66 @@
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.cas.authentication;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.cache.CacheManager;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import static org.junit.Assert.*;
/**
* Tests {@link org.springframework.security.cas.authentication.SpringCacheBasedTicketCache}.
*
* @author Marten Deinum
* @since 3.2
*/
public class SpringCacheBasedTicketCacheTests extends AbstractStatelessTicketCacheTests {
private static CacheManager cacheManager;
//~ Methods ========================================================================================================
@BeforeClass
public static void initCacheManaer() {
cacheManager = new ConcurrentMapCacheManager();
cacheManager.getCache("castickets");
}
@Test
public void testCacheOperation() throws Exception {
SpringCacheBasedTicketCache cache = new SpringCacheBasedTicketCache(cacheManager.getCache("castickets"));
final CasAuthenticationToken token = getToken();
// Check it gets stored in the cache
cache.putTicketInCache(token);
assertEquals(token, cache.getByTicketId("ST-0-ER94xMJmn6pha35CQRoZ"));
// Check it gets removed from the cache
cache.removeTicketFromCache(getToken());
assertNull(cache.getByTicketId("ST-0-ER94xMJmn6pha35CQRoZ"));
// Check it doesn't return values for null or unknown service tickets
assertNull(cache.getByTicketId(null));
assertNull(cache.getByTicketId("UNKNOWN_SERVICE_TICKET"));
}
@Test(expected = IllegalArgumentException.class)
public void testStartupDetectsMissingCache() throws Exception {
new SpringCacheBasedTicketCache(null);
}
}
@@ -172,6 +172,7 @@ public class CasAuthenticationFilterTests {
serviceProperties.setAuthenticateAllArtifacts(true);
MockHttpServletRequest request = new MockHttpServletRequest();
request.setParameter("ticket", "ST-1-123");
request.setRequestURI("/authenticate");
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain chain = mock(FilterChain.class);
+1
View File
@@ -15,6 +15,7 @@ Import-Template:
org.springframework.security.authentication.*;version="${secRange}",
org.springframework.security.web.*;version="${secRange}",
org.springframework.beans.factory;version="${springRange}",
org.springframework.cache.*;version="${springRange}";resolution:=optional,
org.springframework.context.*;version="${springRange}",
org.springframework.dao;version="${springRange}",
org.springframework.util;version="${springRange}",
+32 -20
View File
@@ -1,46 +1,50 @@
import javax.security.auth.login.ConfigurationSpi;
// Config Module build file
apply plugin: 'groovy'
apply plugin: 'trang'
compileTestJava.dependsOn(':spring-security-core:compileTestJava')
configurations {
// GRADLE-1124
compile.extendsFrom = []
testCompile.extendsFrom groovy
}
dependencies {
// NB: Don't add other compile time dependencies to the config module as this breaks tooling
compile project(':spring-security-core'),
project(':spring-security-web'),
"org.aspectj:aspectjweaver:$aspectjVersion",
'aopalliance:aopalliance:1.0',
"org.springframework:spring-aop:$springVersion",
"org.springframework:spring-context:$springVersion",
"org.springframework:spring-web:$springVersion",
"org.springframework:spring-beans:$springVersion"
provided "javax.servlet:servlet-api:2.5"
optional project(':spring-security-web'),
project(':spring-security-ldap'),
project(':spring-security-openid'),
"org.springframework:spring-web:$springVersion",
"org.springframework:spring-webmvc:$springVersion",
"org.aspectj:aspectjweaver:$aspectjVersion"
groovy 'org.codehaus.groovy:groovy:1.8.7'
provided "org.apache.tomcat:tomcat-servlet-api:$servletApiVersion"
testCompile project(':spring-security-ldap'),
project(':spring-security-openid'),
testCompile project(':spring-security-cas'),
project(':spring-security-core').sourceSets.test.output,
'javax.annotation:jsr250-api:1.0',
"org.springframework.ldap:spring-ldap-core:$springLdapVersion",
"org.springframework:spring-expression:$springVersion",
"org.springframework:spring-jdbc:$springVersion",
"org.springframework:spring-orm:$springVersion",
"org.springframework:spring-tx:$springVersion",
'org.spockframework:spock-core:0.6-groovy-1.8',
"org.spockframework:spock-core:$spockVersion",
"org.slf4j:jcl-over-slf4j:$slf4jVersion",
"org.powermock:powermock-core:$powerMockVersion",
"org.powermock:powermock-api-support:$powerMockVersion",
"org.powermock:powermock-module-junit4-common:$powerMockVersion",
"org.powermock:powermock-module-junit4:$powerMockVersion",
"org.powermock:powermock-api-mockito:$powerMockVersion",
"org.powermock:powermock-reflect:$powerMockVersion"
"org.hibernate.javax.persistence:hibernate-jpa-2.0-api:1.0.1.Final",
"org.hibernate:hibernate-entitymanager:4.1.0.Final",
"org.codehaus.groovy:groovy-all:$groovyVersion",
"org.apache.directory.server:apacheds-core:$apacheDsVersion",
"org.apache.directory.server:apacheds-core-entry:$apacheDsVersion",
"org.apache.directory.server:apacheds-protocol-shared:$apacheDsVersion",
"org.apache.directory.server:apacheds-protocol-ldap:$apacheDsVersion",
"org.apache.directory.server:apacheds-server-jndi:$apacheDsVersion",
'org.apache.directory.shared:shared-ldap:0.9.15',
'ldapsdk:ldapsdk:4.1',
powerMockDependencies
testCompile('org.openid4java:openid4java-nodeps:0.9.6') {
exclude group: 'com.google.code.guice', module: 'guice'
}
@@ -57,3 +61,11 @@ test {
integrationTest {
systemProperties['apacheDSWorkDir'] = "${buildDir}/apacheDSWork"
}
rncToXsd {
rncDir = file('src/main/resources/org/springframework/security/config/')
xsdDir = rncDir
xslFile = new File(rncDir, 'spring-security.xsl')
}
build.dependsOn rncToXsd
-11
View File
@@ -1,11 +0,0 @@
#! /bin/sh
pushd src/main/resources/org/springframework/security/config/
echo "Converting rnc file to xsd ..."
java -jar ~/bin/trang.jar spring-security-3.1.rnc spring-security-3.1.xsd
echo "Applying XSL transformation to xsd ..."
xsltproc --output spring-security-3.1.xsd spring-security.xsl spring-security-3.1.xsd
popd
+354
View File
@@ -0,0 +1,354 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>3.2.0.RC1</version>
<name>spring-security-config</name>
<description>spring-security-config</description>
<url>http://springsource.org/spring-security</url>
<organization>
<name>SpringSource</name>
<url>http://springsource.org/</url>
</organization>
<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<developers>
<developer>
<id>rwinch</id>
<name>Rob Winch</name>
<email>rwinch@vmware.com</email>
</developer>
</developers>
<scm>
<connection>scm:git:git://github.com/SpringSource/spring-security</connection>
<developerConnection>scm:git:git://github.com/SpringSource/spring-security</developerConnection>
<url>https://github.com/SpringSource/spring-security</url>
</scm>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-snasphot</id>
<url>http://repo.springsource.org/libs-snapshot</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>3.2.0.RC1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>3.2.3.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>3.2.3.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.2.3.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>3.2.3.RELEASE</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.10</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-ldap</artifactId>
<version>3.2.0.RC1</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-openid</artifactId>
<version>3.2.0.RC1</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>3.2.0.RC1</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>3.2.3.RELEASE</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>3.2.3.RELEASE</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-servlet-api</artifactId>
<version>7.0.33</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>2.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>0.9.29</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>1.8.0.10</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>jsr250-api</artifactId>
<version>1.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.7</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ldapsdk</groupId>
<artifactId>ldapsdk</artifactId>
<version>4.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.directory.server</groupId>
<artifactId>apacheds-core</artifactId>
<version>1.5.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.directory.server</groupId>
<artifactId>apacheds-core-entry</artifactId>
<version>1.5.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.directory.server</groupId>
<artifactId>apacheds-protocol-ldap</artifactId>
<version>1.5.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.directory.server</groupId>
<artifactId>apacheds-protocol-shared</artifactId>
<version>1.5.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.directory.server</groupId>
<artifactId>apacheds-server-jndi</artifactId>
<version>1.5.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.directory.shared</groupId>
<artifactId>shared-ldap</artifactId>
<version>0.9.15</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.0.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.easytesting</groupId>
<artifactId>fest-assert</artifactId>
<version>1.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.0-api</artifactId>
<version>1.0.1.Final</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>4.1.0.Final</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.8.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.openid4java</groupId>
<artifactId>openid4java-nodeps</artifactId>
<version>0.9.6</version>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>guice</artifactId>
<groupId>com.google.code.guice</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>1.4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-support</artifactId>
<version>1.4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-core</artifactId>
<version>1.4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>1.4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4-common</artifactId>
<version>1.4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-reflect</artifactId>
<version>1.4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<version>0.7-groovy-2.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.ldap</groupId>
<artifactId>spring-ldap-core</artifactId>
<version>1.3.1.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-cas</artifactId>
<version>3.2.0.RC1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>3.2.3.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>3.2.3.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>3.2.3.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>3.2.3.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>3.2.3.RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,177 @@
/*
* 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.authentication.ldap
import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.AuthenticationProvider
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.config.annotation.BaseSpringSpec
import org.springframework.security.config.annotation.SecurityBuilder;
import org.springframework.security.config.annotation.authentication.AuthenticationManagerBuilder
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
import org.springframework.security.ldap.authentication.LdapAuthenticationProvider;
import org.springframework.security.ldap.server.ApacheDSContainer;
import org.springframework.test.util.ReflectionTestUtils;
/**
*
* @author Rob Winch
*
*/
class LdapAuthenticationProviderBuilderSecurityBuilderTests extends BaseSpringSpec {
def "default configuration"() {
when:
loadConfig(DefaultLdapConfig)
LdapAuthenticationProvider provider = ldapProvider()
then:
provider.authoritiesPopulator.groupRoleAttribute == "cn"
provider.authoritiesPopulator.groupSearchBase == ""
provider.authoritiesPopulator.groupSearchFilter == "(uniqueMember={0})"
ReflectionTestUtils.getField(provider,"authoritiesMapper").prefix == "ROLE_"
}
@Configuration
static class DefaultLdapConfig extends BaseLdapProviderConfig {
protected void registerAuthentication(
AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.contextSource(contextSource())
}
}
def "group roles custom"() {
when:
loadConfig(GroupRolesConfig)
LdapAuthenticationProvider provider = ldapProvider()
then:
provider.authoritiesPopulator.groupRoleAttribute == "group"
}
@Configuration
static class GroupRolesConfig extends BaseLdapProviderConfig {
protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.contextSource(contextSource())
.groupRoleAttribute("group")
}
}
def "group search custom"() {
when:
loadConfig(GroupSearchConfig)
LdapAuthenticationProvider provider = ldapProvider()
then:
provider.authoritiesPopulator.groupSearchFilter == "ou=groupName"
}
@Configuration
static class GroupSearchConfig extends BaseLdapProviderConfig {
protected void registerAuthentication(
AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.contextSource(contextSource())
.groupSearchFilter("ou=groupName");
}
}
def "role prefix custom"() {
when:
loadConfig(RolePrefixConfig)
LdapAuthenticationProvider provider = ldapProvider()
then:
ReflectionTestUtils.getField(provider,"authoritiesMapper").prefix == "role_"
}
@Configuration
static class RolePrefixConfig extends BaseLdapProviderConfig {
protected void registerAuthentication(
AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.contextSource(contextSource())
.rolePrefix("role_")
}
}
def "bind authentication"() {
when:
loadConfig(BindAuthenticationConfig)
AuthenticationManager auth = context.getBean(AuthenticationManager)
then:
auth
auth.authenticate(new UsernamePasswordAuthenticationToken("admin","password")).authorities.collect { it.authority }.sort() == ["ROLE_ADMIN","ROLE_USER"]
}
@Configuration
static class BindAuthenticationConfig extends BaseLdapServerConfig {
protected void registerAuthentication(
AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.contextSource(contextSource())
.groupSearchBase("ou=groups")
.userDnPatterns("uid={0},ou=people");
}
}
def ldapProvider() {
context.getBean(AuthenticationManager).providers[0]
}
@Configuration
static abstract class BaseLdapServerConfig extends BaseLdapProviderConfig {
@Bean
public ApacheDSContainer ldapServer() throws Exception {
ApacheDSContainer apacheDSContainer = new ApacheDSContainer("dc=springframework,dc=org", "classpath:/users.ldif");
apacheDSContainer.setPort(33389);
return apacheDSContainer;
}
}
@Configuration
static abstract class BaseLdapProviderConfig {
@Bean
public AuthenticationManager authenticationManager() {
AuthenticationManagerBuilder registry = new AuthenticationManagerBuilder();
registerAuthentication(registry);
return registry.build();
}
protected abstract void registerAuthentication(
AuthenticationManagerBuilder auth) throws Exception;
@Bean
public BaseLdapPathContextSource contextSource() throws Exception {
DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource(
"ldap://127.0.0.1:33389/dc=springframework,dc=org")
contextSource.userDn = "uid=admin,ou=system"
contextSource.password = "secret"
contextSource.afterPropertiesSet();
return contextSource;
}
}
}
@@ -0,0 +1,68 @@
/*
* 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.authentication.ldap
import static org.springframework.security.config.annotation.authentication.ldap.NamespaceLdapAuthenticationProviderTestsConfigs.*
import org.springframework.ldap.core.support.BaseLdapPathContextSource
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.AuthenticationProvider
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.authentication.ldap.NamespaceLdapAuthenticationProviderTestsConfigs.LdapAuthenticationProviderConfig;
import org.springframework.security.ldap.authentication.LdapAuthenticationProvider;
import org.springframework.security.ldap.authentication.PasswordComparisonAuthenticator;
import org.springframework.security.ldap.userdetails.PersonContextMapper;
import org.springframework.test.util.ReflectionTestUtils;
/**
*
* @author Rob Winch
*
*/
class NamespaceLdapAuthenticationProviderTests extends BaseSpringSpec {
def "ldap-authentication-provider"() {
when:
loadConfig(LdapAuthenticationProviderConfig)
then:
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken("user","password")).authorities*.authority.sort() == ['ROLE_USER']
}
def "ldap-authentication-provider custom"() {
when:
loadConfig(CustomLdapAuthenticationProviderConfig)
LdapAuthenticationProvider provider = findAuthenticationProvider(LdapAuthenticationProvider)
then:
provider.authoritiesPopulator.groupRoleAttribute == "cn"
provider.authoritiesPopulator.groupSearchBase == "ou=groups"
provider.authoritiesPopulator.groupSearchFilter == "(member={0})"
ReflectionTestUtils.getField(provider,"authoritiesMapper").prefix == "PREFIX_"
provider.userDetailsContextMapper instanceof PersonContextMapper
provider.authenticator.getUserDns("user") == ["uid=user,ou=people"]
provider.authenticator.userSearch.searchBase == "ou=users"
provider.authenticator.userSearch.searchFilter == "(uid={0})"
}
def "ldap-authentication-provider password compare"() {
when:
loadConfig(PasswordCompareLdapConfig)
LdapAuthenticationProvider provider = findAuthenticationProvider(LdapAuthenticationProvider)
then:
provider.authenticator instanceof PasswordComparisonAuthenticator
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken("user","password")).authorities*.authority.sort() == ['ROLE_USER']
}
}
@@ -0,0 +1,84 @@
/*
* 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.authentication.ldap;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.encoding.PlaintextPasswordEncoder;
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.ldap.userdetails.PersonContextMapper;
/**
* @author Rob Winch
*
*/
public class NamespaceLdapAuthenticationProviderTestsConfigs {
@Configuration
@EnableWebSecurity
static class LdapAuthenticationProviderConfig extends WebSecurityConfigurerAdapter {
protected void registerAuthentication(
AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.groupSearchBase("ou=groups")
.userDnPatterns("uid={0},ou=people"); // ldap-server@user-dn-pattern
}
}
@Configuration
@EnableWebSecurity
static class CustomLdapAuthenticationProviderConfig extends WebSecurityConfigurerAdapter {
protected void registerAuthentication(
AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.groupRoleAttribute("cn") // ldap-authentication-provider@group-role-attribute
.groupSearchBase("ou=groups") // ldap-authentication-provider@group-search-base
.groupSearchFilter("(member={0})") // ldap-authentication-provider@group-search-filter
.rolePrefix("PREFIX_") // ldap-authentication-provider@group-search-filter
.userDetailsContextMapper(new PersonContextMapper()) // ldap-authentication-provider@user-context-mapper-ref / ldap-authentication-provider@user-details-class
.userDnPatterns("uid={0},ou=people") // ldap-authentication-provider@user-dn-pattern
.userSearchBase("ou=users") // ldap-authentication-provider@user-dn-pattern
.userSearchFilter("(uid={0})") // ldap-authentication-provider@user-search-filter
// .contextSource(contextSource) // ldap-authentication-provider@server-ref
.contextSource()
.ldif("classpath:user.ldif") // ldap-server@ldif
.managerDn("uid=admin,ou=system") // ldap-server@manager-dn
.managerPassword("secret") // ldap-server@manager-password
.port(33399) // ldap-server@port
.root("dc=springframework,dc=org") // ldap-server@root
// .url("ldap://localhost:33389/dc-springframework,dc=org") this overrides root and port and is used for external
;
}
}
@Configuration
@EnableWebSecurity
static class PasswordCompareLdapConfig extends WebSecurityConfigurerAdapter {
protected void registerAuthentication(
AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.groupSearchBase("ou=groups")
.userSearchFilter("(uid={0})")
.passwordCompare()
.passwordEncoder(new PlaintextPasswordEncoder()) // ldap-authentication-provider/password-compare/password-encoder@ref
.passwordAttribute("userPassword"); // ldap-authentication-provider/password-compare@password-attribute
}
}
}
@@ -0,0 +1,42 @@
dn: ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: groups
dn: ou=people,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: people
dn: uid=admin,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Rod Johnson
sn: Johnson
uid: admin
userPassword: password
dn: uid=user,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Dianne Emu
sn: Emu
uid: user
userPassword: password
dn: cn=user,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: groupOfNames
cn: user
uniqueMember: uid=admin,ou=people,dc=springframework,dc=org
uniqueMember: uid=user,ou=people,dc=springframework,dc=org
dn: cn=admin,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: groupOfNames
cn: admin
uniqueMember: uid=admin,ou=people,dc=springframework,dc=org
@@ -54,4 +54,6 @@ public abstract class Elements {
public static final String LDAP_PASSWORD_COMPARE = "password-compare";
public static final String DEBUG = "debug";
public static final String HTTP_FIREWALL = "http-firewall";
public static final String HEADERS = "headers";
public static final String CSRF = "csrf";
}
@@ -1,5 +1,23 @@
/*
* Copyright 2009-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;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.config.BeanDefinition;
@@ -28,8 +46,6 @@ import org.springframework.util.ClassUtils;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import java.util.*;
/**
* Parses elements from the "security" namespace (http://www.springframework.org/schema/security).
*
@@ -65,8 +81,8 @@ public final class SecurityNamespaceHandler implements NamespaceHandler {
public BeanDefinition parse(Element element, ParserContext pc) {
if (!namespaceMatchesVersion(element)) {
pc.getReaderContext().fatal("You cannot use a spring-security-2.0.xsd or spring-security-3.0.xsd schema " +
"with Spring Security 3.1. Please update your schema declarations to the 3.1 schema.", element);
pc.getReaderContext().fatal("You cannot use a spring-security-2.0.xsd or spring-security-3.0.xsd or spring-security-3.1.xsd schema " +
"with Spring Security 3.2. Please update your schema declarations to the 3.2 schema.", element);
}
String name = pc.getDelegate().getLocalName(element);
BeanDefinitionParser parser = parsers.get(name);
@@ -180,7 +196,7 @@ public final class SecurityNamespaceHandler implements NamespaceHandler {
private boolean matchesVersionInternal(Element element) {
String schemaLocation = element.getAttributeNS("http://www.w3.org/2001/XMLSchema-instance", "schemaLocation");
return schemaLocation.matches("(?m).*spring-security-3\\.1.*.xsd.*")
return schemaLocation.matches("(?m).*spring-security-3\\.2.*.xsd.*")
|| schemaLocation.matches("(?m).*spring-security.xsd.*")
|| !schemaLocation.matches("(?m).*spring-security.*");
}
@@ -0,0 +1,466 @@
/*
* 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;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.util.Assert;
import org.springframework.web.filter.DelegatingFilterProxy;
import com.google.inject.internal.ImmutableList.Builder;
/**
* <p>A base {@link SecurityBuilder} that allows {@link SecurityConfigurer} to be
* applied to it. This makes modifying the {@link SecurityBuilder} a strategy
* that can be customized and broken up into a number of
* {@link SecurityConfigurer} objects that have more specific goals than that
* of the {@link SecurityBuilder}.</p>
*
* <p>For example, a {@link SecurityBuilder} may build an
* {@link DelegatingFilterProxy}, but a {@link SecurityConfigurer} might
* populate the {@link SecurityBuilder} with the filters necessary for session
* management, form based login, authorization, etc.</p>
*
* @see WebSecurity
*
* @author Rob Winch
*
* @param <O>
* The object that this builder returns
* @param <B>
* The type of this builder (that is returned by the base class)
*/
public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBuilder<O>> extends AbstractSecurityBuilder<O> {
private final Log logger = LogFactory.getLog(getClass());
private final LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> configurers =
new LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>>();
private final Map<Class<Object>,Object> sharedObjects = new HashMap<Class<Object>,Object>();
private final boolean allowConfigurersOfSameType;
private BuildState buildState = BuildState.UNBUILT;
private ObjectPostProcessor<Object> objectPostProcessor;
/**
* Creates a new instance without post processing
*/
protected AbstractConfiguredSecurityBuilder() {
this(ObjectPostProcessor.QUIESCENT_POSTPROCESSOR);
}
/***
* Creates a new instance with the provided {@link ObjectPostProcessor}.
* This post processor must support Object since there are many types of
* objects that may be post processed.
*
* @param objectPostProcessor the {@link ObjectPostProcessor} to use
*/
protected AbstractConfiguredSecurityBuilder(ObjectPostProcessor<Object> objectPostProcessor) {
this(objectPostProcessor,false);
}
/***
* Creates a new instance with the provided {@link ObjectPostProcessor}.
* This post processor must support Object since there are many types of
* objects that may be post processed.
*
* @param objectPostProcessor the {@link ObjectPostProcessor} to use
* @param allowConfigurersOfSameType if true, will not override other {@link SecurityConfigurer}'s when performing apply
*/
protected AbstractConfiguredSecurityBuilder(ObjectPostProcessor<Object> objectPostProcessor, boolean allowConfigurersOfSameType) {
Assert.notNull(objectPostProcessor, "objectPostProcessor cannot be null");
this.objectPostProcessor = objectPostProcessor;
this.allowConfigurersOfSameType = allowConfigurersOfSameType;
}
/**
* Similar to {@link #build()} and {@link #getObject()} but checks the state
* to determine if {@link #build()} needs to be called first.
*
* @return the result of {@link #build()} or {@link #getObject()}. If an
* error occurs while building, returns null.
*/
public O getOrBuild() {
if(isUnbuilt()) {
try {
return build();
} catch(Exception e) {
logger.debug("Failed to perform build. Returning null", e);
return null;
}
} else {
return getObject();
}
}
/**
* Applies a {@link SecurityConfigurerAdapter} to this
* {@link SecurityBuilder} and invokes
* {@link SecurityConfigurerAdapter#setBuilder(SecurityBuilder)}.
*
* @param configurer
* @return
* @throws Exception
*/
@SuppressWarnings("unchecked")
public <C extends SecurityConfigurerAdapter<O, B>> C apply(C configurer)
throws Exception {
add(configurer);
configurer.addObjectPostProcessor(objectPostProcessor);
configurer.setBuilder((B) this);
return configurer;
}
/**
* Applies a {@link SecurityConfigurer} to this {@link SecurityBuilder}
* overriding any {@link SecurityConfigurer} of the exact same class. Note
* that object hierarchies are not considered.
*
* @param configurer
* @return
* @throws Exception
*/
public <C extends SecurityConfigurer<O, B>> C apply(C configurer)
throws Exception {
add(configurer);
return configurer;
}
/**
* Sets an object that is shared by multiple {@link SecurityConfigurer}.
*
* @param sharedType the Class to key the shared object by.
* @param object the Object to store
*/
@SuppressWarnings("unchecked")
public <C> void setSharedObject(Class<C> sharedType, C object) {
this.sharedObjects.put((Class<Object>) sharedType, object);
}
/**
* Gets a shared Object. Note that object heirarchies are not considered.
*
* @param sharedType the type of the shared Object
* @return the shared Object or null if it is not found
*/
@SuppressWarnings("unchecked")
public <C> C getSharedObject(Class<C> sharedType) {
return (C) this.sharedObjects.get(sharedType);
}
/**
* Gets the shared objects
* @return
*/
public Map<Class<Object>,Object> getSharedObjects() {
return Collections.unmodifiableMap(this.sharedObjects);
}
/**
* Adds {@link SecurityConfigurer} ensuring that it is allowed and
* invoking {@link SecurityConfigurer#init(SecurityBuilder)} immediately
* if necessary.
*
* @param configurer the {@link SecurityConfigurer} to add
* @throws Exception if an error occurs
*/
@SuppressWarnings("unchecked")
private <C extends SecurityConfigurer<O, B>> void add(C configurer) throws Exception {
Assert.notNull(configurer, "configurer cannot be null");
Class<? extends SecurityConfigurer<O, B>> clazz = (Class<? extends SecurityConfigurer<O, B>>) configurer
.getClass();
synchronized(configurers) {
if(buildState.isConfigured()) {
throw new IllegalStateException("Cannot apply "+configurer+" to already built object");
}
List<SecurityConfigurer<O, B>> configs = allowConfigurersOfSameType ? this.configurers.get(clazz) : null;
if(configs == null) {
configs = new ArrayList<SecurityConfigurer<O,B>>(1);
}
configs.add(configurer);
this.configurers.put(clazz, configs);
if(buildState.isInitializing()) {
configurer.init((B)this);
}
}
}
/**
* Gets all the {@link SecurityConfigurer} instances by its class name or an
* empty List if not found. Note that object hierarchies are not considered.
*
* @param clazz the {@link SecurityConfigurer} class to look for
* @return
*/
@SuppressWarnings("unchecked")
public <C extends SecurityConfigurer<O, B>> List<C> getConfigurers(
Class<C> clazz) {
List<C> configs = (List<C>) this.configurers.get(clazz);
if(configs == null) {
return new ArrayList<C>();
}
return new ArrayList<C>(configs);
}
/**
* Removes all the {@link SecurityConfigurer} instances by its class name or an
* empty List if not found. Note that object hierarchies are not considered.
*
* @param clazz the {@link SecurityConfigurer} class to look for
* @return
*/
@SuppressWarnings("unchecked")
public <C extends SecurityConfigurer<O, B>> List<C> removeConfigurers(
Class<C> clazz) {
List<C> configs = (List<C>) this.configurers.remove(clazz);
if(configs == null) {
return new ArrayList<C>();
}
return new ArrayList<C>(configs);
}
/**
* Gets the {@link SecurityConfigurer} by its class name or
* <code>null</code> if not found. Note that object hierarchies are not
* considered.
*
* @param clazz
* @return
*/
@SuppressWarnings("unchecked")
public <C extends SecurityConfigurer<O, B>> C getConfigurer(
Class<C> clazz) {
List<SecurityConfigurer<O,B>> configs = this.configurers.get(clazz);
if(configs == null) {
return null;
}
if(configs.size() != 1) {
throw new IllegalStateException("Only one configurer expected for type " + clazz + ", but got " + configs);
}
return (C) configs.get(0);
}
/**
* Removes and returns the {@link SecurityConfigurer} by its class name or
* <code>null</code> if not found. Note that object hierarchies are not
* considered.
*
* @param clazz
* @return
*/
@SuppressWarnings("unchecked")
public <C extends SecurityConfigurer<O,B>> C removeConfigurer(Class<C> clazz) {
List<SecurityConfigurer<O,B>> configs = this.configurers.remove(clazz);
if(configs == null) {
return null;
}
if(configs.size() != 1) {
throw new IllegalStateException("Only one configurer expected for type " + clazz + ", but got " + configs);
}
return (C) configs.get(0);
}
/**
* Specifies the {@link ObjectPostProcessor} to use.
* @param objectPostProcessor the {@link ObjectPostProcessor} to use. Cannot be null
* @return the {@link SecurityBuilder} for further customizations
*/
@SuppressWarnings("unchecked")
public O objectPostProcessor(ObjectPostProcessor<Object> objectPostProcessor) {
Assert.notNull(objectPostProcessor,"objectPostProcessor cannot be null");
this.objectPostProcessor = objectPostProcessor;
return (O) this;
}
/**
* Performs post processing of an object. The default is to delegate to the
* {@link ObjectPostProcessor}.
*
* @param object the Object to post process
* @return the possibly modified Object to use
*/
protected <P> P postProcess(P object) {
return (P) this.objectPostProcessor.postProcess(object);
}
/**
* Executes the build using the {@link SecurityConfigurer}'s that have been applied using the following steps:
*
* <ul>
* <li>Invokes {@link #beforeInit()} for any subclass to hook into</li>
* <li>Invokes {@link SecurityConfigurer#init(SecurityBuilder)} for any {@link SecurityConfigurer} that was applied to this builder.</li>
* <li>Invokes {@link #beforeConfigure()} for any subclass to hook into</li>
* <li>Invokes {@link #performBuild()} which actually builds the Object</li>
* </ul>
*/
@Override
protected final O doBuild() throws Exception {
synchronized(configurers) {
buildState = BuildState.INITIALIZING;
beforeInit();
init();
buildState = BuildState.CONFIGURING;
beforeConfigure();
configure();
buildState = BuildState.BUILDING;
O result = performBuild();
buildState = BuildState.BUILT;
return result;
}
}
/**
* Invoked prior to invoking each
* {@link SecurityConfigurer#init(SecurityBuilder)} method. Subclasses may
* override this method to hook into the lifecycle without using a
* {@link SecurityConfigurer}.
*/
protected void beforeInit() throws Exception {
}
/**
* Invoked prior to invoking each
* {@link SecurityConfigurer#configure(SecurityBuilder)} method.
* Subclasses may override this method to hook into the lifecycle without
* using a {@link SecurityConfigurer}.
*/
protected void beforeConfigure() throws Exception {
}
/**
* Subclasses must implement this method to build the object that is being returned.
*
* @return
*/
protected abstract O performBuild() throws Exception;
@SuppressWarnings("unchecked")
private void init() throws Exception {
Collection<SecurityConfigurer<O,B>> configurers = getConfigurers();
for(SecurityConfigurer<O,B> configurer : configurers ) {
configurer.init((B) this);
}
}
@SuppressWarnings("unchecked")
private void configure() throws Exception {
Collection<SecurityConfigurer<O,B>> configurers = getConfigurers();
for(SecurityConfigurer<O,B> configurer : configurers ) {
configurer.configure((B) this);
}
}
private Collection<SecurityConfigurer<O, B>> getConfigurers() {
List<SecurityConfigurer<O,B>> result = new ArrayList<SecurityConfigurer<O,B>>();
for(List<SecurityConfigurer<O,B>> configs : this.configurers.values()) {
result.addAll(configs);
}
return result;
}
/**
* Determines if the object is unbuilt.
* @return true, if unbuilt else false
*/
private boolean isUnbuilt() {
synchronized(configurers) {
return buildState == BuildState.UNBUILT;
}
}
/**
* The build state for the application
*
* @author Rob Winch
* @since 3.2
*/
private static enum BuildState {
/**
* This is the state before the {@link Builder#build()} is invoked
*/
UNBUILT(0),
/**
* The state from when {@link Builder#build()} is first invoked until
* all the {@link SecurityConfigurer#init(SecurityBuilder)} methods
* have been invoked.
*/
INITIALIZING(1),
/**
* The state from after all
* {@link SecurityConfigurer#init(SecurityBuilder)} have been invoked
* until after all the
* {@link SecurityConfigurer#configure(SecurityBuilder)} methods have
* been invoked.
*/
CONFIGURING(2),
/**
* From the point after all the
* {@link SecurityConfigurer#configure(SecurityBuilder)} have
* completed to just after
* {@link AbstractConfiguredSecurityBuilder#performBuild()}.
*/
BUILDING(3),
/**
* After the object has been completely built.
*/
BUILT(4);
private final int order;
BuildState(int order) {
this.order = order;
}
public boolean isInitializing() {
return INITIALIZING.order == order;
}
/**
* Determines if the state is CONFIGURING or later
* @return
*/
public boolean isConfigured() {
return order >= CONFIGURING.order;
}
}
}
@@ -0,0 +1,66 @@
/*
* 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;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* A base {@link SecurityBuilder} that ensures the object being built is only
* built one time.
*
* @param <O> the type of Object that is being built
*
* @author Rob Winch
*
*/
public abstract class AbstractSecurityBuilder<O> implements SecurityBuilder<O> {
private AtomicBoolean building = new AtomicBoolean();
private O object;
/* (non-Javadoc)
* @see org.springframework.security.config.annotation.SecurityBuilder#build()
*/
public final O build() throws Exception {
if(building.compareAndSet(false, true)) {
object = doBuild();
return object;
}
throw new IllegalStateException("This object has already been built");
}
/**
* Gets the object that was built. If it has not been built yet an Exception
* is thrown.
*
* @return the Object that was built
*/
public final O getObject() {
if(!building.get()) {
throw new IllegalStateException("This object has not been built");
}
return object;
}
/**
* Subclasses should implement this to perform the build.
*
* @return the object that should be returned by {@link #build()}.
*
* @throws Exception if an error occurs
*/
protected abstract O doBuild() throws Exception;
}
@@ -0,0 +1,51 @@
/*
* 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;
import org.springframework.beans.factory.Aware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
/**
* Allows initialization of Objects. Typically this is used to call the
* {@link Aware} methods, {@link InitializingBean#afterPropertiesSet()}, and
* ensure that {@link DisposableBean#destroy()} has been invoked.
*
* @param <T> the bound of the types of Objects this {@link ObjectPostProcessor} supports.
*
* @author Rob Winch
* @since 3.2
*/
public interface ObjectPostProcessor<T> {
/**
* Initialize the object possibly returning a modified instance that should
* be used instead.
*
* @param object the object to initialize
* @return the initialized version of the object
*/
<O extends T> O postProcess(O object);
/**
* A do nothing implementation of the {@link ObjectPostProcessor}
*/
ObjectPostProcessor<Object> QUIESCENT_POSTPROCESSOR = new ObjectPostProcessor<Object>() {
public <T> T postProcess(T object) {
return object;
}
};
}
@@ -0,0 +1,35 @@
/*
* 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;
/**
* Interface for building an Object
*
* @author Rob Winch
* @since 3.2
*
* @param <O> The type of the Object being built
*/
public interface SecurityBuilder<O> {
/**
* Builds the object and returns it or null.
*
* @return the Object to be built or null if the implementation allows it.
* @throws Exception if an error occured when building the Object
*/
O build() throws Exception;
}
@@ -0,0 +1,55 @@
/*
* 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;
/**
* Allows for configuring a {@link SecurityBuilder}. All
* {@link SecurityConfigurer} first have their {@link #init(SecurityBuilder)}
* method invoked. After all {@link #init(SecurityBuilder)} methods have been
* invoked, each {@link #configure(SecurityBuilder)} method is invoked.
*
* @see AbstractConfiguredSecurityBuilder
*
* @author Rob Winch
*
* @param <O>
* The object being built by the {@link SecurityBuilder} B
* @param <B>
* The {@link SecurityBuilder} that builds objects of type O. This is
* also the {@link SecurityBuilder} that is being configured.
*/
public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> {
/**
* Initialize the {@link SecurityBuilder}. Here only shared state should be
* created and modified, but not properties on the {@link SecurityBuilder}
* used for building the object. This ensures that the
* {@link #configure(SecurityBuilder)} method uses the correct shared
* objects when building.
*
* @param builder
* @throws Exception
*/
void init(B builder) throws Exception;
/**
* Configure the {@link SecurityBuilder} by setting the necessary properties
* on the {@link SecurityBuilder}.
*
* @param builder
* @throws Exception
*/
void configure(B builder) throws Exception;
}
@@ -0,0 +1,132 @@
/*
* 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;
import java.util.ArrayList;
import java.util.List;
import org.springframework.core.GenericTypeResolver;
/**
* A base class for {@link SecurityConfigurer} that allows subclasses to only
* implement the methods they are interested in. It also provides a mechanism
* for using the {@link SecurityConfigurer} and when done gaining access to the
* {@link SecurityBuilder} that is being configured.
*
* @author Rob Winch
*
* @param <O>
* The Object being built by B
* @param <B>
* The Builder that is building O and is configured by {@link SecurityConfigurerAdapter}
*/
public abstract class SecurityConfigurerAdapter<O,B extends SecurityBuilder<O>> implements SecurityConfigurer<O,B> {
private B securityBuilder;
private CompositeObjectPostProcessor objectPostProcessor = new CompositeObjectPostProcessor();
public void init(B builder) throws Exception {}
public void configure(B builder) throws Exception {}
/**
* Return the {@link SecurityBuilder} when done using the
* {@link SecurityConfigurer}. This is useful for method chaining.
*
* @return
*/
public B and() {
return getBuilder();
}
/**
* Gets the {@link SecurityBuilder}. Cannot be null.
*
* @return the {@link SecurityBuilder}
* @throw {@link IllegalStateException} if {@link SecurityBuilder} is null
*/
protected final B getBuilder() {
if(securityBuilder == null) {
throw new IllegalStateException("securityBuilder cannot be null");
}
return securityBuilder;
}
/**
* Performs post processing of an object. The default is to delegate to the
* {@link ObjectPostProcessor}.
*
* @param object the Object to post process
* @return the possibly modified Object to use
*/
@SuppressWarnings("unchecked")
protected <T> T postProcess(T object) {
return (T) this.objectPostProcessor.postProcess(object);
}
/**
* Adds an {@link ObjectPostProcessor} to be used for this
* {@link SecurityConfigurerAdapter}. The default implementation does
* nothing to the object.
*
* @param objectPostProcessor the {@link ObjectPostProcessor} to use
*/
public void addObjectPostProcessor(ObjectPostProcessor<?> objectPostProcessor) {
this.objectPostProcessor.addObjectPostProcessor(objectPostProcessor);
}
/**
* Sets the {@link SecurityBuilder} to be used. This is automatically set
* when using
* {@link AbstractConfiguredSecurityBuilder#apply(SecurityConfigurerAdapter)}
*
* @param builder the {@link SecurityBuilder} to set
*/
public void setBuilder(B builder) {
this.securityBuilder = builder;
}
/**
* An {@link ObjectPostProcessor} that delegates work to numerous
* {@link ObjectPostProcessor} implementations.
*
* @author Rob Winch
*/
private static final class CompositeObjectPostProcessor implements ObjectPostProcessor<Object> {
private List<ObjectPostProcessor<? extends Object>> postProcessors = new ArrayList<ObjectPostProcessor<?>>();
@SuppressWarnings({ "rawtypes", "unchecked" })
public Object postProcess(Object object) {
for(ObjectPostProcessor opp : postProcessors) {
Class<?> oppClass = opp.getClass();
Class<?> oppType = GenericTypeResolver.resolveTypeArgument(oppClass,ObjectPostProcessor.class);
if(oppType == null || oppType.isAssignableFrom(object.getClass())) {
object = opp.postProcess(object);
}
}
return object;
}
/**
* Adds an {@link ObjectPostProcessor} to use
* @param objectPostProcessor the {@link ObjectPostProcessor} to add
* @return true if the {@link ObjectPostProcessor} was added, else false
*/
private boolean addObjectPostProcessor(ObjectPostProcessor<?extends Object> objectPostProcessor) {
return this.postProcessors.add(objectPostProcessor);
}
}
}
@@ -0,0 +1,44 @@
/*
* 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.authentication;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.SecurityBuilder;
/**
* Interface for operating on a SecurityBuilder that creates a {@link ProviderManager}
*
* @author Rob Winch
*
* @param <B> the type of the {@link SecurityBuilder}
*/
public interface ProviderManagerBuilder<B extends ProviderManagerBuilder<B>> extends SecurityBuilder<AuthenticationManager> {
/**
* Add authentication based upon the custom {@link AuthenticationProvider}
* that is passed in. Since the {@link AuthenticationProvider}
* implementation is unknown, all customizations must be done externally and
* the {@link ProviderManagerBuilder} is returned immediately.
*
* @return a {@link ProviderManagerBuilder} to allow further authentication
* to be provided to the {@link ProviderManagerBuilder}
* @throws Exception
* if an error occurs when adding the {@link AuthenticationProvider}
*/
B authenticationProvider(AuthenticationProvider authenticationProvider);
}
@@ -0,0 +1,255 @@
/*
* 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.authentication.builders;
import java.util.ArrayList;
import java.util.List;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.SecurityBuilder;
import org.springframework.security.config.annotation.authentication.ProviderManagerBuilder;
import org.springframework.security.config.annotation.authentication.configurers.ldap.LdapAuthenticationProviderConfigurer;
import org.springframework.security.config.annotation.authentication.configurers.provisioning.InMemoryUserDetailsManagerConfigurer;
import org.springframework.security.config.annotation.authentication.configurers.provisioning.JdbcUserDetailsManagerConfigurer;
import org.springframework.security.config.annotation.authentication.configurers.userdetails.DaoAuthenticationConfigurer;
import org.springframework.security.config.annotation.authentication.configurers.userdetails.UserDetailsAwareConfigurer;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.util.Assert;
/**
* {@link SecurityBuilder} used to create an {@link AuthenticationManager}.
* Allows for easily building in memory authentication, LDAP authentication,
* JDBC based authentication, adding {@link UserDetailsService}, and adding
* {@link AuthenticationProvider}'s.
*
* @author Rob Winch
* @since 3.2
*/
public class AuthenticationManagerBuilder extends AbstractConfiguredSecurityBuilder<AuthenticationManager, AuthenticationManagerBuilder> implements ProviderManagerBuilder<AuthenticationManagerBuilder> {
private AuthenticationManager parentAuthenticationManager;
private List<AuthenticationProvider> authenticationProviders = new ArrayList<AuthenticationProvider>();
private UserDetailsService defaultUserDetailsService;
private Boolean eraseCredentials;
private AuthenticationEventPublisher eventPublisher;
/**
* Creates a new instance
* @param the {@link ObjectPostProcessor} instance to use.
*/
public AuthenticationManagerBuilder(ObjectPostProcessor<Object> objectPostProcessor) {
super(objectPostProcessor,true);
}
/**
* Allows providing a parent {@link AuthenticationManager} that will be
* tried if this {@link AuthenticationManager} was unable to attempt to
* authenticate the provided {@link Authentication}.
*
* @param authenticationManager
* the {@link AuthenticationManager} that should be used if the
* current {@link AuthenticationManager} was unable to attempt to
* authenticate the provided {@link Authentication}.
* @return the {@link AuthenticationManagerBuilder} for further adding types
* of authentication
*/
public AuthenticationManagerBuilder parentAuthenticationManager(
AuthenticationManager authenticationManager) {
this.parentAuthenticationManager = authenticationManager;
return this;
}
/**
* Sets the {@link AuthenticationEventPublisher}
*
* @param eventPublisher
* the {@link AuthenticationEventPublisher} to use
* @return the {@link AuthenticationManagerBuilder} for further
* customizations
*/
public AuthenticationManagerBuilder authenticationEventPublisher(AuthenticationEventPublisher eventPublisher) {
Assert.notNull(eventPublisher, "AuthenticationEventPublisher cannot be null");
this.eventPublisher = eventPublisher;
return this;
}
/**
*
*
* @param eraseCredentials
* true if {@link AuthenticationManager} should clear the
* credentials from the {@link Authentication} object after
* authenticating
* @return the {@link AuthenticationManagerBuilder} for further customizations
*/
public AuthenticationManagerBuilder eraseCredentials(boolean eraseCredentials) {
this.eraseCredentials = eraseCredentials;
return this;
}
/**
* Add in memory authentication to the {@link AuthenticationManagerBuilder}
* and return a {@link InMemoryUserDetailsManagerConfigurer} to
* allow customization of the in memory authentication.
*
* <p>
* This method also ensure that a {@link UserDetailsService} is available
* for the {@link #getDefaultUserDetailsService()} method. Note that
* additional {@link UserDetailsService}'s may override this
* {@link UserDetailsService} as the default.
* </p>
*
* @return a {@link InMemoryUserDetailsManagerConfigurer} to allow
* customization of the in memory authentication
* @throws Exception
* if an error occurs when adding the in memory authentication
*/
public InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> inMemoryAuthentication()
throws Exception {
return apply(new InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder>());
}
/**
* Add JDBC authentication to the {@link AuthenticationManagerBuilder} and
* return a {@link JdbcUserDetailsManagerConfigurer} to allow customization of the
* JDBC authentication.
*
* <p>
* This method also ensure that a {@link UserDetailsService} is available
* for the {@link #getDefaultUserDetailsService()} method. Note that
* additional {@link UserDetailsService}'s may override this
* {@link UserDetailsService} as the default.
* </p>
*
* @return a {@link JdbcUserDetailsManagerConfigurer} to allow customization of the
* JDBC authentication
* @throws Exception if an error occurs when adding the JDBC authentication
*/
public JdbcUserDetailsManagerConfigurer<AuthenticationManagerBuilder> jdbcAuthentication()
throws Exception {
return apply(new JdbcUserDetailsManagerConfigurer<AuthenticationManagerBuilder>());
}
/**
* Add authentication based upon the custom {@link UserDetailsService} that
* is passed in. It then returns a {@link DaoAuthenticationConfigurer} to
* allow customization of the authentication.
*
* <p>
* This method also ensure that the {@link UserDetailsService} is available
* for the {@link #getDefaultUserDetailsService()} method. Note that
* additional {@link UserDetailsService}'s may override this
* {@link UserDetailsService} as the default.
* </p>
*
* @return a {@link DaoAuthenticationConfigurer} to allow customization
* of the DAO authentication
* @throws Exception
* if an error occurs when adding the {@link UserDetailsService}
* based authentication
*/
public <T extends UserDetailsService> DaoAuthenticationConfigurer<AuthenticationManagerBuilder,T> userDetailsService(
T userDetailsService) throws Exception {
this.defaultUserDetailsService = userDetailsService;
return apply(new DaoAuthenticationConfigurer<AuthenticationManagerBuilder,T>(userDetailsService));
}
/**
* Add LDAP authentication to the {@link AuthenticationManagerBuilder} and
* return a {@link LdapAuthenticationProviderConfigurer} to allow
* customization of the LDAP authentication.
*
* <p>
* This method <b>does NOT</b> ensure that a {@link UserDetailsService} is
* available for the {@link #getDefaultUserDetailsService()} method.
* </p>
*
* @return a {@link LdapAuthenticationProviderConfigurer} to allow
* customization of the LDAP authentication
* @throws Exception
* if an error occurs when adding the LDAP authentication
*/
public LdapAuthenticationProviderConfigurer<AuthenticationManagerBuilder> ldapAuthentication()
throws Exception {
return apply(new LdapAuthenticationProviderConfigurer<AuthenticationManagerBuilder>());
}
/**
* Add authentication based upon the custom {@link AuthenticationProvider}
* that is passed in. Since the {@link AuthenticationProvider}
* implementation is unknown, all customizations must be done externally and
* the {@link AuthenticationManagerBuilder} is returned immediately.
*
* <p>
* This method <b>does NOT</b> ensure that the {@link UserDetailsService} is
* available for the {@link #getDefaultUserDetailsService()} method.
* </p>
*
* @return a {@link AuthenticationManagerBuilder} to allow further authentication
* to be provided to the {@link AuthenticationManagerBuilder}
* @throws Exception
* if an error occurs when adding the {@link AuthenticationProvider}
*/
public AuthenticationManagerBuilder authenticationProvider(
AuthenticationProvider authenticationProvider) {
this.authenticationProviders.add(authenticationProvider);
return this;
}
@Override
protected ProviderManager performBuild() throws Exception {
ProviderManager providerManager = new ProviderManager(authenticationProviders, parentAuthenticationManager);
if(eraseCredentials != null) {
providerManager.setEraseCredentialsAfterAuthentication(eraseCredentials);
}
if(eventPublisher != null) {
providerManager.setAuthenticationEventPublisher(eventPublisher);
}
providerManager = postProcess(providerManager);
return providerManager;
}
/**
* Gets the default {@link UserDetailsService} for the
* {@link AuthenticationManagerBuilder}. The result may be null in some
* circumstances.
*
* @return the default {@link UserDetailsService} for the
* {@link AuthenticationManagerBuilder}
*/
public UserDetailsService getDefaultUserDetailsService() {
return this.defaultUserDetailsService;
}
/**
* Captures the {@link UserDetailsService} from any {@link UserDetailsAwareConfigurer}.
*
* @param configurer the {@link UserDetailsAwareConfigurer} to capture the {@link UserDetailsService} from.
* @return the {@link UserDetailsAwareConfigurer} for further customizations
* @throws Exception if an error occurs
*/
private <C extends UserDetailsAwareConfigurer<AuthenticationManagerBuilder,? extends UserDetailsService>> C apply(C configurer) throws Exception {
this.defaultUserDetailsService = configurer.getUserDetailsService();
return (C) super.apply(configurer);
}
}
@@ -0,0 +1,37 @@
/*
* 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.authentication.configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
/**
* Exports the authentication {@link Configuration}
*
* @author Rob Winch
* @since 3.2
*
*/
@Configuration
public class AuthenticationConfiguration {
@Bean
public AuthenticationManagerBuilder authenticationManagerBuilder(ObjectPostProcessor<Object> objectPostProcessor) {
return new AuthenticationManagerBuilder(objectPostProcessor);
}
}
@@ -0,0 +1,482 @@
/*
* 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.authentication.configurers.ldap;
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.encoding.PasswordEncoder;
import org.springframework.security.authentication.encoding.PlaintextPasswordEncoder;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.authentication.ProviderManagerBuilder;
import org.springframework.security.config.annotation.web.configurers.ChannelSecurityConfigurer;
import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
import org.springframework.security.ldap.authentication.AbstractLdapAuthenticator;
import org.springframework.security.ldap.authentication.BindAuthenticator;
import org.springframework.security.ldap.authentication.LdapAuthenticationProvider;
import org.springframework.security.ldap.authentication.LdapAuthenticator;
import org.springframework.security.ldap.authentication.PasswordComparisonAuthenticator;
import org.springframework.security.ldap.search.FilterBasedLdapUserSearch;
import org.springframework.security.ldap.search.LdapUserSearch;
import org.springframework.security.ldap.server.ApacheDSContainer;
import org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator;
import org.springframework.security.ldap.userdetails.InetOrgPersonContextMapper;
import org.springframework.security.ldap.userdetails.LdapUserDetailsMapper;
import org.springframework.security.ldap.userdetails.PersonContextMapper;
import org.springframework.security.ldap.userdetails.UserDetailsContextMapper;
/**
* Configures LDAP {@link AuthenticationProvider} in the {@link ProviderManagerBuilder}.
*
* @param <B> the {@link ProviderManagerBuilder} type that this is configuring.
*
* @author Rob Winch
* @since 3.2
*/
public class LdapAuthenticationProviderConfigurer<B extends ProviderManagerBuilder<B>> extends SecurityConfigurerAdapter<AuthenticationManager,B> {
private String groupRoleAttribute = "cn";
private String groupSearchBase = "";
private String groupSearchFilter = "(uniqueMember={0})";
private String rolePrefix = "ROLE_";
private String userSearchBase = ""; // only for search
private String userSearchFilter = null;//"uid={0}"; // only for search
private String[] userDnPatterns;
private BaseLdapPathContextSource contextSource;
private ContextSourceBuilder contextSourceBuilder = new ContextSourceBuilder();
private UserDetailsContextMapper userDetailsContextMapper;
private PasswordEncoder passwordEncoder;
private String passwordAttribute;
private LdapAuthenticationProvider build() throws Exception {
BaseLdapPathContextSource contextSource = getContextSource();
LdapAuthenticator ldapAuthenticator = createLdapAuthenticator(contextSource);
DefaultLdapAuthoritiesPopulator authoritiesPopulator = new DefaultLdapAuthoritiesPopulator(
contextSource, groupSearchBase);
authoritiesPopulator.setGroupRoleAttribute(groupRoleAttribute);
authoritiesPopulator.setGroupSearchFilter(groupSearchFilter);
LdapAuthenticationProvider ldapAuthenticationProvider = new LdapAuthenticationProvider(
ldapAuthenticator, authoritiesPopulator);
SimpleAuthorityMapper simpleAuthorityMapper = new SimpleAuthorityMapper();
simpleAuthorityMapper.setPrefix(rolePrefix);
simpleAuthorityMapper.afterPropertiesSet();
ldapAuthenticationProvider.setAuthoritiesMapper(simpleAuthorityMapper);
if(userDetailsContextMapper != null) {
ldapAuthenticationProvider.setUserDetailsContextMapper(userDetailsContextMapper);
}
return ldapAuthenticationProvider;
}
/**
* Adds an {@link ObjectPostProcessor} for this class.
*
* @param objectPostProcessor
* @return the {@link ChannelSecurityConfigurer} for further customizations
*/
public LdapAuthenticationProviderConfigurer<B> withObjectPostProcessor(ObjectPostProcessor<?> objectPostProcessor) {
addObjectPostProcessor(objectPostProcessor);
return this;
}
/**
* Creates the {@link LdapAuthenticator} to use
*
* @param contextSource the {@link BaseLdapPathContextSource} to use
* @return the {@link LdapAuthenticator} to use
*/
private LdapAuthenticator createLdapAuthenticator(BaseLdapPathContextSource contextSource) {
AbstractLdapAuthenticator ldapAuthenticator = passwordEncoder == null ? createBindAuthenticator(contextSource) : createPasswordCompareAuthenticator(contextSource);
LdapUserSearch userSearch = createUserSearch();
if(userSearch != null) {
ldapAuthenticator.setUserSearch(userSearch);
}
if(userDnPatterns != null && userDnPatterns.length > 0) {
ldapAuthenticator.setUserDnPatterns(userDnPatterns);
}
return postProcess(ldapAuthenticator);
}
/**
* Creates {@link PasswordComparisonAuthenticator}
*
* @param contextSource the {@link BaseLdapPathContextSource} to use
* @return
*/
private PasswordComparisonAuthenticator createPasswordCompareAuthenticator(BaseLdapPathContextSource contextSource) {
PasswordComparisonAuthenticator ldapAuthenticator = new PasswordComparisonAuthenticator(contextSource);
ldapAuthenticator.setPasswordAttributeName(passwordAttribute);
ldapAuthenticator.setPasswordEncoder(passwordEncoder);
return ldapAuthenticator;
}
/**
* Creates a {@link BindAuthenticator}
*
* @param contextSource the {@link BaseLdapPathContextSource} to use
* @return the {@link BindAuthenticator} to use
*/
private BindAuthenticator createBindAuthenticator(
BaseLdapPathContextSource contextSource) {
return new BindAuthenticator(contextSource);
}
private LdapUserSearch createUserSearch() {
if(userSearchFilter == null) {
return null;
}
return new FilterBasedLdapUserSearch(userSearchBase, userSearchFilter, contextSource);
}
/**
* Specifies the {@link BaseLdapPathContextSource} to be used. If not
* specified, an embedded LDAP server will be created using
* {@link #contextSource()}.
*
* @param contextSource
* the {@link BaseLdapPathContextSource} to use
* @return the {@link LdapAuthenticationProviderConfigurer} for further
* customizations
* @see #contextSource()
*/
public LdapAuthenticationProviderConfigurer<B> contextSource(BaseLdapPathContextSource contextSource) {
this.contextSource = contextSource;
return this;
}
/**
* Allows easily configuring of a {@link BaseLdapPathContextSource} with
* defaults pointing to an embedded LDAP server that is created.
*
* @return the {@link ContextSourceBuilder} for further customizations
*/
public ContextSourceBuilder contextSource() {
return contextSourceBuilder;
}
/**
* Specifies the {@link PasswordEncoder} to be used when authenticating with
* password comparison.
*
* @param passwordEncoder the {@link PasswordEncoder} to use
* @return the {@link LdapAuthenticationProviderConfigurer} for further customization
*/
public LdapAuthenticationProviderConfigurer<B> passwordEncoder(PasswordEncoder passwordEncoder) {
this.passwordEncoder = passwordEncoder;
return this;
}
/**
* If your users are at a fixed location in the directory (i.e. you can work
* out the DN directly from the username without doing a directory search),
* you can use this attribute to map directly to the DN. It maps directly to
* the userDnPatterns property of AbstractLdapAuthenticator. The value is a
* specific pattern used to build the user's DN, for example
* "uid={0},ou=people". The key "{0}" must be present and will be
* substituted with the username.
*
* @param userDnPatterns the LDAP patterns for finding the usernames
* @return the {@link LdapAuthenticationProviderConfigurer} for further customizations
*/
public LdapAuthenticationProviderConfigurer<B> userDnPatterns(String...userDnPatterns) {
this.userDnPatterns = userDnPatterns;
return this;
}
/**
* Allows explicit customization of the loaded user object by specifying a
* UserDetailsContextMapper bean which will be called with the context
* information from the user's directory entry.
*
* @param userDetailsContextMapper the {@link UserDetailsContextMapper} to use
* @return the {@link LdapAuthenticationProviderConfigurer} for further
* customizations
*
* @see PersonContextMapper
* @see InetOrgPersonContextMapper
* @see LdapUserDetailsMapper
*/
public LdapAuthenticationProviderConfigurer<B> userDetailsContextMapper(UserDetailsContextMapper userDetailsContextMapper) {
this.userDetailsContextMapper = userDetailsContextMapper;
return this;
}
/**
* Specifies the attribute name which contains the role name. Default is "cn".
* @param groupRoleAttribute the attribute name that maps a group to a role.
* @return
*/
public LdapAuthenticationProviderConfigurer<B> groupRoleAttribute(String groupRoleAttribute) {
this.groupRoleAttribute = groupRoleAttribute;
return this;
}
/**
* The search base for group membership searches. Defaults to "".
* @param groupSearchBase
* @return the {@link LdapAuthenticationProviderConfigurer} for further customizations
*/
public LdapAuthenticationProviderConfigurer<B> groupSearchBase(String groupSearchBase) {
this.groupSearchBase = groupSearchBase;
return this;
}
/**
* The LDAP filter to search for groups. Defaults to "(uniqueMember={0})".
* The substituted parameter is the DN of the user.
*
* @param groupSearchFilter the LDAP filter to search for groups
* @return the {@link LdapAuthenticationProviderConfigurer} for further customizations
*/
public LdapAuthenticationProviderConfigurer<B> groupSearchFilter(String groupSearchFilter) {
this.groupSearchFilter = groupSearchFilter;
return this;
}
/**
* A non-empty string prefix that will be added as a prefix to the existing
* roles. The default is "ROLE_".
*
* @param rolePrefix the prefix to be added to the roles that are loaded.
* @return the {@link LdapAuthenticationProviderConfigurer} for further customizations
* @see SimpleAuthorityMapper#setPrefix(String)
*/
public LdapAuthenticationProviderConfigurer<B> rolePrefix(String rolePrefix) {
this.rolePrefix = rolePrefix;
return this;
}
/**
* Search base for user searches. Defaults to "". Only used with {@link #userSearchFilter(String)}.
*
* @param userSearchBase search base for user searches
* @return the {@link LdapAuthenticationProviderConfigurer} for further customizations
*/
public LdapAuthenticationProviderConfigurer<B> userSearchBase(String userSearchBase) {
this.userSearchBase = userSearchBase;
return this;
}
/**
* The LDAP filter used to search for users (optional). For example
* "(uid={0})". The substituted parameter is the user's login name.
*
* @param userSearchFilter
* the LDAP filter used to search for users
* @return the {@link LdapAuthenticationProviderConfigurer} for further
* customizations
*/
public LdapAuthenticationProviderConfigurer<B> userSearchFilter(String userSearchFilter) {
this.userSearchFilter = userSearchFilter;
return this;
}
@Override
public void configure(B builder) throws Exception {
LdapAuthenticationProvider provider = postProcess(build());
builder.authenticationProvider(provider);
}
/**
* Sets up Password based comparison
*
* @author Rob Winch
*/
public final class PasswordCompareConfigurer {
/**
* Allows specifying the {@link PasswordEncoder} to use. The default is {@link PlaintextPasswordEncoder}.
* @param passwordEncoder the {@link PasswordEncoder} to use
* @return the {@link PasswordEncoder} to use
*/
public PasswordCompareConfigurer passwordEncoder(PasswordEncoder passwordEncoder) {
LdapAuthenticationProviderConfigurer.this.passwordEncoder = passwordEncoder;
return this;
}
/**
* The attribute in the directory which contains the user password. Defaults to "userPassword".
*
* @param passwordAttribute the attribute in the directory which contains the user password
* @return the {@link PasswordCompareConfigurer} for further customizations
*/
public PasswordCompareConfigurer passwordAttribute(String passwordAttribute) {
LdapAuthenticationProviderConfigurer.this.passwordAttribute = passwordAttribute;
return this;
}
/**
* Allows obtaining a reference to the
* {@link LdapAuthenticationProviderConfigurer} for further
* customizations
*
* @return attribute in the directory which contains the user password
*/
public LdapAuthenticationProviderConfigurer<B> and() {
return LdapAuthenticationProviderConfigurer.this;
}
private PasswordCompareConfigurer() {}
}
/**
* Allows building a {@link BaseLdapPathContextSource} and optionally
* creating an embedded LDAP instance.
*
* @author Rob Winch
* @since 3.2
*/
public final class ContextSourceBuilder {
private String ldif = "classpath*:*.ldif";
private String managerPassword;
private String managerDn;
private int port = 33389;
private String root = "dc=springframework,dc=org";
private String url;
/**
* Specifies an ldif to load at startup for an embedded LDAP server.
* This only loads if using an embedded instance. The default is
* "classpath*:*.ldif".
*
* @param ldif
* the ldif to load at startup for an embedded LDAP server.
* @return the {@link ContextSourceBuilder} for further customization
*/
public ContextSourceBuilder ldif(String ldif) {
this.ldif = ldif;
return this;
}
/**
* Username (DN) of the "manager" user identity (i.e.
* "uid=admin,ou=system") which will be used to authenticate to a
* (non-embedded) LDAP server. If omitted, anonymous access will be
* used.
*
* @param managerDn
* the username (DN) of the "manager" user identity used to
* authenticate to a LDAP server.
* @return the {@link ContextSourceBuilder} for further customization
*/
public ContextSourceBuilder managerDn(String managerDn) {
this.managerDn = managerDn;
return this;
}
/**
* The password for the manager DN. This is required if the manager-dn is specified.
* @param managerPassword password for the manager DN
* @return the {@link ContextSourceBuilder} for further customization
*/
public ContextSourceBuilder managerPassword(String managerPassword) {
this.managerPassword = managerPassword;
return this;
}
/**
* The port to connect to LDAP to (the default is 33389).
* @param port the port to connect to
* @return the {@link ContextSourceBuilder} for further customization
*/
public ContextSourceBuilder port(int port) {
this.port = port;
return this;
}
/**
* Optional root suffix for the embedded LDAP server. Default is
* "dc=springframework,dc=org"
*
* @param root
* root suffix for the embedded LDAP server
* @return the {@link ContextSourceBuilder} for further customization
*/
public ContextSourceBuilder root(String root) {
this.root = root;
return this;
}
/**
* Specifies the ldap server URL when not using the embedded LDAP
* server. For example, "ldaps://ldap.example.com:33389/dc=myco,dc=org".
*
* @param url
* the ldap server URL
* @return the {@link ContextSourceBuilder} for further customization
*/
public ContextSourceBuilder url(String url) {
this.url = url;
return this;
}
/**
* Gets the {@link LdapAuthenticationProviderConfigurer} for further
* customizations
*
* @return the {@link LdapAuthenticationProviderConfigurer} for further
* customizations
*/
public LdapAuthenticationProviderConfigurer<B> and() {
return LdapAuthenticationProviderConfigurer.this;
}
private DefaultSpringSecurityContextSource build() throws Exception {
DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource(getProviderUrl());
if(managerDn != null) {
contextSource.setUserDn(managerDn);
if(managerPassword == null) {
throw new IllegalStateException("managerPassword is required if managerDn is supplied");
}
contextSource.setPassword(managerPassword);
}
contextSource = postProcess(contextSource);
if(url != null) {
return contextSource;
}
ApacheDSContainer apacheDsContainer = new ApacheDSContainer(root, ldif);
apacheDsContainer.setPort(port);
postProcess(apacheDsContainer);
return contextSource;
}
private String getProviderUrl() {
if(url == null) {
return "ldap://127.0.0.1:" + port + "/" + root;
}
return url;
}
private ContextSourceBuilder() {}
}
private BaseLdapPathContextSource getContextSource() throws Exception {
if(contextSource == null) {
contextSource = contextSourceBuilder.build();
}
return contextSource;
}
/**
* @return
*/
public PasswordCompareConfigurer passwordCompare() {
return new PasswordCompareConfigurer()
.passwordAttribute("password")
.passwordEncoder(new PlaintextPasswordEncoder());
}
}
@@ -0,0 +1,42 @@
/*
* 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.authentication.configurers.provisioning;
import java.util.ArrayList;
import org.springframework.security.config.annotation.authentication.ProviderManagerBuilder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
/**
* Configures an {@link org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder} to
* have in memory authentication. It also allows easily adding users to the in memory authentication.
*
* @param <B> the type of the {@link SecurityBuilder} that is being configured
*
* @author Rob Winch
* @since 3.2
*/
public class InMemoryUserDetailsManagerConfigurer<B extends ProviderManagerBuilder<B>> extends
UserDetailsManagerConfigurer<B,InMemoryUserDetailsManagerConfigurer<B>> {
/**
* Creates a new instance
*/
public InMemoryUserDetailsManagerConfigurer() {
super(new InMemoryUserDetailsManager(new ArrayList<UserDetails>()));
}
}
@@ -0,0 +1,192 @@
/*
* 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.authentication.configurers.provisioning;
import java.util.ArrayList;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.datasource.init.DataSourceInitializer;
import org.springframework.jdbc.datasource.init.DatabasePopulator;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.security.config.annotation.authentication.ProviderManagerBuilder;
import org.springframework.security.core.userdetails.UserCache;
import org.springframework.security.provisioning.JdbcUserDetailsManager;
/**
* Configures an {@link org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder} to
* have JDBC authentication. It also allows easily adding users to the database used for authentication and setting up
* the schema.
*
* <p>
* The only required method is the {@link #dataSource(javax.sql.DataSource)} all other methods have reasonable defaults.
* </p>
*
* @param <B> the type of the {@link ProviderManagerBuilder} that is being configured
*
* @author Rob Winch
* @since 3.2
*/
public class JdbcUserDetailsManagerConfigurer<B extends ProviderManagerBuilder<B>> extends
UserDetailsManagerConfigurer<B,JdbcUserDetailsManagerConfigurer<B>> {
private DataSource dataSource;
private List<Resource> initScripts = new ArrayList<Resource>();
public JdbcUserDetailsManagerConfigurer(JdbcUserDetailsManager manager) {
super(manager);
}
public JdbcUserDetailsManagerConfigurer() {
this(new JdbcUserDetailsManager());
}
/**
* Populates the {@link DataSource} to be used. This is the only required attribute.
*
* @param dataSource the {@link DataSource} to be used. Cannot be null.
* @return
* @throws Exception
*/
public JdbcUserDetailsManagerConfigurer<B> dataSource(DataSource dataSource) throws Exception {
this.dataSource = dataSource;
getUserDetailsService().setDataSource(dataSource);
return this;
}
/**
* Sets the query to be used for finding a user by their username. For example:
*
* <code>
* select username,password,enabled from users where username = ?
* </code>
* @param query The query to use for selecting the username, password, and if the user is enabled by username.
* Must contain a single parameter for the username.
* @return The {@link JdbcUserDetailsManagerRegistry} used for additional customizations
* @throws Exception
*/
public JdbcUserDetailsManagerConfigurer<B> usersByUsernameQuery(String query) throws Exception {
getUserDetailsService().setUsersByUsernameQuery(query);
return this;
}
/**
* Sets the query to be used for finding a user's authorities by their username. For example:
*
* <code>
* select username,authority from authorities where username = ?
* </code>
*
* @param query The query to use for selecting the username, authority by username.
* Must contain a single parameter for the username.
* @return The {@link JdbcUserDetailsManagerRegistry} used for additional customizations
* @throws Exception
*/
public JdbcUserDetailsManagerConfigurer<B> authoritiesByUsernameQuery(String query) throws Exception {
getUserDetailsService().setAuthoritiesByUsernameQuery(query);
return this;
}
/**
* An SQL statement to query user's group authorities given a username. For example:
*
* <code>
* select
* g.id, g.group_name, ga.authority
* from
* groups g, group_members gm, group_authorities ga
* where
* gm.username = ? and g.id = ga.group_id and g.id = gm.group_id
* </code>
*
* @param query The query to use for selecting the authorities by group.
* Must contain a single parameter for the username.
* @return The {@link JdbcUserDetailsManagerRegistry} used for additional customizations
* @throws Exception
*/
public JdbcUserDetailsManagerConfigurer<B> groupAuthoritiesByUsername(String query) throws Exception {
JdbcUserDetailsManager userDetailsService = getUserDetailsService();
userDetailsService.setEnableGroups(true);
userDetailsService.setGroupAuthoritiesByUsernameQuery(query);
return this;
}
/**
* A non-empty string prefix that will be added to role strings loaded from persistent storage (default is "").
*
* @param rolePrefix
* @return
* @throws Exception
*/
public JdbcUserDetailsManagerConfigurer<B> rolePrefix(String rolePrefix) throws Exception {
getUserDetailsService().setRolePrefix(rolePrefix);
return this;
}
/**
* Defines the {@link UserCache} to use
*
* @param userCache the {@link UserCache} to use
* @return the {@link JdbcUserDetailsManagerConfigurer} for further customizations
* @throws Exception
*/
public JdbcUserDetailsManagerConfigurer<B> userCache(UserCache userCache) throws Exception {
getUserDetailsService().setUserCache(userCache);
return this;
}
@Override
protected void initUserDetailsService() throws Exception {
if(!initScripts.isEmpty()) {
getDataSourceInit().afterPropertiesSet();
}
super.initUserDetailsService();
}
@Override
public JdbcUserDetailsManager getUserDetailsService() {
return (JdbcUserDetailsManager) super.getUserDetailsService();
}
/**
* Populates the default schema that allows users and authorities to be stored.
*
* @return The {@link JdbcUserDetailsManagerRegistry} used for additional customizations
*/
public JdbcUserDetailsManagerConfigurer<B> withDefaultSchema() {
this.initScripts.add(new ClassPathResource("org/springframework/security/core/userdetails/jdbc/users.ddl"));
return this;
}
protected DatabasePopulator getDatabasePopulator() {
ResourceDatabasePopulator dbp = new ResourceDatabasePopulator();
dbp.setScripts(initScripts.toArray(new Resource[initScripts.size()]));
return dbp;
}
private DataSourceInitializer getDataSourceInit() {
DataSourceInitializer dsi = new DataSourceInitializer();
dsi.setDatabasePopulator(getDatabasePopulator());
dsi.setDataSource(dataSource);
return dsi;
}
}
@@ -0,0 +1,263 @@
/*
* 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.authentication.configurers.provisioning;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.security.config.annotation.SecurityBuilder;
import org.springframework.security.config.annotation.authentication.ProviderManagerBuilder;
import org.springframework.security.config.annotation.authentication.configurers.userdetails.UserDetailsServiceConfigurer;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.provisioning.UserDetailsManager;
import org.springframework.util.Assert;
/**
* Base class for populating an
* {@link org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder} with a
* {@link UserDetailsManager}.
*
* @param <B> the type of the {@link SecurityBuilder} that is being configured
* @param <C> the type of {@link UserDetailsManagerConfigurer}
*
* @author Rob Winch
* @since 3.2
*/
public class UserDetailsManagerConfigurer<B extends ProviderManagerBuilder<B>, C extends UserDetailsManagerConfigurer<B,C>> extends
UserDetailsServiceConfigurer<B,C,UserDetailsManager> {
private final List<UserDetailsBuilder> userBuilders = new ArrayList<UserDetailsBuilder>();
protected UserDetailsManagerConfigurer(UserDetailsManager userDetailsManager) {
super(userDetailsManager);
}
/**
* Populates the users that have been added.
*
* @throws Exception
*/
@Override
protected void initUserDetailsService() throws Exception {
for(UserDetailsBuilder userBuilder : userBuilders) {
getUserDetailsService().createUser(userBuilder.build());
}
}
/**
* Allows adding a user to the {@link UserDetailsManager} that is being created. This method can be invoked
* multiple times to add multiple users.
*
* @param username the username for the user being added. Cannot be null.
* @return
*/
@SuppressWarnings("unchecked")
public final UserDetailsBuilder withUser(String username) {
UserDetailsBuilder userBuilder = new UserDetailsBuilder((C)this);
userBuilder.username(username);
this.userBuilders.add(userBuilder);
return userBuilder;
}
/**
* Builds the user to be added. At minimum the username, password, and authorities should provided. The remaining
* attributes have reasonable defaults.
*
* @param <T> the type of {@link UserDetailsManagerConfigurer} to return for chaining methods.
*/
public class UserDetailsBuilder {
private String username;
private String password;
private List<GrantedAuthority> authorities;
private boolean accountExpired;
private boolean accountLocked;
private boolean credentialsExpired;
private boolean disabled;
private final C builder;
/**
* Creates a new instance
* @param builder the builder to return
*/
private UserDetailsBuilder(C builder) {
this.builder = builder;
}
/**
* Returns the {@link UserDetailsManagerRegistry} for method chaining (i.e. to add another user)
*
* @return the {@link UserDetailsManagerRegistry} for method chaining (i.e. to add another user)
*/
public C and() {
return builder;
}
/**
* Populates the username. This attribute is required.
*
* @param username the username. Cannot be null.
* @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate additional attributes for this
* user)
*/
private UserDetailsBuilder username(String username) {
Assert.notNull(username, "username cannot be null");
this.username = username;
return this;
}
/**
* Populates the password. This attribute is required.
*
* @param password the password. Cannot be null.
* @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate additional attributes for this
* user)
*/
public UserDetailsBuilder password(String password) {
Assert.notNull(password, "password cannot be null");
this.password = password;
return this;
}
/**
* Populates the roles. This method is a shortcut for calling {@link #authorities(String...)}, but automatically
* prefixes each entry with "ROLE_". This means the following:
*
* <code>
* builder.roles("USER","ADMIN");
* </code>
*
* is equivalent to
*
* <code>
* builder.authorities("ROLE_USER","ROLE_ADMIN");
* </code>
*
* <p>This attribute is required, but can also be populated with {@link #authorities(String...)}.</p>
*
* @param roles the roles for this user (i.e. USER, ADMIN, etc). Cannot be null, contain null values or start
* with "ROLE_"
* @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate additional attributes for this
* user)
*/
public UserDetailsBuilder roles(String... roles) {
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(roles.length);
for(String role : roles) {
Assert.isTrue(!role.startsWith("ROLE_"), role + " cannot start with ROLE_ (it is automatically added)");
authorities.add(new SimpleGrantedAuthority("ROLE_"+role));
}
return authorities(authorities);
}
/**
* Populates the authorities. This attribute is required.
*
* @param authorities the authorities for this user. Cannot be null, or contain null
* values
* @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate additional attributes for this
* user)
* @see #roles(String...)
*/
public UserDetailsBuilder authorities(GrantedAuthority...authorities) {
return authorities(Arrays.asList(authorities));
}
/**
* Populates the authorities. This attribute is required.
*
* @param authorities the authorities for this user. Cannot be null, or contain null
* values
* @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate additional attributes for this
* user)
* @see #roles(String...)
*/
public UserDetailsBuilder authorities(List<? extends GrantedAuthority> authorities) {
this.authorities = new ArrayList<GrantedAuthority>(authorities);
return this;
}
/**
* Populates the authorities. This attribute is required.
*
* @param authorities the authorities for this user (i.e. ROLE_USER, ROLE_ADMIN, etc). Cannot be null, or contain null
* values
* @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate additional attributes for this
* user)
* @see #roles(String...)
*/
public UserDetailsBuilder authorities(String... authorities) {
return authorities(AuthorityUtils.createAuthorityList(authorities));
}
/**
* Defines if the account is expired or not. Default is false.
*
* @param accountExpired true if the account is expired, false otherwise
* @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate additional attributes for this
* user)
*/
public UserDetailsBuilder accountExpired(boolean accountExpired) {
this.accountExpired = accountExpired;
return this;
}
/**
* Defines if the account is locked or not. Default is false.
*
* @param accountLocked true if the account is locked, false otherwise
* @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate additional attributes for this
* user)
*/
public UserDetailsBuilder accountLocked(boolean accountLocked) {
this.accountLocked = accountLocked;
return this;
}
/**
* Defines if the credentials are expired or not. Default is false.
*
* @param credentialsExpired true if the credentials are expired, false otherwise
* @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate additional attributes for this
* user)
*/
public UserDetailsBuilder credentialsExpired(boolean credentialsExpired) {
this.credentialsExpired = credentialsExpired;
return this;
}
/**
* Defines if the account is disabled or not. Default is false.
*
* @param disabled true if the account is disabled, false otherwise
* @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate additional attributes for this
* user)
*/
public UserDetailsBuilder disabled(boolean disabled) {
this.disabled = disabled;
return this;
}
private UserDetails build() {
return new User(username, password, !disabled, !accountExpired,
!credentialsExpired, !accountLocked, authorities);
}
}
}
@@ -0,0 +1,108 @@
/*
* 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.authentication.configurers.userdetails;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.SecurityBuilder;
import org.springframework.security.config.annotation.SecurityConfigurer;
import org.springframework.security.config.annotation.authentication.ProviderManagerBuilder;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* Allows configuring a {@link DaoAuthenticationProvider}
*
* @author Rob Winch
* @since 3.2
*
* @param <B> the type of the {@link SecurityBuilder}
* @param <C> the type of {@link AbstractDaoAuthenticationConfigurer} this is
* @param <U> The type of {@link UserDetailsService} that is being used
*
*/
abstract class AbstractDaoAuthenticationConfigurer<B extends ProviderManagerBuilder<B>, C extends AbstractDaoAuthenticationConfigurer<B,C,U>,U extends UserDetailsService> extends UserDetailsAwareConfigurer<B,U> {
private DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
private final U userDetailsService;
/**
* Creates a new instance
*
* @param userDetailsService
*/
protected AbstractDaoAuthenticationConfigurer(U userDetailsService) {
this.userDetailsService = userDetailsService;
provider.setUserDetailsService(userDetailsService);
}
/**
* Adds an {@link ObjectPostProcessor} for this class.
*
* @param objectPostProcessor
* @return the {@link AbstractDaoAuthenticationConfigurer} for further customizations
*/
@SuppressWarnings("unchecked")
public C withObjectPostProcessor(ObjectPostProcessor<?> objectPostProcessor) {
addObjectPostProcessor(objectPostProcessor);
return (C) this;
}
/**
* Allows specifying the {@link PasswordEncoder} to use with the {@link DaoAuthenticationProvider}. The default is
* is to use plain text.
*
* @param passwordEncoder The {@link PasswordEncoder} to use.
* @return
*/
@SuppressWarnings("unchecked")
public C passwordEncoder(PasswordEncoder passwordEncoder) {
provider.setPasswordEncoder(passwordEncoder);
return (C) this;
}
/**
* Allows specifying the
* {@link org.springframework.security.authentication.encoding.PasswordEncoder}
* to use with the {@link DaoAuthenticationProvider}. The default is is to
* use plain text.
*
* @param passwordEncoder
* The
* {@link org.springframework.security.authentication.encoding.PasswordEncoder}
* to use.
* @return the {@link SecurityConfigurer} for further customizations
*/
@SuppressWarnings("unchecked")
public C passwordEncoder(org.springframework.security.authentication.encoding.PasswordEncoder passwordEncoder) {
provider.setPasswordEncoder(passwordEncoder);
return (C) this;
}
@Override
public void configure(B builder) throws Exception {
provider = postProcess(provider);
builder.authenticationProvider(provider);
}
/**
* Gets the {@link UserDetailsService} that is used with the {@link DaoAuthenticationProvider}
*
* @return the {@link UserDetailsService} that is used with the {@link DaoAuthenticationProvider}
*/
public U getUserDetailsService() {
return userDetailsService;
}
}
@@ -0,0 +1,41 @@
/*
* 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.authentication.configurers.userdetails;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.ProviderManagerBuilder;
import org.springframework.security.core.userdetails.UserDetailsService;
/**
* Allows configuring a {@link DaoAuthenticationProvider}
*
* @author Rob Winch
* @since 3.2
*
* @param <B> The type of {@link ProviderManagerBuilder} this is
* @param <U> The type of {@link UserDetailsService} that is being used
*
*/
public class DaoAuthenticationConfigurer<B extends ProviderManagerBuilder<B>, U extends UserDetailsService> extends AbstractDaoAuthenticationConfigurer<B,DaoAuthenticationConfigurer<B,U>, U>{
/**
* Creates a new instance
* @param userDetailsService
*/
public DaoAuthenticationConfigurer(U userDetailsService) {
super(userDetailsService);
}
}
@@ -0,0 +1,39 @@
/*
* 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.authentication.configurers.userdetails;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.authentication.ProviderManagerBuilder;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.core.userdetails.UserDetailsService;
/**
* Base class that allows access to the {@link UserDetailsService} for using as a default value with {@link AuthenticationManagerBuilder}.
*
* @author Rob Winch
*
* @param <B> the type of the {@link ProviderManagerBuilder}
* @param <U> the type of {@link UserDetailsService}
*/
public abstract class UserDetailsAwareConfigurer<B extends ProviderManagerBuilder<B>, U extends UserDetailsService> extends SecurityConfigurerAdapter<AuthenticationManager,B> {
/**
* Gets the {@link UserDetailsService} or null if it is not available
* @return the {@link UserDetailsService} or null if it is not available
*/
public abstract U getUserDetailsService();
}
@@ -0,0 +1,58 @@
/*
* 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.authentication.configurers.userdetails;
import org.springframework.security.config.annotation.SecurityConfigurer;
import org.springframework.security.config.annotation.authentication.ProviderManagerBuilder;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.core.userdetails.UserDetailsService;
/**
* Allows configuring a {@link UserDetailsService} within a {@link AuthenticationManagerBuilder}.
*
* @author Rob Winch
* @since 3.2
*
* @param <B> the type of the {@link SecurityBuilder}
* @param <C> the {@link SecurityConfigurer} (or this)
* @param <U> the type of UserDetailsService being used to allow for returning the concrete UserDetailsService.
*/
public class UserDetailsServiceConfigurer<B extends ProviderManagerBuilder<B>,
C extends UserDetailsServiceConfigurer<B, C, U>,
U extends UserDetailsService>
extends AbstractDaoAuthenticationConfigurer<B, C, U> {
/**
* Creates a new instance
* @param userDetailsService the {@link UserDetailsService} that should be used
*/
public UserDetailsServiceConfigurer(U userDetailsService) {
super(userDetailsService);
}
@Override
public void configure(B builder) throws Exception {
initUserDetailsService();
super.configure(builder);
}
/**
* Allows subclasses to initialize the {@link UserDetailsService}. For example, it might add users, initialize
* schema, etc.
*/
protected void initUserDetailsService() throws Exception {}
}
@@ -0,0 +1,75 @@
/*
* 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.configuration;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.Aware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.util.Assert;
/**
* Allows registering Objects to participate with an
* {@link AutowireCapableBeanFactory}'s post processing of {@link Aware}
* methods, {@link InitializingBean#afterPropertiesSet()}, and
* {@link DisposableBean#destroy()}.
*
* @author Rob Winch
* @since 3.2
*/
final class AutowireBeanFactoryObjectPostProcessor implements ObjectPostProcessor<Object>, DisposableBean {
private final Log logger = LogFactory.getLog(getClass());
private final AutowireCapableBeanFactory autowireBeanFactory;
private final List<DisposableBean> disposableBeans = new ArrayList<DisposableBean>();
public AutowireBeanFactoryObjectPostProcessor(
AutowireCapableBeanFactory autowireBeanFactory) {
Assert.notNull(autowireBeanFactory, "autowireBeanFactory cannot be null");
this.autowireBeanFactory = autowireBeanFactory;
}
/* (non-Javadoc)
* @see org.springframework.security.config.annotation.web.Initializer#initialize(java.lang.Object)
*/
@SuppressWarnings("unchecked")
public <T> T postProcess(T object) {
T result = (T) autowireBeanFactory.initializeBean(object, null);
if(result instanceof DisposableBean) {
disposableBeans.add((DisposableBean) result);
}
return result;
}
/* (non-Javadoc)
* @see org.springframework.beans.factory.DisposableBean#destroy()
*/
public void destroy() throws Exception {
for(DisposableBean disposable : disposableBeans) {
try {
disposable.destroy();
} catch(Exception error) {
logger.error(error);
}
}
}
}
@@ -0,0 +1,44 @@
/*
* 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.configuration;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
/**
* Spring {@link Configuration} that exports the default
* {@link ObjectPostProcessor}. This class is not intended to be imported
* manually rather it is imported automatically when using
* {@link EnableWebSecurity} or {@link EnableGlobalMethodSecurity}.
*
* @see EnableWebSecurity
* @see EnableGlobalMethodSecurity
*
* @author Rob Winch
* @since 3.2
*/
@Configuration
public class ObjectPostProcessorConfiguration {
@Bean
public ObjectPostProcessor<Object> objectPostProcessor(AutowireCapableBeanFactory beanFactory) {
return new AutowireBeanFactoryObjectPostProcessor(beanFactory);
}
}
@@ -0,0 +1,102 @@
/*
* 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 java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import org.springframework.context.annotation.AdviceMode;
import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.configuration.ObjectPostProcessorConfiguration;
/**
* <p>Enables Spring Security global method security similar to the
* <global-method-security> xml support.</p>
*
* <p>
* More advanced configurations may wish to extend
* {@link GlobalMethodSecurityConfiguration} and override the protected methods
* to provide custom implementations. Note that
* {@link EnableGlobalMethodSecurity} still must be included on the class
* extending {@link GlobalMethodSecurityConfiguration} to determine the
* settings.
*
* @author Rob Winch
* @since 3.2
*/
@Retention(value=java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value={java.lang.annotation.ElementType.TYPE})
@Documented
@Import({GlobalMethodSecuritySelector.class,ObjectPostProcessorConfiguration.class,AuthenticationConfiguration.class})
public @interface EnableGlobalMethodSecurity {
/**
* Determines if Spring Security's pre post annotations should be enabled. Default is false.
* @return true if pre post annotations should be enabled false otherwise.
*/
boolean prePostEnabled() default false;
/**
* Determines if Spring Security's {@link Secured} annotations should be enabled.
* @return true if {@link Secured} annotations should be enabled false otherwise. Default is false.
*/
boolean securedEnabled() default false;
/**
* Determines if JSR-250 annotations should be enabled. Default is false.
* @return true if JSR-250 should be enabled false otherwise.
*/
boolean jsr250Enabled() default false;
/**
* Indicate whether subclass-based (CGLIB) proxies are to be created ({@code true}) as
* opposed to standard Java interface-based proxies ({@code false}). The default is
* {@code false}. <strong>Applicable only if {@link #mode()} is set to
* {@link AdviceMode#PROXY}</strong>.
*
* <p>Note that setting this attribute to {@code true} will affect <em>all</em>
* Spring-managed beans requiring proxying, not just those marked with
* the Security annotations. For example, other beans marked with Spring's
* {@code @Transactional} annotation will be upgraded to subclass proxying at the same
* time. This approach has no negative impact in practice unless one is explicitly
* expecting one type of proxy vs another, e.g. in tests.
*
* @return true if CGILIB proxies should be created instead of interface based proxies, else false
*/
boolean proxyTargetClass() default false;
/**
* Indicate how security advice should be applied. The default is
* {@link AdviceMode#PROXY}.
* @see AdviceMode
*
* @return the {@link AdviceMode} to use
*/
AdviceMode mode() default AdviceMode.PROXY;
/**
* Indicate the ordering of the execution of the security advisor
* when multiple advices are applied at a specific joinpoint.
* The default is {@link Ordered#LOWEST_PRECEDENCE}.
*
* @return the order the security advisor should be applied
*/
int order() default Ordered.LOWEST_PRECEDENCE;
}
@@ -0,0 +1,67 @@
/*
* 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 java.util.Map;
import org.springframework.aop.config.AopConfigUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
/**
* Registers an
* {@link org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator
* AnnotationAwareAspectJAutoProxyCreator} against the current
* {@link BeanDefinitionRegistry} as appropriate based on a given @
* {@link EnableGlobalMethodSecurity} annotation.
*
* <p>
* Note: This class is necessary because AspectJAutoProxyRegistrar only supports
* EnableAspectJAutoProxy.
* </p>
*
* @author Rob Winch
* @since 3.2
*/
class GlobalMethodSecurityAspectJAutoProxyRegistrar implements
ImportBeanDefinitionRegistrar {
/**
* Register, escalate, and configure the AspectJ auto proxy creator based on
* the value of the @{@link EnableGlobalMethodSecurity#proxyTargetClass()}
* attribute on the importing {@code @Configuration} class.
*/
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
AopConfigUtils
.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
Map<String, Object> annotationAttributes = importingClassMetadata
.getAnnotationAttributes(EnableGlobalMethodSecurity.class
.getName());
AnnotationAttributes enableAJAutoProxy = AnnotationAttributes
.fromMap(annotationAttributes);
if (enableAJAutoProxy.getBoolean("proxyTargetClass")) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
}
}
@@ -0,0 +1,402 @@
/*
* 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 java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.aopalliance.intercept.MethodInterceptor;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.framework.ProxyFactoryBean;
import org.springframework.aop.target.LazyInitTargetSource;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportAware;
import org.springframework.context.annotation.Role;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.AfterInvocationProvider;
import org.springframework.security.access.annotation.Jsr250MethodSecurityMetadataSource;
import org.springframework.security.access.annotation.SecuredAnnotationSecurityMetadataSource;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.ExpressionBasedAnnotationAttributeFactory;
import org.springframework.security.access.expression.method.ExpressionBasedPostInvocationAdvice;
import org.springframework.security.access.expression.method.ExpressionBasedPreInvocationAdvice;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.access.intercept.AfterInvocationManager;
import org.springframework.security.access.intercept.AfterInvocationProviderManager;
import org.springframework.security.access.intercept.RunAsManager;
import org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor;
import org.springframework.security.access.intercept.aopalliance.MethodSecurityMetadataSourceAdvisor;
import org.springframework.security.access.method.DelegatingMethodSecurityMetadataSource;
import org.springframework.security.access.method.MethodSecurityMetadataSource;
import org.springframework.security.access.prepost.PostInvocationAdviceProvider;
import org.springframework.security.access.prepost.PreInvocationAuthorizationAdvice;
import org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter;
import org.springframework.security.access.prepost.PrePostAnnotationSecurityMetadataSource;
import org.springframework.security.access.vote.AffirmativeBased;
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.DefaultAuthenticationEventPublisher;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.util.Assert;
/**
* Base {@link Configuration} for enabling global method security. Classes may
* extend this class to customize the defaults, but must be sure to specify the
* {@link EnableGlobalMethodSecurity} annotation on the subclass.
*
* @author Rob Winch
* @since 3.2
* @see EnableGlobalMethodSecurity
*/
@Configuration
public class GlobalMethodSecurityConfiguration implements ImportAware {
private static final Log logger = LogFactory.getLog(GlobalMethodSecurityConfiguration.class);
private ApplicationContext context;
private ObjectPostProcessor<Object> objectPostProcessor = new ObjectPostProcessor<Object>() {
public <T> T postProcess(T object) {
throw new IllegalStateException(ObjectPostProcessor.class.getName()+ " is a required bean. Ensure you have used @"+EnableGlobalMethodSecurity.class.getName());
}
};
private AuthenticationManager authenticationManager;
private AuthenticationManagerBuilder auth = new AuthenticationManagerBuilder(ObjectPostProcessor.QUIESCENT_POSTPROCESSOR);
private boolean disableAuthenticationRegistry;
private AnnotationAttributes enableMethodSecurity;
private MethodSecurityExpressionHandler expressionHandler;
/**
* Creates the default MethodInterceptor which is a MethodSecurityInterceptor using the following methods to
* construct it.
* <ul>
* <li>{@link #accessDecisionManager()}</li>
* <li>{@link #afterInvocationManager()}</li>
* <li>{@link #authenticationManager()}</li>
* <li>{@link #methodSecurityMetadataSource()}</li>
* <li>{@link #runAsManager()}</li>
*
* </ul>
*
* <p>
* Subclasses can override this method to provide a different {@link MethodInterceptor}.
* </p>
*
* @return
* @throws Exception
*/
@Bean
public MethodInterceptor methodSecurityInterceptor() throws Exception {
MethodSecurityInterceptor methodSecurityInterceptor = new MethodSecurityInterceptor();
methodSecurityInterceptor
.setAccessDecisionManager(accessDecisionManager());
methodSecurityInterceptor
.setAfterInvocationManager(afterInvocationManager());
methodSecurityInterceptor
.setAuthenticationManager(authenticationManager());
methodSecurityInterceptor
.setSecurityMetadataSource(methodSecurityMetadataSource());
RunAsManager runAsManager = runAsManager();
if (runAsManager != null) {
methodSecurityInterceptor.setRunAsManager(runAsManager);
}
return methodSecurityInterceptor;
}
/**
* Provide a custom {@link AfterInvocationManager} for the default
* implementation of {@link #methodSecurityInterceptor()}. The default is
* null if pre post is not enabled. Otherwise, it returns a {@link AfterInvocationProviderManager}.
*
* <p>
* Subclasses should override this method to provide a custom {@link AfterInvocationManager}
* </p>
*
* @return
*/
protected AfterInvocationManager afterInvocationManager() {
if(prePostEnabled()) {
AfterInvocationProviderManager invocationProviderManager = new AfterInvocationProviderManager();
ExpressionBasedPostInvocationAdvice postAdvice = new ExpressionBasedPostInvocationAdvice(getExpressionHandler());
PostInvocationAdviceProvider postInvocationAdviceProvider = new PostInvocationAdviceProvider(postAdvice);
List<AfterInvocationProvider> afterInvocationProviders = new ArrayList<AfterInvocationProvider>();
afterInvocationProviders.add(postInvocationAdviceProvider);
invocationProviderManager.setProviders(afterInvocationProviders);
return invocationProviderManager;
}
return null;
}
/**
* Provide a custom {@link RunAsManager} for the default implementation of
* {@link #methodSecurityInterceptor()}. The default is null.
*
* @return
*/
protected RunAsManager runAsManager() {
return null;
}
/**
* Allows subclasses to provide a custom {@link AccessDecisionManager}. The default is a {@link AffirmativeBased}
* with the following voters:
*
* <ul>
* <li>{@link PreInvocationAuthorizationAdviceVoter}</li>
* <li>{@link RoleVoter} </li>
* <li>{@link AuthenticatedVoter} </li>
* </ul>
*
* @return
*/
@SuppressWarnings("rawtypes")
protected AccessDecisionManager accessDecisionManager() {
List<AccessDecisionVoter> decisionVoters = new ArrayList<AccessDecisionVoter>();
ExpressionBasedPreInvocationAdvice expressionAdvice = new ExpressionBasedPreInvocationAdvice();
expressionAdvice.setExpressionHandler(getExpressionHandler());
decisionVoters.add(new PreInvocationAuthorizationAdviceVoter(
expressionAdvice));
decisionVoters.add(new RoleVoter());
decisionVoters.add(new AuthenticatedVoter());
return new AffirmativeBased(decisionVoters);
}
/**
* Provide a {@link MethodSecurityExpressionHandler} that is
* registered with the {@link ExpressionBasedPreInvocationAdvice}. The default is
* {@link DefaultMethodSecurityExpressionHandler}
*
* <p>Subclasses may override this method to provide a custom {@link MethodSecurityExpressionHandler}</p>
*
* @return
*/
protected MethodSecurityExpressionHandler expressionHandler() {
return new DefaultMethodSecurityExpressionHandler();
}
/**
* Gets the {@link MethodSecurityExpressionHandler} or creates it using {@link #expressionHandler}.
*
* @return a non {@code null} {@link MethodSecurityExpressionHandler}
*/
protected final MethodSecurityExpressionHandler getExpressionHandler() {
if(expressionHandler == null) {
expressionHandler = expressionHandler();
}
return expressionHandler;
}
/**
* Provides a custom {@link MethodSecurityMetadataSource} that is registered
* with the {@link #methodSecurityMetadataSource()}. Default is null.
*
* @return a custom {@link MethodSecurityMetadataSource} that is registered
* with the {@link #methodSecurityMetadataSource()}
*/
protected MethodSecurityMetadataSource customMethodSecurityMetadataSource() {
return null;
}
/**
* Allows providing a custom {@link AuthenticationManager}. The default is
* to use any authentication mechanisms registered by {@link #registerAuthentication(AuthenticationManagerBuilder)}. If
* {@link #registerAuthentication(AuthenticationManagerBuilder)} was not overriden, then an {@link AuthenticationManager}
* is attempted to be autowired by type.
*
* @return
*/
protected AuthenticationManager authenticationManager() throws Exception {
if(authenticationManager == null) {
DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor.postProcess(new DefaultAuthenticationEventPublisher());
auth.authenticationEventPublisher(eventPublisher);
auth.objectPostProcessor(objectPostProcessor);
registerAuthentication(auth);
if(!disableAuthenticationRegistry) {
authenticationManager = auth.build();
}
if(authenticationManager == null) {
try {
authenticationManager = context.getBean(AuthenticationManagerBuilder.class).getOrBuild();
} catch(NoSuchBeanDefinitionException e) {
logger.debug("Could not obtain the AuthenticationManagerBuilder. This is ok for now, we will try using an AuthenticationManager directly",e);
}
}
if(authenticationManager == null) {
authenticationManager = lazyBean(AuthenticationManager.class);
}
}
return authenticationManager;
}
/**
* Sub classes can override this method to register different types of authentication. If not overridden,
* {@link #registerAuthentication(AuthenticationManagerBuilder)} will attempt to autowire by type.
*
* @param auth the {@link AuthenticationManagerBuilder} used to register different authentication mechanisms for the
* global method security.
* @throws Exception
*/
protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
this.disableAuthenticationRegistry = true;
}
/**
* Provides the default {@link MethodSecurityMetadataSource} that will be
* used. It creates a {@link DelegatingMethodSecurityMetadataSource} based
* upon {@link #customMethodSecurityMetadataSource()} and the attributes on
* {@link EnableGlobalMethodSecurity}.
*
* @return
*/
@Bean
public MethodSecurityMetadataSource methodSecurityMetadataSource() {
List<MethodSecurityMetadataSource> sources = new ArrayList<MethodSecurityMetadataSource>();
ExpressionBasedAnnotationAttributeFactory attributeFactory = new ExpressionBasedAnnotationAttributeFactory(
methodExpressionHandler());
MethodSecurityMetadataSource customMethodSecurityMetadataSource = customMethodSecurityMetadataSource();
if (customMethodSecurityMetadataSource != null) {
sources.add(customMethodSecurityMetadataSource);
}
if (prePostEnabled()) {
sources.add(new PrePostAnnotationSecurityMetadataSource(
attributeFactory));
}
if (securedEnabled()) {
sources.add(new SecuredAnnotationSecurityMetadataSource());
}
if (jsr250Enabled()) {
sources.add(new Jsr250MethodSecurityMetadataSource());
}
return new DelegatingMethodSecurityMetadataSource(sources);
}
/**
* Creates the {@link MethodSecurityExpressionHandler} to be used.
*
* @return
*/
@Bean
public MethodSecurityExpressionHandler methodExpressionHandler() {
return new DefaultMethodSecurityExpressionHandler();
}
/**
* Creates the {@link PreInvocationAuthorizationAdvice} to be used. The
* default is {@link ExpressionBasedPreInvocationAdvice}.
*
* @return
*/
@Bean
public PreInvocationAuthorizationAdvice preInvocationAuthorizationAdvice() {
ExpressionBasedPreInvocationAdvice preInvocationAdvice = new ExpressionBasedPreInvocationAdvice();
preInvocationAdvice.setExpressionHandler(methodExpressionHandler());
return preInvocationAdvice;
}
/**
* Obtains the {@link MethodSecurityMetadataSourceAdvisor} to be used.
*
* @return
*/
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@Bean
public MethodSecurityMetadataSourceAdvisor metaDataSourceAdvisor() {
MethodSecurityMetadataSourceAdvisor methodAdvisor = new MethodSecurityMetadataSourceAdvisor(
"methodSecurityInterceptor", methodSecurityMetadataSource(),
"methodSecurityMetadataSource");
methodAdvisor.setOrder(order());
return methodAdvisor;
}
/**
* Obtains the attributes from {@link EnableGlobalMethodSecurity} if this class was imported using the {@link EnableGlobalMethodSecurity} annotation.
*/
public final void setImportMetadata(AnnotationMetadata importMetadata) {
Map<String, Object> annotationAttributes = importMetadata
.getAnnotationAttributes(EnableGlobalMethodSecurity.class
.getName());
enableMethodSecurity = AnnotationAttributes
.fromMap(annotationAttributes);
}
@Autowired
public void setApplicationContext(ApplicationContext context) {
this.context = context;
}
@Autowired(required=false)
public void setObjectPostProcessor(ObjectPostProcessor<Object> objectPostProcessor) {
this.objectPostProcessor = objectPostProcessor;
}
@SuppressWarnings("unchecked")
private <T> T lazyBean(Class<T> interfaceName) {
LazyInitTargetSource lazyTargetSource = new LazyInitTargetSource();
String[] beanNamesForType = context.getBeanNamesForType(interfaceName);
Assert.isTrue(beanNamesForType.length == 1 , "Expecting to only find a single bean for type " + interfaceName + ", but found " + Arrays.asList(beanNamesForType));
lazyTargetSource.setTargetBeanName(beanNamesForType[0]);
lazyTargetSource.setBeanFactory(context);
ProxyFactoryBean proxyFactory = new ProxyFactoryBean();
proxyFactory.setTargetSource(lazyTargetSource);
proxyFactory.setInterfaces(new Class[] { interfaceName });
return (T) proxyFactory.getObject();
}
private boolean prePostEnabled() {
return enableMethodSecurity().getBoolean("prePostEnabled");
}
private boolean securedEnabled() {
return enableMethodSecurity().getBoolean("securedEnabled");
}
private boolean jsr250Enabled() {
return enableMethodSecurity().getBoolean("jsr250Enabled");
}
private int order() {
return (Integer) enableMethodSecurity().get("order");
}
private AnnotationAttributes enableMethodSecurity() {
if (enableMethodSecurity == null) {
// if it is null look at this instance (i.e. a subclass was used)
EnableGlobalMethodSecurity methodSecurityAnnotation = AnnotationUtils
.findAnnotation(getClass(),
EnableGlobalMethodSecurity.class);
Assert.notNull(methodSecurityAnnotation,
EnableGlobalMethodSecurity.class.getName() + " is required");
Map<String, Object> methodSecurityAttrs = AnnotationUtils
.getAnnotationAttributes(methodSecurityAnnotation);
this.enableMethodSecurity = AnnotationAttributes
.fromMap(methodSecurityAttrs);
}
return this.enableMethodSecurity;
}
}
@@ -0,0 +1,58 @@
/*
* 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 java.util.Map;
import org.springframework.context.annotation.AdviceMode;
import org.springframework.context.annotation.AutoProxyRegistrar;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* Dynamically determines which imports to include using the
* {@link EnableGlobalMethodSecurity} annotation.
*
* @author Rob Winch
* @since 3.2
*/
final class GlobalMethodSecuritySelector implements ImportSelector {
public final String[] selectImports(AnnotationMetadata importingClassMetadata) {
Class<EnableGlobalMethodSecurity> annoType = EnableGlobalMethodSecurity.class;
Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(annoType.getName(), false);
AnnotationAttributes attributes = AnnotationAttributes.fromMap(annotationAttributes);
Assert.notNull(attributes, String.format(
"@%s is not present on importing class '%s' as expected",
annoType.getSimpleName(), importingClassMetadata.getClassName()));
// TODO would be nice if could use BeanClassLoaderAware (does not work)
Class<?> importingClass = ClassUtils.resolveClassName(importingClassMetadata.getClassName(), ClassUtils.getDefaultClassLoader());
boolean skipMethodSecurityConfiguration = GlobalMethodSecurityConfiguration.class.isAssignableFrom(importingClass);
AdviceMode mode = attributes.getEnum("mode");
String autoProxyClassName = AdviceMode.PROXY == mode ? AutoProxyRegistrar.class.getName()
: GlobalMethodSecurityAspectJAutoProxyRegistrar.class.getName();
if(skipMethodSecurityConfiguration) {
return new String[] { autoProxyClassName };
}
return new String[] { autoProxyClassName,
GlobalMethodSecurityConfiguration.class.getName()};
}
}
@@ -0,0 +1,198 @@
/*
* 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;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.SecurityBuilder;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.AbstractRequestMatcherMappingConfigurer;
import org.springframework.security.web.util.AntPathRequestMatcher;
import org.springframework.security.web.util.AnyRequestMatcher;
import org.springframework.security.web.util.RegexRequestMatcher;
import org.springframework.security.web.util.RequestMatcher;
/**
* A base class for registering {@link RequestMatcher}'s. For example, it might allow for specifying which
* {@link RequestMatcher} require a certain level of authorization.
*
*
* @param <B> The Builder that is building Object O and is configured by this {@link AbstractRequestMatcherMappingConfigurer}
* @param <C> The object that is returned or Chained after creating the RequestMatcher
* @param <O> The Object being built by Builder B
*
* @author Rob Winch
* @since 3.2
*/
public abstract class AbstractRequestMatcherConfigurer<B extends SecurityBuilder<O>,C,O> extends SecurityConfigurerAdapter<O,B> {
private static final RequestMatcher ANY_REQUEST = new AnyRequestMatcher();
/**
* Maps any request.
*
* @param method the {@link HttpMethod} to use or {@code null} for any {@link HttpMethod}.
* @param antPatterns the ant patterns to create {@link org.springframework.security.web.util.AntPathRequestMatcher}
* from
*
* @return the object that is chained after creating the {@link RequestMatcher}
*/
public C anyRequest() {
return requestMatchers(ANY_REQUEST);
}
/**
* Maps a {@link List} of {@link org.springframework.security.web.util.AntPathRequestMatcher} instances.
*
* @param method the {@link HttpMethod} to use or {@code null} for any {@link HttpMethod}.
* @param antPatterns the ant patterns to create {@link org.springframework.security.web.util.AntPathRequestMatcher}
* from
*
* @return the object that is chained after creating the {@link RequestMatcher}
*/
public C antMatchers(HttpMethod method, String... antPatterns) {
return chainRequestMatchers(RequestMatchers.antMatchers(method, antPatterns));
}
/**
* Maps a {@link List} of {@link org.springframework.security.web.util.AntPathRequestMatcher} instances that do
* not care which {@link HttpMethod} is used.
*
* @param antPatterns the ant patterns to create {@link org.springframework.security.web.util.AntPathRequestMatcher}
* from
*
* @return the object that is chained after creating the {@link RequestMatcher}
*/
public C antMatchers(String... antPatterns) {
return chainRequestMatchers(RequestMatchers.antMatchers(antPatterns));
}
/**
* Maps a {@link List} of {@link org.springframework.security.web.util.RegexRequestMatcher} instances.
*
* @param method the {@link HttpMethod} to use or {@code null} for any {@link HttpMethod}.
* @param regexPatterns the regular expressions to create
* {@link org.springframework.security.web.util.RegexRequestMatcher} from
*
* @return the object that is chained after creating the {@link RequestMatcher}
*/
public C regexMatchers(HttpMethod method, String... regexPatterns) {
return chainRequestMatchers(RequestMatchers.regexMatchers(method,
regexPatterns));
}
/**
* Create a {@link List} of {@link org.springframework.security.web.util.RegexRequestMatcher} instances that do not
* specify an {@link HttpMethod}.
*
* @param regexPatterns the regular expressions to create
* {@link org.springframework.security.web.util.RegexRequestMatcher} from
*
* @return the object that is chained after creating the {@link RequestMatcher}
*/
public C regexMatchers(String... regexPatterns) {
return chainRequestMatchers(RequestMatchers.regexMatchers(regexPatterns));
}
/**
* Associates a list of {@link RequestMatcher} instances with the {@link AbstractRequestMatcherMappingConfigurer}
*
* @param requestMatchers the {@link RequestMatcher} instances
*
* @return the object that is chained after creating the {@link RequestMatcher}
*/
public C requestMatchers(RequestMatcher... requestMatchers) {
return chainRequestMatchers(Arrays.asList(requestMatchers));
}
/**
* Subclasses should implement this method for returning the object that is chained to the creation of the
* {@link RequestMatcher} instances.
*
* @param requestMatchers the {@link RequestMatcher} instances that were created
* @return the chained Object for the subclass which allows association of something else to the
* {@link RequestMatcher}
*/
protected abstract C chainRequestMatchers(List<RequestMatcher> requestMatchers);
/**
* Utilities for creating {@link RequestMatcher} instances.
*
* @author Rob Winch
* @since 3.2
*/
private static final class RequestMatchers {
/**
* Create a {@link List} of {@link AntPathRequestMatcher} instances.
*
* @param httpMethod the {@link HttpMethod} to use or {@code null} for any {@link HttpMethod}.
* @param antPatterns the ant patterns to create {@link AntPathRequestMatcher} from
*
* @return a {@link List} of {@link AntPathRequestMatcher} instances
*/
public static List<RequestMatcher> antMatchers(HttpMethod httpMethod, String...antPatterns) {
String method = httpMethod == null ? null : httpMethod.toString();
List<RequestMatcher> matchers = new ArrayList<RequestMatcher>();
for(String pattern : antPatterns) {
matchers.add(new AntPathRequestMatcher(pattern, method));
}
return matchers;
}
/**
* Create a {@link List} of {@link AntPathRequestMatcher} instances that do not specify an {@link HttpMethod}.
*
* @param antPatterns the ant patterns to create {@link AntPathRequestMatcher} from
*
* @return a {@link List} of {@link AntPathRequestMatcher} instances
*/
public static List<RequestMatcher> antMatchers(String...antPatterns) {
return antMatchers(null, antPatterns);
}
/**
* Create a {@link List} of {@link RegexRequestMatcher} instances.
*
* @param httpMethod the {@link HttpMethod} to use or {@code null} for any {@link HttpMethod}.
* @param regexPatterns the regular expressions to create {@link RegexRequestMatcher} from
*
* @return a {@link List} of {@link RegexRequestMatcher} instances
*/
public static List<RequestMatcher> regexMatchers(HttpMethod httpMethod, String...regexPatterns) {
String method = httpMethod == null ? null : httpMethod.toString();
List<RequestMatcher> matchers = new ArrayList<RequestMatcher>();
for(String pattern : regexPatterns) {
matchers.add(new RegexRequestMatcher(pattern, method));
}
return matchers;
}
/**
* Create a {@link List} of {@link RegexRequestMatcher} instances that do not specify an {@link HttpMethod}.
*
* @param regexPatterns the regular expressions to create {@link RegexRequestMatcher} from
*
* @return a {@link List} of {@link RegexRequestMatcher} instances
*/
public static List<RequestMatcher> regexMatchers(String...regexPatterns) {
return regexMatchers(null, regexPatterns);
}
private RequestMatchers() {}
}
}
@@ -0,0 +1,174 @@
/*
* 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;
import javax.servlet.Filter;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.SecurityBuilder;
import org.springframework.security.config.annotation.SecurityConfigurer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.openid.OpenIDAuthenticationFilter;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.access.ExceptionTranslationFilter;
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter;
import org.springframework.security.web.authentication.switchuser.SwitchUserFilter;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.authentication.www.DigestAuthenticationFilter;
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
import org.springframework.security.web.jaasapi.JaasApiIntegrationFilter;
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter;
import org.springframework.security.web.session.ConcurrentSessionFilter;
import org.springframework.security.web.session.SessionManagementFilter;
/**
*
* @author Rob Winch
*
* @param <H>
*/
public interface HttpSecurityBuilder<H extends HttpSecurityBuilder<H>> extends SecurityBuilder<DefaultSecurityFilterChain> {
/**
* Gets the {@link SecurityConfigurer} by its class name or
* <code>null</code> if not found. Note that object hierarchies are not
* considered.
*
* @param clazz the Class of the {@link SecurityConfigurer} to attempt to get.
*/
<C extends SecurityConfigurer<DefaultSecurityFilterChain, H>> C getConfigurer(
Class<C> clazz);
/**
* Removes the {@link SecurityConfigurer} by its class name or
* <code>null</code> if not found. Note that object hierarchies are not
* considered.
*
* @param clazz the Class of the {@link SecurityConfigurer} to attempt to remove.
* @return the {@link SecurityConfigurer} that was removed or null if not found
*/
<C extends SecurityConfigurer<DefaultSecurityFilterChain, H>> C removeConfigurer(Class<C> clazz);
/**
* Sets an object that is shared by multiple {@link SecurityConfigurer}.
*
* @param sharedType the Class to key the shared object by.
* @param object the Object to store
*/
<C> void setSharedObject(Class<C> sharedType, C object);
/**
* Gets a shared Object. Note that object heirarchies are not considered.
*
* @param sharedType the type of the shared Object
* @return the shared Object or null if it is not found
*/
<C> C getSharedObject(Class<C> sharedType);
/**
* Allows adding an additional {@link AuthenticationProvider} to be used
*
* @param authenticationProvider the {@link AuthenticationProvider} to be added
* @return the {@link HttpSecurity} for further customizations
*/
H authenticationProvider(
AuthenticationProvider authenticationProvider);
/**
* Allows adding an additional {@link UserDetailsService} to be used
*
* @param userDetailsService the {@link UserDetailsService} to be added
* @return the {@link HttpSecurity} for further customizations
*/
H userDetailsService(
UserDetailsService userDetailsService) throws Exception;
/**
* Allows adding a {@link Filter} after one of the known {@link Filter}
* classes. The known {@link Filter} instances are either a {@link Filter}
* listed in {@link #addFilter(Filter)} or a {@link Filter} that has already
* been added using {@link #addFilterAfter(Filter, Class)} or
* {@link #addFilterBefore(Filter, Class)}.
*
* @param filter the {@link Filter} to register before the type {@code afterFilter}
* @param afterFilter the Class of the known {@link Filter}.
* @return the {@link HttpSecurity} for further customizations
*/
H addFilterAfter(Filter filter,
Class<? extends Filter> afterFilter);
/**
* Allows adding a {@link Filter} before one of the known {@link Filter}
* classes. The known {@link Filter} instances are either a {@link Filter}
* listed in {@link #addFilter(Filter)} or a {@link Filter} that has already
* been added using {@link #addFilterAfter(Filter, Class)} or
* {@link #addFilterBefore(Filter, Class)}.
*
* @param filter the {@link Filter} to register before the type {@code beforeFilter}
* @param beforeFilter the Class of the known {@link Filter}.
* @return the {@link HttpSecurity} for further customizations
*/
H addFilterBefore(Filter filter,
Class<? extends Filter> beforeFilter);
/**
* Adds a {@link Filter} that must be an instance of or extend one of the
* Filters provided within the Security framework. The method ensures that
* the ordering of the Filters is automatically taken care of.
*
* The ordering of the Filters is:
*
* <ul>
* <li>{@link ChannelProcessingFilter}</li>
* <li>{@link ConcurrentSessionFilter}</li>
* <li>{@link SecurityContextPersistenceFilter}</li>
* <li>{@link LogoutFilter}</li>
* <li>{@link X509AuthenticationFilter}</li>
* <li>{@link AbstractPreAuthenticatedProcessingFilter}</li>
* <li>{@link org.springframework.security.cas.web.CasAuthenticationFilter}</li>
* <li>{@link UsernamePasswordAuthenticationFilter}</li>
* <li>{@link ConcurrentSessionFilter}</li>
* <li>{@link OpenIDAuthenticationFilter}</li>
* <li>{@link DefaultLoginPageViewFilter}</li>
* <li>{@link ConcurrentSessionFilter}</li>
* <li>{@link DigestAuthenticationFilter}</li>
* <li>{@link BasicAuthenticationFilter}</li>
* <li>{@link RequestCacheAwareFilter}</li>
* <li>{@link SecurityContextHolderAwareRequestFilter}</li>
* <li>{@link JaasApiIntegrationFilter}</li>
* <li>{@link RememberMeAuthenticationFilter}</li>
* <li>{@link AnonymousAuthenticationFilter}</li>
* <li>{@link SessionManagementFilter}</li>
* <li>{@link ExceptionTranslationFilter}</li>
* <li>{@link FilterSecurityInterceptor}</li>
* <li>{@link SwitchUserFilter}</li>
* </ul>
*
* @param filter the {@link Filter} to add
* @return the {@link HttpSecurity} for further customizations
*/
H addFilter(Filter filter);
}
@@ -0,0 +1,41 @@
/*
* 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;
import javax.servlet.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.SecurityBuilder;
import org.springframework.security.config.annotation.SecurityConfigurer;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/**
* Allows customization to the {@link WebSecurity}. In most instances
* users will use {@link EnableWebSecurity} and a create {@link Configuration}
* that extends {@link WebSecurityConfigurerAdapter} which will automatically be
* applied to the {@link WebSecurity} by the {@link EnableWebSecurity}
* annotation.
*
* @see WebSecurityConfigurerAdapter
*
* @author Rob Winch
* @since 3.2
*/
public interface WebSecurityConfigurer<T extends SecurityBuilder<Filter>> extends SecurityConfigurer<Filter, T> {
}
@@ -0,0 +1,191 @@
/*
* 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 java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.List;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.UrlUtils;
/**
* Spring Security debugging filter.
* <p>
* Logs information (such as session creation) to help the user understand how requests are being handled
* by Spring Security and provide them with other relevant information (such as when sessions are being created).
*
*
* @author Luke Taylor
* @author Rob Winch
* @since 3.1
*/
class DebugFilter implements Filter {
private static final String ALREADY_FILTERED_ATTR_NAME = DebugFilter.class.getName().concat(".FILTERED");
private final FilterChainProxy fcp;
private final Logger logger = new Logger();
public DebugFilter(FilterChainProxy fcp) {
this.fcp = fcp;
}
public final void doFilter(ServletRequest srvltRequest, ServletResponse srvltResponse, FilterChain filterChain)
throws ServletException, IOException {
if (!(srvltRequest instanceof HttpServletRequest) || !(srvltResponse instanceof HttpServletResponse)) {
throw new ServletException("DebugFilter just supports HTTP requests");
}
HttpServletRequest request = (HttpServletRequest) srvltRequest;
HttpServletResponse response = (HttpServletResponse) srvltResponse;
List<Filter> filters = getFilters(request);
logger.log("Request received for '" + UrlUtils.buildRequestUrl(request) + "':\n\n" +
request + "\n\n" +
"servletPath:" + request.getServletPath() + "\n" +
"pathInfo:" + request.getPathInfo() + "\n\n" +
formatFilters(filters));
if (request.getAttribute(ALREADY_FILTERED_ATTR_NAME) == null) {
invokeWithWrappedRequest(request, response, filterChain);
} else {
fcp.doFilter(request, response, filterChain);
}
}
private void invokeWithWrappedRequest(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
request.setAttribute(ALREADY_FILTERED_ATTR_NAME, Boolean.TRUE);
request = new DebugRequestWrapper(request);
try {
fcp.doFilter(request, response, filterChain);
}
finally {
request.removeAttribute(ALREADY_FILTERED_ATTR_NAME);
}
}
String formatFilters(List<Filter> filters) {
StringBuilder sb = new StringBuilder();
sb.append("Security filter chain: ");
if (filters == null) {
sb.append("no match");
} else if (filters.isEmpty()) {
sb.append("[] empty (bypassed by security='none') ");
} else {
sb.append("[\n");
for (Filter f : filters) {
sb.append(" ").append(f.getClass().getSimpleName()).append("\n");
}
sb.append("]");
}
return sb.toString();
}
private List<Filter> getFilters(HttpServletRequest request) {
for (SecurityFilterChain chain : fcp.getFilterChains()) {
if (chain.matches(request)) {
return chain.getFilters();
}
}
return null;
}
public void init(FilterConfig filterConfig) throws ServletException {
}
public void destroy() {
}
}
class DebugRequestWrapper extends HttpServletRequestWrapper {
private static final Logger logger = new Logger();
public DebugRequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public HttpSession getSession() {
boolean sessionExists = super.getSession(false) != null;
HttpSession session = super.getSession();
if (!sessionExists) {
logger.log("New HTTP session created: " + session.getId(), true);
}
return session;
}
@Override
public HttpSession getSession(boolean create) {
if (!create) {
return super.getSession(create);
}
return getSession();
}
}
/**
* Controls output for the Spring Security debug feature.
*
* @author Luke Taylor
* @since 3.1
*/
final class Logger {
final static Log logger = LogFactory.getLog("Spring Security Debugger");
void log(String message) {
log(message, false);
}
void log(String message, boolean dumpStack) {
StringBuilder output = new StringBuilder(256);
output.append("\n\n************************************************************\n\n");
output.append(message).append("\n");
if (dumpStack) {
StringWriter os = new StringWriter();
new Exception().printStackTrace(new PrintWriter(os));
StringBuffer buffer = os.getBuffer();
// Remove the exception in case it scares people.
int start = buffer.indexOf("java.lang.Exception");
buffer.replace(start, start + 19, "");
output.append("\nCall stack: \n").append(os.toString());
}
output.append("\n\n************************************************************\n\n");
logger.info(output.toString());
}
}
@@ -0,0 +1,181 @@
/*
* 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 java.io.Serializable;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.Filter;
import org.springframework.security.web.access.ExceptionTranslationFilter;
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter;
import org.springframework.security.web.authentication.switchuser.SwitchUserFilter;
import org.springframework.security.web.authentication.ui.DefaultLoginPageViewFilter;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.authentication.www.DigestAuthenticationFilter;
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.header.HeaderWriterFilter;
import org.springframework.security.web.jaasapi.JaasApiIntegrationFilter;
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter;
import org.springframework.security.web.session.ConcurrentSessionFilter;
import org.springframework.security.web.session.SessionManagementFilter;
/**
* An internal use only {@link Comparator} that sorts the Security {@link Filter} instances to ensure they are in the
* correct order.
*
* @author Rob Winch
* @since 3.2
*/
@SuppressWarnings("serial")
final class FilterComparator implements Comparator<Filter>, Serializable {
private static final int STEP = 100;
private Map<String,Integer> filterToOrder = new HashMap<String,Integer>();
FilterComparator() {
int order = 100;
put(ChannelProcessingFilter.class, order);
order += STEP;
put(ConcurrentSessionFilter.class, order);
order += STEP;
put(WebAsyncManagerIntegrationFilter.class, order);
order += STEP;
put(SecurityContextPersistenceFilter.class, order);
order += STEP;
put(HeaderWriterFilter.class, order);
order += STEP;
put(CsrfFilter.class, order);
order += STEP;
put(LogoutFilter.class, order);
order += STEP;
put(X509AuthenticationFilter.class, order);
order += STEP;
put(AbstractPreAuthenticatedProcessingFilter.class, order);
order += STEP;
filterToOrder.put("org.springframework.security.cas.web.CasAuthenticationFilter", order);
order += STEP;
put(UsernamePasswordAuthenticationFilter.class, order);
order += STEP;
put(ConcurrentSessionFilter.class, order);
order += STEP;
filterToOrder.put("org.springframework.security.openid.OpenIDAuthenticationFilter", order);
order += STEP;
put(DefaultLoginPageViewFilter.class, order);
order += STEP;
put(ConcurrentSessionFilter.class, order);
order += STEP;
put(DigestAuthenticationFilter.class, order);
order += STEP;
put(BasicAuthenticationFilter.class, order);
order += STEP;
put(RequestCacheAwareFilter.class, order);
order += STEP;
put(SecurityContextHolderAwareRequestFilter.class, order);
order += STEP;
put(JaasApiIntegrationFilter.class, order);
order += STEP;
put(RememberMeAuthenticationFilter.class, order);
order += STEP;
put(AnonymousAuthenticationFilter.class, order);
order += STEP;
put(SessionManagementFilter.class, order);
order += STEP;
put(ExceptionTranslationFilter.class, order);
order += STEP;
put(FilterSecurityInterceptor.class, order);
order += STEP;
put(SwitchUserFilter.class, order);
}
public int compare(Filter lhs, Filter rhs) {
Integer left = getOrder(lhs.getClass());
Integer right = getOrder(rhs.getClass());
return left - right;
}
/**
* Determines if a particular {@link Filter} is registered to be sorted
*
* @param filter
* @return
*/
public boolean isRegistered(Class<? extends Filter> filter) {
return getOrder(filter) != null;
}
/**
* Registers a {@link Filter} to exist after a particular {@link Filter} that is already registered.
* @param filter the {@link Filter} to register
* @param afterFilter the {@link Filter} that is already registered and that {@code filter} should be placed after.
*/
public void registerAfter(Class<? extends Filter> filter, Class<? extends Filter> afterFilter) {
Integer position = getOrder(afterFilter);
if(position == null) {
throw new IllegalArgumentException("Cannot register after unregistered Filter "+afterFilter);
}
put(filter, position + 1);
}
/**
* Registers a {@link Filter} to exist before a particular {@link Filter} that is already registered.
* @param filter the {@link Filter} to register
* @param beforeFilter the {@link Filter} that is already registered and that {@code filter} should be placed before.
*/
public void registerBefore(Class<? extends Filter> filter, Class<? extends Filter> beforeFilter) {
Integer position = getOrder(beforeFilter);
if(position == null) {
throw new IllegalArgumentException("Cannot register after unregistered Filter "+beforeFilter);
}
put(filter, position - 1);
}
private void put(Class<? extends Filter> filter, int position) {
String className = filter.getName();
filterToOrder.put(className, position);
}
/**
* Gets the order of a particular {@link Filter} class taking into consideration superclasses.
*
* @param clazz the {@link Filter} class to determine the sort order
* @return the sort order or null if not defined
*/
private Integer getOrder(Class<?> clazz) {
while(clazz != null) {
Integer result = filterToOrder.get(clazz.getName());
if(result != null) {
return result;
}
clazz = clazz.getSuperclass();
}
return null;
}
}
@@ -0,0 +1,327 @@
/*
* 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 java.util.ArrayList;
import java.util.List;
import javax.servlet.Filter;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.access.expression.SecurityExpressionHandler;
import org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder;
import org.springframework.security.config.annotation.SecurityBuilder;
import org.springframework.security.config.annotation.web.AbstractRequestMatcherConfigurer;
import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.DefaultWebInvocationPrivilegeEvaluator;
import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.firewall.DefaultHttpFirewall;
import org.springframework.security.web.firewall.HttpFirewall;
import org.springframework.security.web.util.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.web.filter.DelegatingFilterProxy;
/**
* <p>
* The {@link WebSecurity} is created by {@link WebSecurityConfiguration}
* to create the {@link FilterChainProxy} known as the Spring Security Filter
* Chain (springSecurityFilterChain). The springSecurityFilterChain is the
* {@link Filter} that the {@link DelegatingFilterProxy} delegates to.
* </p>
*
* <p>
* Customizations to the {@link WebSecurity} can be made by creating a
* {@link WebSecurityConfigurer} or more likely by overriding
* {@link WebSecurityConfigurerAdapter}.
* </p>
*
* @see EnableWebSecurity
* @see WebSecurityConfiguration
*
* @author Rob Winch
* @since 3.2
*/
public final class WebSecurity extends
AbstractConfiguredSecurityBuilder<Filter, WebSecurity> implements SecurityBuilder<Filter> {
private final Log logger = LogFactory.getLog(getClass());
private final List<RequestMatcher> ignoredRequests = new ArrayList<RequestMatcher>();
private final List<SecurityBuilder<? extends SecurityFilterChain>> securityFilterChainBuilders =
new ArrayList<SecurityBuilder<? extends SecurityFilterChain>>();
private final IgnoredRequestConfigurer ignoredRequestRegistry =
new IgnoredRequestConfigurer();
private FilterSecurityInterceptor filterSecurityInterceptor;
private HttpFirewall httpFirewall;
private boolean debugEnabled;
private WebInvocationPrivilegeEvaluator privilegeEvaluator;
private SecurityExpressionHandler<FilterInvocation> expressionHandler = new DefaultWebSecurityExpressionHandler();
private Runnable postBuildAction = new Runnable() {
public void run() {}
};
/**
* Creates a new instance
* @see WebSecurityConfiguration
*/
public WebSecurity() {
}
/**
* <p>
* Allows adding {@link RequestMatcher} instances that should that Spring
* Security should ignore. Web Security provided by Spring Security
* (including the {@link SecurityContext}) will not be available on
* {@link HttpServletRequest} that match. Typically the requests that are
* registered should be that of only static resources. For requests that are
* dynamic, consider mapping the request to allow all users instead.
* </p>
*
* Example Usage:
*
* <pre>
* webSecurityBuilder
* .ignoring()
* // ignore all URLs that start with /resources/ or /static/
* .antMatchers(&quot;/resources/**&quot;, &quot;/static/**&quot;);
* </pre>
*
* Alternatively this will accomplish the same result:
*
* <pre>
* webSecurityBuilder
* .ignoring()
* // ignore all URLs that start with /resources/ or /static/
* .antMatchers(&quot;/resources/**&quot;)
* .antMatchers(&quot;/static/**&quot;);
* </pre>
*
* Multiple invocations of ignoring() are also additive, so the following is
* also equivalent to the previous two examples:
*
* Alternatively this will accomplish the same result:
*
* <pre>
* webSecurityBuilder
* .ignoring()
* // ignore all URLs that start with /resources/
* .antMatchers(&quot;/resources/**&quot;);
* webSecurityBuilder
* .ignoring()
* // ignore all URLs that start with /static/
* .antMatchers(&quot;/static/**&quot;);
* // now both URLs that start with /resources/ and /static/ will be ignored
* </pre>
*
* @return the {@link IgnoredRequestConfigurer} to use for registering request
* that should be ignored
*/
public IgnoredRequestConfigurer ignoring() {
return ignoredRequestRegistry;
}
/**
* Allows customizing the {@link HttpFirewall}. The default is
* {@link DefaultHttpFirewall}.
*
* @param httpFirewall the custom {@link HttpFirewall}
* @return the {@link WebSecurity} for further customizations
*/
public WebSecurity httpFirewall(HttpFirewall httpFirewall) {
this.httpFirewall = httpFirewall;
return this;
}
/**
* Controls debugging support for Spring Security.
*
* @param debugEnabled
* if true, enables debug support with Spring Security. Default
* is false.
*
* @return the {@link WebSecurity} for further customization.
* @see EnableWebSecurity#debug()
*/
public WebSecurity debug(boolean debugEnabled) {
this.debugEnabled = debugEnabled;
return this;
}
/**
* <p>
* Adds builders to create {@link SecurityFilterChain} instances.
* </p>
*
* <p>
* Typically this method is invoked automatically within the framework from
* {@link WebSecurityConfigurerAdapter#init(WebSecurity)}
* </p>
*
* @param securityFilterChainBuilder
* the builder to use to create the {@link SecurityFilterChain}
* instances
* @return the {@link WebSecurity} for further customizations
*/
public WebSecurity addSecurityFilterChainBuilder(SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder) {
this.securityFilterChainBuilders.add(securityFilterChainBuilder);
return this;
}
/**
* Set the {@link WebInvocationPrivilegeEvaluator} to be used. If this is
* null, then a {@link DefaultWebInvocationPrivilegeEvaluator} will be
* created when {@link #securityInterceptor(FilterSecurityInterceptor)}
* is non null.
*
* @param privilegeEvaluator
* the {@link WebInvocationPrivilegeEvaluator} to use
* @return the {@link WebSecurity} for further customizations
*/
public WebSecurity privilegeEvaluator(WebInvocationPrivilegeEvaluator privilegeEvaluator) {
this.privilegeEvaluator = privilegeEvaluator;
return this;
}
/**
* Set the {@link SecurityExpressionHandler} to be used. If this is null,
* then a {@link DefaultWebSecurityExpressionHandler} will be used.
*
* @param expressionHandler
* the {@link SecurityExpressionHandler} to use
* @return the {@link WebSecurity} for further customizations
*/
public WebSecurity expressionHandler(SecurityExpressionHandler<FilterInvocation> expressionHandler) {
Assert.notNull(expressionHandler, "expressionHandler cannot be null");
this.expressionHandler = expressionHandler;
return this;
}
/**
* Gets the {@link SecurityExpressionHandler} to be used.
* @return
*/
public SecurityExpressionHandler<FilterInvocation> getExpressionHandler() {
return expressionHandler;
}
/**
* Gets the {@link WebInvocationPrivilegeEvaluator} to be used.
* @return
*/
public WebInvocationPrivilegeEvaluator getPrivilegeEvaluator() {
if(privilegeEvaluator != null) {
return privilegeEvaluator;
}
return filterSecurityInterceptor == null ? null : new DefaultWebInvocationPrivilegeEvaluator(filterSecurityInterceptor);
}
/**
* Sets the {@link FilterSecurityInterceptor}. This is typically invoked by {@link WebSecurityConfigurerAdapter}.
* @param securityInterceptor the {@link FilterSecurityInterceptor} to use
* @return the {@link WebSecurity} for further customizations
*/
public WebSecurity securityInterceptor(FilterSecurityInterceptor securityInterceptor) {
this.filterSecurityInterceptor = securityInterceptor;
return this;
}
/**
* Executes the Runnable immediately after the build takes place
*
* @param postBuildAction
* @return the {@link WebSecurity} for further customizations
*/
public WebSecurity postBuildAction(Runnable postBuildAction) {
this.postBuildAction = postBuildAction;
return this;
}
@Override
protected Filter performBuild() throws Exception {
Assert.state(!securityFilterChainBuilders.isEmpty(), "At least one SecurityFilterBuilder needs to be specified. Invoke FilterChainProxyBuilder.securityFilterChains");
int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size();
List<SecurityFilterChain> securityFilterChains = new ArrayList<SecurityFilterChain>(chainSize);
for(RequestMatcher ignoredRequest : ignoredRequests) {
securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
}
for(SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
securityFilterChains.add(securityFilterChainBuilder.build());
}
FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
if(httpFirewall != null) {
filterChainProxy.setFirewall(httpFirewall);
}
filterChainProxy.afterPropertiesSet();
Filter result = filterChainProxy;
if(debugEnabled) {
logger.warn("\n\n" +
"********************************************************************\n" +
"********** Security debugging is enabled. *************\n" +
"********** This may include sensitive information. *************\n" +
"********** Do not use in a production system! *************\n" +
"********************************************************************\n\n");
result = new DebugFilter(filterChainProxy);
}
postBuildAction.run();
return result;
}
/**
* Allows registering {@link RequestMatcher} instances that should be
* ignored by Spring Security.
*
* @author Rob Winch
* @since 3.2
*/
public final class IgnoredRequestConfigurer extends AbstractRequestMatcherConfigurer<WebSecurity,IgnoredRequestConfigurer,Filter> {
@Override
protected IgnoredRequestConfigurer chainRequestMatchers(List<RequestMatcher> requestMatchers) {
ignoredRequests.addAll(requestMatchers);
return this;
}
/**
* Returns the {@link WebSecurity} to be returned for chaining.
*/
@Override
public WebSecurity and() {
return WebSecurity.this;
}
private IgnoredRequestConfigurer(){}
}
}
@@ -0,0 +1,40 @@
/*
* 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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.support.RequestDataValueProcessor;
/**
* Used to add a {@link RequestDataValueProcessor} for Spring MVC and Spring
* Security CSRF integration. This configuration is added whenever
* {@link EnableWebMvc} is added by {@link SpringWebMvcImportSelector} and the
* DispatcherServlet is present on the classpath.
*
* @author Rob Winch
* @since 3.2
*/
@Configuration
class CsrfWebMvcConfiguration {
@Bean
public RequestDataValueProcessor requestDataValueProcessor() {
return new CsrfRequestDataValueProcessor();
}
}
@@ -0,0 +1,88 @@
/*
* 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.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.configuration.ObjectPostProcessorConfiguration;
import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
/**
* Add this annotation to an {@code @Configuration} class to have the Spring Security
* configuration defined in any {@link WebSecurityConfigurer} or more likely by extending the
* {@link WebSecurityConfigurerAdapter} base class and overriding individual methods:
*
* <pre class="code">
* &#064;Configuration
* &#064;EnableWebSecurity
* public class MyWebSecurityConfiguration extends WebSecurityConfigurerAdapter {
*
* &#064;Override
* public void configure(WebSecurity web) throws Exception {
* web
* .ignoring()
* // Spring Security should completely ignore URLs starting with /resources/
* .antMatchers("/resources/**");
* }
*
* &#064;Override
* protected void configure(HttpSecurity http) throws Exception {
* http
* .authorizeRequests()
* .antMatchers("/public/**").permitAll()
* .anyRequest().hasRole("USER")
* .and()
* // Possibly more configuration ...
* .formLogin() // enable form based log in
* // set permitAll for all URLs associated with Form Login
* .permitAll();
* }
*
* &#064;Override
* protected void registerAuthentication(AuthenticationManagerBuilder auth) {
* auth
* // enable in memory based authentication with a user named "user" and "admin"
* .inMemoryAuthentication()
* .withUser("user").password("password").roles("USER").and()
* .withUser("admin").password("password").roles("USER", "ADMIN");
* }
*
* // Possibly more overridden methods ...
* }
* </pre>
*
* @see WebSecurityConfigurer
* @see WebSecurityConfigurerAdapter
*
* @author Rob Winch
* @since 3.2
*/
@Retention(value=java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value={java.lang.annotation.ElementType.TYPE})
@Documented
@Import({WebSecurityConfiguration.class,ObjectPostProcessorConfiguration.class,AuthenticationConfiguration.class, SpringWebMvcImportSelector.class})
public @interface EnableWebSecurity {
/**
* Controls debugging support for Spring Security. Default is false.
* @return if true, enables debug support with Spring Security
*/
boolean debug() default false;
}
@@ -0,0 +1,39 @@
/*
* 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.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.ClassUtils;
/**
* Used by {@link EnableWebSecurity} to conditionaly import
* {@link CsrfWebMvcConfiguration} when the DispatcherServlet is present on the
* classpath.
*
* @author Rob Winch
* @since 3.2
*/
class SpringWebMvcImportSelector implements ImportSelector {
/* (non-Javadoc)
* @see org.springframework.context.annotation.ImportSelector#selectImports(org.springframework.core.type.AnnotationMetadata)
*/
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.CsrfWebMvcConfiguration"} : new String[] {};
}
}
@@ -0,0 +1,187 @@
/*
* 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.util.Collections;
import java.util.List;
import java.util.Map;
import javax.servlet.Filter;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.ImportAware;
import org.springframework.core.OrderComparator;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.security.access.expression.SecurityExpressionHandler;
import org.springframework.security.config.annotation.SecurityConfigurer;
import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
import org.springframework.util.ClassUtils;
/**
* Uses a {@link WebSecurity} to create the {@link FilterChainProxy} that
* performs the web based security for Spring Security. It then exports the
* necessary beans. Customizations can be made to {@link WebSecurity} by
* extending {@link WebSecurityConfigurerAdapter} and exposing it as a
* {@link Configuration} or implementing {@link WebSecurityConfigurer} and
* exposing it as a {@link Configuration}. This configuration is imported when
* using {@link EnableWebSecurity}.
*
* @see EnableWebSecurity
* @see WebSecurity
*
* @author Rob Winch
* @author Keesun Baik
* @since 3.2
*/
@Configuration
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
private final WebSecurity webSecurity = new WebSecurity();
private List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers;
private ClassLoader beanClassLoader;
@Bean
@DependsOn(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public SecurityExpressionHandler<FilterInvocation> webSecurityExpressionHandler() {
return webSecurity.getExpressionHandler();
}
/**
* Creates the Spring Security Filter Chain
* @return
* @throws Exception
*/
@Bean(name=AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public Filter springSecurityFilterChain() throws Exception {
boolean hasConfigurers = webSecurityConfigurers != null && !webSecurityConfigurers.isEmpty();
if(!hasConfigurers) {
throw new IllegalStateException("At least one non-null instance of "+ WebSecurityConfigurer.class.getSimpleName()+" must be exposed as a @Bean when using @EnableWebSecurity. Hint try extending "+ WebSecurityConfigurerAdapter.class.getSimpleName());
}
return webSecurity.build();
}
/**
* Creates the {@link WebInvocationPrivilegeEvaluator} that is necessary for the JSP tag support.
* @return the {@link WebInvocationPrivilegeEvaluator}
* @throws Exception
*/
@Bean
@DependsOn(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public WebInvocationPrivilegeEvaluator privilegeEvaluator() throws Exception {
return webSecurity.getPrivilegeEvaluator();
}
/**
* Sets the {@code <SecurityConfigurer<FilterChainProxy, WebSecurityBuilder>} instances used to create the web configuration.
*
* @param webSecurityConfigurers the {@code <SecurityConfigurer<FilterChainProxy, WebSecurityBuilder>} instances used to create the web configuration
* @throws Exception
*/
@Autowired(required = false)
public void setFilterChainProxySecurityConfigurer(
List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers) throws Exception {
Collections.sort(webSecurityConfigurers, AnnotationAwareOrderComparator.INSTANCE);
Integer previousOrder = null;
for(SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) {
Integer order = AnnotationAwareOrderComparator.lookupOrder(config);
if(previousOrder != null && previousOrder.equals(order)) {
throw new IllegalStateException("@Order on WebSecurityConfigurers must be unique. Order of " + order + " was already used, so it cannot be used on " + config + " too.");
}
previousOrder = order;
}
for(SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
webSecurity.apply(webSecurityConfigurer);
}
this.webSecurityConfigurers = webSecurityConfigurers;
}
/**
* A custom verision of the Spring provided AnnotationAwareOrderComparator
* that uses {@link AnnotationUtils#findAnnotation(Class, Class)} to look on
* super class instances for the {@link Order} annotation.
*
* @author Rob Winch
* @since 3.2
*/
private static class AnnotationAwareOrderComparator extends OrderComparator {
private static final AnnotationAwareOrderComparator INSTANCE = new AnnotationAwareOrderComparator();
@Override
protected int getOrder(Object obj) {
return lookupOrder(obj);
}
private static int lookupOrder(Object obj) {
if (obj instanceof Ordered) {
return ((Ordered) obj).getOrder();
}
if (obj != null) {
Class<?> clazz = (obj instanceof Class ? (Class<?>) obj : obj.getClass());
Order order = AnnotationUtils.findAnnotation(clazz,Order.class);
if (order != null) {
return order.value();
}
}
return Ordered.LOWEST_PRECEDENCE;
}
}
/* (non-Javadoc)
* @see org.springframework.context.annotation.ImportAware#setImportMetadata(org.springframework.core.type.AnnotationMetadata)
*/
public void setImportMetadata(AnnotationMetadata importMetadata) {
Map<String, Object> enableWebSecurityAttrMap = importMetadata.getAnnotationAttributes(EnableWebSecurity.class.getName());
AnnotationAttributes enableWebSecurityAttrs = AnnotationAttributes.fromMap(enableWebSecurityAttrMap);
if(enableWebSecurityAttrs == null) {
// search parent classes
Class<?> currentClass = ClassUtils.resolveClassName(importMetadata.getClassName(), beanClassLoader);
for(Class<?> classToInspect = currentClass ;classToInspect != null; classToInspect = classToInspect.getSuperclass()) {
EnableWebSecurity enableWebSecurityAnnotation = AnnotationUtils.findAnnotation(classToInspect, EnableWebSecurity.class);
if(enableWebSecurityAnnotation == null) {
continue;
}
enableWebSecurityAttrMap = AnnotationUtils
.getAnnotationAttributes(enableWebSecurityAnnotation);
enableWebSecurityAttrs = AnnotationAttributes.fromMap(enableWebSecurityAttrMap);
}
}
boolean debugEnabled = enableWebSecurityAttrs.getBoolean("debug");
this.webSecurity.debug(debugEnabled);
}
/* (non-Javadoc)
* @see org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(java.lang.ClassLoader)
*/
public void setBeanClassLoader(ClassLoader classLoader) {
this.beanClassLoader = classLoader;
}
}
@@ -0,0 +1,391 @@
/*
* 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 javax.servlet.Filter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.SecurityConfigurer;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
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.configurers.DefaultLoginPageConfigurer;
import org.springframework.security.config.annotation.web.configurers.SecurityContextConfigurer;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;
import org.springframework.web.accept.ContentNegotiationStrategy;
import org.springframework.web.accept.HeaderContentNegotiationStrategy;
/**
* Provides a convenient base class for creating a {@link WebSecurityConfigurer}
* instance. The implementation allows customization by overriding methods.
*
* @see EnableWebSecurity
*
* @author Rob Winch
*/
public abstract class WebSecurityConfigurerAdapter implements SecurityConfigurer<Filter,WebSecurity> {
private final Log logger = LogFactory.getLog(WebSecurityConfigurerAdapter.class);
private ApplicationContext context;
private ContentNegotiationStrategy contentNegotiationStrategy = new HeaderContentNegotiationStrategy();
private ObjectPostProcessor<Object> objectPostProcessor = new ObjectPostProcessor<Object>() {
public <T> T postProcess(T object) {
throw new IllegalStateException(ObjectPostProcessor.class.getName()+ " is a required bean. Ensure you have used @EnableWebSecurity and @Configuration");
}
};
private final AuthenticationManagerBuilder authenticationBuilder = new AuthenticationManagerBuilder(ObjectPostProcessor.QUIESCENT_POSTPROCESSOR);
private final AuthenticationManagerBuilder parentAuthenticationBuilder = new AuthenticationManagerBuilder(ObjectPostProcessor.QUIESCENT_POSTPROCESSOR) {
@Override
public AuthenticationManagerBuilder eraseCredentials(boolean eraseCredentials) {
authenticationBuilder.eraseCredentials(eraseCredentials);
return super.eraseCredentials(eraseCredentials);
}
};
private boolean disableAuthenticationRegistration;
private boolean authenticationManagerInitialized;
private AuthenticationManager authenticationManager;
private HttpSecurity http;
private boolean disableDefaults;
/**
* Creates an instance with the default configuration enabled.
*/
protected WebSecurityConfigurerAdapter() {
this(false);
}
/**
* Creates an instance which allows specifying if the default configuration
* should be enabled. Disabling the default configuration should be
* considered more advanced usage as it requires more understanding of how
* the framework is implemented.
*
* @param disableDefaults
* true if the default configuration should be enabled, else
* false
*/
protected WebSecurityConfigurerAdapter(boolean disableDefaults) {
this.disableDefaults = disableDefaults;
}
/**
* Used by the default implementation of {@link #authenticationManager()} to attempt to obtain an
* {@link AuthenticationManager}. If overridden, the {@link AuthenticationManagerBuilder} should be used to specify
* the {@link AuthenticationManager}. The resulting {@link AuthenticationManager}
* will be exposed as a Bean as will the last populated {@link UserDetailsService} that is created with the
* {@link AuthenticationManagerBuilder}. The {@link UserDetailsService} will also automatically be populated on
* {@link HttpSecurity#getSharedObject(Class)} for use with other {@link SecurityContextConfigurer}
* (i.e. RememberMeConfigurer )
*
* <p>For example, the following configuration could be used to register
* in memory authentication that exposes an in memory {@link UserDetailsService}:</p>
*
* <pre>
* &#064;Override
* protected void registerAuthentication(AuthenticationManagerBuilder auth) {
* auth
* // enable in memory based authentication with a user named "user" and "admin"
* .inMemoryAuthentication()
* .withUser("user").password("password").roles("USER").and()
* .withUser("admin").password("password").roles("USER", "ADMIN");
* }
* </pre>
*
* @param auth the {@link AuthenticationManagerBuilder} to use
* @throws Exception
*/
protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
this.disableAuthenticationRegistration = true;
}
/**
* Creates the {@link HttpSecurity} or returns the current instance
*
* @return the {@link HttpSecurity}
* @throws Exception
*/
protected final HttpSecurity getHttp() throws Exception {
if(http != null) {
return http;
}
authenticationBuilder.objectPostProcessor(objectPostProcessor);
parentAuthenticationBuilder.objectPostProcessor(objectPostProcessor);
DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor.postProcess(new DefaultAuthenticationEventPublisher());
parentAuthenticationBuilder.authenticationEventPublisher(eventPublisher);
AuthenticationManager authenticationManager = authenticationManager();
authenticationBuilder.parentAuthenticationManager(authenticationManager);
http = new HttpSecurity(objectPostProcessor,authenticationBuilder, parentAuthenticationBuilder.getSharedObjects());
http.setSharedObject(UserDetailsService.class, userDetailsService());
http.setSharedObject(ContentNegotiationStrategy.class, contentNegotiationStrategy);
if(!disableDefaults) {
http
.csrf().and()
.addFilter(new WebAsyncManagerIntegrationFilter())
.exceptionHandling().and()
.headers().and()
.sessionManagement().and()
.securityContext().and()
.requestCache().and()
.anonymous().and()
.servletApi().and()
.apply(new DefaultLoginPageConfigurer<HttpSecurity>()).and()
.logout();
}
configure(http);
return http;
}
/**
* Override this method to expose the {@link AuthenticationManager} from
* {@link #registerAuthentication(AuthenticationManagerBuilder)} to be exposed as
* a Bean. For example:
*
* <pre>
* &#064;Bean(name name="myAuthenticationManager")
* &#064;Override
* public AuthenticationManager authenticationManagerBean() throws Exception {
* return super.authenticationManagerBean();
* }
* </pre>
*
* @return the {@link AuthenticationManager}
* @throws Exception
*/
public AuthenticationManager authenticationManagerBean() throws Exception {
return new AuthenticationManagerDelegator(authenticationBuilder);
}
/**
* Gets the {@link AuthenticationManager} to use. The default strategy is if
* {@link #registerAuthentication(AuthenticationManagerBuilder)} method is
* overridden to use the {@link AuthenticationManagerBuilder} that was passed in.
* Otherwise, autowire the {@link AuthenticationManager} by type.
*
* @return
* @throws Exception
*/
protected AuthenticationManager authenticationManager() throws Exception {
if(!authenticationManagerInitialized) {
registerAuthentication(parentAuthenticationBuilder);
if(disableAuthenticationRegistration) {
try {
authenticationManager = context.getBean(AuthenticationManagerBuilder.class).getOrBuild();
} catch(NoSuchBeanDefinitionException e) {
logger.debug("Could not obtain the AuthenticationManagerBuilder. This is ok for now, we will try using an AuthenticationManager directly",e);
}
if(authenticationManager == null) {
try {
authenticationManager = context.getBean(AuthenticationManager.class);
} catch(NoSuchBeanDefinitionException e) {
logger.debug("The AuthenticationManager was not found. This is ok for now as it may not be required.",e);
}
}
} else {
authenticationManager = parentAuthenticationBuilder.build();
}
authenticationManagerInitialized = true;
}
return authenticationManager;
}
/**
* Override this method to expose a {@link UserDetailsService} created from
* {@link #registerAuthentication(AuthenticationManagerBuilder)} as a bean. In
* general only the following override should be done of this method:
*
* <pre>
* &#064;Bean(name = "myUserDetailsService") // any or no name specified is allowed
* &#064;Override
* public UserDetailsService userDetailsServiceBean() throws Exception {
* return super.userDetailsServiceBean();
* }
* </pre>
*
* To change the instance returned, developers should change
* {@link #userDetailsService()} instead
* @return
* @throws Exception
* @see {@link #userDetailsService()}
*/
public UserDetailsService userDetailsServiceBean() throws Exception {
return new UserDetailsServiceDelegator(parentAuthenticationBuilder);
}
/**
* Allows modifying and accessing the {@link UserDetailsService} from
* {@link #userDetailsServiceBean()()} without interacting with the
* {@link ApplicationContext}. Developers should override this method when
* changing the instance of {@link #userDetailsServiceBean()}.
*
* @return
*/
protected UserDetailsService userDetailsService() {
return parentAuthenticationBuilder.getDefaultUserDetailsService();
}
public void init(final WebSecurity web) throws Exception {
final HttpSecurity http = getHttp();
web
.addSecurityFilterChainBuilder(http)
.postBuildAction(new Runnable() {
public void run() {
FilterSecurityInterceptor securityInterceptor = http.getSharedObject(FilterSecurityInterceptor.class);
web.securityInterceptor(securityInterceptor);
}
});
}
/**
* Override this method to configure {@link WebSecurity}. For
* example, if you wish to ignore certain requests.
*/
public void configure(WebSecurity web) throws Exception {
}
/**
* Override this method to configure the {@link HttpSecurity}.
* Typically subclasses should not invoke this method by calling super
* as it may override their configuration. The default configuration is:
*
* <pre>
* http
* .authorizeRequests()
* .anyRequest().authenticated().and()
* .formLogin().and()
* .httpBasic();
* </pre>
*
* @param http
* the {@link HttpSecurity} to modify
* @throws Exception
* if an error occurs
*/
protected void configure(HttpSecurity http) throws Exception {
logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().and()
.httpBasic();
}
@Autowired
public void setApplicationContext(ApplicationContext context) {
this.context = context;
}
@Autowired(required=false)
public void setContentNegotationStrategy(ContentNegotiationStrategy contentNegotiationStrategy) {
this.contentNegotiationStrategy = contentNegotiationStrategy;
}
@Autowired(required=false)
public void setObjectPostProcessor(ObjectPostProcessor<Object> objectPostProcessor) {
this.objectPostProcessor = objectPostProcessor;
}
/**
* Delays the use of the {@link UserDetailsService} from the
* {@link AuthenticationManagerBuilder} to ensure that it has been fully
* configured.
*
* @author Rob Winch
* @since 3.2
*/
static final class UserDetailsServiceDelegator implements UserDetailsService {
private AuthenticationManagerBuilder delegateBuilder;
private UserDetailsService delegate;
private final Object delegateMonitor = new Object();
UserDetailsServiceDelegator(AuthenticationManagerBuilder authentication) {
this.delegateBuilder = authentication;
}
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if(delegate != null) {
return delegate.loadUserByUsername(username);
}
synchronized(delegateMonitor) {
if (delegate == null) {
delegate = this.delegateBuilder.getDefaultUserDetailsService();
this.delegateBuilder = null;
}
}
return delegate.loadUserByUsername(username);
}
}
/**
* Delays the use of the {@link AuthenticationManager} build from the
* {@link AuthenticationManagerBuilder} to ensure that it has been fully
* configured.
*
* @author Rob Winch
* @since 3.2
*/
static final class AuthenticationManagerDelegator implements AuthenticationManager {
private AuthenticationManagerBuilder delegateBuilder;
private AuthenticationManager delegate;
private final Object delegateMonitor = new Object();
AuthenticationManagerDelegator(AuthenticationManagerBuilder authentication) {
this.delegateBuilder = authentication;
}
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if(delegate != null) {
return delegate.authenticate(authentication);
}
synchronized(delegateMonitor) {
if (delegate == null) {
delegate = this.delegateBuilder.getObject();
this.delegateBuilder = null;
}
}
return delegate.authenticate(authentication);
}
}
}
@@ -0,0 +1,367 @@
/*
* 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 javax.servlet.http.HttpServletRequest;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.openid.OpenIDLoginConfigurer;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.PortMapper;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.security.web.util.MediaTypeRequestMatcher;
import org.springframework.security.web.util.RequestMatcher;
import org.springframework.web.accept.ContentNegotiationStrategy;
import org.springframework.web.accept.HeaderContentNegotiationStrategy;
/**
* Base class for confuring {@link AbstractAuthenticationFilterConfigurer}. This is intended for internal use only.
*
* @see FormLoginConfigurer
* @see OpenIDLoginConfigurer
*
* @param T refers to "this" for returning the current configurer
* @param F refers to the {@link AbstractAuthenticationProcessingFilter} that is being built
*
* @author Rob Winch
* @since 3.2
*/
public abstract class AbstractAuthenticationFilterConfigurer<B extends HttpSecurityBuilder<B>,T extends AbstractAuthenticationFilterConfigurer<B,T, F>, F extends AbstractAuthenticationProcessingFilter> extends AbstractHttpConfigurer<T,B> {
private final F authFilter;
private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource;
private AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
private LoginUrlAuthenticationEntryPoint authenticationEntryPoint;
private boolean customLoginPage;
private String loginPage;
private String loginProcessingUrl;
private AuthenticationFailureHandler failureHandler;
private boolean permitAll;
private String failureUrl;
/**
* Creates a new instance
* @param authenticationFilter the {@link AbstractAuthenticationProcessingFilter} to use
* @param defaultLoginProcessingUrl the default URL to use for {@link #loginProcessingUrl(String)}
*/
protected AbstractAuthenticationFilterConfigurer(F authenticationFilter, String defaultLoginProcessingUrl) {
this.authFilter = authenticationFilter;
setLoginPage("/login");
if(defaultLoginProcessingUrl != null) {
loginProcessingUrl(defaultLoginProcessingUrl);
}
}
/**
* Specifies where users will go after authenticating successfully if they
* have not visited a secured page prior to authenticating. This is a
* shortcut for calling {@link #defaultSuccessUrl(String)}.
*
* @param defaultSuccessUrl
* the default success url
* @return the {@link FormLoginConfigurer} for additional customization
*/
public final T defaultSuccessUrl(String defaultSuccessUrl) {
return defaultSuccessUrl(defaultSuccessUrl, false);
}
/**
* Specifies where users will go after authenticating successfully if they
* have not visited a secured page prior to authenticating or
* {@code alwaysUse} is true. This is a shortcut for calling
* {@link #successHandler(AuthenticationSuccessHandler)}.
*
* @param defaultSuccessUrl
* the default success url
* @param alwaysUse
* true if the {@code defaultSuccesUrl} should be used after
* authentication despite if a protected page had been previously
* visited
* @return the {@link FormLoginConfigurer} for additional customization
*/
public final T defaultSuccessUrl(String defaultSuccessUrl, boolean alwaysUse) {
SavedRequestAwareAuthenticationSuccessHandler handler = new SavedRequestAwareAuthenticationSuccessHandler();
handler.setDefaultTargetUrl(defaultSuccessUrl);
handler.setAlwaysUseDefaultTargetUrl(alwaysUse);
return successHandler(handler);
}
/**
* Specifies the URL to validate the credentials.
*
* @param loginProcessingUrl
* the URL to validate username and password
* @return the {@link FormLoginConfigurer} for additional customization
*/
public T loginProcessingUrl(String loginProcessingUrl) {
this.loginProcessingUrl = loginProcessingUrl;
authFilter.setRequiresAuthenticationRequestMatcher(createLoginProcessingUrlMatcher(loginProcessingUrl));
return getSelf();
}
/**
* Create the {@link RequestMatcher} given a loginProcessingUrl
* @param loginProcessingUrl creates the {@link RequestMatcher} based upon the loginProcessingUrl
* @return the {@link RequestMatcher} to use based upon the loginProcessingUrl
*/
protected abstract RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl);
/**
* Specifies a custom {@link AuthenticationDetailsSource}. The default is {@link WebAuthenticationDetailsSource}.
*
* @param authenticationDetailsSource the custom {@link AuthenticationDetailsSource}
* @return the {@link FormLoginConfigurer} for additional customization
*/
public final T authenticationDetailsSource(AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource) {
this.authenticationDetailsSource = authenticationDetailsSource;
return getSelf();
}
/**
* Specifies the {@link AuthenticationSuccessHandler} to be used. The
* default is {@link SavedRequestAwareAuthenticationSuccessHandler} with no
* additional properites set.
*
* @param successHandler
* the {@link AuthenticationSuccessHandler}.
* @return the {@link FormLoginConfigurer} for additional customization
*/
public final T successHandler(AuthenticationSuccessHandler successHandler) {
this.successHandler = successHandler;
return getSelf();
}
/**
* Equivalent of invoking permitAll(true)
* @return
*/
public final T permitAll() {
return permitAll(true);
}
/**
* Ensures the urls for {@link #failureUrl(String)} and
* {@link #authenticationUrls(String)} are granted access to any user.
*
* @param permitAll true to grant access to the URLs false to skip this step
* @return the {@link FormLoginConfigurer} for additional customization
*/
public final T permitAll(boolean permitAll) {
this.permitAll = permitAll;
return getSelf();
}
/**
* The URL to send users if authentication fails. This is a shortcut for
* invoking {@link #failureHandler(AuthenticationFailureHandler)}. The
* default is "/login?error".
*
* @param authenticationFailureUrl
* the URL to send users if authentication fails (i.e.
* "/login?error").
* @return the {@link FormLoginConfigurer} for additional customization
*/
public final T failureUrl(String authenticationFailureUrl) {
T result = failureHandler(new SimpleUrlAuthenticationFailureHandler(authenticationFailureUrl));
this.failureUrl = authenticationFailureUrl;
return result;
}
/**
* Specifies the {@link AuthenticationFailureHandler} to use when
* authentication fails. The default is redirecting to "/login?error" using
* {@link SimpleUrlAuthenticationFailureHandler}
*
* @param authenticationFailureHandler
* the {@link AuthenticationFailureHandler} to use when
* authentication fails.
* @return the {@link FormLoginConfigurer} for additional customization
*/
public final T failureHandler(AuthenticationFailureHandler authenticationFailureHandler) {
this.failureUrl = null;
this.failureHandler = authenticationFailureHandler;
return getSelf();
}
@Override
public void init(B http) throws Exception {
updateAuthenticationDefaults();
if(permitAll) {
PermitAllSupport.permitAll(http, loginPage, loginProcessingUrl, failureUrl);
}
registerDefaultAuthenticationEntryPoint(http);
}
@SuppressWarnings("unchecked")
private void registerDefaultAuthenticationEntryPoint(B http) {
ExceptionHandlingConfigurer<B> exceptionHandling = http.getConfigurer(ExceptionHandlingConfigurer.class);
if(exceptionHandling == null) {
return;
}
ContentNegotiationStrategy contentNegotiationStrategy = http.getSharedObject(ContentNegotiationStrategy.class);
if(contentNegotiationStrategy == null) {
contentNegotiationStrategy = new HeaderContentNegotiationStrategy();
}
RequestMatcher preferredMatcher = new MediaTypeRequestMatcher(contentNegotiationStrategy, MediaType.APPLICATION_XHTML_XML, new MediaType("image","*"), MediaType.TEXT_HTML, MediaType.TEXT_PLAIN);
exceptionHandling.defaultAuthenticationEntryPointFor(postProcess(authenticationEntryPoint), preferredMatcher);
}
@Override
public void configure(B http) throws Exception {
PortMapper portMapper = http.getSharedObject(PortMapper.class);
if(portMapper != null) {
authenticationEntryPoint.setPortMapper(portMapper);
}
authFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
authFilter.setAuthenticationSuccessHandler(successHandler);
authFilter.setAuthenticationFailureHandler(failureHandler);
if(authenticationDetailsSource != null) {
authFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
}
SessionAuthenticationStrategy sessionAuthenticationStrategy = http.getSharedObject(SessionAuthenticationStrategy.class);
if(sessionAuthenticationStrategy != null) {
authFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy);
}
RememberMeServices rememberMeServices = http.getSharedObject(RememberMeServices.class);
if(rememberMeServices != null) {
authFilter.setRememberMeServices(rememberMeServices);
}
F filter = postProcess(authFilter);
http.addFilter(filter);
}
/**
* <p>
* Specifies the URL to send users to if login is required. If used with
* {@link WebSecurityConfigurerAdapter} a default login page will be
* generated when this attribute is not specified.
* </p>
*
* <p>
* If a URL is specified or this is not being used in conjuction with
* {@link WebSecurityConfigurerAdapter}, users are required to process the
* specified URL to generate a login page.
* </p>
*/
protected T loginPage(String loginPage) {
setLoginPage(loginPage);
updateAuthenticationDefaults();
this.customLoginPage = true;
return getSelf();
}
/**
*
* @return true if a custom login page has been specified, else false
*/
public final boolean isCustomLoginPage() {
return customLoginPage;
}
/**
* Gets the Authentication Filter
*
* @return
*/
protected final F getAuthenticationFilter() {
return authFilter;
}
/**
* Gets the login page
*
* @return the login page
*/
protected final String getLoginPage() {
return loginPage;
}
/**
* Gets the URL to submit an authentication request to (i.e. where
* username/password must be submitted)
*
* @return the URL to submit an authentication request to
*/
protected final String getLoginProcessingUrl() {
return loginProcessingUrl;
}
/**
* Gets the URL to send users to if authentication fails
*
* @return
*/
protected final String getFailureUrl() {
return failureUrl;
}
/**
* Updates the default values for authentication.
*
* @throws Exception
*/
private void updateAuthenticationDefaults() {
if (loginProcessingUrl == null) {
loginProcessingUrl(loginPage);
}
if (failureHandler == null) {
failureUrl(loginPage + "?error");
}
final LogoutConfigurer<B> logoutConfigurer = getBuilder()
.getConfigurer(LogoutConfigurer.class);
if (logoutConfigurer != null
&& !logoutConfigurer.isCustomLogoutSuccess()) {
logoutConfigurer.logoutSuccessUrl(loginPage + "?logout");
}
}
/**
* Sets the loginPage and updates the {@link AuthenticationEntryPoint}.
* @param loginPage
*/
private void setLoginPage(String loginPage) {
this.loginPage = loginPage;
this.authenticationEntryPoint = new LoginUrlAuthenticationEntryPoint(
loginPage);
}
@SuppressWarnings("unchecked")
private T getSelf() {
return (T) this;
}
}
@@ -0,0 +1,51 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.configurers;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.SecurityConfigurer;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultSecurityFilterChain;
/**
* Adds a convenient base class for {@link SecurityConfigurer} instances that
* operate on {@link HttpSecurity}.
*
* @author Rob Winch
*
*/
abstract class AbstractHttpConfigurer<T extends AbstractHttpConfigurer<T, B>,B extends HttpSecurityBuilder<B>> extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, B> {
/**
* Disables the {@link AbstractHttpConfigurer} by removing it. After doing
* so a fresh version of the configuration can be applied.
*
* @return the {@link HttpSecurityBuilder} for additional customizations
*/
@SuppressWarnings("unchecked")
public B disable() {
getBuilder().removeConfigurer(getClass());
return getBuilder();
}
@SuppressWarnings("unchecked")
public T withObjectPostProcessor(ObjectPostProcessor<?> objectPostProcessor) {
addObjectPostProcessor(objectPostProcessor);
return (T) this;
}
}
@@ -0,0 +1,182 @@
/*
* 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 java.util.List;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.vote.AffirmativeBased;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.SecurityConfigurer;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
/**
* A base class for configuring the {@link FilterSecurityInterceptor}.
*
* <h2>Security Filters</h2>
*
* The following Filters are populated
*
* <ul>
* <li>{@link FilterSecurityInterceptor}</li>
* </ul>
*
* <h2>Shared Objects Created</h2>
*
* The following shared objects are populated to allow other {@link SecurityConfigurer}'s to customize:
* <ul>
* <li>{@link FilterSecurityInterceptor}</li>
* </ul>
*
* <h2>Shared Objects Used</h2>
*
* The following shared objects are used:
*
* <ul>
* <li>{@link org.springframework.security.config.annotation.web.builders.HttpSecurity#getAuthenticationManager()}</li>
* </ul>
*
* @param <H> the type of {@link HttpSecurityBuilder} that is being configured
* @param <C> the type of object that is changed
* @param <R> the type of object that is changed for the {@link AbstractRequestMatcherMappingConfigurer}
*
* @author Rob Winch
* @since 3.2
* @see ExpressionUrlAuthorizationConfigurer
* @see UrlAuthorizationConfigurer
*/
abstract class AbstractInterceptUrlConfigurer<H extends HttpSecurityBuilder<H>,C,R> extends
AbstractRequestMatcherMappingConfigurer<H,R,DefaultSecurityFilterChain> implements
SecurityConfigurer<DefaultSecurityFilterChain,H> {
private Boolean filterSecurityInterceptorOncePerRequest;
private AccessDecisionManager accessDecisionManager;
/**
* Allows setting the {@link AccessDecisionManager}. If none is provided, a default {@l AccessDecisionManager} is
* created.
*
* @param accessDecisionManager the {@link AccessDecisionManager} to use
* @return the {@link AbstractInterceptUrlConfigurer} for further customization
*/
public C accessDecisionManager(
AccessDecisionManager accessDecisionManager) {
this.accessDecisionManager = accessDecisionManager;
return getSelf();
}
/**
* Allows setting if the {@link FilterSecurityInterceptor} should be only applied once per request (i.e. if the
* filter intercepts on a forward, should it be applied again).
*
* @param filterSecurityInterceptorOncePerRequest if the {@link FilterSecurityInterceptor} should be only applied
* once per request
* @return the {@link AbstractInterceptUrlConfigurer} for further customization
*/
public C filterSecurityInterceptorOncePerRequest(
boolean filterSecurityInterceptorOncePerRequest) {
this.filterSecurityInterceptorOncePerRequest = filterSecurityInterceptorOncePerRequest;
return getSelf();
}
@Override
public void configure(H http) throws Exception {
FilterInvocationSecurityMetadataSource metadataSource = createMetadataSource();
if(metadataSource == null) {
return;
}
FilterSecurityInterceptor securityInterceptor = createFilterSecurityInterceptor(metadataSource, http.getSharedObject(AuthenticationManager.class));
if(filterSecurityInterceptorOncePerRequest != null) {
securityInterceptor.setObserveOncePerRequest(filterSecurityInterceptorOncePerRequest);
}
securityInterceptor = postProcess(securityInterceptor);
http.addFilter(securityInterceptor);
http.setSharedObject(FilterSecurityInterceptor.class, securityInterceptor);
}
/**
* Subclasses should implement this method to provide a {@link FilterInvocationSecurityMetadataSource} for the
* {@link FilterSecurityInterceptor}.
*
* @return the {@link FilterInvocationSecurityMetadataSource} to set on the {@link FilterSecurityInterceptor}.
* Cannot be null.
*/
abstract FilterInvocationSecurityMetadataSource createMetadataSource();
/**
* Subclasses should implement this method to provide the {@link AccessDecisionVoter} instances used to create the
* default {@link AccessDecisionManager}
*
* @return the {@link AccessDecisionVoter} instances used to create the
* default {@link AccessDecisionManager}
*/
@SuppressWarnings("rawtypes")
abstract List<AccessDecisionVoter> getDecisionVoters();
/**
* Creates the default {@code AccessDecisionManager}
* @return the default {@code AccessDecisionManager}
*/
private AccessDecisionManager createDefaultAccessDecisionManager() {
return new AffirmativeBased(getDecisionVoters());
}
/**
* If currently null, creates a default {@link AccessDecisionManager} using
* {@link #createDefaultAccessDecisionManager()}. Otherwise returns the {@link AccessDecisionManager}.
*
* @return the {@link AccessDecisionManager} to use
*/
private AccessDecisionManager getAccessDecisionManager() {
if (accessDecisionManager == null) {
accessDecisionManager = createDefaultAccessDecisionManager();
}
return accessDecisionManager;
}
/**
* Creates the {@link FilterSecurityInterceptor}
*
* @param metadataSource the {@link FilterInvocationSecurityMetadataSource} to use
* @param authenticationManager the {@link AuthenticationManager} to use
* @return the {@link FilterSecurityInterceptor}
* @throws Exception
*/
private FilterSecurityInterceptor createFilterSecurityInterceptor(FilterInvocationSecurityMetadataSource metadataSource,
AuthenticationManager authenticationManager) throws Exception {
FilterSecurityInterceptor securityInterceptor = new FilterSecurityInterceptor();
securityInterceptor.setSecurityMetadataSource(metadataSource);
securityInterceptor.setAccessDecisionManager(getAccessDecisionManager());
securityInterceptor.setAuthenticationManager(authenticationManager);
securityInterceptor.afterPropertiesSet();
return securityInterceptor;
}
/**
* Returns a reference to the current object with a single suppression of
* the type
*
* @return a reference to the current object
*/
@SuppressWarnings("unchecked")
private C getSelf() {
return (C) this;
}
}
@@ -0,0 +1,144 @@
/*
* 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 java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.config.annotation.SecurityBuilder;
import org.springframework.security.config.annotation.web.AbstractRequestMatcherConfigurer;
import org.springframework.security.web.util.RequestMatcher;
/**
* A base class for registering {@link RequestMatcher}'s. For example, it might allow for specifying which
* {@link RequestMatcher} require a certain level of authorization.
*
* @author Rob Winch
* @since 3.2
*
* @param <B> The Builder that is building Object O and is configured by this {@link AbstractRequestMatcherMappingConfigurer}
* @param <C> The object that is returned or Chained after creating the RequestMatcher
* @param <O> The Object being built by Builder B
*
* @see ChannelSecurityConfigurer
* @see UrlAuthorizationConfigurer
* @see ExpressionUrlAuthorizationConfigurer
*/
public abstract class AbstractRequestMatcherMappingConfigurer<B extends SecurityBuilder<O>,C,O> extends AbstractRequestMatcherConfigurer<B,C,O> {
private List<UrlMapping> urlMappings = new ArrayList<UrlMapping>();
private List<RequestMatcher> unmappedMatchers;
/**
* Gets the {@link UrlMapping} added by subclasses in {@link #chainRequestMatchers(java.util.List)}. May be empty.
*
* @return the {@link UrlMapping} added by subclasses in {@link #chainRequestMatchers(java.util.List)}
*/
final List<UrlMapping> getUrlMappings() {
return urlMappings;
}
/**
* Adds a {@link UrlMapping} added by subclasses in
* {@link #chainRequestMatchers(java.util.List)} and resets the unmapped
* {@link RequestMatcher}'s.
*
* @param urlMapping
* {@link UrlMapping} the mapping to add
*/
final void addMapping(UrlMapping urlMapping) {
this.unmappedMatchers = null;
this.urlMappings.add(urlMapping);
}
/**
* Marks the {@link RequestMatcher}'s as unmapped and then calls {@link #chainRequestMatchersInternal(List)}.
*
* @param requestMatchers the {@link RequestMatcher} instances that were created
* @return the chained Object for the subclass which allows association of something else to the
* {@link RequestMatcher}
*/
protected final C chainRequestMatchers(List<RequestMatcher> requestMatchers) {
this.unmappedMatchers = requestMatchers;
return chainRequestMatchersInternal(requestMatchers);
}
/**
* Subclasses should implement this method for returning the object that is chained to the creation of the
* {@link RequestMatcher} instances.
*
* @param requestMatchers the {@link RequestMatcher} instances that were created
* @return the chained Object for the subclass which allows association of something else to the
* {@link RequestMatcher}
*/
protected abstract C chainRequestMatchersInternal(List<RequestMatcher> requestMatchers);
/**
* Adds a {@link UrlMapping} added by subclasses in {@link #chainRequestMatchers(java.util.List)} at a particular
* index.
*
* @param index the index to add a {@link UrlMapping}
* @param urlMapping {@link UrlMapping} the mapping to add
*/
final void addMapping(int index, UrlMapping urlMapping) {
this.urlMappings.add(index, urlMapping);
}
/**
* Creates the mapping of {@link RequestMatcher} to {@link Collection} of {@link ConfigAttribute} instances
*
* @return the mapping of {@link RequestMatcher} to {@link Collection} of {@link ConfigAttribute} instances. Cannot
* be null.
*/
final LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> createRequestMap() {
if(unmappedMatchers != null) {
throw new IllegalStateException("An incomplete mapping was found for " + unmappedMatchers +". Try completing it with something like requestUrls().<something>.hasRole('USER')");
}
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap = new LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>>();
for (UrlMapping mapping : getUrlMappings()) {
RequestMatcher matcher = mapping.getRequestMatcher();
Collection<ConfigAttribute> configAttrs = mapping.getConfigAttrs();
requestMap.put(matcher,configAttrs);
}
return requestMap;
}
/**
* A mapping of {@link RequestMatcher} to {@link Collection} of {@link ConfigAttribute} instances
*/
static final class UrlMapping {
private RequestMatcher requestMatcher;
private Collection<ConfigAttribute> configAttrs;
UrlMapping(RequestMatcher requestMatcher,
Collection<ConfigAttribute> configAttrs) {
this.requestMatcher = requestMatcher;
this.configAttrs = configAttrs;
}
public RequestMatcher getRequestMatcher() {
return requestMatcher;
}
public Collection<ConfigAttribute> getConfigAttrs() {
return configAttrs;
}
}
}
@@ -0,0 +1,151 @@
/*
* 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 java.util.List;
import java.util.UUID;
import org.springframework.security.authentication.AnonymousAuthenticationProvider;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.SecurityConfigurer;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
/**
* Configures Anonymous authentication (i.e. populate an {@link Authentication} that represents an anonymous user
* instead of having a null value) for an {@link HttpSecurity}. Specifically this will configure an
* {@link AnonymousAuthenticationFilter} and an {@link AnonymousAuthenticationProvider}. All properties have reasonable
* defaults, so no additional configuration is required other than applying this {@link SecurityConfigurer}.
*
* @author Rob Winch
* @since 3.2
*/
public final class AnonymousConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<AnonymousConfigurer<H>,H> {
private String key;
private AuthenticationProvider authenticationProvider;
private AnonymousAuthenticationFilter authenticationFilter;
private Object principal = "anonymousUser";
private List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS");
/**
* Creates a new instance
* @see HttpSecurity#anonymous()
*/
public AnonymousConfigurer() {
}
/**
* Sets the key to identify tokens created for anonymous authentication. Default is a secure randomly generated
* key.
*
* @param key the key to identify tokens created for anonymous authentication. Default is a secure randomly generated
* key.
* @return the {@link AnonymousConfigurer} for further customization of anonymous authentication
*/
public AnonymousConfigurer<H> key(String key) {
this.key = key;
return this;
}
/**
* Sets the principal for {@link Authentication} objects of anonymous users
*
* @param principal used for the {@link Authentication} object of anonymous users
* @return the {@link AnonymousConfigurer} for further customization of anonymous authentication
*/
public AnonymousConfigurer<H> principal(Object principal) {
this.principal = principal;
return this;
}
/**
* Sets the {@link org.springframework.security.core.Authentication#getAuthorities()} for anonymous users
*
* @param authorities Sets the {@link org.springframework.security.core.Authentication#getAuthorities()} for anonymous users
* @return the {@link AnonymousConfigurer} for further customization of anonymous authentication
*/
public AnonymousConfigurer<H> authorities(List<GrantedAuthority> authorities) {
this.authorities = authorities;
return this;
}
/**
* Sets the {@link org.springframework.security.core.Authentication#getAuthorities()} for anonymous users
*
* @param authorities Sets the {@link org.springframework.security.core.Authentication#getAuthorities()} for
* anonymous users (i.e. "ROLE_ANONYMOUS")
* @return the {@link AnonymousConfigurer} for further customization of anonymous authentication
*/
public AnonymousConfigurer<H> authorities(String... authorities) {
return authorities(AuthorityUtils.createAuthorityList(authorities));
}
/**
* Sets the {@link AuthenticationProvider} used to validate an anonymous user. If this is set, no attributes
* on the {@link AnonymousConfigurer} will be set on the {@link AuthenticationProvider}.
*
* @param authenticationProvider the {@link AuthenticationProvider} used to validate an anonymous user. Default is
* {@link AnonymousAuthenticationProvider}
*
* @return the {@link AnonymousConfigurer} for further customization of anonymous authentication
*/
public AnonymousConfigurer<H> authenticationProvider(AuthenticationProvider authenticationProvider) {
this.authenticationProvider = authenticationProvider;
return this;
}
/**
* Sets the {@link AnonymousAuthenticationFilter} used to populate an anonymous user. If this is set, no attributes
* on the {@link AnonymousConfigurer} will be set on the {@link AnonymousAuthenticationFilter}.
*
* @param authenticationFilter the {@link AnonymousAuthenticationFilter} used to populate an anonymous user.
*
* @return the {@link AnonymousConfigurer} for further customization of anonymous authentication
*/
public AnonymousConfigurer<H> authenticationFilter(AnonymousAuthenticationFilter authenticationFilter) {
this.authenticationFilter = authenticationFilter;
return this;
}
@Override
public void init(H http) throws Exception {
if(authenticationProvider == null) {
authenticationProvider = new AnonymousAuthenticationProvider(getKey());
}
if(authenticationFilter == null) {
authenticationFilter = new AnonymousAuthenticationFilter(getKey(), principal, authorities);
}
authenticationProvider = postProcess(authenticationProvider);
http.authenticationProvider(authenticationProvider);
}
@Override
public void configure(H http) throws Exception {
authenticationFilter.afterPropertiesSet();
http.addFilter(authenticationFilter);
}
private String getKey() {
if(key == null) {
key = UUID.randomUUID().toString();
}
return key;
}
}
@@ -0,0 +1,180 @@
/*
* 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 java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.PortMapper;
import org.springframework.security.web.access.channel.ChannelDecisionManagerImpl;
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
import org.springframework.security.web.access.channel.ChannelProcessor;
import org.springframework.security.web.access.channel.InsecureChannelProcessor;
import org.springframework.security.web.access.channel.RetryWithHttpEntryPoint;
import org.springframework.security.web.access.channel.RetryWithHttpsEntryPoint;
import org.springframework.security.web.access.channel.SecureChannelProcessor;
import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource;
import org.springframework.security.web.util.RequestMatcher;
/**
* Adds channel security (i.e. requires HTTPS or HTTP) to an application. In order for
* {@link ChannelSecurityConfigurer} to be useful, at least one {@link RequestMatcher} should be mapped to HTTP
* or HTTPS.
*
* <p>
* By default an {@link InsecureChannelProcessor} and a {@link SecureChannelProcessor} will be registered.
* </p>
*
* <h2>Security Filters</h2>
*
* The following Filters are populated
*
* <ul>
* <li>{@link ChannelProcessingFilter}</li>
* </ul>
*
* <h2>Shared Objects Created</h2>
*
* No shared objects are created.
*
* <h2>Shared Objects Used</h2>
*
* The following shared objects are used:
*
* <ul>
* <li>{@link PortMapper} is used to create the default {@link ChannelProcessor} instances</li>
* </ul>
*
* @param <H> the type of {@link HttpSecurityBuilder} that is being configured
*
* @author Rob Winch
* @since 3.2
*/
public final class ChannelSecurityConfigurer<H extends HttpSecurityBuilder<H>> extends
AbstractRequestMatcherMappingConfigurer<H,ChannelSecurityConfigurer<H>.RequiresChannelUrl,DefaultSecurityFilterChain> {
private ChannelProcessingFilter channelFilter = new ChannelProcessingFilter();
private LinkedHashMap<RequestMatcher,Collection<ConfigAttribute>> requestMap = new LinkedHashMap<RequestMatcher,Collection<ConfigAttribute>>();
private List<ChannelProcessor> channelProcessors;
/**
* Creates a new instance
* @see HttpSecurity#requiresChannel()
*/
public ChannelSecurityConfigurer() {
}
@Override
public void configure(H http) throws Exception {
ChannelDecisionManagerImpl channelDecisionManager = new ChannelDecisionManagerImpl();
channelDecisionManager.setChannelProcessors(getChannelProcessors(http));
channelDecisionManager = postProcess(channelDecisionManager);
channelFilter.setChannelDecisionManager(channelDecisionManager);
DefaultFilterInvocationSecurityMetadataSource filterInvocationSecurityMetadataSource =
new DefaultFilterInvocationSecurityMetadataSource(requestMap);
channelFilter.setSecurityMetadataSource(filterInvocationSecurityMetadataSource);
channelFilter = postProcess(channelFilter);
http.addFilter(channelFilter);
}
/**
* Adds an {@link ObjectPostProcessor} for this class.
*
* @param objectPostProcessor
* @return the {@link ChannelSecurityConfigurer} for further customizations
*/
public ChannelSecurityConfigurer<H> withObjectPostProcessor(ObjectPostProcessor<?> objectPostProcessor) {
addObjectPostProcessor(objectPostProcessor);
return this;
}
/**
* Sets the {@link ChannelProcessor} instances to use in {@link ChannelDecisionManagerImpl}
* @param channelProcessors
* @return
*/
public ChannelSecurityConfigurer<H> channelProcessors(List<ChannelProcessor> channelProcessors) {
this.channelProcessors = channelProcessors;
return this;
}
private List<ChannelProcessor> getChannelProcessors(H http) {
if(channelProcessors != null) {
return channelProcessors;
}
InsecureChannelProcessor insecureChannelProcessor = new InsecureChannelProcessor();
SecureChannelProcessor secureChannelProcessor = new SecureChannelProcessor();
PortMapper portMapper = http.getSharedObject(PortMapper.class);
if(portMapper != null) {
RetryWithHttpEntryPoint httpEntryPoint = new RetryWithHttpEntryPoint();
httpEntryPoint.setPortMapper(portMapper);
insecureChannelProcessor.setEntryPoint(httpEntryPoint);
RetryWithHttpsEntryPoint httpsEntryPoint = new RetryWithHttpsEntryPoint();
httpsEntryPoint.setPortMapper(portMapper);
secureChannelProcessor.setEntryPoint(httpsEntryPoint);
}
insecureChannelProcessor = postProcess(insecureChannelProcessor);
secureChannelProcessor = postProcess(secureChannelProcessor);
return Arrays.<ChannelProcessor>asList(insecureChannelProcessor, secureChannelProcessor);
}
private ChannelSecurityConfigurer<H> addAttribute(String attribute, List<RequestMatcher> matchers) {
for(RequestMatcher matcher : matchers) {
Collection<ConfigAttribute> attrs = Arrays.<ConfigAttribute>asList(new SecurityConfig(attribute));
requestMap.put(matcher, attrs);
}
return this;
}
@Override
protected RequiresChannelUrl chainRequestMatchersInternal(List<RequestMatcher> requestMatchers) {
return new RequiresChannelUrl(requestMatchers);
}
public final class RequiresChannelUrl {
private List<RequestMatcher> requestMatchers;
private RequiresChannelUrl(List<RequestMatcher> requestMatchers) {
this.requestMatchers = requestMatchers;
}
public ChannelSecurityConfigurer<H> requiresSecure() {
return requires("REQUIRES_SECURE_CHANNEL");
}
public ChannelSecurityConfigurer<H> requiresInsecure() {
return requires("REQUIRES_INSECURE_CHANNEL");
}
public ChannelSecurityConfigurer<H> requires(String attribute) {
return addAttribute(attribute, requestMatchers);
}
}
}
@@ -0,0 +1,121 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.configurers;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.csrf.CsrfAuthenticationStrategy;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.csrf.CsrfLogoutHandler;
import org.springframework.security.web.csrf.CsrfTokenRepository;
import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
import org.springframework.security.web.util.RequestMatcher;
import org.springframework.util.Assert;
/**
* Adds <a
* href="https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)"
* >CSRF</a> protection for the methods as specified by
* {@link #requireCsrfProtectionMatcher(RequestMatcher)}.
*
* <h2>Security Filters</h2>
*
* The following Filters are populated
*
* <ul>
* <li>{@link CsrfFilter}</li>
* </ul>
*
* <h2>Shared Objects Created</h2>
*
* No shared objects are created.
*
* <h2>Shared Objects Used</h2>
*
* <ul>
* <li>
* {@link ExceptionHandlingConfigurer#accessDeniedHandler(AccessDeniedHandler)}
* is used to determine how to handle CSRF attempts</li>
* </ul>
*
* @author Rob Winch
* @since 3.2
*/
public final class CsrfConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<CsrfConfigurer<H>,H> {
private CsrfTokenRepository csrfTokenRepository = new HttpSessionCsrfTokenRepository();
private RequestMatcher requireCsrfProtectionMatcher;
/**
* Creates a new instance
* @see HttpSecurity#csrf()
*/
public CsrfConfigurer() {
}
/**
* Specify the {@link CsrfTokenRepository} to use. The default is an {@link HttpSessionCsrfTokenRepository}.
*
* @param csrfTokenRepository the {@link CsrfTokenRepository} to use
* @return the {@link CsrfConfigurer} for further customizations
*/
public CsrfConfigurer<H> csrfTokenRepository(CsrfTokenRepository csrfTokenRepository) {
Assert.notNull(csrfTokenRepository, "csrfTokenRepository cannot be null");
this.csrfTokenRepository = csrfTokenRepository;
return this;
}
/**
* Specify the {@link RequestMatcher} to use for determining when CSRF
* should be applied. The default is to ignore GET, HEAD, TRACE, OPTIONS and
* process all other requests.
*
* @param requireCsrfProtectionMatcher
* the {@link RequestMatcher} to use
* @return the {@link CsrfConfigurer} for further customizations
*/
public CsrfConfigurer<H> requireCsrfProtectionMatcher(RequestMatcher requireCsrfProtectionMatcher) {
Assert.notNull(csrfTokenRepository, "requireCsrfProtectionMatcher cannot be null");
this.requireCsrfProtectionMatcher = requireCsrfProtectionMatcher;
return this;
}
@SuppressWarnings("unchecked")
@Override
public void configure(H http) throws Exception {
CsrfFilter filter = new CsrfFilter(csrfTokenRepository);
if(requireCsrfProtectionMatcher != null) {
filter.setRequireCsrfProtectionMatcher(requireCsrfProtectionMatcher);
}
ExceptionHandlingConfigurer<H> exceptionConfig = http.getConfigurer(ExceptionHandlingConfigurer.class);
if(exceptionConfig != null) {
AccessDeniedHandler accessDeniedHandler = exceptionConfig.getAccessDeniedHandler();
if(accessDeniedHandler != null) {
filter.setAccessDeniedHandler(accessDeniedHandler);
}
}
LogoutConfigurer<H> logoutConfigurer = http.getConfigurer(LogoutConfigurer.class);
if(logoutConfigurer != null) {
logoutConfigurer.addLogoutHandler(new CsrfLogoutHandler(csrfTokenRepository));
}
SessionManagementConfigurer<H> sessionConfigurer = http.getConfigurer(SessionManagementConfigurer.class);
if(sessionConfigurer != null) {
sessionConfigurer.addSessionAuthenticationStrategy(new CsrfAuthenticationStrategy(csrfTokenRepository));
}
filter = postProcess(filter);
http.addFilter(filter);
}
}
@@ -0,0 +1,82 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.configurers;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.ui.DefaultLoginPageViewFilter;
/**
* Adds a Filter that will generate a login page if one is not specified otherwise when using {@link WebSecurityConfigurerAdapter}.
*
* <p>
* By default an {@link org.springframework.security.web.access.channel.InsecureChannelProcessor} and a {@link org.springframework.security.web.access.channel.SecureChannelProcessor} will be registered.
* </p>
*
* <h2>Security Filters</h2>
*
* The following Filters are conditionally populated
*
* <ul>
* <li>{@link DefaultLoginPageViewFilter} if the {@link FormLoginConfigurer} did not have a login page specified</li>
* </ul>
*
* <h2>Shared Objects Created</h2>
*
* No shared objects are created.
*isLogoutRequest
* <h2>Shared Objects Used</h2>
*
* The following shared objects are used:
*
* <ul>
* <li>{@link org.springframework.security.web.PortMapper} is used to create the default {@link org.springframework.security.web.access.channel.ChannelProcessor} instances</li>
* <li>{@link FormLoginConfigurer} is used to determine if the {@link DefaultLoginPageConfigurer} should be added and how to configure it.</li>
* </ul>
*
* @see WebSecurityConfigurerAdapter
*
* @author Rob Winch
* @since 3.2
*/
public final class DefaultLoginPageConfigurer<H extends HttpSecurityBuilder<H>> extends
AbstractHttpConfigurer<DefaultLoginPageConfigurer<H>,H> {
private DefaultLoginPageViewFilter loginPageGeneratingFilter = new DefaultLoginPageViewFilter();
@Override
public void init(H http) throws Exception {
http.setSharedObject(DefaultLoginPageViewFilter.class, loginPageGeneratingFilter);
}
@Override
@SuppressWarnings("unchecked")
public void configure(H http) throws Exception {
AuthenticationEntryPoint authenticationEntryPoint = null;
ExceptionHandlingConfigurer<?> exceptionConf = http.getConfigurer(ExceptionHandlingConfigurer.class);
if(exceptionConf != null) {
authenticationEntryPoint = exceptionConf.getAuthenticationEntryPoint();
}
if(loginPageGeneratingFilter.isEnabled() && authenticationEntryPoint == null) {
loginPageGeneratingFilter = postProcess(loginPageGeneratingFilter);
http.addFilter(loginPageGeneratingFilter);
}
}
}
@@ -0,0 +1,216 @@
/*
* 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 java.util.LinkedHashMap;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.access.AccessDeniedHandlerImpl;
import org.springframework.security.web.access.ExceptionTranslationFilter;
import org.springframework.security.web.authentication.DelegatingAuthenticationEntryPoint;
import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.util.RequestMatcher;
/**
* Adds exception handling for Spring Security related exceptions to an application. All properties have reasonable
* defaults, so no additional configuration is required other than applying this
* {@link org.springframework.security.config.annotation.SecurityConfigurer}.
*
* <h2>Security Filters</h2>
*
* The following Filters are populated
*
* <ul>
* <li>{@link ExceptionTranslationFilter}</li>
* </ul>
*
* <h2>Shared Objects Created</h2>
*
* No shared objects are created.
*
* <h2>Shared Objects Used</h2>
*
* The following shared objects are used:
*
* <ul>
* <li>If no explicit {@link RequestCache}, is provided a {@link RequestCache} shared object is used to replay
* the request after authentication is successful</li>
* <li>{@link AuthenticationEntryPoint} - see {@link #authenticationEntryPoint(AuthenticationEntryPoint)} </li>
* </ul>
*
* @author Rob Winch
* @since 3.2
*/
public final class ExceptionHandlingConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<ExceptionHandlingConfigurer<H>,H> {
private AuthenticationEntryPoint authenticationEntryPoint;
private AccessDeniedHandler accessDeniedHandler;
private LinkedHashMap<RequestMatcher,AuthenticationEntryPoint> defaultEntryPointMappings = new LinkedHashMap<RequestMatcher, AuthenticationEntryPoint>();
/**
* Creates a new instance
* @see HttpSecurity#exceptionHandling()
*/
public ExceptionHandlingConfigurer() {
}
/**
* Shortcut to specify the {@link AccessDeniedHandler} to be used is a specific error page
*
* @param accessDeniedUrl the URL to the access denied page (i.e. /errors/401)
* @return the {@link ExceptionHandlingConfigurer} for further customization
* @see AccessDeniedHandlerImpl
* @see {@link #accessDeniedHandler(org.springframework.security.web.access.AccessDeniedHandler)}
*/
public ExceptionHandlingConfigurer<H> accessDeniedPage(String accessDeniedUrl) {
AccessDeniedHandlerImpl accessDeniedHandler = new AccessDeniedHandlerImpl();
accessDeniedHandler.setErrorPage(accessDeniedUrl);
return accessDeniedHandler(accessDeniedHandler);
}
/**
* Specifies the {@link AccessDeniedHandler} to be used
*
* @param accessDeniedHandler the {@link AccessDeniedHandler} to be used
* @return the {@link ExceptionHandlingConfigurer} for further customization
*/
public ExceptionHandlingConfigurer<H> accessDeniedHandler(AccessDeniedHandler accessDeniedHandler) {
this.accessDeniedHandler = accessDeniedHandler;
return this;
}
/**
* Sets the {@link AuthenticationEntryPoint} to be used.
*
* <p>
* If no {@link #authenticationEntryPoint(AuthenticationEntryPoint)} is
* specified, then
* {@link #defaultAuthenticationEntryPointFor(AuthenticationEntryPoint, RequestMatcher)}
* will be used. The first {@link AuthenticationEntryPoint} will be used as
* the default is no matches were found.
* </p>
*
* <p>
* If that is not provided defaults to {@link Http403ForbiddenEntryPoint}.
* </p>
*
* @param authenticationEntryPoint
* the {@link AuthenticationEntryPoint} to use
* @return the {@link ExceptionHandlingConfigurer} for further
* customizations
*/
public ExceptionHandlingConfigurer<H> authenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) {
this.authenticationEntryPoint = authenticationEntryPoint;
return this;
}
/**
* Sets a default {@link AuthenticationEntryPoint} to be used which prefers
* being invoked for the provided {@link RequestMatcher}. If only a single
* default {@link AuthenticationEntryPoint} is specified, it will be what is
* used for the default {@link AuthenticationEntryPoint}. If multiple
* default {@link AuthenticationEntryPoint} instances are configured, then a
* {@link DelegatingAuthenticationEntryPoint} will be used.
*
* @param entryPoint
* the {@link AuthenticationEntryPoint} to use
* @param preferredMatcher
* the {@link RequestMatcher} for this default
* {@link AuthenticationEntryPoint}
* @return the {@link ExceptionHandlingConfigurer} for further
* customizations
*/
public ExceptionHandlingConfigurer<H> defaultAuthenticationEntryPointFor(AuthenticationEntryPoint entryPoint, RequestMatcher preferredMatcher) {
this.defaultEntryPointMappings.put(preferredMatcher, entryPoint);
return this;
}
/**
* Gets any explicitly configured {@link AuthenticationEntryPoint}
* @return
*/
AuthenticationEntryPoint getAuthenticationEntryPoint() {
return this.authenticationEntryPoint;
}
/**
* Gets the {@link AccessDeniedHandler} that is configured.
*
* @return the {@link AccessDeniedHandler}
*/
AccessDeniedHandler getAccessDeniedHandler() {
return this.accessDeniedHandler;
}
@Override
public void configure(H http) throws Exception {
AuthenticationEntryPoint entryPoint = getAuthenticationEntryPoint(http);
ExceptionTranslationFilter exceptionTranslationFilter = new ExceptionTranslationFilter(entryPoint, getRequestCache(http));
if(accessDeniedHandler != null) {
exceptionTranslationFilter.setAccessDeniedHandler(accessDeniedHandler);
}
exceptionTranslationFilter = postProcess(exceptionTranslationFilter);
http.addFilter(exceptionTranslationFilter);
}
/**
* Gets the {@link AuthenticationEntryPoint} according to the rules specified by {@link #authenticationEntryPoint(AuthenticationEntryPoint)}
* @param http the {@link HttpSecurity} used to look up shared {@link AuthenticationEntryPoint}
* @return the {@link AuthenticationEntryPoint} to use
*/
AuthenticationEntryPoint getAuthenticationEntryPoint(H http) {
AuthenticationEntryPoint entryPoint = this.authenticationEntryPoint;
if(entryPoint == null) {
entryPoint = createDefaultEntryPoint(http);
}
return entryPoint;
}
private AuthenticationEntryPoint createDefaultEntryPoint(H http) {
if(defaultEntryPointMappings.isEmpty()) {
return new Http403ForbiddenEntryPoint();
}
if(defaultEntryPointMappings.size() == 1) {
return defaultEntryPointMappings.values().iterator().next();
}
DelegatingAuthenticationEntryPoint entryPoint = new DelegatingAuthenticationEntryPoint(defaultEntryPointMappings);
entryPoint.setDefaultEntryPoint(defaultEntryPointMappings.values().iterator().next());
return entryPoint;
}
/**
* Gets the {@link RequestCache} to use. If one is defined using
* {@link #requestCache(org.springframework.security.web.savedrequest.RequestCache)}, then it is used. Otherwise, an
* attempt to find a {@link RequestCache} shared object is made. If that fails, an {@link HttpSessionRequestCache}
* is used
*
* @param http the {@link HttpSecurity} to attempt to fined the shared object
* @return the {@link RequestCache} to use
*/
private RequestCache getRequestCache(H http) {
RequestCache result = http.getSharedObject(RequestCache.class);
if(result != null) {
return result;
}
return new HttpSessionRequestCache();
}
}
@@ -0,0 +1,304 @@
/*
* 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 java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.access.expression.SecurityExpressionHandler;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
import org.springframework.security.web.access.expression.ExpressionBasedFilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.expression.WebExpressionVoter;
import org.springframework.security.web.util.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Adds URL based authorization based upon SpEL expressions to an application. At least one
* {@link org.springframework.web.bind.annotation.RequestMapping} needs to be mapped to {@link ConfigAttribute}'s for
* this {@link SecurityContextConfigurer} to have meaning.
* <h2>Security Filters</h2>
*
* The following Filters are populated
*
* <ul>
* <li>{@link org.springframework.security.web.access.intercept.FilterSecurityInterceptor}</li>
* </ul>
*
* <h2>Shared Objects Created</h2>
*
* The following shared objects are populated to allow other {@link org.springframework.security.config.annotation.SecurityConfigurer}'s to customize:
* <ul>
* <li>{@link org.springframework.security.web.access.intercept.FilterSecurityInterceptor}</li>
* </ul>
*
* <h2>Shared Objects Used</h2>
*
* No shared objects are used.
*
* @param <H> the type of {@link HttpSecurityBuilder} that is being configured
*
* @author Rob Winch
* @since 3.2
* @see {@link org.springframework.security.config.annotation.web.builders.HttpSecurity#authorizeRequests()}
*/
public final class ExpressionUrlAuthorizationConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractInterceptUrlConfigurer<H,ExpressionUrlAuthorizationConfigurer<H>,ExpressionUrlAuthorizationConfigurer<H>.AuthorizedUrl> {
static final String permitAll = "permitAll";
private static final String denyAll = "denyAll";
private static final String anonymous = "anonymous";
private static final String authenticated = "authenticated";
private static final String fullyAuthenticated = "fullyAuthenticated";
private static final String rememberMe = "rememberMe";
private SecurityExpressionHandler<FilterInvocation> expressionHandler = new DefaultWebSecurityExpressionHandler();
/**
* Creates a new instance
* @see HttpSecurity#authorizeRequests()
*/
public ExpressionUrlAuthorizationConfigurer() {
}
/**
* Allows customization of the {@link SecurityExpressionHandler} to be used. The default is {@link DefaultWebSecurityExpressionHandler}
*
* @param expressionHandler the {@link SecurityExpressionHandler} to be used
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further customization.
*/
public ExpressionUrlAuthorizationConfigurer<H> expressionHandler(SecurityExpressionHandler<FilterInvocation> expressionHandler) {
this.expressionHandler = expressionHandler;
return this;
}
/**
* Adds an {@link ObjectPostProcessor} for this class.
*
* @param objectPostProcessor
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further customizations
*/
public ExpressionUrlAuthorizationConfigurer<H> withObjectPostProcessor(ObjectPostProcessor<?> objectPostProcessor) {
addObjectPostProcessor(objectPostProcessor);
return this;
}
@Override
protected final AuthorizedUrl chainRequestMatchersInternal(List<RequestMatcher> requestMatchers) {
return new AuthorizedUrl(requestMatchers);
}
@Override
@SuppressWarnings("rawtypes")
final List<AccessDecisionVoter> getDecisionVoters() {
List<AccessDecisionVoter> decisionVoters = new ArrayList<AccessDecisionVoter>();
WebExpressionVoter expressionVoter = new WebExpressionVoter();
expressionVoter.setExpressionHandler(expressionHandler);
decisionVoters.add(expressionVoter);
return decisionVoters;
}
@Override
final ExpressionBasedFilterInvocationSecurityMetadataSource createMetadataSource() {
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap = createRequestMap();
if(requestMap.isEmpty()) {
throw new IllegalStateException("At least one mapping is required (i.e. authorizeRequests().anyRequest.authenticated())");
}
return new ExpressionBasedFilterInvocationSecurityMetadataSource(requestMap, expressionHandler);
}
/**
* Allows registering multiple {@link RequestMatcher} instances to a collection of {@link ConfigAttribute} instances
*
* @param requestMatchers the {@link RequestMatcher} instances to register to the {@link ConfigAttribute} instances
* @param configAttributes the {@link ConfigAttribute} to be mapped by the {@link RequestMatcher} instances
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further customization.
*/
private ExpressionUrlAuthorizationConfigurer<H> interceptUrl(Iterable<? extends RequestMatcher> requestMatchers, Collection<ConfigAttribute> configAttributes) {
for(RequestMatcher requestMatcher : requestMatchers) {
addMapping(new UrlMapping(requestMatcher, configAttributes));
}
return this;
}
private static String hasRole(String role) {
Assert.notNull(role, "role cannot be null");
if (role.startsWith("ROLE_")) {
throw new IllegalArgumentException("role should not start with 'ROLE_' since it is automatically inserted. Got '" + role + "'");
}
return "hasRole('ROLE_" + role + "')";
}
private static String hasAuthority(String authority) {
return "hasAuthority('" + authority + "')";
}
private static String hasAnyAuthority(String... authorities) {
String anyAuthorities = StringUtils.arrayToDelimitedString(authorities, "','");
return "hasAnyAuthority('" + anyAuthorities + "')";
}
private static String hasIpAddress(String ipAddressExpression) {
return "hasIpAddress('" + ipAddressExpression + "')";
}
public final class AuthorizedUrl {
private List<RequestMatcher> requestMatchers;
private boolean not;
/**
* Creates a new instance
*
* @param requestMatchers the {@link RequestMatcher} instances to map
*/
private AuthorizedUrl(List<RequestMatcher> requestMatchers) {
this.requestMatchers = requestMatchers;
}
/**
* Negates the following expression.
*
* @param role the role to require (i.e. USER, ADMIN, etc). Note, it should not start with "ROLE_" as
* this is automatically inserted.
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further customization
*/
public AuthorizedUrl not() {
this.not = true;
return this;
}
/**
* Shortcut for specifying URLs require a particular role. If you do not want to have "ROLE_" automatically
* inserted see {@link #hasAuthority(String)}.
*
* @param role the role to require (i.e. USER, ADMIN, etc). Note, it should not start with "ROLE_" as
* this is automatically inserted.
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further customization
*/
public ExpressionUrlAuthorizationConfigurer<H> hasRole(String role) {
return access(ExpressionUrlAuthorizationConfigurer.hasRole(role));
}
/**
* Specify that URLs require a particular authority.
*
* @param authority the authority to require (i.e. ROLE_USER, ROLE_ADMIN, etc).
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further customization
*/
public ExpressionUrlAuthorizationConfigurer<H> hasAuthority(String authority) {
return access(ExpressionUrlAuthorizationConfigurer.hasAuthority(authority));
}
/**
* Specify that URLs requires any of a number authorities.
*
* @param authorities the requests require at least one of the authorities (i.e. "ROLE_USER","ROLE_ADMIN" would
* mean either "ROLE_USER" or "ROLE_ADMIN" is required).
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further customization
*/
public ExpressionUrlAuthorizationConfigurer<H> hasAnyAuthority(String... authorities) {
return access(ExpressionUrlAuthorizationConfigurer.hasAnyAuthority(authorities));
}
/**
* Specify that URLs requires a specific IP Address or
* <a href="http://forum.springsource.org/showthread.php?102783-How-to-use-hasIpAddress&p=343971#post343971">subnet</a>.
*
* @param ipaddressExpression the ipaddress (i.e. 192.168.1.79) or local subnet (i.e. 192.168.0/24)
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further customization
*/
public ExpressionUrlAuthorizationConfigurer<H> hasIpAddress(String ipaddressExpression) {
return access(ExpressionUrlAuthorizationConfigurer.hasIpAddress(ipaddressExpression));
}
/**
* Specify that URLs are allowed by anyone.
*
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further customization
*/
public ExpressionUrlAuthorizationConfigurer<H> permitAll() {
return access(permitAll);
}
/**
* Specify that URLs are allowed by anonymous users.
*
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further customization
*/
public ExpressionUrlAuthorizationConfigurer<H> anonymous() {
return access(anonymous);
}
/**
* Specify that URLs are allowed by users that have been remembered.
*
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further customization
* @see {@link RememberMeConfigurer}
*/
public ExpressionUrlAuthorizationConfigurer<H> rememberMe() {
return access(rememberMe);
}
/**
* Specify that URLs are not allowed by anyone.
*
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further customization
*/
public ExpressionUrlAuthorizationConfigurer<H> denyAll() {
return access(denyAll);
}
/**
* Specify that URLs are allowed by any authenticated user.
*
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further customization
*/
public ExpressionUrlAuthorizationConfigurer<H> authenticated() {
return access(authenticated);
}
/**
* Specify that URLs are allowed by users who have authenticated and were not "remembered".
*
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further customization
* @see {@link RememberMeConfigurer}
*/
public ExpressionUrlAuthorizationConfigurer<H> fullyAuthenticated() {
return access(fullyAuthenticated);
}
/**
* Allows specifying that URLs are secured by an arbitrary expression
*
* @param attribute the expression to secure the URLs (i.e. "hasRole('ROLE_USER') and hasRole('ROLE_SUPER')")
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further customization
*/
public ExpressionUrlAuthorizationConfigurer<H> access(String attribute) {
if(not) {
attribute = "!" + attribute;
}
interceptUrl(requestMatchers, SecurityConfig.createList(attribute));
return ExpressionUrlAuthorizationConfigurer.this;
}
}
}
@@ -0,0 +1,263 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.configurers;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.security.web.authentication.ui.DefaultLoginPageViewFilter;
import org.springframework.security.web.util.AntPathRequestMatcher;
import org.springframework.security.web.util.RequestMatcher;
/**
* Adds form based authentication. All attributes have reasonable defaults
* making all parameters are optional. If no {@link #loginPage(String)} is
* specified, a default login page will be generated by the framework.
*
* <h2>Security Filters</h2>
*
* The following Filters are populated
*
* <ul>
* <li>
* {@link UsernamePasswordAuthenticationFilter}
* </li>
* </ul>
*
* <h2>Shared Objects Created</h2>
*
* The following shared objects are populated
*
* <ul>
* <li> {@link AuthenticationEntryPoint} </li>
* </ul>
*
* <h2>Shared Objects Used</h2>
*
* The following shared objects are used:
*
* <ul>
* <li>{@link AuthenticationManager}</li>
* <li>{@link RememberMeServices} - is optionally used. See {@link RememberMeConfigurer}</li>
* <li>{@link SessionAuthenticationStrategy} - is optionally used. See {@link SessionManagementConfigurer}</li>
* <li>{@link DefaultLoginPageViewFilter} - if present will be populated with information from the configuration</li>
* </ul>
*
* @author Rob Winch
* @since 3.2
*/
public final class FormLoginConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractAuthenticationFilterConfigurer<H,FormLoginConfigurer<H>,UsernamePasswordAuthenticationFilter> {
/**
* Creates a new instance
* @see HttpSecurity#formLogin()
*/
public FormLoginConfigurer() {
super(new UsernamePasswordAuthenticationFilter(),null);
usernameParameter("username");
passwordParameter("password");
}
/**
* <p>
* Specifies the URL to send users to if login is required. If used with
* {@link WebSecurityConfigurerAdapter} a default login page will be
* generated when this attribute is not specified.
* </p>
*
* <p>
* If a URL is specified or this is not being used in conjuction with
* {@link WebSecurityConfigurerAdapter}, users are required to process the
* specified URL to generate a login page. In general, the login page should
* create a form that submits a request with the following requirements to
* work with {@link UsernamePasswordAuthenticationFilter}:
* </p>
*
* <ul>
* <li>It must be an HTTP POST</li>
* <li>It must be submitted to {@link #loginProcessingUrl(String)}</li>
* <li>It should include the username as an HTTP parameter by the name of
* {@link #usernameParameter(String)}</li>
* <li>It should include the password as an HTTP parameter by the name of
* {@link #passwordParameter(String)}</li>
* </ul>
*
* <h2>Example login.jsp</h2>
*
* Login pages can be rendered with any technology you choose so long as the
* rules above are followed. Below is an example login.jsp that can be used as
* a quick start when using JSP's or as a baseline to translate into another view
* technology.
*
* <pre>
* <!-- loginProcessingUrl should correspond to FormLoginConfigurer#loginProcessingUrl. Don't forget to perform a POST -->
* &lt;c:url value="/login" var="loginProcessingUrl"/&gt;
* &lt;form action="${loginProcessingUrl}" method="post"&gt;
* &lt;fieldset&gt;
* &lt;legend&gt;Please Login&lt;/legend&gt;
* &lt;!-- use param.error assuming FormLoginConfigurer#failureUrl contains the query parameter error --&gt;
* &lt;c:if test="${param.error != null}"&gt;
* &lt;div&gt;
* Failed to login.
* &lt;c:if test="${SPRING_SECURITY_LAST_EXCEPTION != null}"&gt;
* Reason: &lt;c:out value="${SPRING_SECURITY_LAST_EXCEPTION.message}" /&gt;
* &lt;/c:if&gt;
* &lt;/div&gt;
* &lt;/c:if&gt;
* &lt;!-- the configured LogoutConfigurer#logoutSuccessUrl is /login?logout and contains the query param logout --&gt;
* &lt;c:if test="${param.logout != null}"&gt;
* &lt;div&gt;
* You have been logged out.
* &lt;/div&gt;
* &lt;/c:if&gt;
* &lt;p&gt;
* &lt;label for="username"&gt;Username&lt;/label&gt;
* &lt;input type="text" id="username" name="username"/&gt;
* &lt;/p&gt;
* &lt;p&gt;
* &lt;label for="password"&gt;Password&lt;/label&gt;
* &lt;input type="password" id="password" name="password"/&gt;
* &lt;/p&gt;
* &lt;!-- if using RememberMeConfigurer make sure remember-me matches RememberMeConfigurer#rememberMeParameter --&gt;
* &lt;p&gt;
* &lt;label for="remember-me"&gt;Remember Me?&lt;/label&gt;
* &lt;input type="checkbox" id="remember-me" name="remember-me"/&gt;
* &lt;/p&gt;
* &lt;div&gt;
* &lt;button type="submit" class="btn"&gt;Log in&lt;/button&gt;
* &lt;/div&gt;
* &lt;/fieldset&gt;
* &lt;/form&gt;
* </pre>
*
* <h2>Impact on other defaults</h2>
*
* Updating this value, also impacts a number of other default values. For example,
* the following are the default values when only formLogin() was specified.
*
* <ul>
* <li>/login GET - the login form</li>
* <li>/login POST - process the credentials and if valid authenticate the
* user</li>
* <li>/login?error GET - redirect here for failed authentication attempts</li>
* <li>/login?logout GET - redirect here after successfully logging out</li>
* </ul>
*
* If "/authenticate" was passed to this method it update the defaults as shown
* below:
*
* <ul>
* <li>/authenticate GET - the login form</li>
* <li>/authenticate POST - process the credentials and if valid authenticate the
* user</li>
* <li>/authenticate?error GET - redirect here for failed authentication attempts</li>
* <li>/authenticate?logout GET - redirect here after successfully logging out</li>
* </ul>
*
*
* @param loginPage
* the login page to redirect to if authentication is required
* (i.e. "/login")
* @return the {@link FormLoginConfigurer} for additional customization
*/
public FormLoginConfigurer<H> loginPage(String loginPage) {
return super.loginPage(loginPage);
}
/**
* The HTTP parameter to look for the username when performing
* authentication. Default is "username".
*
* @param usernameParameter
* the HTTP parameter to look for the username when performing
* authentication
* @return the {@link FormLoginConfigurer} for additional customization
*/
public FormLoginConfigurer<H> usernameParameter(String usernameParameter) {
getAuthenticationFilter().setUsernameParameter(usernameParameter);
return this;
}
/**
* The HTTP parameter to look for the password when performing
* authentication. Default is "password".
*
* @param passwordParameter
* the HTTP parameter to look for the password when performing
* authentication
* @return the {@link FormLoginConfigurer} for additional customization
*/
public FormLoginConfigurer<H> passwordParameter(String passwordParameter) {
getAuthenticationFilter().setPasswordParameter(passwordParameter);
return this;
}
@Override
public void init(H http) throws Exception {
super.init(http);
initDefaultLoginFilter(http);
}
/* (non-Javadoc)
* @see org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer#createLoginProcessingUrlMatcher(java.lang.String)
*/
@Override
protected RequestMatcher createLoginProcessingUrlMatcher(
String loginProcessingUrl) {
return new AntPathRequestMatcher(loginProcessingUrl, "POST");
}
/**
* Gets the HTTP parameter that is used to submit the username.
*
* @return the HTTP parameter that is used to submit the username
*/
private String getUsernameParameter() {
return getAuthenticationFilter().getUsernameParameter();
}
/**
* Gets the HTTP parameter that is used to submit the password.
*
* @return the HTTP parameter that is used to submit the password
*/
private String getPasswordParameter() {
return getAuthenticationFilter().getPasswordParameter();
}
/**
* If available, initializes the {@link DefaultLoginPageViewFilter} shared object.
*
* @param http the {@link HttpSecurityBuilder} to use
*/
private void initDefaultLoginFilter(H http) {
DefaultLoginPageViewFilter loginPageGeneratingFilter = http.getSharedObject(DefaultLoginPageViewFilter.class);
if(loginPageGeneratingFilter != null && !isCustomLoginPage()) {
loginPageGeneratingFilter.setFormLoginEnabled(true);
loginPageGeneratingFilter.setUsernameParameter(getUsernameParameter());
loginPageGeneratingFilter.setPasswordParameter(getPasswordParameter());
loginPageGeneratingFilter.setLoginPageUrl(getLoginPage());
loginPageGeneratingFilter.setFailureUrl(getFailureUrl());
loginPageGeneratingFilter.setAuthenticationUrl(getLoginProcessingUrl());
}
}
}
@@ -0,0 +1,144 @@
/*
* 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 java.util.ArrayList;
import java.util.List;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.header.HeaderWriter;
import org.springframework.security.web.header.HeaderWriterFilter;
import org.springframework.security.web.header.writers.CacheControlHeadersWriter;
import org.springframework.security.web.header.writers.HstsHeaderWriter;
import org.springframework.security.web.header.writers.XContentTypeOptionsHeaderWriter;
import org.springframework.security.web.header.writers.XXssProtectionHeaderWriter;
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter;
import org.springframework.util.Assert;
/**
* @author Rob Winch
* @since 3.2
* @see RememberMeConfigurer
*/
public final class HeadersConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<HeadersConfigurer<H>,H> {
private List<HeaderWriter> headerWriters = new ArrayList<HeaderWriter>();
/**
* Creates a new instance
* @see HttpSecurity#headers()
*/
public HeadersConfigurer() {
}
/**
* Adds a {@link HeaderWriter} instance
* @param headerWriter the {@link HeaderWriter} instance to add
* @return the {@link HeadersConfigurer} for additional customizations
*/
public HeadersConfigurer<H> addHeaderWriter(HeaderWriter headerWriter) {
Assert.notNull(headerWriter, "headerWriter cannot be null");
this.headerWriters.add(headerWriter);
return this;
}
/**
* Adds {@link XContentTypeOptionsHeaderWriter}
*
* @return the {@link HeadersConfigurer} for additional customizations
*/
public HeadersConfigurer<H> contentTypeOptions() {
return addHeaderWriter(new XContentTypeOptionsHeaderWriter());
}
/**
* Adds {@link XXssProtectionHeaderWriter}. Note this is not comprehensive
* XSS protection!
*
* @return the {@link HeadersConfigurer} for additional customizations
*/
public HeadersConfigurer<H> xssProtection() {
return addHeaderWriter(new XXssProtectionHeaderWriter());
}
/**
* Adds {@link CacheControlHeadersWriter}.
*
* @return the {@link HeadersConfigurer} for additional customizations
*/
public HeadersConfigurer<H> cacheControl() {
return addHeaderWriter(new CacheControlHeadersWriter());
}
/**
* Adds {@link HstsHeaderWriter}.
*
* @return the {@link HeadersConfigurer} for additional customizations
*/
public HeadersConfigurer<H> httpStrictTransportSecurity() {
return addHeaderWriter(new HstsHeaderWriter());
}
/**
* Adds {@link XFrameOptionsHeaderWriter} with all the default settings.
*
* @return the {@link HeadersConfigurer} for additional customizations
*/
public HeadersConfigurer<H> frameOptions() {
return addHeaderWriter(new XFrameOptionsHeaderWriter());
}
@Override
public void configure(H http) throws Exception {
HeaderWriterFilter headersFilter = createHeaderWriterFilter();
http.addFilter(headersFilter);
}
/**
* Creates the {@link HeaderWriter}
* @return the {@link HeaderWriter}
*/
private HeaderWriterFilter createHeaderWriterFilter() {
HeaderWriterFilter headersFilter = new HeaderWriterFilter(getHeaderWriters());
headersFilter = postProcess(headersFilter);
return headersFilter;
}
/**
* Gets the {@link HeaderWriter} instances and possibly initializes with the defaults.
* @return
*/
private List<HeaderWriter> getHeaderWriters() {
if(headerWriters.isEmpty()) {
addDefaultHeaderWriters();
}
return headerWriters;
}
/**
* Explicitly adds the default {@link HeaderWriter} instances. If no,
* {@link HeaderWriter} instances have been added this is automatically
* invoked.
*
*/
private void addDefaultHeaderWriters() {
contentTypeOptions();
xssProtection();
cacheControl();
httpStrictTransportSecurity();
frameOptions();
}
}
@@ -0,0 +1,156 @@
/*
* 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 java.util.Collections;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.util.MediaTypeRequestMatcher;
import org.springframework.security.web.util.RequestMatcher;
import org.springframework.web.accept.ContentNegotiationStrategy;
import org.springframework.web.accept.HeaderContentNegotiationStrategy;
/**
* Adds HTTP basic based authentication. All attributes have reasonable defaults
* making all parameters are optional.
*
* <h2>Security Filters</h2>
*
* The following Filters are populated
*
* <ul>
* <li>
* {@link BasicAuthenticationFilter}</li>
* </ul>
*
* <h2>Shared Objects Created</h2>
*
* <ul>
* <li>AuthenticationEntryPoint - populated with the
* {@link #authenticationEntryPoint(AuthenticationEntryPoint)} (default
* {@link BasicAuthenticationEntryPoint})</li>
* </ul>
*
* <h2>Shared Objects Used</h2>
*
* The following shared objects are used:
*
* <ul>
* <li>{@link AuthenticationManager}</li>
* </ul>
*
* @author Rob Winch
* @since 3.2
*/
public final class HttpBasicConfigurer<B extends HttpSecurityBuilder<B>> extends AbstractHttpConfigurer<HttpBasicConfigurer<B>,B> {
private static final String DEFAULT_REALM = "Spring Security Application";
private AuthenticationEntryPoint authenticationEntryPoint = new BasicAuthenticationEntryPoint();
private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource;
/**
* Creates a new instance
* @throws Exception
* @see {@link HttpSecurity#httpBasic()}
*/
public HttpBasicConfigurer() throws Exception {
realmName(DEFAULT_REALM);
}
/**
* Shortcut for {@link #authenticationEntryPoint(AuthenticationEntryPoint)}
* specifying a {@link BasicAuthenticationEntryPoint} with the specified
* realm name.
*
* @param realmName
* the HTTP Basic realm to use
* @return {@link HttpBasicConfigurer} for additional customization
* @throws Exception
*/
public HttpBasicConfigurer<B> realmName(String realmName) throws Exception {
BasicAuthenticationEntryPoint basicAuthEntryPoint = new BasicAuthenticationEntryPoint();
basicAuthEntryPoint.setRealmName(realmName);
basicAuthEntryPoint.afterPropertiesSet();
return authenticationEntryPoint(basicAuthEntryPoint);
}
/**
* The {@link AuthenticationEntryPoint} to be po pulated on
* {@link BasicAuthenticationFilter} in the event that authentication fails.
* The default to use {@link BasicAuthenticationEntryPoint} with the realm
* "Spring Security Application".
*
* @param authenticationEntryPoint the {@link AuthenticationEntryPoint} to use
* @return {@link HttpBasicConfigurer} for additional customization
*/
public HttpBasicConfigurer<B> authenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) {
this.authenticationEntryPoint = authenticationEntryPoint;
return this;
}
/**
* Specifies a custom {@link AuthenticationDetailsSource} to use for basic
* authentication. The default is {@link WebAuthenticationDetailsSource}.
*
* @param authenticationDetailsSource
* the custom {@link AuthenticationDetailsSource} to use
* @return {@link HttpBasicConfigurer} for additional customization
*/
public HttpBasicConfigurer<B> authenticationDetailsSource(AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource) {
this.authenticationDetailsSource = authenticationDetailsSource;
return this;
}
public void init(B http) throws Exception {
registerDefaultAuthenticationEntryPoint(http);
}
@SuppressWarnings("unchecked")
private void registerDefaultAuthenticationEntryPoint(B http) {
ExceptionHandlingConfigurer<B> exceptionHandling = http.getConfigurer(ExceptionHandlingConfigurer.class);
if(exceptionHandling == null) {
return;
}
ContentNegotiationStrategy contentNegotiationStrategy = http.getSharedObject(ContentNegotiationStrategy.class);
if(contentNegotiationStrategy == null) {
contentNegotiationStrategy = new HeaderContentNegotiationStrategy();
}
MediaTypeRequestMatcher preferredMatcher = new MediaTypeRequestMatcher(contentNegotiationStrategy, MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_JSON, MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_XML, MediaType.MULTIPART_FORM_DATA, MediaType.TEXT_XML);
preferredMatcher.setIgnoredMediaTypes(Collections.singleton(MediaType.ALL));
exceptionHandling.defaultAuthenticationEntryPointFor(postProcess(authenticationEntryPoint), preferredMatcher);
}
@Override
public void configure(B http) throws Exception {
AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class);
BasicAuthenticationFilter basicAuthenticationFilter = new BasicAuthenticationFilter(authenticationManager, authenticationEntryPoint);
if(authenticationDetailsSource != null) {
basicAuthenticationFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
}
basicAuthenticationFilter = postProcess(basicAuthenticationFilter);
http.addFilter(basicAuthenticationFilter);
}
}
@@ -0,0 +1,260 @@
/*
* 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 java.util.HashSet;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.authority.mapping.SimpleMappableAttributesRetriever;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesUserDetailsService;
import org.springframework.security.web.authentication.preauth.j2ee.J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource;
import org.springframework.security.web.authentication.preauth.j2ee.J2eePreAuthenticatedProcessingFilter;
/**
* Adds support for J2EE pre authentication.
*
* <h2>Security Filters</h2>
*
* The following Filters are populated
*
* <ul>
* <li>
* {@link J2eePreAuthenticatedProcessingFilter}</li>
* </ul>
*
* <h2>Shared Objects Created</h2>
*
* <ul>
* <li>
* {@link AuthenticationEntryPoint}
* is populated with an {@link Http403ForbiddenEntryPoint}</li>
* <li>A {@link PreAuthenticatedAuthenticationProvider} is populated into
* {@link HttpSecurity#authenticationProvider(org.springframework.security.authentication.AuthenticationProvider)}
* </li>
* </ul>
*
* <h2>Shared Objects Used</h2>
*
* The following shared objects are used:
*
* <ul>
* <li>{@link AuthenticationManager}</li>
* </ul>
*
* @author Rob Winch
* @since 3.2
*/
public final class JeeConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<JeeConfigurer<H>,H> {
private J2eePreAuthenticatedProcessingFilter j2eePreAuthenticatedProcessingFilter;
private AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> authenticationUserDetailsService;
private Set<String> mappableRoles = new HashSet<String>();
/**
* Creates a new instance
* @see HttpSecurity#jee()
*/
public JeeConfigurer() {
}
/**
* Specifies roles to use map from the {@link HttpServletRequest} to the
* {@link UserDetails}. If {@link HttpServletRequest#isUserInRole(String)}
* returns true, the role is added to the {@link UserDetails}. This method
* is the equivalent of invoking {@link #mappableAuthorities(Set)}. Multiple
* invocations of {@link #mappableAuthorities(String...)} will override previous
* invocations.
*
* <p>
* There are no default roles that are mapped.
* </p>
*
* @param mappableRoles
* the roles to attempt to map to the {@link UserDetails} (i.e.
* "ROLE_USER", "ROLE_ADMIN", etc).
* @return the {@link JeeConfigurer} for further customizations
* @see SimpleMappableAttributesRetriever
* @see #mappableRoles(String...)
*/
public JeeConfigurer<H> mappableAuthorities(String... mappableRoles) {
this.mappableRoles.clear();
for(String role : mappableRoles) {
this.mappableRoles.add(role);
}
return this;
}
/**
* Specifies roles to use map from the {@link HttpServletRequest} to the
* {@link UserDetails} and automatically prefixes it with "ROLE_". If
* {@link HttpServletRequest#isUserInRole(String)} returns true, the role is
* added to the {@link UserDetails}. This method is the equivalent of
* invoking {@link #mappableAuthorities(Set)}. Multiple invocations of
* {@link #mappableRoles(String...)} will override previous invocations.
*
* <p>
* There are no default roles that are mapped.
* </p>
*
* @param mappableRoles
* the roles to attempt to map to the {@link UserDetails} (i.e.
* "USER", "ADMIN", etc).
* @return the {@link JeeConfigurer} for further customizations
* @see SimpleMappableAttributesRetriever
* @see #mappableAuthorities(String...)
*/
public JeeConfigurer<H> mappableRoles(String... mappableRoles) {
this.mappableRoles.clear();
for(String role : mappableRoles) {
this.mappableRoles.add("ROLE_" + role);
}
return this;
}
/**
* Specifies roles to use map from the {@link HttpServletRequest} to the
* {@link UserDetails}. If {@link HttpServletRequest#isUserInRole(String)}
* returns true, the role is added to the {@link UserDetails}. This is the
* equivalent of {@link #mappableRoles(String...)}. Multiple invocations of
* {@link #mappableAuthorities(Set)} will override previous invocations.
*
* <p>
* There are no default roles that are mapped.
* </p>
*
* @param mappableRoles
* the roles to attempt to map to the {@link UserDetails}.
* @return the {@link JeeConfigurer} for further customizations
* @see SimpleMappableAttributesRetriever
*/
public JeeConfigurer<H> mappableAuthorities(Set<String> mappableRoles) {
this.mappableRoles = mappableRoles;
return this;
}
/**
* Specifies the {@link AuthenticationUserDetailsService} that is used with
* the {@link PreAuthenticatedAuthenticationProvider}. The default is a
* {@link PreAuthenticatedGrantedAuthoritiesUserDetailsService}.
*
* @param authenticatedUserDetailsService the {@link AuthenticationUserDetailsService} to use.
* @return the {@link JeeConfigurer} for further configuration
*/
public JeeConfigurer<H> authenticatedUserDetailsService(
AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> authenticatedUserDetailsService) {
this.authenticationUserDetailsService = authenticatedUserDetailsService;
return this;
}
/**
* Allows specifying the {@link J2eePreAuthenticatedProcessingFilter} to
* use. If {@link J2eePreAuthenticatedProcessingFilter} is provided, all of its attributes must also be
* configured manually (i.e. all attributes populated in the {@link JeeConfigurer} are not used).
*
* @param j2eePreAuthenticatedProcessingFilter the {@link J2eePreAuthenticatedProcessingFilter} to use.
* @return the {@link JeeConfigurer} for further configuration
*/
public JeeConfigurer<H> j2eePreAuthenticatedProcessingFilter(
J2eePreAuthenticatedProcessingFilter j2eePreAuthenticatedProcessingFilter) {
this.j2eePreAuthenticatedProcessingFilter = j2eePreAuthenticatedProcessingFilter;
return this;
}
/**
* Populates a {@link PreAuthenticatedAuthenticationProvider} into
* {@link HttpSecurity#authenticationProvider(org.springframework.security.authentication.AuthenticationProvider)}
* and a {@link Http403ForbiddenEntryPoint} into
* {@link HttpSecurity#authenticationEntryPoint(org.springframework.security.web.AuthenticationEntryPoint)}
*
* @see org.springframework.security.config.annotation.SecurityConfigurerAdapter#init(org.springframework.security.config.annotation.SecurityBuilder)
*/
@Override
public void init(H http) throws Exception {
PreAuthenticatedAuthenticationProvider authenticationProvider = new PreAuthenticatedAuthenticationProvider();
authenticationProvider.setPreAuthenticatedUserDetailsService(getUserDetailsService());
authenticationProvider = postProcess(authenticationProvider);
http
.authenticationProvider(authenticationProvider)
.setSharedObject(AuthenticationEntryPoint.class,new Http403ForbiddenEntryPoint());
}
@Override
public void configure(H http) throws Exception {
J2eePreAuthenticatedProcessingFilter filter = getFilter(http.getSharedObject(AuthenticationManager.class));
http.addFilter(filter);
}
/**
* Gets the {@link J2eePreAuthenticatedProcessingFilter} or creates a default instance using the properties provided.
* @param authenticationManager the {@link AuthenticationManager} to use.
* @return the {@link J2eePreAuthenticatedProcessingFilter} to use.
*/
private J2eePreAuthenticatedProcessingFilter getFilter(
AuthenticationManager authenticationManager) {
if (j2eePreAuthenticatedProcessingFilter == null) {
j2eePreAuthenticatedProcessingFilter = new J2eePreAuthenticatedProcessingFilter();
j2eePreAuthenticatedProcessingFilter
.setAuthenticationManager(authenticationManager);
j2eePreAuthenticatedProcessingFilter
.setAuthenticationDetailsSource(createWebAuthenticationDetailsSource());
j2eePreAuthenticatedProcessingFilter = postProcess(j2eePreAuthenticatedProcessingFilter);
}
return j2eePreAuthenticatedProcessingFilter;
}
/**
* Gets the {@link AuthenticationUserDetailsService} that was specified or
* defaults to {@link PreAuthenticatedGrantedAuthoritiesUserDetailsService}.
*
* @return the {@link AuthenticationUserDetailsService} to use
*/
private AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> getUserDetailsService() {
return authenticationUserDetailsService == null ? new PreAuthenticatedGrantedAuthoritiesUserDetailsService()
: authenticationUserDetailsService;
}
/**
* Creates the
* {@link J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource} to set on
* the {@link J2eePreAuthenticatedProcessingFilter}. It is populated with a
* {@link SimpleMappableAttributesRetriever}.
*
* @return the
* {@link J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource}
* to use.
*/
private J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource createWebAuthenticationDetailsSource() {
J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource detailsSource = new J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource();
SimpleMappableAttributesRetriever rolesRetriever = new SimpleMappableAttributesRetriever();
rolesRetriever.setMappableAttributes(mappableRoles);
detailsSource.setMappableRolesRetriever(rolesRetriever);
detailsSource = postProcess(detailsSource);
return detailsSource;
}
}
@@ -0,0 +1,264 @@
/*
* 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 java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpSession;
import org.springframework.security.config.annotation.SecurityConfigurer;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.authentication.logout.CookieClearingLogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
import org.springframework.security.web.authentication.ui.DefaultLoginPageViewFilter;
import org.springframework.security.web.util.AntPathRequestMatcher;
import org.springframework.security.web.util.RequestMatcher;
/**
* Adds logout support. Other {@link SecurityConfigurer} instances may invoke
* {@link #addLogoutHandler(LogoutHandler)} in the
* {@link #init(HttpSecurity)} phase.
*
* <h2>Security Filters</h2>
*
* The following Filters are populated
*
* <ul>
* <li>
* {@link LogoutFilter}</li>
* </ul>
*
* <h2>Shared Objects Created</h2>
*
* No shared Objects are created
*
* <h2>Shared Objects Used</h2>
*
* No shared objects are used.
*
* @author Rob Winch
* @since 3.2
* @see RememberMeConfigurer
*/
public final class LogoutConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<LogoutConfigurer<H>,H> {
private List<LogoutHandler> logoutHandlers = new ArrayList<LogoutHandler>();
private SecurityContextLogoutHandler contextLogoutHandler = new SecurityContextLogoutHandler();
private String logoutSuccessUrl = "/login?logout";
private LogoutSuccessHandler logoutSuccessHandler;
private RequestMatcher logoutRequestMatcher = new AntPathRequestMatcher("/logout", "POST");
private boolean permitAll;
private boolean customLogoutSuccess;
/**
* Creates a new instance
* @see HttpSecurity#logout()
*/
public LogoutConfigurer() {
}
/**
* Adds a {@link LogoutHandler}. The {@link SecurityContextLogoutHandler} is
* added as the last {@link LogoutHandler} by default.
*
* @param logoutHandler the {@link LogoutHandler} to add
* @return the {@link LogoutConfigurer} for further customization
*/
public LogoutConfigurer<H> addLogoutHandler(LogoutHandler logoutHandler) {
this.logoutHandlers.add(logoutHandler);
return this;
}
/**
* Configures {@link SecurityContextLogoutHandler} to invalidate the {@link HttpSession} at the time of logout.
* @param invalidateHttpSession true if the {@link HttpSession} should be invalidated (default), or false otherwise.
* @return the {@link LogoutConfigurer} for further customization
*/
public LogoutConfigurer<H> invalidateHttpSession(boolean invalidateHttpSession) {
contextLogoutHandler.setInvalidateHttpSession(invalidateHttpSession);
return this;
}
/**
* The URL that triggers logout to occur on HTTP POST. The default is "/logout"
* @param logoutUrl the URL that will invoke logout.
* @return the {@link LogoutConfigurer} for further customization
*/
public LogoutConfigurer<H> logoutUrl(String logoutUrl) {
return logoutRequestMatcher(new AntPathRequestMatcher(logoutUrl, "POST"));
}
/**
* The RequestMatcher that triggers logout to occur on HTTP POST. The default is "/logout"
* @param logoutRequestMatcher the RequestMatcher used to determine if logout should occur.
* @return the {@link LogoutConfigurer} for further customization
*/
public LogoutConfigurer<H> logoutRequestMatcher(RequestMatcher logoutRequestMatcher) {
this.logoutRequestMatcher = logoutRequestMatcher;
return this;
}
/**
* The URL to redirect to after logout has occurred. The default is
* "/login?logout". This is a shortcut for invoking
* {@link #logoutSuccessHandler(LogoutSuccessHandler)} with a
* {@link SimpleUrlLogoutSuccessHandler}.
*
* @param logoutSuccessUrl
* the URL to redirect to after logout occurred
* @return the {@link LogoutConfigurer} for further customization
*/
public LogoutConfigurer<H> logoutSuccessUrl(String logoutSuccessUrl) {
this.customLogoutSuccess = true;
this.logoutSuccessUrl = logoutSuccessUrl;
return this;
}
/**
* A shortcut for {@link #permitAll(boolean)} with <code>true</code> as an argument.
* @return the {@link LogoutConfigurer} for further customizations
*/
public LogoutConfigurer<H> permitAll() {
return permitAll(true);
}
/**
* Allows specifying the names of cookies to be removed on logout success.
* This is a shortcut to easily invoke
* {@link #addLogoutHandler(LogoutHandler)} with a
* {@link CookieClearingLogoutHandler}.
*
* @param cookieNamesToClear the names of cookies to be removed on logout success.
* @return the {@link LogoutConfigurer} for further customization
*/
public LogoutConfigurer<H> deleteCookies(String... cookieNamesToClear) {
return addLogoutHandler(new CookieClearingLogoutHandler(cookieNamesToClear));
}
/**
* Sets the {@link LogoutSuccessHandler} to use. If this is specified,
* {@link #logoutSuccessUrl(String)} is ignored.
*
* @param logoutSuccessHandler
* the {@link LogoutSuccessHandler} to use after a user has been
* logged out.
* @return the {@link LogoutConfigurer} for further customizations
*/
public LogoutConfigurer<H> logoutSuccessHandler(LogoutSuccessHandler logoutSuccessHandler) {
this.logoutSuccessUrl = null;
this.customLogoutSuccess = true;
this.logoutSuccessHandler = logoutSuccessHandler;
return this;
}
/**
* Grants access to the {@link #logoutSuccessUrl(String)} and the {@link #logoutUrl(String)} for every user.
*
* @param permitAll if true grants access, else nothing is done
* @return the {@link LogoutConfigurer} for further customization.
*/
public LogoutConfigurer<H> permitAll(boolean permitAll) {
this.permitAll = permitAll;
return this;
}
/**
* Gets the {@link LogoutSuccessHandler} if not null, otherwise creates a
* new {@link SimpleUrlLogoutSuccessHandler} using the
* {@link #logoutSuccessUrl(String)}.
*
* @return the {@link LogoutSuccessHandler} to use
*/
private LogoutSuccessHandler getLogoutSuccessHandler() {
if(logoutSuccessHandler != null) {
return logoutSuccessHandler;
}
SimpleUrlLogoutSuccessHandler logoutSuccessHandler = new SimpleUrlLogoutSuccessHandler();
logoutSuccessHandler.setDefaultTargetUrl(logoutSuccessUrl);
return logoutSuccessHandler;
}
@Override
public void init(H http) throws Exception {
if(permitAll) {
PermitAllSupport.permitAll(http, this.logoutSuccessUrl);
PermitAllSupport.permitAll(http, this.logoutRequestMatcher);
}
DefaultLoginPageViewFilter loginPageGeneratingFilter = http.getSharedObject(DefaultLoginPageViewFilter.class);
if(loginPageGeneratingFilter != null && !isCustomLogoutSuccess()) {
loginPageGeneratingFilter.setLogoutSuccessUrl(getLogoutSuccessUrl());
}
}
@Override
public void configure(H http) throws Exception {
LogoutFilter logoutFilter = createLogoutFilter();
http.addFilter(logoutFilter);
}
/**
* Returns true if the logout success has been customized via
* {@link #logoutSuccessUrl(String)} or
* {@link #logoutSuccessHandler(LogoutSuccessHandler)}.
*
* @return true if logout success handling has been customized, else false
*/
boolean isCustomLogoutSuccess() {
return customLogoutSuccess;
}
/**
* Gets the logoutSuccesUrl or null if a
* {@link #logoutSuccessHandler(LogoutSuccessHandler)} was configured.
*
* @return the logoutSuccessUrl
*/
private String getLogoutSuccessUrl() {
return logoutSuccessUrl;
}
/**
* Gets the {@link LogoutHandler} instances that will be used.
* @return the {@link LogoutHandler} instances. Cannot be null.
*/
List<LogoutHandler> getLogoutHandlers() {
return logoutHandlers;
}
/**
* Creates the {@link LogoutFilter} using the {@link LogoutHandler}
* instances, the {@link #logoutSuccessHandler(LogoutSuccessHandler)} and
* the {@link #logoutUrl(String)}.
*
* @return the {@link LogoutFilter} to use.
* @throws Exception
*/
private LogoutFilter createLogoutFilter() throws Exception {
logoutHandlers.add(contextLogoutHandler);
LogoutHandler[] handlers = logoutHandlers.toArray(new LogoutHandler[logoutHandlers.size()]);
LogoutFilter result = new LogoutFilter(getLogoutSuccessHandler(), handlers);
result.setLogoutRequestMatcher(logoutRequestMatcher);
result = postProcess(result);
return result;
}
}
@@ -0,0 +1,81 @@
/*
* 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 javax.servlet.http.HttpServletRequest;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.configurers.AbstractRequestMatcherMappingConfigurer.UrlMapping;
import org.springframework.security.web.util.RequestMatcher;
/**
* Configures non-null URL's to grant access to every URL
* @author Rob Winch
* @since 3.2
*/
final class PermitAllSupport {
public static void permitAll(HttpSecurityBuilder<? extends HttpSecurityBuilder<?>> http, String... urls) {
for(String url : urls) {
if(url != null) {
permitAll(http, new ExactUrlRequestMatcher(url));
}
}
}
@SuppressWarnings("unchecked")
public static void permitAll(HttpSecurityBuilder<? extends HttpSecurityBuilder<?>> http, RequestMatcher... requestMatchers) {
ExpressionUrlAuthorizationConfigurer<?> configurer = http.getConfigurer(ExpressionUrlAuthorizationConfigurer.class);
if(configurer == null) {
throw new IllegalStateException("permitAll only works with HttpSecurity.authorizeRequests()");
}
for(RequestMatcher matcher : requestMatchers) {
if(matcher != null) {
configurer.addMapping(0, new UrlMapping(matcher, SecurityConfig.createList(ExpressionUrlAuthorizationConfigurer.permitAll)));
}
}
}
private final static class ExactUrlRequestMatcher implements RequestMatcher {
private String processUrl;
private ExactUrlRequestMatcher(String processUrl) {
this.processUrl = processUrl;
}
public boolean matches(HttpServletRequest request) {
String uri = request.getRequestURI();
String query = request.getQueryString();
if(query != null) {
uri += "?" + query;
}
if ("".equals(request.getContextPath())) {
return uri.equals(processUrl);
}
return uri.equals(request.getContextPath() + processUrl);
}
}
private PermitAllSupport() {}
}
@@ -0,0 +1,115 @@
/*
* 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 java.util.HashMap;
import java.util.Map;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.PortMapper;
import org.springframework.security.web.PortMapperImpl;
/**
* Allows configuring a shared {@link PortMapper} instance used to determine the
* ports when redirecting between HTTP and HTTPS. The {@link PortMapper} can be
* obtained from {@link HttpSecurity#getSharedObject(Class)}.
*
* @author Rob Winch
* @since 3.2
*/
public final class PortMapperConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<PortMapperConfigurer<H>,H> {
private PortMapper portMapper;
private Map<String, String> httpsPortMappings = new HashMap<String,String>();
/**
* Creates a new instance
*/
public PortMapperConfigurer() {
}
/**
* Allows specifying the {@link PortMapper} instance.
* @param portMapper
* @return
*/
public PortMapperConfigurer<H> portMapper(PortMapper portMapper) {
this.portMapper = portMapper;
return this;
}
/**
* Adds a port mapping
* @param httpPort the HTTP port that maps to a specific HTTPS port.
* @return {@link HttpPortMapping} to define the HTTPS port
*/
public HttpPortMapping http(int httpPort) {
return new HttpPortMapping(httpPort);
}
@Override
public void init(H http) throws Exception {
http.setSharedObject(PortMapper.class, getPortMapper());
}
/**
* Gets the {@link PortMapper} to use. If {@link #portMapper(PortMapper)}
* was not invoked, builds a {@link PortMapperImpl} using the port mappings
* specified with {@link #http(int)}.
*
* @return the {@link PortMapper} to use
*/
private PortMapper getPortMapper() {
if(portMapper == null) {
PortMapperImpl portMapper = new PortMapperImpl();
portMapper.setPortMappings(httpsPortMappings);
this.portMapper = portMapper;
}
return portMapper;
}
/**
* Allows specifying the HTTPS port for a given HTTP port when redirecting
* between HTTP and HTTPS.
*
* @author Rob Winch
* @since 3.2
*/
public final class HttpPortMapping {
private final int httpPort;
/**
* Creates a new instance
* @param httpPort
* @see PortMapperConfigurer#http(int)
*/
private HttpPortMapping(int httpPort) {
this.httpPort = httpPort;
}
/**
* Maps the given HTTP port to the provided HTTPS port and vice versa.
* @param httpsPort the HTTPS port to map to
* @return the {@link PortMapperConfigurer} for further customization
*/
public PortMapperConfigurer<H> mapsTo(int httpsPort) {
httpsPortMappings.put(String.valueOf(httpPort), String.valueOf(httpsPort));
return PortMapperConfigurer.this;
}
}
}
@@ -0,0 +1,353 @@
/*
* 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 java.util.UUID;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.RememberMeAuthenticationProvider;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices;
import org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices;
import org.springframework.security.web.authentication.ui.DefaultLoginPageViewFilter;
/**
* Configures Remember Me authentication. This typically involves the user
* checking a box when they enter their username and password that states to
* "Remember Me".
*
* <h2>Security Filters</h2>
*
* The following Filters are populated
*
* <ul>
* <li>
* {@link RememberMeAuthenticationFilter}</li>
* </ul>
*
* <h2>Shared Objects Created</h2>
*
* The following shared objects are populated
*
* <ul>
* <li>
* {@link HttpSecurity#authenticationProvider(org.springframework.security.authentication.AuthenticationProvider)}
* is populated with a {@link RememberMeAuthenticationProvider}</li>
* <li>{@link RememberMeServices} is populated as a shared object and available on {@link HttpSecurity#getSharedObject(Class)}</li>
* <li>{@link LogoutConfigurer#addLogoutHandler(LogoutHandler)} is used to add a logout handler to clean up the remember me authentication.</li>
* </ul>
*
* <h2>Shared Objects Used</h2>
*
* The following shared objects are used:
*
* <ul>
* <li>{@link AuthenticationManager}</li>
* <li>{@link UserDetailsService} if no {@link #userDetailsService(UserDetailsService)} was specified.</li>
* <li> {@link DefaultLoginPageViewFilter} - if present will be populated with information from the configuration</li>
* </ul>
*
* @author Rob Winch
* @since 3.2
*/
public final class RememberMeConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<RememberMeConfigurer<H>,H> {
private AuthenticationSuccessHandler authenticationSuccessHandler;
private String key;
private RememberMeServices rememberMeServices;
private LogoutHandler logoutHandler;
private String rememberMeParameter = "remember-me";
private String rememberMeCookieName = "remember-me";
private PersistentTokenRepository tokenRepository;
private UserDetailsService userDetailsService;
private Integer tokenValiditySeconds;
private Boolean useSecureCookie;
/**
* Creates a new instance
*/
public RememberMeConfigurer() {
}
/**
* Allows specifying how long (in seconds) a token is valid for
*
* @param tokenValiditySeconds
* @return {@link RememberMeConfigurer} for further customization
* @see AbstractRememberMeServices#setTokenValiditySeconds(int)
*/
public RememberMeConfigurer<H> tokenValiditySeconds(int tokenValiditySeconds) {
this.tokenValiditySeconds = tokenValiditySeconds;
return this;
}
/**
*Whether the cookie should be flagged as secure or not. Secure cookies can only be sent over an HTTPS connection
* and thus cannot be accidentally submitted over HTTP where they could be intercepted.
* <p>
* By default the cookie will be secure if the request is secure. If you only want to use remember-me over
* HTTPS (recommended) you should set this property to {@code true}.
*
* @param useSecureCookie set to {@code true} to always user secure cookies, {@code false} to disable their use.
* @return the {@link RememberMeConfigurer} for further customization
* @see AbstractRememberMeServices#setUseSecureCookie(boolean)
*/
public RememberMeConfigurer<H> useSecureCookie(boolean useSecureCookie) {
this.useSecureCookie = useSecureCookie;
return this;
}
/**
* Specifies the {@link UserDetailsService} used to look up the
* {@link UserDetails} when a remember me token is valid. The default is to
* use the {@link UserDetailsService} found by invoking
* {@link HttpSecurity#getSharedObject(Class)} which is set when using
* {@link WebSecurityConfigurerAdapter#registerAuthentication(org.springframework.security.config.annotation.authentication.AuthenticationManagerBuilder)}.
* Alternatively, one can populate {@link #rememberMeServices(RememberMeServices)}.
*
* @param userDetailsService
* the {@link UserDetailsService} to configure
* @return the {@link RememberMeConfigurer} for further customization
* @see AbstractRememberMeServices
*/
public RememberMeConfigurer<H> userDetailsService(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
return this;
}
/**
* Specifies the {@link PersistentTokenRepository} to use. The default is to
* use {@link TokenBasedRememberMeServices} instead.
*
* @param tokenRepository
* the {@link PersistentTokenRepository} to use
* @return the {@link RememberMeConfigurer} for further customization
*/
public RememberMeConfigurer<H> tokenRepository(PersistentTokenRepository tokenRepository) {
this.tokenRepository = tokenRepository;
return this;
}
/**
* Sets the key to identify tokens created for remember me authentication. Default is a secure randomly generated
* key.
*
* @param key the key to identify tokens created for remember me authentication
* @return the {@link RememberMeConfigurer} for further customization
*/
public RememberMeConfigurer<H> key(String key) {
this.key = key;
return this;
}
/**
* Allows control over the destination a remembered user is sent to when they are successfully authenticated.
* By default, the filter will just allow the current request to proceed, but if an
* {@code AuthenticationSuccessHandler} is set, it will be invoked and the {@code doFilter()} method will return
* immediately, thus allowing the application to redirect the user to a specific URL, regardless of what the original
* request was for.
*
* @param authenticationSuccessHandler the strategy to invoke immediately before returning from {@code doFilter()}.
* @return {@link RememberMeConfigurer} for further customization
* @see RememberMeAuthenticationFilter#setAuthenticationSuccessHandler(AuthenticationSuccessHandler)
*/
public RememberMeConfigurer<H> authenticationSuccessHandler(AuthenticationSuccessHandler authenticationSuccessHandler) {
this.authenticationSuccessHandler = authenticationSuccessHandler;
return this;
}
/**
* Specify the {@link RememberMeServices} to use.
* @param rememberMeServices the {@link RememberMeServices} to use
* @return the {@link RememberMeConfigurer} for further customizations
* @see RememberMeServices
*/
public RememberMeConfigurer<H> rememberMeServices(RememberMeServices rememberMeServices) {
this.rememberMeServices = rememberMeServices;
return this;
}
@SuppressWarnings("unchecked")
@Override
public void init(H http) throws Exception {
String key = getKey();
RememberMeServices rememberMeServices = getRememberMeServices(http, key);
http.setSharedObject(RememberMeServices.class, rememberMeServices);
LogoutConfigurer<H> logoutConfigurer = http.getConfigurer(LogoutConfigurer.class);
if(logoutConfigurer != null) {
logoutConfigurer.addLogoutHandler(logoutHandler);
}
RememberMeAuthenticationProvider authenticationProvider = new RememberMeAuthenticationProvider(
key);
authenticationProvider = postProcess(authenticationProvider);
http.authenticationProvider(authenticationProvider);
initDefaultLoginFilter(http);
}
@Override
public void configure(H http) throws Exception {
RememberMeAuthenticationFilter rememberMeFilter = new RememberMeAuthenticationFilter(
http.getSharedObject(AuthenticationManager.class), rememberMeServices);
if (authenticationSuccessHandler != null) {
rememberMeFilter
.setAuthenticationSuccessHandler(authenticationSuccessHandler);
}
rememberMeFilter = postProcess(rememberMeFilter);
http.addFilter(rememberMeFilter);
}
/**
* Returns the HTTP parameter used to indicate to remember the user at time of login.
* @return the HTTP parameter used to indicate to remember the user
*/
private String getRememberMeParameter() {
return rememberMeParameter;
}
/**
* If available, initializes the {@link DefaultLoginPageViewFilter} shared object.
*
* @param http the {@link HttpSecurityBuilder} to use
*/
private void initDefaultLoginFilter(H http) {
DefaultLoginPageViewFilter loginPageGeneratingFilter = http.getSharedObject(DefaultLoginPageViewFilter.class);
if(loginPageGeneratingFilter != null) {
loginPageGeneratingFilter.setRememberMeParameter(getRememberMeParameter());
}
}
/**
* Gets the {@link RememberMeServices} or creates the {@link RememberMeServices}.
* @param http the {@link HttpSecurity} to lookup shared objects
* @param key the {@link #key(String)}
* @return the {@link RememberMeServices} to use
* @throws Exception
*/
private RememberMeServices getRememberMeServices(H http,
String key) throws Exception {
if (rememberMeServices != null) {
if (rememberMeServices instanceof LogoutHandler
&& logoutHandler == null) {
this.logoutHandler = (LogoutHandler) rememberMeServices;
}
return rememberMeServices;
}
AbstractRememberMeServices tokenRememberMeServices = createRememberMeServices(
http, key);
tokenRememberMeServices.setParameter(rememberMeParameter);
tokenRememberMeServices.setCookieName(rememberMeCookieName);
if (tokenValiditySeconds != null) {
tokenRememberMeServices
.setTokenValiditySeconds(tokenValiditySeconds);
}
if (useSecureCookie != null) {
tokenRememberMeServices.setUseSecureCookie(useSecureCookie);
}
tokenRememberMeServices.afterPropertiesSet();
logoutHandler = tokenRememberMeServices;
rememberMeServices = tokenRememberMeServices;
return tokenRememberMeServices;
}
/**
* Creates the {@link RememberMeServices} to use when none is provided. The
* result is either {@link PersistentTokenRepository} (if a
* {@link PersistentTokenRepository} is specified, else
* {@link TokenBasedRememberMeServices}.
*
* @param http the {@link HttpSecurity} to lookup shared objects
* @param key the {@link #key(String)}
* @return the {@link RememberMeServices} to use
* @throws Exception
*/
private AbstractRememberMeServices createRememberMeServices(
H http, String key) throws Exception {
return tokenRepository == null ? createTokenBasedRememberMeServices(
http, key) : createPersistentRememberMeServices(http, key);
}
/**
* Creates {@link TokenBasedRememberMeServices}
*
* @param http the {@link HttpSecurity} to lookup shared objects
* @param key the {@link #key(String)}
* @return the {@link TokenBasedRememberMeServices}
*/
private AbstractRememberMeServices createTokenBasedRememberMeServices(
H http, String key) {
UserDetailsService userDetailsService = getUserDetailsService(http);
return new TokenBasedRememberMeServices(key, userDetailsService);
}
/**
* Creates {@link PersistentTokenBasedRememberMeServices}
*
* @param http the {@link HttpSecurity} to lookup shared objects
* @param key the {@link #key(String)}
* @return the {@link PersistentTokenBasedRememberMeServices}
*/
private AbstractRememberMeServices createPersistentRememberMeServices(
H http, String key) {
UserDetailsService userDetailsService = getUserDetailsService(http);
return new PersistentTokenBasedRememberMeServices(key,
userDetailsService, tokenRepository);
}
/**
* Gets the {@link UserDetailsService} to use. Either the explicitly
* configure {@link UserDetailsService} from
* {@link #userDetailsService(UserDetailsService)} or a shared object from
* {@link HttpSecurity#getSharedObject(Class)}.
*
* @param http {@link HttpSecurity} to get the shared {@link UserDetailsService}
* @return the {@link UserDetailsService} to use
*/
private UserDetailsService getUserDetailsService(H http) {
if(userDetailsService == null) {
userDetailsService = http.getSharedObject(UserDetailsService.class);
}
if(userDetailsService == null) {
throw new IllegalStateException("userDetailsService cannot be null. Invoke "
+ RememberMeConfigurer.class.getSimpleName() + "#userDetailsService(UserDetailsService) or see its javadoc for alternative approaches.");
}
return userDetailsService;
}
/**
* Gets the key to use for validating remember me tokens. Either the value
* passed into {@link #key(String)}, or a secure random string if none was
* specified.
*
* @return the remember me key to use
*/
private String getKey() {
if (key == null) {
key = UUID.randomUUID().toString();
}
return key;
}
}
@@ -0,0 +1,106 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.configurers;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
import org.springframework.security.web.util.AntPathRequestMatcher;
/**
* Adds request cache for Spring Security. Specifically this ensures that
* requests that are saved (i.e. after authentication is required) are later
* replayed. All properties have reasonable defaults, so no additional
* configuration is required other than applying this
* {@link org.springframework.security.config.annotation.SecurityConfigurer}.
*
* <h2>Security Filters</h2>
*
* The following Filters are populated
*
* <ul>
* <li>{@link RequestCacheAwareFilter}</li>
* </ul>
*
* <h2>Shared Objects Created</h2>
*
* No shared objects are created.
*
* <h2>Shared Objects Used</h2>
*
* The following shared objects are used:
*
* <ul>
* <li>If no explicit {@link RequestCache}, is provided a {@link RequestCache}
* shared object is used to replay the request after authentication is
* successful</li>
* </ul>
*
* @author Rob Winch
* @since 3.2
* @see RequestCache
*/
public final class RequestCacheConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<RequestCacheConfigurer<H>,H> {
public RequestCacheConfigurer() {
}
/**
* Allows explicit configuration of the {@link RequestCache} to be used. Defaults to try finding a
* {@link RequestCache} as a shared object. Then falls back to a {@link HttpSessionRequestCache}.
*
* @param requestCache the explicit {@link RequestCache} to use
* @return the {@link RequestCacheConfigurer} for further customization
*/
public RequestCacheConfigurer<H> requestCache(RequestCache requestCache) {
getBuilder().setSharedObject(RequestCache.class, requestCache);
return this;
}
@Override
public void init(H http) throws Exception {
http.setSharedObject(RequestCache.class, getRequestCache(http));
}
@Override
public void configure(H http) throws Exception {
RequestCache requestCache = getRequestCache(http);
RequestCacheAwareFilter requestCacheFilter = new RequestCacheAwareFilter(requestCache);
requestCacheFilter = postProcess(requestCacheFilter);
http.addFilter(requestCacheFilter);
}
/**
* Gets the {@link RequestCache} to use. If one is defined using
* {@link #requestCache(org.springframework.security.web.savedrequest.RequestCache)}, then it is used. Otherwise, an
* attempt to find a {@link RequestCache} shared object is made. If that fails, an {@link HttpSessionRequestCache}
* is used
*
* @param http the {@link HttpSecurity} to attempt to fined the shared object
* @return the {@link RequestCache} to use
*/
private RequestCache getRequestCache(H http) {
RequestCache result = http.getSharedObject(RequestCache.class);
if(result != null) {
return result;
}
HttpSessionRequestCache defaultCache = new HttpSessionRequestCache();
defaultCache.setRequestMatcher(new AntPathRequestMatcher("/**", "GET"));
return defaultCache;
}
}
@@ -0,0 +1,96 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.configurers;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
import org.springframework.security.web.context.SecurityContextRepository;
/**
* Allows persisting and restoring of the {@link SecurityContext} found on the
* {@link SecurityContextHolder} for each request by configuring the
* {@link SecurityContextPersistenceFilter}. All properties have reasonable
* defaults, so no additional configuration is required other than applying this
* {@link org.springframework.security.config.annotation.SecurityConfigurer}.
*
* <h2>Security Filters</h2>
*
* The following Filters are populated
*
* <ul>
* <li>{@link SecurityContextPersistenceFilter}</li>
* </ul>
*
* <h2>Shared Objects Created</h2>
*
* No shared objects are created.
*
* <h2>Shared Objects Used</h2>
*
* The following shared objects are used:
*
* <ul>
* <li>If {@link SessionManagementConfigurer}, is provided and set to always,
* then the
* {@link SecurityContextPersistenceFilter#setForceEagerSessionCreation(boolean)}
* will be set to true.</li>
* <li>{@link SecurityContextRepository} must be set and is used on
* {@link SecurityContextPersistenceFilter}.</li>
* </ul>
*
* @author Rob Winch
* @since 3.2
*/
public final class SecurityContextConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<SecurityContextConfigurer<H>,H> {
/**
* Creates a new instance
* @see HttpSecurity#securityContext()
*/
public SecurityContextConfigurer() {
}
/**
* Specifies the shared {@link SecurityContextRepository} that is to be used
* @param securityContextRepository the {@link SecurityContextRepository} to use
* @return the {@link HttpSecurity} for further customizations
*/
public SecurityContextConfigurer<H> securityContextRepository(SecurityContextRepository securityContextRepository) {
getBuilder().setSharedObject(SecurityContextRepository.class, securityContextRepository);
return this;
}
@Override
@SuppressWarnings("unchecked")
public void configure(H http) throws Exception {
SecurityContextRepository securityContextRepository = http.getSharedObject(SecurityContextRepository.class);
SecurityContextPersistenceFilter securityContextFilter = new SecurityContextPersistenceFilter(
securityContextRepository);
SessionManagementConfigurer<?> sessionManagement = http.getConfigurer(SessionManagementConfigurer.class);
SessionCreationPolicy sessionCreationPolicy = sessionManagement == null ? null
: sessionManagement.getSessionCreationPolicy();
if (SessionCreationPolicy.ALWAYS == sessionCreationPolicy) {
securityContextFilter.setForceEagerSessionCreation(true);
}
securityContextFilter = postProcess(securityContextFilter);
http.addFilter(securityContextFilter);
}
}
@@ -0,0 +1,81 @@
/*
* 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 java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter;
/**
* Implements select methods from the {@link HttpServletRequest} using the {@link SecurityContext} from the {@link SecurityContextHolder}.
*
* <h2>Security Filters</h2>
*
* The following Filters are populated
*
* <ul>
* <li>{@link SecurityContextHolderAwareRequestFilter}</li>
* </ul>
*
* <h2>Shared Objects Created</h2>
*
* No shared objects are created.
*
* <h2>Shared Objects Used</h2>
*
* No shared Objects are used.
*
* @author Rob Winch
* @since 3.2
*/
public final class ServletApiConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<ServletApiConfigurer<H>,H> {
private SecurityContextHolderAwareRequestFilter securityContextRequestFilter = new SecurityContextHolderAwareRequestFilter();
/**
* Creates a new instance
* @see HttpSecurity#servletApi()
*/
public ServletApiConfigurer() {
}
public ServletApiConfigurer<H> rolePrefix(String rolePrefix) {
securityContextRequestFilter.setRolePrefix(rolePrefix);
return this;
}
@Override
@SuppressWarnings("unchecked")
public void configure(H http) throws Exception {
securityContextRequestFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
ExceptionHandlingConfigurer<H> exceptionConf = http.getConfigurer(ExceptionHandlingConfigurer.class);
AuthenticationEntryPoint authenticationEntryPoint = exceptionConf == null ? null : exceptionConf.getAuthenticationEntryPoint(http);
securityContextRequestFilter.setAuthenticationEntryPoint(authenticationEntryPoint);
LogoutConfigurer<H> logoutConf = http.getConfigurer(LogoutConfigurer.class);
List<LogoutHandler> logoutHandlers = logoutConf == null ? null : logoutConf.getLogoutHandlers();
securityContextRequestFilter.setLogoutHandlers(logoutHandlers);
securityContextRequestFilter = postProcess(securityContextRequestFilter);
http.addFilter(securityContextRequestFilter);
}
}
@@ -0,0 +1,454 @@
/*
* 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 java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.session.ChangeSessionIdAuthenticationStrategy;
import org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy;
import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy;
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.security.web.context.NullSecurityContextRepository;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.security.web.savedrequest.NullRequestCache;
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.web.session.SimpleRedirectInvalidSessionStrategy;
import org.springframework.util.Assert;
/**
* Allows configuring session management.
*
* <h2>Security Filters</h2>
*
* The following Filters are populated
*
* <ul>
* <li>{@link SessionManagementFilter}</li>
* <li>{@link ConcurrentSessionFilter} if there are restrictions on how many concurrent sessions a user can have</li>
* </ul>
*
* <h2>Shared Objects Created</h2>
*
* The following shared objects are created:
*
* <ul>
* <li>{@link RequestCache}</li>
* <li>{@link SecurityContextRepository}</li>
* <li>{@link SessionManagementConfigurer}</li>
* </ul>
*
* <h2>Shared Objects Used</h2>
*
* <ul>
* <li>{@link SecurityContextRepository}</li>
* </ul>
*
* @author Rob Winch
* @since 3.2
* @see SessionManagementFilter
* @see ConcurrentSessionFilter
*/
public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<SessionManagementConfigurer<H>,H> {
private SessionAuthenticationStrategy sessionFixationAuthenticationStrategy = createDefaultSessionFixationProtectionStrategy();
private SessionAuthenticationStrategy sessionAuthenticationStrategy;
private List<SessionAuthenticationStrategy> sessionAuthenticationStrategies = new ArrayList<SessionAuthenticationStrategy>();
private SessionRegistry sessionRegistry = new SessionRegistryImpl();
private Integer maximumSessions;
private String expiredUrl;
private boolean maxSessionsPreventsLogin;
private SessionCreationPolicy sessionPolicy = SessionCreationPolicy.IF_REQUIRED;
private boolean enableSessionUrlRewriting;
private String invalidSessionUrl;
private String sessionAuthenticationErrorUrl;
/**
* Creates a new instance
* @see HttpSecurity#sessionManagement()
*/
public SessionManagementConfigurer() {
}
/**
* Setting this attribute will inject the {@link SessionManagementFilter} with a
* {@link SimpleRedirectInvalidSessionStrategy} configured with the attribute value.
* When an invalid session ID is submitted, the strategy will be invoked,
* redirecting to the configured URL.
*
* @param invalidSessionUrl the URL to redirect to when an invalid session is detected
* @return the {@link SessionManagementConfigurer} for further customization
*/
public SessionManagementConfigurer<H> invalidSessionUrl(String invalidSessionUrl) {
this.invalidSessionUrl = invalidSessionUrl;
return this;
}
/**
* Defines the URL of the error page which should be shown when the
* SessionAuthenticationStrategy raises an exception. If not set, an
* unauthorized (402) error code will be returned to the client. Note that
* this attribute doesn't apply if the error occurs during a form-based
* login, where the URL for authentication failure will take precedence.
*
* @param sessionAuthenticationErrorUrl
* the URL to redirect to
* @return the {@link SessionManagementConfigurer} for further customization
*/
public SessionManagementConfigurer<H> sessionAuthenticationErrorUrl(String sessionAuthenticationErrorUrl) {
this.sessionAuthenticationErrorUrl = sessionAuthenticationErrorUrl;
return this;
}
/**
* If set to true, allows HTTP sessions to be rewritten in the URLs when
* using {@link HttpServletResponse#encodeRedirectURL(String)} or
* {@link HttpServletResponse#encodeURL(String)}, otherwise disallows HTTP
* sessions to be included in the URL. This prevents leaking information to
* external domains.
*
* @param enableSessionUrlRewriting true if should allow the JSESSIONID to be rewritten into the URLs, else false (default)
* @return the {@link SessionManagementConfigurer} for further customization
* @see HttpSessionSecurityContextRepository#setDisableUrlRewriting(boolean)
*/
public SessionManagementConfigurer<H> enableSessionUrlRewriting(boolean enableSessionUrlRewriting) {
this.enableSessionUrlRewriting = enableSessionUrlRewriting;
return this;
}
/**
* Allows specifying the {@link SessionCreationPolicy}
* @param sessionCreationPolicy the {@link SessionCreationPolicy} to use. Cannot be null.
* @return the {@link SessionManagementConfigurer} for further customizations
* @see SessionCreationPolicy
* @throws IllegalArgumentException if {@link SessionCreationPolicy} is null.
*/
public SessionManagementConfigurer<H> sessionCreationPolicy(SessionCreationPolicy sessionCreationPolicy) {
Assert.notNull(sessionCreationPolicy, "sessionCreationPolicy cannot be null");
this.sessionPolicy = sessionCreationPolicy;
return this;
}
/**
* Allows explicitly specifying the {@link SessionAuthenticationStrategy}.
* The default is to use {@link SessionFixationProtectionStrategy}. If
* restricting the maximum number of sessions is configured, then
* {@link CompositeSessionAuthenticationStrategy} delegating to
* {@link ConcurrentSessionControlAuthenticationStrategy},
* {@link SessionFixationProtectionStrategy} (optional), and
* {@link RegisterSessionAuthenticationStrategy} will be used.
*
* @param sessionAuthenticationStrategy
* @return the {@link SessionManagementConfigurer} for further
* customizations
*/
public SessionManagementConfigurer<H> sessionAuthenticationStrategy(SessionAuthenticationStrategy sessionAuthenticationStrategy) {
this.sessionFixationAuthenticationStrategy = sessionAuthenticationStrategy;
return this;
}
/**
* Adds an additional {@link SessionAuthenticationStrategy} to be used within the {@link CompositeSessionAuthenticationStrategy}.
*
* @param sessionAuthenticationStrategy
* @return the {@link SessionManagementConfigurer} for further
* customizations
*/
SessionManagementConfigurer<H> addSessionAuthenticationStrategy(SessionAuthenticationStrategy sessionAuthenticationStrategy) {
this.sessionAuthenticationStrategies.add(sessionAuthenticationStrategy);
return this;
}
public SessionFixationConfigurer sessionFixation() {
return new SessionFixationConfigurer();
}
/**
* Controls the maximum number of sessions for a user. The default is to allow any number of users.
* @param maximumSessions the maximum number of sessions for a user
* @return the {@link SessionManagementConfigurer} for further customizations
*/
public ConcurrencyControlConfigurer maximumSessions(int maximumSessions) {
this.maximumSessions = maximumSessions;
return new ConcurrencyControlConfigurer();
}
/**
* Invokes {@link #postProcess(Object)} and sets the {@link SessionAuthenticationStrategy} for session fixation.
* @param sessionFixationAuthenticationStrategy
*/
private void setSessionFixationAuthenticationStrategy(SessionAuthenticationStrategy sessionFixationAuthenticationStrategy) {
this.sessionFixationAuthenticationStrategy = postProcess(sessionFixationAuthenticationStrategy);
}
/**
* Allows configuring SessionFixation protection
*
* @author Rob Winch
*/
public final class SessionFixationConfigurer {
/**
* Specifies that a new session should be created, but the session
* attributes from the original {@link HttpSession} should not be
* retained.
*
* @return the {@link SessionManagementConfigurer} for further customizations
*/
public SessionManagementConfigurer<H> newSession() {
SessionFixationProtectionStrategy sessionFixationProtectionStrategy = new SessionFixationProtectionStrategy();
sessionFixationProtectionStrategy.setMigrateSessionAttributes(false);
setSessionFixationAuthenticationStrategy(sessionFixationProtectionStrategy);
return SessionManagementConfigurer.this;
}
/**
* Specifies that a new session should be created and the session
* attributes from the original {@link HttpSession} should be
* retained.
*
* @return the {@link SessionManagementConfigurer} for further customizations
*/
public SessionManagementConfigurer<H> migrateSession() {
setSessionFixationAuthenticationStrategy(new SessionFixationProtectionStrategy());
return SessionManagementConfigurer.this;
}
/**
* Specifies that no session fixation protection should be enabled. This
* may be useful when utilizing other mechanisms for protecting against
* session fixation. For example, if application container session
* fixation protection is already in use. Otherwise, this option is not
* recommended.
*
* @return the {@link SessionManagementConfigurer} for further
* customizations
*/
public SessionManagementConfigurer<H> changeSessionId() {
setSessionFixationAuthenticationStrategy(new ChangeSessionIdAuthenticationStrategy());
return SessionManagementConfigurer.this;
}
/**
* Specifies that no session fixation protection should be enabled. This
* may be useful when utilizing other mechanisms for protecting against
* session fixation. For example, if application container session
* fixation protection is already in use. Otherwise, this option is not
* recommended.
*
* @return the {@link SessionManagementConfigurer} for further
* customizations
*/
public SessionManagementConfigurer<H> none() {
setSessionFixationAuthenticationStrategy(new NullAuthenticatedSessionStrategy());
return SessionManagementConfigurer.this;
}
}
/**
* Allows configuring controlling of multiple sessions.
*
* @author Rob Winch
*/
public final class ConcurrencyControlConfigurer {
/**
* The URL to redirect to if a user tries to access a resource and their
* session has been expired due to too many sessions for the current user.
* The default is to write a simple error message to the response.
*
* @param expiredUrl the URL to redirect to
* @return the {@link ConcurrencyControlConfigurer} for further customizations
*/
public ConcurrencyControlConfigurer expiredUrl(String expiredUrl) {
SessionManagementConfigurer.this.expiredUrl = expiredUrl;
return this;
}
/**
* If true, prevents a user from authenticating when the
* {@link #maximumSessions(int)} has been reached. Otherwise (default), the user who
* authenticates is allowed access and an existing user's session is
* expired. The user's who's session is forcibly expired is sent to
* {@link #expiredUrl(String)}. The advantage of this approach is if a user
* accidentally does not log out, there is no need for an administrator to
* intervene or wait till their session expires.
*
* @param maxSessionsPreventsLogin true to have an error at time of authentication, else false (default)
* @return the {@link ConcurrencyControlConfigurer} for further customizations
*/
public ConcurrencyControlConfigurer maxSessionsPreventsLogin(boolean maxSessionsPreventsLogin) {
SessionManagementConfigurer.this.maxSessionsPreventsLogin = maxSessionsPreventsLogin;
return this;
}
/**
* Controls the {@link SessionRegistry} implementation used. The default
* is {@link SessionRegistryImpl} which is an in memory implementation.
*
* @param sessionRegistry the {@link SessionRegistry} to use
* @return the {@link ConcurrencyControlConfigurer} for further customizations
*/
public ConcurrencyControlConfigurer sessionRegistry(SessionRegistry sessionRegistry) {
SessionManagementConfigurer.this.sessionRegistry = sessionRegistry;
return this;
}
/**
* Used to chain back to the {@link SessionManagementConfigurer}
*
* @return the {@link SessionManagementConfigurer} for further customizations
*/
public SessionManagementConfigurer<H> and() {
return SessionManagementConfigurer.this;
}
private ConcurrencyControlConfigurer() {}
}
@Override
public void init(H builder) throws Exception {
SecurityContextRepository securityContextRepository = builder.getSharedObject(SecurityContextRepository.class);
boolean stateless = isStateless();
if(securityContextRepository == null) {
if(stateless) {
builder.setSharedObject(SecurityContextRepository.class, new NullSecurityContextRepository());
} else {
HttpSessionSecurityContextRepository httpSecurityRepository = new HttpSessionSecurityContextRepository();
httpSecurityRepository.setDisableUrlRewriting(!enableSessionUrlRewriting);
httpSecurityRepository.setAllowSessionCreation(isAllowSessionCreation());
builder.setSharedObject(SecurityContextRepository.class, httpSecurityRepository);
}
}
RequestCache requestCache = builder.getSharedObject(RequestCache.class);
if(requestCache == null) {
if(stateless) {
builder.setSharedObject(RequestCache.class, new NullRequestCache());
}
}
builder.setSharedObject(SessionAuthenticationStrategy.class, getSessionAuthenticationStrategy());
}
@Override
public void configure(H http) throws Exception {
SecurityContextRepository securityContextRepository = http.getSharedObject(SecurityContextRepository.class);
SessionManagementFilter sessionManagementFilter = new SessionManagementFilter(securityContextRepository, getSessionAuthenticationStrategy());
if(sessionAuthenticationErrorUrl != null) {
sessionManagementFilter.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler(sessionAuthenticationErrorUrl));
}
if(invalidSessionUrl != null) {
sessionManagementFilter.setInvalidSessionStrategy(new SimpleRedirectInvalidSessionStrategy(invalidSessionUrl));
}
sessionManagementFilter = postProcess(sessionManagementFilter);
http.addFilter(sessionManagementFilter);
if(isConcurrentSessionControlEnabled()) {
ConcurrentSessionFilter concurrentSessionFilter = new ConcurrentSessionFilter(sessionRegistry, expiredUrl);
concurrentSessionFilter = postProcess(concurrentSessionFilter);
http.addFilter(concurrentSessionFilter);
}
}
/**
* Gets the {@link SessionCreationPolicy}. Can not be null.
* @return the {@link SessionCreationPolicy}
*/
SessionCreationPolicy getSessionCreationPolicy() {
return sessionPolicy;
}
/**
* Returns true if the {@link SessionCreationPolicy} allows session creation, else false
* @return true if the {@link SessionCreationPolicy} allows session creation
*/
private boolean isAllowSessionCreation() {
return SessionCreationPolicy.ALWAYS == sessionPolicy || SessionCreationPolicy.IF_REQUIRED == sessionPolicy;
}
/**
* Returns true if the {@link SessionCreationPolicy} is stateless
* @return
*/
private boolean isStateless() {
return SessionCreationPolicy.STATELESS == sessionPolicy;
}
/**
* Gets the customized {@link SessionAuthenticationStrategy} if
* {@link #sessionAuthenticationStrategy(SessionAuthenticationStrategy)} was
* specified. Otherwise creates a default
* {@link SessionAuthenticationStrategy}.
*
* @return the {@link SessionAuthenticationStrategy} to use
*/
private SessionAuthenticationStrategy getSessionAuthenticationStrategy() {
if(sessionAuthenticationStrategy != null) {
return sessionAuthenticationStrategy;
}
List<SessionAuthenticationStrategy> delegateStrategies = sessionAuthenticationStrategies;
if(isConcurrentSessionControlEnabled()) {
ConcurrentSessionControlAuthenticationStrategy concurrentSessionControlStrategy = new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry);
concurrentSessionControlStrategy.setMaximumSessions(maximumSessions);
concurrentSessionControlStrategy.setExceptionIfMaximumExceeded(maxSessionsPreventsLogin);
concurrentSessionControlStrategy = postProcess(concurrentSessionControlStrategy);
RegisterSessionAuthenticationStrategy registerSessionStrategy = new RegisterSessionAuthenticationStrategy(sessionRegistry);
registerSessionStrategy = postProcess(registerSessionStrategy);
delegateStrategies.addAll(Arrays.asList(concurrentSessionControlStrategy, sessionFixationAuthenticationStrategy, registerSessionStrategy));
} else {
delegateStrategies.add(sessionFixationAuthenticationStrategy);
}
sessionAuthenticationStrategy = postProcess(new CompositeSessionAuthenticationStrategy(delegateStrategies));
return sessionAuthenticationStrategy;
}
/**
* Returns true if the number of concurrent sessions per user should be restricted.
* @return
*/
private boolean isConcurrentSessionControlEnabled() {
return maximumSessions != null;
}
/**
* Creates the default {@link SessionAuthenticationStrategy} for session fixation
* @return the default {@link SessionAuthenticationStrategy} for session fixation
*/
private static SessionAuthenticationStrategy createDefaultSessionFixationProtectionStrategy() {
try {
return new ChangeSessionIdAuthenticationStrategy();
} catch(IllegalStateException e) {
return new SessionFixationProtectionStrategy();
}
}
}
@@ -0,0 +1,253 @@
/*
* 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 java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.access.vote.AuthenticatedVoter;
import org.springframework.security.access.vote.RoleVoter;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.util.RequestMatcher;
import org.springframework.util.Assert;
/**
* Adds URL based authorization using {@link DefaultFilterInvocationSecurityMetadataSource}. At least one
* {@link org.springframework.web.bind.annotation.RequestMapping} needs to be mapped to {@link ConfigAttribute}'s for
* this {@link SecurityContextConfigurer} to have meaning.
* <h2>Security Filters</h2>
*
* The following Filters are populated
*
* <ul>
* <li>{@link org.springframework.security.web.access.intercept.FilterSecurityInterceptor}</li>
* </ul>
*
* <h2>Shared Objects Created</h2>
*
* The following shared objects are populated to allow other {@link org.springframework.security.config.annotation.SecurityConfigurer}'s to customize:
* <ul>
* <li>{@link org.springframework.security.web.access.intercept.FilterSecurityInterceptor}</li>
* </ul>
*
* <h2>Shared Objects Used</h2>
*
* The following shared objects are used:
*
* <ul>
* <li>{@link org.springframework.security.config.annotation.web.builders.HttpSecurity#getAuthenticationManager()}</li>
* </ul>
*
* @param <H> the type of {@link HttpSecurityBuilder} that is being configured
* @param <C> the type of object that is being chained
*
* @author Rob Winch
* @since 3.2
* @see ExpressionUrlAuthorizationConfigurer
*/
public final class UrlAuthorizationConfigurer<H extends HttpSecurityBuilder<H>, C> extends AbstractInterceptUrlConfigurer<H,C,UrlAuthorizationConfigurer<H,C>.AuthorizedUrl> {
/**
* Adds an {@link ObjectPostProcessor} for this class.
*
* @param objectPostProcessor
* @return the {@link UrlAuthorizationConfigurer} for further customizations
*/
public UrlAuthorizationConfigurer<H,C> withObjectPostProcessor(ObjectPostProcessor<?> objectPostProcessor) {
addObjectPostProcessor(objectPostProcessor);
return this;
}
/**
* Creates the default {@link AccessDecisionVoter} instances used if an
* {@link AccessDecisionManager} was not specified using
* {@link #accessDecisionManager(AccessDecisionManager)}.
*/
@Override
@SuppressWarnings("rawtypes")
final List<AccessDecisionVoter> getDecisionVoters() {
List<AccessDecisionVoter> decisionVoters = new ArrayList<AccessDecisionVoter>();
decisionVoters.add(new RoleVoter());
decisionVoters.add(new AuthenticatedVoter());
return decisionVoters;
}
/**
* Creates the {@link FilterInvocationSecurityMetadataSource} to use. The
* implementation is a {@link DefaultFilterInvocationSecurityMetadataSource}
* .
*/
@Override
FilterInvocationSecurityMetadataSource createMetadataSource() {
return new DefaultFilterInvocationSecurityMetadataSource(createRequestMap());
}
/**
* Chains the {@link RequestMatcher} creation to the {@link AuthorizedUrl} class.
*/
@Override
protected AuthorizedUrl chainRequestMatchersInternal(List<RequestMatcher> requestMatchers) {
return new AuthorizedUrl(requestMatchers);
}
/**
* Adds a mapping of the {@link RequestMatcher} instances to the {@link ConfigAttribute} instances.
* @param requestMatchers the {@link RequestMatcher} instances that should map to the provided {@link ConfigAttribute} instances
* @param configAttributes the {@link ConfigAttribute} instances that should be mapped by the {@link RequestMatcher} instances
* @return the {@link UrlAuthorizationConfigurer} for further customizations
*/
private UrlAuthorizationConfigurer<H,C> addMapping(Iterable<? extends RequestMatcher> requestMatchers, Collection<ConfigAttribute> configAttributes) {
for(RequestMatcher requestMatcher : requestMatchers) {
addMapping(new UrlMapping(requestMatcher, configAttributes));
}
return this;
}
/**
* Creates a String for specifying a user requires a role.
*
* @param role
* the role that should be required which is prepended with ROLE_
* automatically (i.e. USER, ADMIN, etc). It should not start
* with ROLE_
* @return the {@link ConfigAttribute} expressed as a String
*/
private static String hasRole(String role) {
Assert.isTrue(
!role.startsWith("ROLE_"),
role
+ " should not start with ROLE_ since ROLE_ is automatically prepended when using hasRole. Consider using hasAuthority or access instead.");
return "ROLE_" + role;
}
/**
* Creates a String for specifying that a user requires one of many roles.
*
* @param roles
* the roles that the user should have at least one of (i.e.
* ADMIN, USER, etc). Each role should not start with ROLE_ since
* it is automatically prepended already.
* @return the {@link ConfigAttribute} expressed as a String
*/
private static String[] hasAnyRole(String... roles) {
for(int i=0;i<roles.length;i++) {
roles[i] = "ROLE_" + roles[i];
}
return roles;
}
/**
* Creates a String for specifying that a user requires one of many authorities
* @param authorities the authorities that the user should have at least one of (i.e. ROLE_USER, ROLE_ADMIN, etc).
* @return the {@link ConfigAttribute} expressed as a String.
*/
private static String[] hasAnyAuthority(String... authorities) {
return authorities;
}
/**
* Maps the specified {@link RequestMatcher} instances to {@link ConfigAttribute} instances.
*
* @author Rob Winch
* @since 3.2
*/
public final class AuthorizedUrl {
private final List<RequestMatcher> requestMatchers;
/**
* Creates a new instance
* @param requestMatchers the {@link RequestMatcher} instances to map to some {@link ConfigAttribute} instances.
* @see UrlAuthorizationConfigurer#chainRequestMatchers(List)
*/
private AuthorizedUrl(List<RequestMatcher> requestMatchers) {
Assert.notEmpty(requestMatchers, "requestMatchers must contain at least one value");
this.requestMatchers = requestMatchers;
}
/**
* Specifies a user requires a role.
*
* @param role
* the role that should be required which is prepended with ROLE_
* automatically (i.e. USER, ADMIN, etc). It should not start
* with ROLE_
* the {@link UrlAuthorizationConfigurer} for further customization
*/
public UrlAuthorizationConfigurer<H,C> hasRole(String role) {
return access(UrlAuthorizationConfigurer.hasRole(role));
}
/**
* Specifies that a user requires one of many roles.
*
* @param roles
* the roles that the user should have at least one of (i.e.
* ADMIN, USER, etc). Each role should not start with ROLE_ since
* it is automatically prepended already.
* @return the {@link UrlAuthorizationConfigurer} for further customization
*/
public UrlAuthorizationConfigurer<H,C> hasAnyRole(String... roles) {
return access(UrlAuthorizationConfigurer.hasAnyRole(roles));
}
/**
* Specifies a user requires an authority.
*
* @param authority
* the authority that should be required
* @return the {@link UrlAuthorizationConfigurer} for further customization
*/
public UrlAuthorizationConfigurer<H,C> hasAuthority(String authority) {
return access(authority);
}
/**
* Specifies that a user requires one of many authorities
* @param authorities the authorities that the user should have at least one of (i.e. ROLE_USER, ROLE_ADMIN, etc).
* @return the {@link UrlAuthorizationConfigurer} for further customization
*/
public UrlAuthorizationConfigurer<H,C> hasAnyAuthority(String... authorities) {
return access(UrlAuthorizationConfigurer.hasAnyAuthority(authorities));
}
/**
* Specifies that an anonymous user is allowed access
* @return the {@link UrlAuthorizationConfigurer} for further customization
*/
public UrlAuthorizationConfigurer<H,C> anonymous() {
return hasRole("ROLE_ANONYMOUS");
}
/**
* Specifies that the user must have the specified {@link ConfigAttribute}'s
* @param attributes the {@link ConfigAttribute}'s that restrict access to a URL
* @return the {@link UrlAuthorizationConfigurer} for further customization
*/
public UrlAuthorizationConfigurer<H,C> access(String... attributes) {
addMapping(requestMatchers, SecurityConfig.createList(attributes));
return UrlAuthorizationConfigurer.this;
}
}
}
@@ -0,0 +1,196 @@
/*
* 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 javax.servlet.http.HttpServletRequest;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails;
import org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor;
import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter;
/**
* Adds X509 based pre authentication to an application. Since validating the
* certificate happens when the client connects, the requesting and validation
* of the client certificate should be performed by the container. Spring Security
* will then use the certificate to look up the {@link Authentication} for the user.
*
* <h2>Security Filters</h2>
*
* The following Filters are populated
*
* <ul>
* <li>{@link X509AuthenticationFilter}</li>
* </ul>
*
* <h2>Shared Objects Created</h2>
*
* The following shared objects are created
*
* <ul>
* <li>
* {@link AuthenticationEntryPoint}
* is populated with an {@link Http403ForbiddenEntryPoint}</li>
* <li>A {@link PreAuthenticatedAuthenticationProvider} is populated into
* {@link HttpSecurity#authenticationProvider(org.springframework.security.authentication.AuthenticationProvider)}
* </li>
* </ul>
*
* <h2>Shared Objects Used</h2>
*
* The following shared objects are used:
*
* <ul>
* <li>A {@link UserDetailsService} shared object is used if no {@link AuthenticationUserDetailsService} is specified</li>
* </ul>
*
* @author Rob Winch
* @since 3.2
*/
public final class X509Configurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<X509Configurer<H>,H> {
private X509AuthenticationFilter x509AuthenticationFilter;
private AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> authenticationUserDetailsService;
private String subjectPrincipalRegex;
private AuthenticationDetailsSource<HttpServletRequest, PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails> authenticationDetailsSource;
/**
* Creates a new instance
* @see HttpSecurity#x509()
*/
public X509Configurer() {
}
/**
* Allows specifying the entire {@link X509AuthenticationFilter}. If this is
* specified, the properties on {@link X509Configurer} will not be
* populated on the {@link X509AuthenticationFilter}.
*
* @param x509AuthenticationFilter the {@link X509AuthenticationFilter} to use
* @return the {@link X509Configurer} for further customizations
*/
public X509Configurer<H> x509AuthenticationFilter(
X509AuthenticationFilter x509AuthenticationFilter) {
this.x509AuthenticationFilter = x509AuthenticationFilter;
return this;
}
/**
* Specifies the {@link AuthenticationDetailsSource}
*
* @param authenticationDetailsSource the {@link AuthenticationDetailsSource} to use
* @return the {@link X509Configurer} to use
*/
public X509Configurer<H> authenticationDetailsSource(AuthenticationDetailsSource<HttpServletRequest, PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails> authenticationDetailsSource) {
this.authenticationDetailsSource = authenticationDetailsSource;
return this;
}
/**
* Shortcut for invoking {@link #authenticationUserDetailsService(AuthenticationUserDetailsService)} with a {@link UserDetailsByNameServiceWrapper}.
*
* @param userDetailsService the {@link UserDetailsService} to use
* @return the {@link X509Configurer} for further customizations
*/
public X509Configurer<H> userDetailsService(
UserDetailsService userDetailsService) {
UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken> authenticationUserDetailsService = new UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken>();
authenticationUserDetailsService.setUserDetailsService(userDetailsService);
return authenticationUserDetailsService(authenticationUserDetailsService);
}
/**
* Specifies the {@link AuthenticationUserDetailsService} to use. If not
* specified, the shared {@link UserDetailsService} will be used to create a
* {@link UserDetailsByNameServiceWrapper}.
*
* @param authenticationUserDetailsService the {@link AuthenticationUserDetailsService} to use
* @return the {@link X509Configurer} for further customizations
*/
public X509Configurer<H> authenticationUserDetailsService(
AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> authenticationUserDetailsService) {
this.authenticationUserDetailsService = authenticationUserDetailsService;
return this;
}
/**
* Specifies the regex to extract the principal from the certificate. If not
* specified, the default expression from
* {@link SubjectDnX509PrincipalExtractor} is used.
*
* @param subjectPrincipalRegex
* the regex to extract the user principal from the certificate
* (i.e. "CN=(.*?)(?:,|$)").
* @return the {@link X509Configurer} for further customizations
*/
public X509Configurer<H> subjectPrincipalRegex(String subjectPrincipalRegex) {
this.subjectPrincipalRegex = subjectPrincipalRegex;
return this;
}
@Override
public void init(H http) throws Exception {
PreAuthenticatedAuthenticationProvider authenticationProvider = new PreAuthenticatedAuthenticationProvider();
authenticationProvider.setPreAuthenticatedUserDetailsService(getAuthenticationUserDetailsService(http));
http
.authenticationProvider(authenticationProvider)
.setSharedObject(AuthenticationEntryPoint.class,new Http403ForbiddenEntryPoint());
}
@Override
public void configure(H http) throws Exception {
X509AuthenticationFilter filter = getFilter(http.getSharedObject(AuthenticationManager.class));
http.addFilter(filter);
}
private X509AuthenticationFilter getFilter(
AuthenticationManager authenticationManager) {
if (x509AuthenticationFilter == null) {
x509AuthenticationFilter = new X509AuthenticationFilter();
x509AuthenticationFilter.setAuthenticationManager(authenticationManager);
if(subjectPrincipalRegex != null) {
SubjectDnX509PrincipalExtractor principalExtractor = new SubjectDnX509PrincipalExtractor();
principalExtractor.setSubjectDnRegex(subjectPrincipalRegex);
x509AuthenticationFilter.setPrincipalExtractor(principalExtractor);
}
if(authenticationDetailsSource != null) {
x509AuthenticationFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
}
x509AuthenticationFilter = postProcess(x509AuthenticationFilter);
}
return x509AuthenticationFilter;
}
private AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> getAuthenticationUserDetailsService(H http) {
if(authenticationUserDetailsService == null) {
userDetailsService(http.getSharedObject(UserDetailsService.class));
}
return authenticationUserDetailsService;
}
}
@@ -0,0 +1,500 @@
/*
* 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.openid;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.openid4java.consumer.ConsumerException;
import org.openid4java.consumer.ConsumerManager;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer;
import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer;
import org.springframework.security.config.annotation.web.configurers.RememberMeConfigurer;
import org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer;
import org.springframework.security.config.annotation.web.configurers.openid.OpenIDLoginConfigurer;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.openid.AxFetchListFactory;
import org.springframework.security.openid.OpenID4JavaConsumer;
import org.springframework.security.openid.OpenIDAttribute;
import org.springframework.security.openid.OpenIDAuthenticationFilter;
import org.springframework.security.openid.OpenIDAuthenticationProvider;
import org.springframework.security.openid.OpenIDAuthenticationToken;
import org.springframework.security.openid.OpenIDConsumer;
import org.springframework.security.openid.RegexBasedAxFetchListFactory;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.security.web.authentication.ui.DefaultLoginPageViewFilter;
import org.springframework.security.web.util.AntPathRequestMatcher;
import org.springframework.security.web.util.RequestMatcher;
/**
* Adds support for OpenID based authentication.
*
* <h2>Example Configuration</h2>
*
* <pre>
*
* &#064;Configuration
* &#064;EnableWebSecurity
* public class OpenIDLoginConfig extends WebSecurityConfigurerAdapter {
*
* &#064;Override
* protected void configure(HttpSecurity http) {
* http
* .authorizeRequests()
* .antMatchers(&quot;/**&quot;).hasRole(&quot;USER&quot;)
* .and()
* .openidLogin()
* .permitAll();
* }
*
* &#064;Override
* protected void registerAuthentication(
* AuthenticationManagerBuilder auth) throws Exception {
* auth
* .inMemoryAuthentication()
* .withUser(&quot;https://www.google.com/accounts/o8/id?id=lmkCn9xzPdsxVwG7pjYMuDgNNdASFmobNkcRPaWU&quot;)
* .password(&quot;password&quot;)
* .roles(&quot;USER&quot;);
* }
* }
* </pre>
*
* <h2>Security Filters</h2>
*
* The following Filters are populated
*
* <ul>
* <li>
* {@link OpenIDAuthenticationFilter}</li>
* </ul>
*
* <h2>Shared Objects Created</h2>
*
* <ul>
* <li>
* {@link AuthenticationEntryPoint}
* is populated with a {@link LoginUrlAuthenticationEntryPoint}</li>
* <li>A {@link OpenIDAuthenticationProvider} is populated into
* {@link HttpSecurity#authenticationProvider(org.springframework.security.authentication.AuthenticationProvider)}
* </li>
* </ul>
*
* <h2>Shared Objects Used</h2>
*
* The following shared objects are used:
*
* <ul>
* <li>{@link AuthenticationManager}</li>
* <li>{@link RememberMeServices} - is optionally used. See
* {@link RememberMeConfigurer}</li>
* <li>{@link SessionAuthenticationStrategy} - is optionally used. See
* {@link SessionManagementConfigurer}</li>
* </ul>
*
* @author Rob Winch
* @since 3.2
*/
public final class OpenIDLoginConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractAuthenticationFilterConfigurer<H,OpenIDLoginConfigurer<H>,OpenIDAuthenticationFilter> {
private OpenIDConsumer openIDConsumer;
private ConsumerManager consumerManager;
private AuthenticationUserDetailsService<OpenIDAuthenticationToken> authenticationUserDetailsService;
private List<AttributeExchangeConfigurer> attributeExchangeConfigurers = new ArrayList<AttributeExchangeConfigurer>();
/**
* Creates a new instance
*/
public OpenIDLoginConfigurer() {
super(new OpenIDAuthenticationFilter(),"/login/openid");
}
/**
* Sets up OpenID attribute exchange for OpenID's matching the specified
* pattern.
*
* @param identifierPattern
* the regular expression for matching on OpenID's (i.e.
* "https://www.google.com/.*", ".*yahoo.com.*", etc)
* @return a {@link AttributeExchangeConfigurer} for further customizations of the attribute exchange
*/
public AttributeExchangeConfigurer attributeExchange(String identifierPattern) {
AttributeExchangeConfigurer attributeExchangeConfigurer = new AttributeExchangeConfigurer(identifierPattern);
this.attributeExchangeConfigurers .add(attributeExchangeConfigurer);
return attributeExchangeConfigurer;
}
/**
* Allows specifying the {@link OpenIDConsumer} to be used. The default is
* using an {@link OpenID4JavaConsumer}.
*
* @param consumer
* the {@link OpenIDConsumer} to be used
* @return the {@link OpenIDLoginConfigurer} for further customizations
*/
public OpenIDLoginConfigurer<H> consumer(OpenIDConsumer consumer) {
this.openIDConsumer = consumer;
return this;
}
/**
* Allows specifying the {@link ConsumerManager} to be used. If specified,
* will be populated into an {@link OpenID4JavaConsumer}.
*
* <p>
* This is a shortcut for specifying the {@link OpenID4JavaConsumer} with a
* specific {@link ConsumerManager} on {@link #consumer(OpenIDConsumer)}.
* </p>
*
* @param consumerManager the {@link ConsumerManager} to use. Cannot be null.
* @return the {@link OpenIDLoginConfigurer} for further customizations
*/
public OpenIDLoginConfigurer<H> consumerManager(ConsumerManager consumerManager) {
this.consumerManager = consumerManager;
return this;
}
/**
* The {@link AuthenticationUserDetailsService} to use. By default a
* {@link UserDetailsByNameServiceWrapper} is used with the
* {@link UserDetailsService} shared object found with
* {@link HttpSecurity#getSharedObject(Class)}.
*
* @param authenticationUserDetailsService the {@link AuthenticationDetailsSource} to use
* @return the {@link OpenIDLoginConfigurer} for further customizations
*/
public OpenIDLoginConfigurer<H> authenticationUserDetailsService(AuthenticationUserDetailsService<OpenIDAuthenticationToken> authenticationUserDetailsService) {
this.authenticationUserDetailsService = authenticationUserDetailsService;
return this;
}
/**
* Specifies the URL used to authenticate OpenID requests. If the {@link HttpServletRequest}
* matches this URL the {@link OpenIDAuthenticationFilter} will attempt to
* authenticate the request. The default is "/login/openid".
*
* @param loginUrl
* the URL used to perform authentication
* @return the {@link OpenIDLoginConfigurer} for additional customization
*/
public OpenIDLoginConfigurer<H> loginProcessingUrl(String loginProcessingUrl) {
return super.loginProcessingUrl(loginProcessingUrl);
}
/**
* <p>
* Specifies the URL to send users to if login is required. If used with
* {@link WebSecurityConfigurerAdapter} a default login page will be
* generated when this attribute is not specified.
* </p>
*
* <p>
* If a URL is specified or this is not being used in conjuction with
* {@link WebSecurityConfigurerAdapter}, users are required to process the
* specified URL to generate a login page.
* </p>
*
* <ul>
* <li>It must be an HTTP POST</li>
* <li>It must be submitted to {@link #loginProcessingUrl(String)}</li>
* <li>It should include the OpenID as an HTTP parameter by the name of
* {@link OpenIDAuthenticationFilter#DEFAULT_CLAIMED_IDENTITY_FIELD}</li>
* </ul>
*
*
* <h2>Impact on other defaults</h2>
*
* Updating this value, also impacts a number of other default values. For example,
* the following are the default values when only formLogin() was specified.
*
* <ul>
* <li>/login GET - the login form</li>
* <li>/login POST - process the credentials and if valid authenticate the
* user</li>
* <li>/login?error GET - redirect here for failed authentication attempts</li>
* <li>/login?logout GET - redirect here after successfully logging out</li>
* </ul>
*
* If "/authenticate" was passed to this method it update the defaults as shown
* below:
*
* <ul>
* <li>/authenticate GET - the login form</li>
* <li>/authenticate POST - process the credentials and if valid authenticate the
* user</li>
* <li>/authenticate?error GET - redirect here for failed authentication attempts</li>
* <li>/authenticate?logout GET - redirect here after successfully logging out</li>
* </ul>
*
* @param loginPage the login page to redirect to if authentication is required (i.e. "/login")
* @return the {@link FormLoginConfigurer} for additional customization
*/
public OpenIDLoginConfigurer<H> loginPage(String loginPage) {
return super.loginPage(loginPage);
}
@Override
public void init(H http) throws Exception {
super.init(http);
OpenIDAuthenticationProvider authenticationProvider = new OpenIDAuthenticationProvider();
authenticationProvider.setAuthenticationUserDetailsService(getAuthenticationUserDetailsService(http));
authenticationProvider = postProcess(authenticationProvider);
http.authenticationProvider(authenticationProvider);
initDefaultLoginFilter(http);
}
@Override
public void configure(H http) throws Exception {
getAuthenticationFilter().setConsumer(getConsumer());
super.configure(http);
}
/* (non-Javadoc)
* @see org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer#createLoginProcessingUrlMatcher(java.lang.String)
*/
@Override
protected RequestMatcher createLoginProcessingUrlMatcher(
String loginProcessingUrl) {
return new AntPathRequestMatcher(loginProcessingUrl);
}
/**
* Gets the {@link OpenIDConsumer} that was configured or defaults to an {@link OpenID4JavaConsumer}.
* @return the {@link OpenIDConsumer} to use
* @throws ConsumerException
*/
private OpenIDConsumer getConsumer() throws ConsumerException {
if(openIDConsumer == null) {
openIDConsumer = new OpenID4JavaConsumer(getConsumerManager(), attributesToFetchFactory());
}
return openIDConsumer;
}
/**
* Gets the {@link ConsumerManager} that was configured or defaults to using a {@link ConsumerManager} with the default constructor.
* @return the {@link ConsumerManager} to use
*/
private ConsumerManager getConsumerManager() {
if(this.consumerManager != null) {
return this.consumerManager;
}
return new ConsumerManager();
}
/**
* Creates an {@link RegexBasedAxFetchListFactory} using the attributes
* populated by {@link AttributeExchangeConfigurer}
*
* @return the {@link AxFetchListFactory} to use
*/
private AxFetchListFactory attributesToFetchFactory() {
Map<String,List<OpenIDAttribute>> identityToAttrs = new HashMap<String,List<OpenIDAttribute>>();
for(AttributeExchangeConfigurer conf : attributeExchangeConfigurers) {
identityToAttrs.put(conf.identifier, conf.getAttributes());
}
return new RegexBasedAxFetchListFactory(identityToAttrs);
}
/**
* Gets the {@link AuthenticationUserDetailsService} that was configured or
* defaults to {@link UserDetailsByNameServiceWrapper} that uses a
* {@link UserDetailsService} looked up using
* {@link HttpSecurity#getSharedObject(Class)}
*
* @param http the current {@link HttpSecurity}
* @return the {@link AuthenticationUserDetailsService}.
*/
private AuthenticationUserDetailsService<OpenIDAuthenticationToken> getAuthenticationUserDetailsService(
H http) {
if(authenticationUserDetailsService != null) {
return authenticationUserDetailsService;
}
return new UserDetailsByNameServiceWrapper<OpenIDAuthenticationToken>(http.getSharedObject(UserDetailsService.class));
}
/**
* If available, initializes the {@link DefaultLoginPageViewFilter} shared object.
*
* @param http the {@link HttpSecurityBuilder} to use
*/
private void initDefaultLoginFilter(H http) {
DefaultLoginPageViewFilter loginPageGeneratingFilter = http.getSharedObject(DefaultLoginPageViewFilter.class);
if(loginPageGeneratingFilter != null && !isCustomLoginPage()) {
loginPageGeneratingFilter.setOpenIdEnabled(true);
loginPageGeneratingFilter.setOpenIDauthenticationUrl(getLoginProcessingUrl());
String loginPageUrl = loginPageGeneratingFilter.getLoginPageUrl();
if(loginPageUrl == null) {
loginPageGeneratingFilter.setLoginPageUrl(getLoginPage());
loginPageGeneratingFilter.setFailureUrl(getFailureUrl());
}
loginPageGeneratingFilter.setOpenIDusernameParameter(OpenIDAuthenticationFilter.DEFAULT_CLAIMED_IDENTITY_FIELD);
}
}
/**
* A class used to add OpenID attributes to look up
*
* @author Rob Winch
*/
public final class AttributeExchangeConfigurer {
private final String identifier;
private List<OpenIDAttribute> attributes = new ArrayList<OpenIDAttribute>();
private List<AttributeConfigurer> attributeConfigurers = new ArrayList<AttributeConfigurer>();
/**
* Creates a new instance
* @param identifierPattern the pattern that attempts to match on the OpenID
* @see OpenIDLoginConfigurer#attributeExchange(String)
*/
private AttributeExchangeConfigurer(String identifierPattern) {
this.identifier = identifierPattern;
}
/**
* Get the {@link OpenIDLoginConfigurer} to customize the OpenID configuration further
* @return the {@link OpenIDLoginConfigurer}
*/
public OpenIDLoginConfigurer<H> and() {
return OpenIDLoginConfigurer.this;
}
/**
* Adds an {@link OpenIDAttribute} to be obtained for the configured OpenID pattern.
* @param attribute the {@link OpenIDAttribute} to obtain
* @return the {@link AttributeExchangeConfigurer} for further customization of attribute exchange
*/
public AttributeExchangeConfigurer attribute(OpenIDAttribute attribute) {
this.attributes.add(attribute);
return this;
}
/**
* Adds an {@link OpenIDAttribute} with the given name
* @param name the name of the {@link OpenIDAttribute} to create
* @return an {@link AttributeConfigurer} to further configure the {@link OpenIDAttribute} that should be obtained.
*/
public AttributeConfigurer attribute(String name) {
AttributeConfigurer attributeConfigurer = new AttributeConfigurer(name);
this.attributeConfigurers.add(attributeConfigurer);
return attributeConfigurer;
}
/**
* Gets the {@link OpenIDAttribute}'s for the configured OpenID pattern
* @return
*/
private List<OpenIDAttribute> getAttributes() {
for(AttributeConfigurer config : attributeConfigurers) {
attributes.add(config.build());
}
attributeConfigurers.clear();
return attributes;
}
/**
* Configures an {@link OpenIDAttribute}
*
* @author Rob Winch
* @since 3.2
*/
public final class AttributeConfigurer {
private String name;
private int count = 1;
private boolean required = false;
private String type;
/**
* Creates a new instance
* @param name the name of the attribute
* @see AttributeExchangeConfigurer#attribute(String)
*/
private AttributeConfigurer(String name) {
this.name = name;
}
/**
* Specifies the number of attribute values to request. Default is 1.
* @param count the number of attributes to request.
* @return the {@link AttributeConfigurer} for further customization
*/
public AttributeConfigurer count(int count) {
this.count = count;
return this;
}
/**
* Specifies that this attribute is required. The default is
* <code>false</code>. Note that as outlined in the OpenID
* specification, required attributes are not validated by the
* OpenID Provider. Developers should perform any validation in
* custom code.
*
* @param required specifies the attribute is required
* @return the {@link AttributeConfigurer} for further customization
*/
public AttributeConfigurer required(boolean required) {
this.required = required;
return this;
}
/**
* The OpenID attribute type.
* @param type
* @return
*/
public AttributeConfigurer type(String type) {
this.type = type;
return this;
}
/**
* Gets the {@link AttributeExchangeConfigurer} for further
* customization of the attributes
*
* @return the {@link AttributeConfigurer}
*/
public AttributeExchangeConfigurer and() {
return AttributeExchangeConfigurer.this;
}
/**
* Builds the {@link OpenIDAttribute}.
* @return
*/
private OpenIDAttribute build() {
OpenIDAttribute attribute = new OpenIDAttribute(name, type);
attribute.setCount(count);
attribute.setRequired(required);
return attribute;
}
}
}
}
@@ -19,6 +19,7 @@ import org.springframework.security.authentication.encoding.PasswordEncoder;
import org.springframework.security.authentication.encoding.PlaintextPasswordEncoder;
import org.springframework.security.authentication.encoding.ShaPasswordEncoder;
import org.springframework.security.config.Elements;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
@@ -34,6 +35,7 @@ public class PasswordEncoderParser {
static final String ATT_REF = "ref";
public static final String ATT_HASH = "hash";
static final String ATT_BASE_64 = "base64";
static final String OPT_HASH_BCRYPT = "bcrypt";
static final String OPT_HASH_PLAINTEXT = "plaintext";
static final String OPT_HASH_SHA = "sha";
static final String OPT_HASH_SHA256 = "sha-256";
@@ -42,11 +44,12 @@ public class PasswordEncoderParser {
static final String OPT_HASH_LDAP_SHA = "{sha}";
static final String OPT_HASH_LDAP_SSHA = "{ssha}";
private static final Map<String, Class<? extends PasswordEncoder>> ENCODER_CLASSES;
private static final Map<String, Class<?>> ENCODER_CLASSES;
static {
ENCODER_CLASSES = new HashMap<String, Class<? extends PasswordEncoder>>();
ENCODER_CLASSES = new HashMap<String, Class<?>>();
ENCODER_CLASSES.put(OPT_HASH_PLAINTEXT, PlaintextPasswordEncoder.class);
ENCODER_CLASSES.put(OPT_HASH_BCRYPT, BCryptPasswordEncoder.class);
ENCODER_CLASSES.put(OPT_HASH_SHA, ShaPasswordEncoder.class);
ENCODER_CLASSES.put(OPT_HASH_SHA256, ShaPasswordEncoder.class);
ENCODER_CLASSES.put(OPT_HASH_MD4, Md4PasswordEncoder.class);
@@ -84,12 +87,17 @@ public class PasswordEncoderParser {
Element saltSourceElt = DomUtils.getChildElementByTagName(element, Elements.SALT_SOURCE);
if (saltSourceElt != null) {
saltSource = new SaltSourceBeanDefinitionParser().parse(saltSourceElt, parserContext);
if (OPT_HASH_BCRYPT.equals(hash)) {
parserContext.getReaderContext().error(Elements.SALT_SOURCE + " isn't compatible with bcrypt",
parserContext.extractSource(saltSourceElt));
} else {
saltSource = new SaltSourceBeanDefinitionParser().parse(saltSourceElt, parserContext);
}
}
}
public static BeanDefinition createPasswordEncoderBeanDefinition(String hash, boolean useBase64) {
Class<? extends PasswordEncoder> beanClass = ENCODER_CLASSES.get(hash);
Class<?> beanClass = ENCODER_CLASSES.get(hash);
BeanDefinitionBuilder beanBldr = BeanDefinitionBuilder.rootBeanDefinition(beanClass);
if (OPT_HASH_SHA256.equals(hash)) {
@@ -115,6 +115,8 @@ final class AuthenticationConfigBuilder {
private BeanDefinition jeeFilter;
private BeanReference jeeProviderRef;
private RootBeanDefinition preAuthEntryPoint;
private BeanMetadataElement mainEntryPoint;
private BeanMetadataElement accessDeniedHandler;
private BeanDefinition logoutFilter;
@SuppressWarnings("rawtypes")
@@ -124,17 +126,19 @@ final class AuthenticationConfigBuilder {
private final BeanReference requestCache;
private final BeanReference portMapper;
private final BeanReference portResolver;
private final BeanMetadataElement csrfLogoutHandler;
public AuthenticationConfigBuilder(Element element, ParserContext pc, SessionCreationPolicy sessionPolicy,
BeanReference requestCache, BeanReference authenticationManager, BeanReference sessionStrategy, BeanReference portMapper, BeanReference portResolver) {
BeanReference requestCache, BeanReference authenticationManager, BeanReference sessionStrategy, BeanReference portMapper, BeanReference portResolver, BeanMetadataElement csrfLogoutHandler) {
this.httpElt = element;
this.pc = pc;
this.requestCache = requestCache;
autoConfig = "true".equals(element.getAttribute(ATT_AUTO_CONFIG));
this.allowSessionCreation = sessionPolicy != SessionCreationPolicy.never
&& sessionPolicy != SessionCreationPolicy.stateless;
this.allowSessionCreation = sessionPolicy != SessionCreationPolicy.NEVER
&& sessionPolicy != SessionCreationPolicy.STATELESS;
this.portMapper = portMapper;
this.portResolver = portResolver;
this.csrfLogoutHandler = csrfLogoutHandler;
createAnonymousFilter();
createRememberMeFilter(authenticationManager);
@@ -482,7 +486,7 @@ final class AuthenticationConfigBuilder {
void createLogoutFilter() {
Element logoutElt = DomUtils.getChildElementByTagName(httpElt, Elements.LOGOUT);
if (logoutElt != null || autoConfig) {
LogoutBeanDefinitionParser logoutParser = new LogoutBeanDefinitionParser(rememberMeServicesId);
LogoutBeanDefinitionParser logoutParser = new LogoutBeanDefinitionParser(rememberMeServicesId, csrfLogoutHandler);
logoutFilter = logoutParser.parse(logoutElt, pc);
logoutHandlers = logoutParser.getLogoutHandlers();
}
@@ -492,6 +496,9 @@ final class AuthenticationConfigBuilder {
ManagedList getLogoutHandlers() {
if(logoutHandlers == null && rememberMeProviderRef != null) {
logoutHandlers = new ManagedList();
if(csrfLogoutHandler != null) {
logoutHandlers.add(csrfLogoutHandler);
}
logoutHandlers.add(new RuntimeBeanReference(rememberMeServicesId));
logoutHandlers.add(new RootBeanDefinition(SecurityContextLogoutHandler.class));
}
@@ -499,6 +506,14 @@ final class AuthenticationConfigBuilder {
return logoutHandlers;
}
BeanMetadataElement getEntryPointBean() {
return mainEntryPoint;
}
BeanMetadataElement getAccessDeniedHandlerBean() {
return accessDeniedHandler;
}
void createAnonymousFilter() {
Element anonymousElt = DomUtils.getChildElementByTagName(httpElt, Elements.ANONYMOUS);
@@ -534,7 +549,7 @@ final class AuthenticationConfigBuilder {
anonymousFilter = new RootBeanDefinition(AnonymousAuthenticationFilter.class);
anonymousFilter.getConstructorArgumentValues().addIndexedArgumentValue(0, key);
anonymousFilter.getConstructorArgumentValues().addIndexedArgumentValue(1, username);
anonymousFilter.getConstructorArgumentValues().addIndexedArgumentValue(2, AuthorityUtils.createAuthorityList(grantedAuthority));
anonymousFilter.getConstructorArgumentValues().addIndexedArgumentValue(2, AuthorityUtils.commaSeparatedStringToAuthorityList(grantedAuthority));
anonymousFilter.setSource(source);
RootBeanDefinition anonymousProviderBean = new RootBeanDefinition(AnonymousAuthenticationProvider.class);
@@ -554,9 +569,11 @@ final class AuthenticationConfigBuilder {
void createExceptionTranslationFilter() {
BeanDefinitionBuilder etfBuilder = BeanDefinitionBuilder.rootBeanDefinition(ExceptionTranslationFilter.class);
etfBuilder.addPropertyValue("accessDeniedHandler", createAccessDeniedHandler(httpElt, pc));
accessDeniedHandler = createAccessDeniedHandler(httpElt, pc);
etfBuilder.addPropertyValue("accessDeniedHandler", accessDeniedHandler);
assert requestCache != null;
etfBuilder.addConstructorArgValue(selectEntryPoint());
mainEntryPoint = selectEntryPoint();
etfBuilder.addConstructorArgValue(mainEntryPoint);
etfBuilder.addConstructorArgValue(requestCache);
etf = etfBuilder.getBeanDefinition();
@@ -0,0 +1,88 @@
/*
* 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 org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.security.web.csrf.CsrfAuthenticationStrategy;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.csrf.CsrfLogoutHandler;
import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
import org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
/**
* Parser for the {@code CsrfFilter}.
*
* @author Rob Winch
* @since 3.2
*/
public class CsrfBeanDefinitionParser implements BeanDefinitionParser {
private static final String REQUEST_DATA_VALUE_PROCESSOR = "requestDataValueProcessor";
private static final String DISPATCHER_SERVLET_CLASS_NAME = "org.springframework.web.servlet.DispatcherServlet";
private static final String ATT_MATCHER = "request-matcher-ref";
private static final String ATT_REPOSITORY = "token-repository-ref";
private String csrfRepositoryRef;
public BeanDefinition parse(Element element, ParserContext pc) {
boolean webmvcPresent = ClassUtils.isPresent(DISPATCHER_SERVLET_CLASS_NAME, getClass().getClassLoader());
if(webmvcPresent) {
RootBeanDefinition beanDefinition = new RootBeanDefinition(CsrfRequestDataValueProcessor.class);
BeanComponentDefinition componentDefinition =
new BeanComponentDefinition(beanDefinition, REQUEST_DATA_VALUE_PROCESSOR);
pc.registerBeanComponent(componentDefinition);
}
csrfRepositoryRef = element.getAttribute(ATT_REPOSITORY);
String matcherRef = element.getAttribute(ATT_MATCHER);
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(CsrfFilter.class);
if(!StringUtils.hasText(csrfRepositoryRef)) {
RootBeanDefinition csrfTokenRepository = new RootBeanDefinition(HttpSessionCsrfTokenRepository.class);
csrfRepositoryRef = pc.getReaderContext().generateBeanName(csrfTokenRepository);
pc.registerBeanComponent(new BeanComponentDefinition(csrfTokenRepository, csrfRepositoryRef));
}
builder.addConstructorArgReference(csrfRepositoryRef);
if(StringUtils.hasText(matcherRef)) {
builder.addPropertyReference("requireCsrfProtectionMatcher", matcherRef);
}
return builder.getBeanDefinition();
}
BeanDefinition getCsrfAuthenticationStrategy() {
BeanDefinitionBuilder csrfAuthenticationStrategy = BeanDefinitionBuilder.rootBeanDefinition(CsrfAuthenticationStrategy.class);
csrfAuthenticationStrategy.addConstructorArgReference(csrfRepositoryRef);
return csrfAuthenticationStrategy.getBeanDefinition();
}
BeanDefinition getCsrfLogoutHandler() {
BeanDefinitionBuilder csrfAuthenticationStrategy = BeanDefinitionBuilder.rootBeanDefinition(CsrfLogoutHandler.class);
csrfAuthenticationStrategy.addConstructorArgReference(csrfRepositoryRef);
return csrfAuthenticationStrategy.getBeanDefinition();
}
}
@@ -0,0 +1,249 @@
/*
* 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 java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.security.web.header.HeaderWriterFilter;
import org.springframework.security.web.header.writers.CacheControlHeadersWriter;
import org.springframework.security.web.header.writers.HstsHeaderWriter;
import org.springframework.security.web.header.writers.StaticHeadersWriter;
import org.springframework.security.web.header.writers.XContentTypeOptionsHeaderWriter;
import org.springframework.security.web.header.writers.XXssProtectionHeaderWriter;
import org.springframework.security.web.header.writers.frameoptions.RegExpAllowFromStrategy;
import org.springframework.security.web.header.writers.frameoptions.StaticAllowFromStrategy;
import org.springframework.security.web.header.writers.frameoptions.WhiteListedAllowFromStrategy;
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
/**
* Parser for the {@code HeadersFilter}.
*
* @author Marten Deinum
* @since 3.2
*/
public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
private static final String ATT_ENABLED = "enabled";
private static final String ATT_BLOCK = "block";
private static final String ATT_POLICY = "policy";
private static final String ATT_STRATEGY = "strategy";
private static final String ATT_FROM_PARAMETER = "from-parameter";
private static final String ATT_NAME = "name";
private static final String ATT_VALUE = "value";
private static final String ATT_REF = "ref";
private static final String ATT_INCLUDE_SUBDOMAINS = "include-subdomains";
private static final String ATT_MAX_AGE_SECONDS = "max-age-seconds";
private static final String ATT_REQUEST_MATCHER_REF = "request-matcher-ref";
private static final String CACHE_CONTROL_ELEMENT = "cache-control";
private static final String HSTS_ELEMENT = "hsts";
private static final String XSS_ELEMENT = "xss-protection";
private static final String CONTENT_TYPE_ELEMENT = "content-type-options";
private static final String FRAME_OPTIONS_ELEMENT = "frame-options";
private static final String GENERIC_HEADER_ELEMENT = "header";
private static final String ALLOW_FROM = "ALLOW-FROM";
private ManagedList<BeanMetadataElement> headerWriters;
public BeanDefinition parse(Element element, ParserContext parserContext) {
headerWriters = new ManagedList<BeanMetadataElement>();
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(HeaderWriterFilter.class);
parseCacheControlElement(element);
parseHstsElement(element);
parseXssElement(element, parserContext);
parseFrameOptionsElement(element, parserContext);
parseContentTypeOptionsElement(element);
parseHeaderElements(element);
if(headerWriters.isEmpty()) {
addCacheControl();
addHsts(null);
addContentTypeOptions();
BeanDefinitionBuilder frameOptions = BeanDefinitionBuilder.genericBeanDefinition(XFrameOptionsHeaderWriter.class);
frameOptions.addConstructorArgValue("DENY");
headerWriters.add(frameOptions.getBeanDefinition());
BeanDefinitionBuilder xss = BeanDefinitionBuilder.genericBeanDefinition(XXssProtectionHeaderWriter.class);
headerWriters.add(xss.getBeanDefinition());
}
builder.addConstructorArgValue(headerWriters);
return builder.getBeanDefinition();
}
private void parseCacheControlElement(Element element) {
Element cacheControlElement = DomUtils.getChildElementByTagName(element, CACHE_CONTROL_ELEMENT);
if (cacheControlElement != null) {
addCacheControl();
}
}
private void addCacheControl() {
BeanDefinitionBuilder headersWriter = BeanDefinitionBuilder.genericBeanDefinition(CacheControlHeadersWriter.class);
headerWriters.add(headersWriter.getBeanDefinition());
}
private void parseHstsElement(Element element) {
Element hstsElement = DomUtils.getChildElementByTagName(element, HSTS_ELEMENT);
if (hstsElement != null) {
addHsts(hstsElement);
}
}
private void addHsts(Element hstsElement) {
BeanDefinitionBuilder headersWriter = BeanDefinitionBuilder.genericBeanDefinition(HstsHeaderWriter.class);
if(hstsElement != null) {
String includeSubDomains = hstsElement.getAttribute(ATT_INCLUDE_SUBDOMAINS);
if(StringUtils.hasText(includeSubDomains)) {
headersWriter.addPropertyValue("includeSubDomains", includeSubDomains);
}
String maxAgeSeconds = hstsElement.getAttribute(ATT_MAX_AGE_SECONDS);
if(StringUtils.hasText(maxAgeSeconds)) {
headersWriter.addPropertyValue("maxAgeInSeconds", maxAgeSeconds);
}
String requestMatcherRef = hstsElement.getAttribute(ATT_REQUEST_MATCHER_REF);
if(StringUtils.hasText(requestMatcherRef)) {
headersWriter.addPropertyReference("requestMatcher", requestMatcherRef);
}
}
headerWriters.add(headersWriter.getBeanDefinition());
}
private void parseHeaderElements(Element element) {
List<Element> headerElts = DomUtils.getChildElementsByTagName(element, GENERIC_HEADER_ELEMENT);
for (Element headerElt : headerElts) {
String headerFactoryRef = headerElt.getAttribute(ATT_REF);
if (StringUtils.hasText(headerFactoryRef)) {
headerWriters.add(new RuntimeBeanReference(headerFactoryRef));
} else {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(StaticHeadersWriter.class);
builder.addConstructorArgValue(headerElt.getAttribute(ATT_NAME));
builder.addConstructorArgValue(headerElt.getAttribute(ATT_VALUE));
headerWriters.add(builder.getBeanDefinition());
}
}
}
private void parseContentTypeOptionsElement(Element element) {
Element contentTypeElt = DomUtils.getChildElementByTagName(element, CONTENT_TYPE_ELEMENT);
if (contentTypeElt != null) {
addContentTypeOptions();
}
}
private void addContentTypeOptions() {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(XContentTypeOptionsHeaderWriter.class);
headerWriters.add(builder.getBeanDefinition());
}
private void parseFrameOptionsElement(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(XFrameOptionsHeaderWriter.class);
Element frameElt = DomUtils.getChildElementByTagName(element, FRAME_OPTIONS_ELEMENT);
if (frameElt != null) {
String header = getAttribute(frameElt, ATT_POLICY, "DENY");
if (ALLOW_FROM.equals(header) ) {
String strategyRef = getAttribute(frameElt, ATT_REF, null);
String strategy = getAttribute(frameElt, ATT_STRATEGY, null);
if (StringUtils.hasText(strategy) && StringUtils.hasText(strategyRef)) {
parserContext.getReaderContext().error("Only one of 'strategy' or 'strategy-ref' can be set.",
frameElt);
} else if (strategyRef != null) {
builder.addConstructorArgReference(strategyRef);
} else if (strategy != null) {
String value = getAttribute(frameElt, ATT_VALUE, null);
if (!StringUtils.hasText(value)) {
parserContext.getReaderContext().error("Strategy requires a 'value' to be set.", frameElt);
}
// static, whitelist, regexp
if ("static".equals(strategy)) {
try {
builder.addConstructorArgValue(new StaticAllowFromStrategy(new URI(value)));
} catch (URISyntaxException e) {
parserContext.getReaderContext().error(
"'value' attribute doesn't represent a valid URI.", frameElt, e);
}
} else {
BeanDefinitionBuilder allowFromStrategy;
if ("whitelist".equals(strategy)) {
allowFromStrategy = BeanDefinitionBuilder.rootBeanDefinition(WhiteListedAllowFromStrategy.class);
allowFromStrategy.addConstructorArgValue(StringUtils.commaDelimitedListToSet(value));
} else {
allowFromStrategy = BeanDefinitionBuilder.rootBeanDefinition(RegExpAllowFromStrategy.class);
allowFromStrategy.addConstructorArgValue(value);
}
String fromParameter = getAttribute(frameElt, ATT_FROM_PARAMETER, "from");
allowFromStrategy.addPropertyValue("allowFromParameterName", fromParameter);
builder.addConstructorArgValue(allowFromStrategy);
}
} else {
parserContext.getReaderContext().error("One of 'strategy' and 'strategy-ref' must be set.",
frameElt);
}
} else {
builder.addConstructorArgValue(header);
}
headerWriters.add(builder.getBeanDefinition());
}
}
private void parseXssElement(Element element, ParserContext parserContext) {
Element xssElt = DomUtils.getChildElementByTagName(element, XSS_ELEMENT);
if (xssElt != null) {
String enabled = xssElt.getAttribute(ATT_ENABLED);
String block = xssElt.getAttribute(ATT_BLOCK);
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(XXssProtectionHeaderWriter.class);
if(StringUtils.hasText(enabled)) {
builder.addPropertyValue("enabled", enabled);
}
if(StringUtils.hasText(block)) {
builder.addPropertyValue("block", block);
}
headerWriters.add(builder.getBeanDefinition());
}
}
private String getAttribute(Element element, String name, String defaultValue) {
String value = element.getAttribute(name);
if (StringUtils.hasText(value)) {
return value;
} else {
return defaultValue;
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* 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.
@@ -18,9 +18,14 @@ package org.springframework.security.config.http;
import static org.springframework.security.config.http.HttpSecurityBeanDefinitionParser.*;
import static org.springframework.security.config.http.SecurityFilters.*;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanReference;
import org.springframework.beans.factory.config.RuntimeBeanReference;
@@ -48,11 +53,15 @@ 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.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.session.ConcurrentSessionControlStrategy;
import org.springframework.security.web.authentication.session.ChangeSessionIdAuthenticationStrategy;
import org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy;
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy;
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.context.request.async.WebAsyncManagerIntegrationFilter;
import org.springframework.security.web.jaasapi.JaasApiIntegrationFilter;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.NullRequestCache;
@@ -61,6 +70,9 @@ import org.springframework.security.web.servletapi.SecurityContextHolderAwareReq
import org.springframework.security.web.session.ConcurrentSessionFilter;
import org.springframework.security.web.session.SessionManagementFilter;
import org.springframework.security.web.session.SimpleRedirectInvalidSessionStrategy;
import org.springframework.security.web.util.AntPathRequestMatcher;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
@@ -78,6 +90,7 @@ class HttpConfigurationBuilder {
private static final String ATT_SESSION_FIXATION_PROTECTION = "session-fixation-protection";
private static final String OPT_SESSION_FIXATION_NO_PROTECTION = "none";
private static final String OPT_SESSION_FIXATION_MIGRATE_SESSION = "migrateSession";
private static final String OPT_CHANGE_SESSION_ID = "changeSessionId";
private static final String ATT_INVALID_SESSION_URL = "invalid-session-url";
private static final String ATT_SESSION_AUTH_STRATEGY_REF = "session-authentication-strategy-ref";
@@ -102,6 +115,7 @@ class HttpConfigurationBuilder {
private BeanReference contextRepoRef;
private BeanReference sessionRegistryRef;
private BeanDefinition concurrentSessionFilter;
private BeanDefinition webAsyncManagerFilter;
private BeanDefinition requestCacheAwareFilter;
private BeanReference sessionStrategyRef;
private RootBeanDefinition sfpf;
@@ -111,10 +125,13 @@ class HttpConfigurationBuilder {
private final BeanReference portResolver;
private BeanReference fsi;
private BeanReference requestCache;
private BeanDefinition addHeadersFilter;
private BeanDefinition csrfFilter;
private BeanMetadataElement csrfLogoutHandler;
private BeanMetadataElement csrfAuthStrategy;
public HttpConfigurationBuilder(Element element, ParserContext pc,
BeanReference portMapper, BeanReference portResolver, BeanReference authenticationManager) {
this.httpElt = element;
this.pc = pc;
this.portMapper = portMapper;
@@ -133,24 +150,58 @@ class HttpConfigurationBuilder {
String createSession = element.getAttribute(ATT_CREATE_SESSION);
if (StringUtils.hasText(createSession)) {
sessionPolicy = SessionCreationPolicy.valueOf(createSession);
sessionPolicy = createPolicy(createSession);
} else {
sessionPolicy = SessionCreationPolicy.ifRequired;
sessionPolicy = SessionCreationPolicy.IF_REQUIRED;
}
createCsrfFilter();
createSecurityContextPersistenceFilter();
createSessionManagementFilters();
createWebAsyncManagerFilter();
createRequestCacheFilter();
createServletApiFilter();
createServletApiFilter(authenticationManager);
createJaasApiFilter();
createChannelProcessingFilter();
createFilterSecurityInterceptor(authenticationManager);
createAddHeadersFilter();
}
private SessionCreationPolicy createPolicy(String createSession) {
if("ifRequired".equals(createSession)) {
return SessionCreationPolicy.IF_REQUIRED;
} else if("always".equals(createSession)) {
return SessionCreationPolicy.ALWAYS;
} else if("never".equals(createSession)) {
return SessionCreationPolicy.NEVER;
} else if("stateless".equals(createSession)) {
return SessionCreationPolicy.STATELESS;
}
throw new IllegalStateException("Cannot convert " + createSession + " to " + SessionCreationPolicy.class.getName());
}
@SuppressWarnings("rawtypes")
void setLogoutHandlers(ManagedList logoutHandlers) {
if(logoutHandlers != null && concurrentSessionFilter != null) {
concurrentSessionFilter.getPropertyValues().add("logoutHandlers", logoutHandlers);
if(logoutHandlers != null) {
if(concurrentSessionFilter != null) {
concurrentSessionFilter.getPropertyValues().add("logoutHandlers", logoutHandlers);
}
if(servApiFilter != null) {
servApiFilter.getPropertyValues().add("logoutHandlers", logoutHandlers);
}
}
}
void setEntryPoint(BeanMetadataElement entryPoint) {
if(servApiFilter != null) {
servApiFilter.getPropertyValues().add("authenticationEntryPoint", entryPoint);
}
}
void setAccessDeniedHandler(BeanMetadataElement accessDeniedHandler) {
if(csrfFilter != null) {
csrfFilter.getPropertyValues().add("accessDeniedHandler", accessDeniedHandler);
}
}
@@ -166,21 +217,21 @@ class HttpConfigurationBuilder {
String disableUrlRewriting = httpElt.getAttribute(ATT_DISABLE_URL_REWRITING);
if (StringUtils.hasText(repoRef)) {
if (sessionPolicy == SessionCreationPolicy.always) {
if (sessionPolicy == SessionCreationPolicy.ALWAYS) {
scpf.addPropertyValue("forceEagerSessionCreation", Boolean.TRUE);
}
} else {
BeanDefinitionBuilder contextRepo;
if (sessionPolicy == SessionCreationPolicy.stateless) {
if (sessionPolicy == SessionCreationPolicy.STATELESS) {
contextRepo = BeanDefinitionBuilder.rootBeanDefinition(NullSecurityContextRepository.class);
} else {
contextRepo = BeanDefinitionBuilder.rootBeanDefinition(HttpSessionSecurityContextRepository.class);
switch (sessionPolicy) {
case always:
case ALWAYS:
contextRepo.addPropertyValue("allowSessionCreation", Boolean.TRUE);
scpf.addPropertyValue("forceEagerSessionCreation", Boolean.TRUE);
break;
case never:
case NEVER:
contextRepo.addPropertyValue("allowSessionCreation", Boolean.FALSE);
scpf.addPropertyValue("forceEagerSessionCreation", Boolean.FALSE);
break;
@@ -214,10 +265,11 @@ class HttpConfigurationBuilder {
String sessionAuthStratRef = null;
String errorUrl = null;
boolean sessionControlEnabled = false;
if (sessionMgmtElt != null) {
if (sessionPolicy == SessionCreationPolicy.stateless) {
if (sessionPolicy == SessionCreationPolicy.STATELESS) {
pc.getReaderContext().error(Elements.SESSION_MANAGEMENT + " cannot be used" +
" in combination with " + ATT_CREATE_SESSION + "='"+ SessionCreationPolicy.stateless +"'",
" in combination with " + ATT_CREATE_SESSION + "='"+ SessionCreationPolicy.STATELESS +"'",
pc.extractSource(sessionMgmtElt));
}
sessionFixationAttribute = sessionMgmtElt.getAttribute(ATT_SESSION_FIXATION_PROTECTION);
@@ -225,8 +277,9 @@ class HttpConfigurationBuilder {
sessionAuthStratRef = sessionMgmtElt.getAttribute(ATT_SESSION_AUTH_STRATEGY_REF);
errorUrl = sessionMgmtElt.getAttribute(ATT_SESSION_AUTH_ERROR_URL);
sessionCtrlElt = DomUtils.getChildElementByTagName(sessionMgmtElt, Elements.CONCURRENT_SESSIONS);
sessionControlEnabled = sessionCtrlElt != null;
if (sessionCtrlElt != null) {
if (sessionControlEnabled) {
if (StringUtils.hasText(sessionAuthStratRef)) {
pc.getReaderContext().error(ATT_SESSION_AUTH_STRATEGY_REF + " attribute cannot be used" +
" in combination with <" + Elements.CONCURRENT_SESSIONS + ">", pc.extractSource(sessionCtrlElt));
@@ -236,41 +289,68 @@ class HttpConfigurationBuilder {
}
if (!StringUtils.hasText(sessionFixationAttribute)) {
sessionFixationAttribute = OPT_SESSION_FIXATION_MIGRATE_SESSION;
Method changeSessionIdMethod = ReflectionUtils.findMethod(HttpServletRequest.class, "changeSessionId");
sessionFixationAttribute = changeSessionIdMethod == null ? OPT_SESSION_FIXATION_MIGRATE_SESSION : OPT_CHANGE_SESSION_ID;
} else if (StringUtils.hasText(sessionAuthStratRef)) {
pc.getReaderContext().error(ATT_SESSION_FIXATION_PROTECTION + " attribute cannot be used" +
" in combination with " + ATT_SESSION_AUTH_STRATEGY_REF, pc.extractSource(sessionMgmtElt));
}
if (sessionPolicy == SessionCreationPolicy.stateless) {
if (sessionPolicy == SessionCreationPolicy.STATELESS) {
// SEC-1424: do nothing
return;
}
boolean sessionFixationProtectionRequired = !sessionFixationAttribute.equals(OPT_SESSION_FIXATION_NO_PROTECTION);
BeanDefinitionBuilder sessionStrategy;
ManagedList<BeanMetadataElement> delegateSessionStrategies = new ManagedList<BeanMetadataElement>();
BeanDefinitionBuilder concurrentSessionStrategy;
BeanDefinitionBuilder sessionFixationStrategy = null;
BeanDefinitionBuilder registerSessionStrategy;
if (sessionCtrlElt != null) {
if(csrfAuthStrategy != null) {
delegateSessionStrategies.add(csrfAuthStrategy);
}
if (sessionControlEnabled) {
assert sessionRegistryRef != null;
sessionStrategy = BeanDefinitionBuilder.rootBeanDefinition(ConcurrentSessionControlStrategy.class);
sessionStrategy.addConstructorArgValue(sessionRegistryRef);
concurrentSessionStrategy = BeanDefinitionBuilder.rootBeanDefinition(ConcurrentSessionControlAuthenticationStrategy.class);
concurrentSessionStrategy.addConstructorArgValue(sessionRegistryRef);
String maxSessions = sessionCtrlElt.getAttribute("max-sessions");
if (StringUtils.hasText(maxSessions)) {
sessionStrategy.addPropertyValue("maximumSessions", maxSessions);
concurrentSessionStrategy.addPropertyValue("maximumSessions", maxSessions);
}
String exceptionIfMaximumExceeded = sessionCtrlElt.getAttribute("error-if-maximum-exceeded");
if (StringUtils.hasText(exceptionIfMaximumExceeded)) {
sessionStrategy.addPropertyValue("exceptionIfMaximumExceeded", exceptionIfMaximumExceeded);
concurrentSessionStrategy.addPropertyValue("exceptionIfMaximumExceeded", exceptionIfMaximumExceeded);
}
} else if (sessionFixationProtectionRequired || StringUtils.hasText(invalidSessionUrl)
|| StringUtils.hasText(sessionAuthStratRef)) {
sessionStrategy = BeanDefinitionBuilder.rootBeanDefinition(SessionFixationProtectionStrategy.class);
} else {
delegateSessionStrategies.add(concurrentSessionStrategy.getBeanDefinition());
}
boolean useChangeSessionId = OPT_CHANGE_SESSION_ID.equals(sessionFixationAttribute);
if (sessionFixationProtectionRequired || StringUtils.hasText(invalidSessionUrl)) {
if(useChangeSessionId) {
sessionFixationStrategy = BeanDefinitionBuilder.rootBeanDefinition(ChangeSessionIdAuthenticationStrategy.class);
} else {
sessionFixationStrategy = BeanDefinitionBuilder.rootBeanDefinition(SessionFixationProtectionStrategy.class);
}
delegateSessionStrategies.add(sessionFixationStrategy.getBeanDefinition());
}
if(StringUtils.hasText(sessionAuthStratRef)) {
delegateSessionStrategies.add(new RuntimeBeanReference(sessionAuthStratRef));
}
if(sessionControlEnabled) {
registerSessionStrategy = BeanDefinitionBuilder.rootBeanDefinition(RegisterSessionAuthenticationStrategy.class);
registerSessionStrategy.addConstructorArgValue(sessionRegistryRef);
delegateSessionStrategies.add(registerSessionStrategy.getBeanDefinition());
}
if(delegateSessionStrategies.isEmpty()) {
sfpf = null;
return;
}
@@ -283,15 +363,21 @@ class HttpConfigurationBuilder {
sessionMgmtFilter.addPropertyValue("authenticationFailureHandler", failureHandler);
sessionMgmtFilter.addConstructorArgValue(contextRepoRef);
if (!StringUtils.hasText(sessionAuthStratRef)) {
BeanDefinition strategyBean = sessionStrategy.getBeanDefinition();
if (!StringUtils.hasText(sessionAuthStratRef) && sessionFixationStrategy != null && !useChangeSessionId ) {
if (sessionFixationProtectionRequired) {
sessionStrategy.addPropertyValue("migrateSessionAttributes",
sessionFixationStrategy.addPropertyValue("migrateSessionAttributes",
Boolean.valueOf(sessionFixationAttribute.equals(OPT_SESSION_FIXATION_MIGRATE_SESSION)));
}
}
if(!delegateSessionStrategies.isEmpty()) {
BeanDefinitionBuilder sessionStrategy = BeanDefinitionBuilder.rootBeanDefinition(CompositeSessionAuthenticationStrategy.class);
BeanDefinition strategyBean = sessionStrategy.getBeanDefinition();
sessionStrategy.addConstructorArgValue(delegateSessionStrategies);
sessionAuthStratRef = pc.getReaderContext().generateBeanName(strategyBean);
pc.registerBeanComponent(new BeanComponentDefinition(strategyBean, sessionAuthStratRef));
}
if (StringUtils.hasText(invalidSessionUrl)) {
@@ -350,8 +436,15 @@ class HttpConfigurationBuilder {
sessionRegistryRef = new RuntimeBeanReference(sessionRegistryId);
}
private void createWebAsyncManagerFilter() {
boolean asyncSupported = ClassUtils.hasMethod(ServletRequest.class, "startAsync");
if(asyncSupported) {
webAsyncManagerFilter = new RootBeanDefinition(WebAsyncManagerIntegrationFilter.class);
}
}
// Adds the servlet-api integration filter if required
private void createServletApiFilter() {
private void createServletApiFilter(BeanReference authenticationManager) {
final String ATT_SERVLET_API_PROVISION = "servlet-api-provision";
final String DEF_SERVLET_API_PROVISION = "true";
@@ -362,6 +455,7 @@ class HttpConfigurationBuilder {
if ("true".equals(provideServletApi)) {
servApiFilter = new RootBeanDefinition(SecurityContextHolderAwareRequestFilter.class);
servApiFilter.getPropertyValues().add("authenticationManager", authenticationManager);
}
}
@@ -455,12 +549,18 @@ class HttpConfigurationBuilder {
} else {
BeanDefinitionBuilder requestCacheBldr;
if (sessionPolicy == SessionCreationPolicy.stateless) {
if (sessionPolicy == SessionCreationPolicy.STATELESS) {
requestCacheBldr = BeanDefinitionBuilder.rootBeanDefinition(NullRequestCache.class);
} else {
requestCacheBldr = BeanDefinitionBuilder.rootBeanDefinition(HttpSessionRequestCache.class);
requestCacheBldr.addPropertyValue("createSessionAllowed", sessionPolicy == SessionCreationPolicy.ifRequired);
requestCacheBldr.addPropertyValue("createSessionAllowed", sessionPolicy == SessionCreationPolicy.IF_REQUIRED);
requestCacheBldr.addPropertyValue("portResolver", portResolver);
if(csrfFilter != null) {
BeanDefinitionBuilder requestCacheMatcherBldr = BeanDefinitionBuilder.rootBeanDefinition(AntPathRequestMatcher.class);
requestCacheMatcherBldr.addConstructorArgValue("/**");
requestCacheMatcherBldr.addConstructorArgValue("GET");
requestCacheBldr.addPropertyValue("requestMatcher", requestCacheMatcherBldr.getBeanDefinition());
}
}
BeanDefinition bean = requestCacheBldr.getBeanDefinition();
@@ -529,6 +629,28 @@ class HttpConfigurationBuilder {
this.fsi = new RuntimeBeanReference(fsiId);
}
private void createAddHeadersFilter() {
Element elmt = DomUtils.getChildElementByTagName(httpElt, Elements.HEADERS);
if (elmt != null) {
this.addHeadersFilter = new HeadersBeanDefinitionParser().parse(elmt, pc);
}
}
private void createCsrfFilter() {
Element elmt = DomUtils.getChildElementByTagName(httpElt, Elements.CSRF);
if (elmt != null) {
CsrfBeanDefinitionParser csrfParser = new CsrfBeanDefinitionParser();
this.csrfFilter = csrfParser.parse(elmt, pc);
this.csrfAuthStrategy = csrfParser.getCsrfAuthenticationStrategy();
this.csrfLogoutHandler = csrfParser.getCsrfLogoutHandler();
}
}
BeanMetadataElement getCsrfLogoutHandler() {
return this.csrfLogoutHandler;
}
BeanReference getSessionStrategy() {
return sessionStrategyRef;
}
@@ -552,6 +674,10 @@ class HttpConfigurationBuilder {
filters.add(new OrderDecorator(concurrentSessionFilter, CONCURRENT_SESSION_FILTER));
}
if (webAsyncManagerFilter != null) {
filters.add(new OrderDecorator(webAsyncManagerFilter, WEB_ASYNC_MANAGER_FILTER));
}
filters.add(new OrderDecorator(securityContextPersistenceFilter, SECURITY_CONTEXT_FILTER));
if (servApiFilter != null) {
@@ -568,10 +694,18 @@ class HttpConfigurationBuilder {
filters.add(new OrderDecorator(fsi, FILTER_SECURITY_INTERCEPTOR));
if (sessionPolicy != SessionCreationPolicy.stateless) {
if (sessionPolicy != SessionCreationPolicy.STATELESS) {
filters.add(new OrderDecorator(requestCacheAwareFilter, REQUEST_CACHE_FILTER));
}
if (addHeadersFilter != null) {
filters.add(new OrderDecorator(addHeadersFilter, HEADERS_FILTER));
}
if (csrfFilter != null) {
filters.add(new OrderDecorator(csrfFilter, CSRF_FILTER));
}
return filters;
}
}
@@ -137,9 +137,11 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
AuthenticationConfigBuilder authBldr = new AuthenticationConfigBuilder(element, pc,
httpBldr.getSessionCreationPolicy(), httpBldr.getRequestCache(), authenticationManager,
httpBldr.getSessionStrategy(), portMapper, portResolver);
httpBldr.getSessionStrategy(), portMapper, portResolver, httpBldr.getCsrfLogoutHandler());
httpBldr.setLogoutHandlers(authBldr.getLogoutHandlers());
httpBldr.setEntryPoint(authBldr.getEntryPointBean());
httpBldr.setAccessDeniedHandler(authBldr.getAccessDeniedHandlerBean());
authenticationProviders.addAll(authBldr.getProviders());
@@ -15,6 +15,7 @@
*/
package org.springframework.security.config.http;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
@@ -44,13 +45,15 @@ class LogoutBeanDefinitionParser implements BeanDefinitionParser {
static final String ATT_DELETE_COOKIES = "delete-cookies";
final String rememberMeServices;
private ManagedList logoutHandlers = new ManagedList();
private ManagedList<BeanMetadataElement> logoutHandlers = new ManagedList<BeanMetadataElement>();
public LogoutBeanDefinitionParser(String rememberMeServices) {
public LogoutBeanDefinitionParser(String rememberMeServices, BeanMetadataElement csrfLogoutHandler) {
this.rememberMeServices = rememberMeServices;
if(csrfLogoutHandler != null) {
logoutHandlers.add(csrfLogoutHandler);
}
}
@SuppressWarnings("unchecked")
public BeanDefinition parse(Element element, ParserContext pc) {
String logoutUrl = null;
String successHandlerRef = null;
@@ -111,7 +114,7 @@ class LogoutBeanDefinitionParser implements BeanDefinitionParser {
return builder.getBeanDefinition();
}
ManagedList getLogoutHandlers() {
ManagedList<BeanMetadataElement> getLogoutHandlers() {
return logoutHandlers;
}
}

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