SEC-2347: CSRF Enabled by default w/ XML Config
This commit is contained in:
parent
eedbf44235
commit
4392205f63
|
@ -56,6 +56,10 @@ public class CsrfBeanDefinitionParser implements BeanDefinitionParser {
|
|||
private BeanDefinition csrfFilter;
|
||||
|
||||
public BeanDefinition parse(Element element, ParserContext pc) {
|
||||
boolean disabled = element != null && "true".equals(element.getAttribute("disabled"));
|
||||
if(disabled) {
|
||||
return null;
|
||||
}
|
||||
boolean webmvcPresent = ClassUtils.isPresent(DISPATCHER_SERVLET_CLASS_NAME, getClass().getClassLoader());
|
||||
if(webmvcPresent) {
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(CsrfRequestDataValueProcessor.class);
|
||||
|
@ -64,8 +68,11 @@ public class CsrfBeanDefinitionParser implements BeanDefinitionParser {
|
|||
pc.registerBeanComponent(componentDefinition);
|
||||
}
|
||||
|
||||
csrfRepositoryRef = element.getAttribute(ATT_REPOSITORY);
|
||||
String matcherRef = element.getAttribute(ATT_MATCHER);
|
||||
String matcherRef = null;
|
||||
if(element != null) {
|
||||
csrfRepositoryRef = element.getAttribute(ATT_REPOSITORY);
|
||||
matcherRef = element.getAttribute(ATT_MATCHER);
|
||||
}
|
||||
|
||||
if(!StringUtils.hasText(csrfRepositoryRef)) {
|
||||
RootBeanDefinition csrfTokenRepository = new RootBeanDefinition(HttpSessionCsrfTokenRepository.class);
|
||||
|
|
|
@ -642,16 +642,18 @@ class HttpConfigurationBuilder {
|
|||
|
||||
}
|
||||
|
||||
private CsrfBeanDefinitionParser createCsrfFilter() {
|
||||
private void createCsrfFilter() {
|
||||
Element elmt = DomUtils.getChildElementByTagName(httpElt, Elements.CSRF);
|
||||
if (elmt != null) {
|
||||
csrfParser = new CsrfBeanDefinitionParser();
|
||||
csrfFilter = csrfParser.parse(elmt, pc);
|
||||
this.csrfAuthStrategy = csrfParser.getCsrfAuthenticationStrategy();
|
||||
this.csrfLogoutHandler = csrfParser.getCsrfLogoutHandler();
|
||||
return csrfParser;
|
||||
csrfParser = new CsrfBeanDefinitionParser();
|
||||
csrfFilter = csrfParser.parse(elmt, pc);
|
||||
|
||||
if(csrfFilter == null) {
|
||||
csrfParser = null;
|
||||
return;
|
||||
}
|
||||
return null;
|
||||
|
||||
this.csrfAuthStrategy = csrfParser.getCsrfAuthenticationStrategy();
|
||||
this.csrfLogoutHandler = csrfParser.getCsrfLogoutHandler();
|
||||
}
|
||||
|
||||
BeanMetadataElement getCsrfLogoutHandler() {
|
||||
|
|
|
@ -721,6 +721,9 @@ jdbc-user-service.attlist &=
|
|||
csrf =
|
||||
## Element for configuration of the CsrfFilter for protection against CSRF. It also updates the default RequestCache to only replay "GET" requests.
|
||||
element csrf {csrf-options.attlist}
|
||||
csrf-options.attlist &=
|
||||
## Specifies if csrf protection should be disabled. Default false (i.e. CSRF protection is enabled).
|
||||
attribute disabled {xsd:boolean}?
|
||||
csrf-options.attlist &=
|
||||
## The RequestMatcher instance to be used to determine if CSRF should be applied. Default is any HTTP method except "GET", "TRACE", "HEAD", "OPTIONS"
|
||||
attribute request-matcher-ref { xsd:token }?
|
||||
|
|
|
@ -2251,6 +2251,13 @@
|
|||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:attributeGroup name="csrf-options.attlist">
|
||||
<xs:attribute name="disabled" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Specifies if csrf protection should be disabled. Default false (i.e. CSRF protection is
|
||||
enabled).
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="request-matcher-ref" type="xs:token">
|
||||
<xs:annotation>
|
||||
<xs:documentation>The RequestMatcher instance to be used to determine if CSRF should be applied. Default is
|
||||
|
|
|
@ -26,7 +26,7 @@ import org.springframework.security.web.FilterInvocation
|
|||
*
|
||||
*/
|
||||
abstract class AbstractHttpConfigTests extends AbstractXmlConfigTests {
|
||||
final int AUTO_CONFIG_FILTERS = 13;
|
||||
final int AUTO_CONFIG_FILTERS = 14;
|
||||
|
||||
def httpAutoConfig(Closure c) {
|
||||
xml.http('auto-config': 'true', c)
|
||||
|
|
|
@ -47,9 +47,34 @@ class CsrfConfigTests extends AbstractHttpConfigTests {
|
|||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
MockFilterChain chain = new MockFilterChain()
|
||||
|
||||
def 'no http csrf filter by default'() {
|
||||
@Unroll
|
||||
def 'csrf is enabled by default'() {
|
||||
setup:
|
||||
httpAutoConfig {
|
||||
}
|
||||
createAppContext()
|
||||
when:
|
||||
request.method = httpMethod
|
||||
springSecurityFilterChain.doFilter(request,response,chain)
|
||||
then:
|
||||
response.status == httpStatus
|
||||
where:
|
||||
httpMethod | httpStatus
|
||||
'POST' | HttpServletResponse.SC_FORBIDDEN
|
||||
'PUT' | HttpServletResponse.SC_FORBIDDEN
|
||||
'PATCH' | HttpServletResponse.SC_FORBIDDEN
|
||||
'DELETE' | HttpServletResponse.SC_FORBIDDEN
|
||||
'INVALID' | HttpServletResponse.SC_FORBIDDEN
|
||||
'GET' | HttpServletResponse.SC_OK
|
||||
'HEAD' | HttpServletResponse.SC_OK
|
||||
'TRACE' | HttpServletResponse.SC_OK
|
||||
'OPTIONS' | HttpServletResponse.SC_OK
|
||||
}
|
||||
|
||||
def 'csrf disabled'() {
|
||||
when:
|
||||
httpAutoConfig {
|
||||
csrf(disabled:true)
|
||||
}
|
||||
createAppContext()
|
||||
then:
|
||||
|
|
|
@ -16,6 +16,7 @@ class FormLoginBeanDefinitionParserTests extends AbstractHttpConfigTests {
|
|||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
MockFilterChain chain = new MockFilterChain()
|
||||
httpAutoConfig {
|
||||
csrf(disabled:true)
|
||||
}
|
||||
createAppContext()
|
||||
when:
|
||||
|
@ -38,6 +39,7 @@ class FormLoginBeanDefinitionParserTests extends AbstractHttpConfigTests {
|
|||
MockFilterChain chain = new MockFilterChain()
|
||||
httpAutoConfig {
|
||||
'form-login'('login-processing-url':'/login_custom','username-parameter':'custom_user','password-parameter':'custom_password')
|
||||
csrf(disabled:true)
|
||||
}
|
||||
createAppContext()
|
||||
when:
|
||||
|
@ -60,6 +62,7 @@ class FormLoginBeanDefinitionParserTests extends AbstractHttpConfigTests {
|
|||
MockFilterChain chain = new MockFilterChain()
|
||||
httpAutoConfig {
|
||||
'openid-login'()
|
||||
csrf(disabled:true)
|
||||
}
|
||||
createAppContext()
|
||||
when:
|
||||
|
@ -87,6 +90,7 @@ class FormLoginBeanDefinitionParserTests extends AbstractHttpConfigTests {
|
|||
MockFilterChain chain = new MockFilterChain()
|
||||
httpAutoConfig {
|
||||
'openid-login'('login-processing-url':'/login_custom')
|
||||
csrf(disabled:true)
|
||||
}
|
||||
createAppContext()
|
||||
when:
|
||||
|
|
|
@ -110,6 +110,7 @@ class InterceptUrlConfigTests extends AbstractHttpConfigTests {
|
|||
xml.http() {
|
||||
'http-basic'()
|
||||
'intercept-url'(pattern: '/**', 'method':'PATCH',access: 'ROLE_ADMIN')
|
||||
csrf(disabled:true)
|
||||
}
|
||||
createAppContext()
|
||||
when: 'Method other than PATCH is used'
|
||||
|
|
|
@ -13,6 +13,7 @@ class LogoutConfigTests extends AbstractHttpConfigTests {
|
|||
when:
|
||||
httpAutoConfig {
|
||||
'logout'('logout-url':'/logout')
|
||||
csrf(disabled:true)
|
||||
}
|
||||
createAppContext()
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
*/
|
||||
package org.springframework.security.config.http
|
||||
|
||||
import org.springframework.security.web.csrf.CsrfFilter
|
||||
import org.springframework.security.web.header.HeaderWriterFilter
|
||||
|
||||
import java.security.Principal
|
||||
|
@ -107,6 +108,7 @@ class MiscHttpConfigTests extends AbstractHttpConfigTests {
|
|||
assert filters.next() instanceof SecurityContextPersistenceFilter
|
||||
assert filters.next() instanceof WebAsyncManagerIntegrationFilter
|
||||
assert filters.next() instanceof HeaderWriterFilter
|
||||
assert filters.next() instanceof CsrfFilter
|
||||
assert filters.next() instanceof LogoutFilter
|
||||
Object authProcFilter = filters.next();
|
||||
assert authProcFilter instanceof UsernamePasswordAuthenticationFilter
|
||||
|
@ -187,7 +189,7 @@ class MiscHttpConfigTests extends AbstractHttpConfigTests {
|
|||
createAppContext()
|
||||
|
||||
expect:
|
||||
getFilters("/anything")[7] instanceof AnonymousAuthenticationFilter
|
||||
getFilters("/anything")[8] instanceof AnonymousAuthenticationFilter
|
||||
}
|
||||
|
||||
def anonymousFilterIsRemovedIfDisabledFlagSet() {
|
||||
|
@ -360,7 +362,7 @@ class MiscHttpConfigTests extends AbstractHttpConfigTests {
|
|||
AUTO_CONFIG_FILTERS + 3 == filters.size();
|
||||
filters[0] instanceof SecurityContextHolderAwareRequestFilter
|
||||
filters[1] instanceof SecurityContextPersistenceFilter
|
||||
filters[6] instanceof SecurityContextHolderAwareRequestFilter
|
||||
filters[7] instanceof SecurityContextHolderAwareRequestFilter
|
||||
filters[1] instanceof SecurityContextPersistenceFilter
|
||||
}
|
||||
|
||||
|
@ -383,7 +385,7 @@ class MiscHttpConfigTests extends AbstractHttpConfigTests {
|
|||
createAppContext()
|
||||
|
||||
expect:
|
||||
getFilters("/someurl")[4] instanceof X509AuthenticationFilter
|
||||
getFilters("/someurl")[5] instanceof X509AuthenticationFilter
|
||||
}
|
||||
|
||||
def x509SubjectPrincipalRegexCanBeSetUsingPropertyPlaceholder() {
|
||||
|
@ -420,21 +422,9 @@ class MiscHttpConfigTests extends AbstractHttpConfigTests {
|
|||
def handlers = getFilter(LogoutFilter).handlers
|
||||
|
||||
expect:
|
||||
handlers[1] instanceof CookieClearingLogoutHandler
|
||||
handlers[1].cookiesToClear[0] == 'JSESSIONID'
|
||||
handlers[1].cookiesToClear[1] == 'mycookie'
|
||||
}
|
||||
|
||||
def invalidLogoutUrlIsDetected() {
|
||||
when:
|
||||
xml.http {
|
||||
'logout'('logout-url': 'noLeadingSlash')
|
||||
'form-login'()
|
||||
}
|
||||
createAppContext()
|
||||
|
||||
then:
|
||||
BeanCreationException e = thrown();
|
||||
handlers[2] instanceof CookieClearingLogoutHandler
|
||||
handlers[2].cookiesToClear[0] == 'JSESSIONID'
|
||||
handlers[2].cookiesToClear[1] == 'mycookie'
|
||||
}
|
||||
|
||||
def logoutSuccessHandlerIsSetCorrectly() {
|
||||
|
@ -615,6 +605,7 @@ class MiscHttpConfigTests extends AbstractHttpConfigTests {
|
|||
xml.debug()
|
||||
xml.http() {
|
||||
'form-login'()
|
||||
csrf(disabled:true)
|
||||
anonymous(enabled: 'false')
|
||||
}
|
||||
createAppContext()
|
||||
|
|
|
@ -74,9 +74,11 @@ class MultiHttpBlockConfigTests extends AbstractHttpConfigTests {
|
|||
setup:
|
||||
xml.http('authentication-manager-ref' : 'authManager', 'pattern' : '/first/**') {
|
||||
'form-login'('login-processing-url': '/first/login')
|
||||
csrf(disabled:true)
|
||||
}
|
||||
xml.http('authentication-manager-ref' : 'authManager2') {
|
||||
'form-login'()
|
||||
csrf(disabled:true)
|
||||
}
|
||||
mockBean(UserDetailsService,'uds')
|
||||
mockBean(UserDetailsService,'uds2')
|
||||
|
|
|
@ -91,6 +91,7 @@ class RememberMeConfigTests extends AbstractHttpConfigTests {
|
|||
def rememberMeServiceWorksWithExternalServicesImpl() {
|
||||
httpAutoConfig () {
|
||||
'remember-me'('key': "#{'our' + 'key'}", 'services-ref': 'rms')
|
||||
csrf(disabled:true)
|
||||
}
|
||||
xml.'b:bean'(id: 'rms', 'class': TokenBasedRememberMeServices.class.name) {
|
||||
'b:constructor-arg'(value: 'ourKey')
|
||||
|
@ -118,6 +119,7 @@ class RememberMeConfigTests extends AbstractHttpConfigTests {
|
|||
def rememberMeAddsLogoutHandlerToLogoutFilter() {
|
||||
httpAutoConfig () {
|
||||
'remember-me'()
|
||||
csrf(disabled:true)
|
||||
}
|
||||
createAppContext(AUTH_PROVIDER_XML)
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ class SecurityContextHolderAwareRequestConfigTests extends AbstractHttpConfigTes
|
|||
|
||||
def withAutoConfig() {
|
||||
httpAutoConfig () {
|
||||
|
||||
csrf(disabled:true)
|
||||
}
|
||||
createAppContext(AUTH_PROVIDER_XML)
|
||||
|
||||
|
@ -93,10 +93,12 @@ class SecurityContextHolderAwareRequestConfigTests extends AbstractHttpConfigTes
|
|||
xml.http('authentication-manager-ref' : 'authManager', 'pattern' : '/first/**') {
|
||||
'form-login'('login-page' : '/login')
|
||||
'logout'('invalidate-session' : 'true')
|
||||
csrf(disabled:true)
|
||||
}
|
||||
xml.http('authentication-manager-ref' : 'authManager2') {
|
||||
'form-login'('login-page' : '/login2')
|
||||
'logout'('invalidate-session' : 'false')
|
||||
csrf(disabled:true)
|
||||
}
|
||||
|
||||
String secondAuthManager = AUTH_PROVIDER_XML.replace("alias='authManager'", "id='authManager2'")
|
||||
|
@ -125,6 +127,7 @@ class SecurityContextHolderAwareRequestConfigTests extends AbstractHttpConfigTes
|
|||
xml.http() {
|
||||
'form-login'('login-page' : '/login')
|
||||
'logout'('invalidate-session' : 'false', 'logout-success-url' : '/login?logout', 'delete-cookies' : 'JSESSIONID')
|
||||
csrf(disabled:true)
|
||||
}
|
||||
createAppContext(AUTH_PROVIDER_XML)
|
||||
|
||||
|
|
|
@ -111,6 +111,7 @@ class SessionManagementConfigTests extends AbstractHttpConfigTests {
|
|||
'session-management'() {
|
||||
'concurrency-control'('max-sessions':1,'error-if-maximum-exceeded':'true')
|
||||
}
|
||||
csrf(disabled:true)
|
||||
}
|
||||
createAppContext()
|
||||
SessionRegistry registry = appContext.getBean(SessionRegistry)
|
||||
|
@ -155,6 +156,7 @@ class SessionManagementConfigTests extends AbstractHttpConfigTests {
|
|||
'session-management'() {
|
||||
'concurrency-control'('session-registry-alias':'sr', 'expired-url': '/expired')
|
||||
}
|
||||
csrf(disabled:true)
|
||||
}
|
||||
createAppContext();
|
||||
List filters = getFilters("/someurl");
|
||||
|
@ -182,8 +184,9 @@ class SessionManagementConfigTests extends AbstractHttpConfigTests {
|
|||
}
|
||||
'logout'('invalidate-session': false, 'delete-cookies': 'testCookie')
|
||||
'remember-me'()
|
||||
csrf(disabled:true)
|
||||
}
|
||||
createAppContext();
|
||||
createAppContext()
|
||||
|
||||
List filters = getFilters("/someurl")
|
||||
ConcurrentSessionFilter concurrentSessionFilter = filters.get(1)
|
||||
|
@ -206,6 +209,7 @@ class SessionManagementConfigTests extends AbstractHttpConfigTests {
|
|||
'concurrency-control'()
|
||||
}
|
||||
'remember-me'()
|
||||
csrf(disabled:true)
|
||||
})
|
||||
bean('entryPoint', 'org.springframework.security.web.authentication.Http403ForbiddenEntryPoint')
|
||||
createAppContext()
|
||||
|
@ -274,6 +278,7 @@ class SessionManagementConfigTests extends AbstractHttpConfigTests {
|
|||
setup:
|
||||
httpAutoConfig {
|
||||
'session-management'('session-authentication-strategy-ref':'ss')
|
||||
csrf(disabled:true)
|
||||
}
|
||||
mockBean(SessionAuthenticationStrategy,'ss')
|
||||
createAppContext()
|
||||
|
@ -298,6 +303,7 @@ class SessionManagementConfigTests extends AbstractHttpConfigTests {
|
|||
'session-management'() {
|
||||
'concurrency-control'('session-registry-ref':'sr')
|
||||
}
|
||||
csrf(disabled:true)
|
||||
}
|
||||
bean('sr', SessionRegistryImpl.class.name)
|
||||
createAppContext();
|
||||
|
@ -354,6 +360,7 @@ class SessionManagementConfigTests extends AbstractHttpConfigTests {
|
|||
def disablingSessionProtectionRemovesSessionManagementFilterIfNoInvalidSessionUrlSet() {
|
||||
httpAutoConfig {
|
||||
'session-management'('session-fixation-protection': 'none')
|
||||
csrf(disabled:true)
|
||||
}
|
||||
createAppContext()
|
||||
|
||||
|
@ -364,6 +371,7 @@ class SessionManagementConfigTests extends AbstractHttpConfigTests {
|
|||
def disablingSessionProtectionRetainsSessionManagementFilterInvalidSessionUrlSet() {
|
||||
httpAutoConfig {
|
||||
'session-management'('session-fixation-protection': 'none', 'invalid-session-url': '/timeoutUrl')
|
||||
csrf(disabled:true)
|
||||
}
|
||||
createAppContext()
|
||||
def filter = getFilters("/someurl")[10]
|
||||
|
|
|
@ -3087,18 +3087,13 @@ This is not a limitation of Spring Security's support, but instead a general req
|
|||
==== Configure CSRF Protection
|
||||
The next step is to include Spring Security's CSRF protection within your application. Some frameworks handle invalid CSRF tokens by invaliding the user's session, but this causes <<csrf-logout,its own problems>>. Instead by default Spring Security's CSRF protection will produce an HTTP 403 access denied. This can be customized by configuring the <<access-denied-handler,AccessDeniedHandler>> to process `InvalidCsrfTokenException` differently.
|
||||
|
||||
For passivity reasons, if you are using the XML configuration, CSRF protection must be explicitly enabled using the <<nsa-csrf,<csrf>>> element. Refer to the <<nsa-csrf,<csrf>>> element's documentation for additional customizations.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
https://jira.springsource.org/browse/SEC-2347[SEC-2347] is logged to ensure Spring Security 4.x's XML namespace configuration will enable CSRF protection by default.
|
||||
====
|
||||
As of Spring Security 4.0, CSRF protection is enabled by default with XML configuration. If you would like to disable CSRF protection, the corresponding XML configuration can be seen below.
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<http>
|
||||
<!-- ... -->
|
||||
<csrf />
|
||||
<csrf disabled="true"/>
|
||||
</http>
|
||||
----
|
||||
|
||||
|
@ -6895,6 +6890,9 @@ This element will add http://en.wikipedia.org/wiki/Cross-site_request_forgery[Cr
|
|||
[[nsa-csrf-attributes]]
|
||||
===== <csrf> Attributes
|
||||
|
||||
[[nsa-csrf-disabled]]
|
||||
* **disabled**
|
||||
Optional attribute that specifies to disable Spring Security's CSRF protection. The default is false (CSRF protection is enabled). It is highly recommended to leave CSRF protection enabled.
|
||||
|
||||
[[nsa-csrf-token-repository-ref]]
|
||||
* **token-repository-ref**
|
||||
|
|
Loading…
Reference in New Issue