From 98a2ca3bbc66ff44244fe57810178e18152a76c5 Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Mon, 30 Dec 2019 09:10:49 -0700 Subject: [PATCH] Add Csrf Ignore Configurability Issue gh-5185 --- .../http/AuthenticationConfigBuilder.java | 27 ++++---- .../config/http/CsrfBeanDefinitionParser.java | 64 +++++++++++++++++-- .../config/http/HttpConfigurationBuilder.java | 8 ++- .../HttpSecurityBeanDefinitionParser.java | 10 ++- 4 files changed, 89 insertions(+), 20 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java b/config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java index eedf87ad53..0b87748861 100644 --- a/config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java +++ b/config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java @@ -15,8 +15,18 @@ */ package org.springframework.security.config.http; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import javax.servlet.http.HttpServletRequest; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.w3c.dom.Element; + import org.springframework.beans.BeanMetadataElement; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanReference; @@ -53,15 +63,6 @@ import org.springframework.security.web.csrf.CsrfToken; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.util.xml.DomUtils; -import org.w3c.dom.Element; - -import javax.servlet.http.HttpServletRequest; -import java.security.SecureRandom; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.function.Function; import static org.springframework.security.config.http.SecurityFilters.ANONYMOUS_FILTER; import static org.springframework.security.config.http.SecurityFilters.BASIC_AUTH_FILTER; @@ -160,11 +161,12 @@ final class AuthenticationConfigBuilder { private BeanReference oauth2LoginAuthenticationProviderRef; private BeanReference oauth2LoginOidcAuthenticationProviderRef; private BeanDefinition oauth2LoginLinks; - private BeanDefinition authorizationRequestRedirectFilter; private BeanDefinition authorizationCodeGrantFilter; private BeanReference authorizationCodeAuthenticationProviderRef; + private final List csrfIgnoreRequestMatchers = new ManagedList<>(); + AuthenticationConfigBuilder(Element element, boolean forceAutoConfig, ParserContext pc, SessionCreationPolicy sessionPolicy, BeanReference requestCache, BeanReference authenticationManager, @@ -194,7 +196,6 @@ final class AuthenticationConfigBuilder { createLoginPageFilterIfNeeded(); createUserDetailsServiceFactory(); createExceptionTranslationFilter(); - } void createRememberMeFilter(BeanReference authenticationManager) { @@ -708,6 +709,10 @@ final class AuthenticationConfigBuilder { return accessDeniedHandler; } + List getCsrfIgnoreRequestMatchers() { + return csrfIgnoreRequestMatchers; + } + void createAnonymousFilter() { Element anonymousElt = DomUtils.getChildElementByTagName(httpElt, Elements.ANONYMOUS); diff --git a/config/src/main/java/org/springframework/security/config/http/CsrfBeanDefinitionParser.java b/config/src/main/java/org/springframework/security/config/http/CsrfBeanDefinitionParser.java index 46f48a4780..9ef1fed832 100644 --- a/config/src/main/java/org/springframework/security/config/http/CsrfBeanDefinitionParser.java +++ b/config/src/main/java/org/springframework/security/config/http/CsrfBeanDefinitionParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,12 +15,19 @@ */ package org.springframework.security.config.http; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import javax.servlet.http.HttpServletRequest; + import org.w3c.dom.Element; import org.springframework.beans.BeanMetadataElement; import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.parsing.BeanComponentDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.ManagedList; import org.springframework.beans.factory.support.ManagedMap; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.xml.BeanDefinitionParser; @@ -38,6 +45,10 @@ import org.springframework.security.web.csrf.MissingCsrfTokenException; import org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor; import org.springframework.security.web.session.InvalidSessionAccessDeniedHandler; import org.springframework.security.web.session.InvalidSessionStrategy; +import org.springframework.security.web.util.matcher.AndRequestMatcher; +import org.springframework.security.web.util.matcher.NegatedRequestMatcher; +import org.springframework.security.web.util.matcher.OrRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; @@ -58,6 +69,8 @@ public class CsrfBeanDefinitionParser implements BeanDefinitionParser { private String csrfRepositoryRef; private BeanDefinition csrfFilter; + private String requestMatcherRef; + @Override public BeanDefinition parse(Element element, ParserContext pc) { boolean disabled = element != null @@ -77,10 +90,9 @@ public class CsrfBeanDefinitionParser implements BeanDefinitionParser { } } - String matcherRef = null; if (element != null) { this.csrfRepositoryRef = element.getAttribute(ATT_REPOSITORY); - matcherRef = element.getAttribute(ATT_MATCHER); + this.requestMatcherRef = element.getAttribute(ATT_MATCHER); } if (!StringUtils.hasText(this.csrfRepositoryRef)) { @@ -100,8 +112,8 @@ public class CsrfBeanDefinitionParser implements BeanDefinitionParser { .rootBeanDefinition(CsrfFilter.class); builder.addConstructorArgReference(this.csrfRepositoryRef); - if (StringUtils.hasText(matcherRef)) { - builder.addPropertyReference("requireCsrfProtectionMatcher", matcherRef); + if (StringUtils.hasText(this.requestMatcherRef)) { + builder.addPropertyReference("requireCsrfProtectionMatcher", this.requestMatcherRef); } this.csrfFilter = builder.getBeanDefinition(); @@ -172,4 +184,46 @@ public class CsrfBeanDefinitionParser implements BeanDefinitionParser { csrfAuthenticationStrategy.addConstructorArgReference(this.csrfRepositoryRef); return csrfAuthenticationStrategy.getBeanDefinition(); } + + void setIgnoreCsrfRequestMatchers(List requestMatchers) { + if (!requestMatchers.isEmpty()) { + BeanMetadataElement requestMatcher; + if (StringUtils.hasText(this.requestMatcherRef)) { + requestMatcher = new RuntimeBeanReference(this.requestMatcherRef); + } else { + requestMatcher = new RootBeanDefinition(DefaultRequiresCsrfMatcher.class); + } + BeanDefinitionBuilder and = BeanDefinitionBuilder + .rootBeanDefinition(AndRequestMatcher.class); + BeanDefinitionBuilder negated = BeanDefinitionBuilder + .rootBeanDefinition(NegatedRequestMatcher.class); + BeanDefinitionBuilder or = BeanDefinitionBuilder + .rootBeanDefinition(OrRequestMatcher.class); + or.addConstructorArgValue(requestMatchers); + negated.addConstructorArgValue(or.getBeanDefinition()); + List ands = new ManagedList<>(); + ands.add(requestMatcher); + ands.add(negated.getBeanDefinition()); + and.addConstructorArgValue(ands); + this.csrfFilter.getPropertyValues() + .add("requireCsrfProtectionMatcher", and.getBeanDefinition()); + } + } + + private static final class DefaultRequiresCsrfMatcher implements RequestMatcher { + private final HashSet allowedMethods = new HashSet<>( + Arrays.asList("GET", "HEAD", "TRACE", "OPTIONS")); + + /* + * (non-Javadoc) + * + * @see + * org.springframework.security.web.util.matcher.RequestMatcher#matches(javax. + * servlet.http.HttpServletRequest) + */ + @Override + public boolean matches(HttpServletRequest request) { + return !this.allowedMethods.contains(request.getMethod()); + } + } } diff --git a/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java b/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java index 24eb66974e..2711ebdb9f 100644 --- a/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java +++ b/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2020 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. @@ -238,6 +238,12 @@ class HttpConfigurationBuilder { } } + void setCsrfIgnoreRequestMatchers(List requestMatchers) { + if (csrfParser != null) { + csrfParser.setIgnoreCsrfRequestMatchers(requestMatchers); + } + } + // Needed to account for placeholders static String createPath(String path, boolean lowerCase) { return lowerCase ? path.toLowerCase() : path; diff --git a/config/src/main/java/org/springframework/security/config/http/HttpSecurityBeanDefinitionParser.java b/config/src/main/java/org/springframework/security/config/http/HttpSecurityBeanDefinitionParser.java index ccb1abf7ae..256cae6dcb 100644 --- a/config/src/main/java/org/springframework/security/config/http/HttpSecurityBeanDefinitionParser.java +++ b/config/src/main/java/org/springframework/security/config/http/HttpSecurityBeanDefinitionParser.java @@ -15,8 +15,14 @@ */ package org.springframework.security.config.http; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.w3c.dom.Element; + import org.springframework.beans.BeanMetadataElement; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanReference; @@ -44,9 +50,6 @@ import org.springframework.security.web.PortResolverImpl; import org.springframework.security.web.util.matcher.AnyRequestMatcher; import org.springframework.util.StringUtils; import org.springframework.util.xml.DomUtils; -import org.w3c.dom.Element; - -import java.util.*; /** * Sets up HTTP security: filter stack and protected URLs. @@ -156,6 +159,7 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { httpBldr.setLogoutHandlers(authBldr.getLogoutHandlers()); httpBldr.setEntryPoint(authBldr.getEntryPointBean()); httpBldr.setAccessDeniedHandler(authBldr.getAccessDeniedHandlerBean()); + httpBldr.setCsrfIgnoreRequestMatchers(authBldr.getCsrfIgnoreRequestMatchers()); authenticationProviders.addAll(authBldr.getProviders());