SEC-1171: Implement parsing of empty filter chain patters via http 'secured' attribute and remove filters='none' support.
This commit is contained in:
parent
05c7abe191
commit
34401416b0
|
@ -4,10 +4,8 @@ import static org.springframework.security.config.http.HttpSecurityBeanDefinitio
|
|||
import static org.springframework.security.config.http.SecurityFilters.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
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;
|
||||
|
@ -82,9 +80,6 @@ class HttpConfigurationBuilder {
|
|||
private final List<Element> interceptUrls;
|
||||
private final MatcherType matcherType;
|
||||
|
||||
// Use ManagedMap to allow placeholder resolution
|
||||
private ManagedMap<BeanDefinition, List<BeanMetadataElement>> filterChainMap;
|
||||
|
||||
private BeanDefinition cpf;
|
||||
private BeanDefinition securityContextPersistenceFilter;
|
||||
private BeanReference contextRepoRef;
|
||||
|
@ -105,6 +100,15 @@ class HttpConfigurationBuilder {
|
|||
this.portMapperName = portMapperName;
|
||||
this.matcherType = matcherType;
|
||||
interceptUrls = DomUtils.getChildElementsByTagName(element, Elements.INTERCEPT_URL);
|
||||
|
||||
for (Element urlElt : interceptUrls) {
|
||||
if (StringUtils.hasText(urlElt.getAttribute(ATT_FILTERS))) {
|
||||
pc.getReaderContext().error("The use of \"filters='none'\" is no longer supported. Please define a" +
|
||||
" separate <http> element for the pattern you want to exclude and use the attribute" +
|
||||
" \"secured='false'\".", pc.extractSource(urlElt));
|
||||
}
|
||||
}
|
||||
|
||||
String createSession = element.getAttribute(ATT_CREATE_SESSION);
|
||||
|
||||
if (StringUtils.hasText(createSession)) {
|
||||
|
@ -113,7 +117,6 @@ class HttpConfigurationBuilder {
|
|||
sessionPolicy = SessionCreationPolicy.ifRequired;
|
||||
}
|
||||
|
||||
parseInterceptUrlsForEmptyFilterChains();
|
||||
createSecurityContextPersistenceFilter();
|
||||
createSessionManagementFilters();
|
||||
createRequestCacheFilter();
|
||||
|
@ -122,32 +125,6 @@ class HttpConfigurationBuilder {
|
|||
createFilterSecurityInterceptor(authenticationManager);
|
||||
}
|
||||
|
||||
private void parseInterceptUrlsForEmptyFilterChains() {
|
||||
filterChainMap = new ManagedMap<BeanDefinition, List<BeanMetadataElement>>();
|
||||
|
||||
for (Element urlElt : interceptUrls) {
|
||||
String path = urlElt.getAttribute(ATT_PATH_PATTERN);
|
||||
|
||||
if(!StringUtils.hasText(path)) {
|
||||
pc.getReaderContext().error("path attribute cannot be empty or null", urlElt);
|
||||
}
|
||||
|
||||
BeanDefinition matcher = matcherType.createMatcher(path, null);
|
||||
|
||||
String filters = urlElt.getAttribute(ATT_FILTERS);
|
||||
|
||||
if (StringUtils.hasText(filters)) {
|
||||
if (!filters.equals(OPT_FILTERS_NONE)) {
|
||||
pc.getReaderContext().error("Currently only 'none' is supported as the custom " +
|
||||
"filters attribute", urlElt);
|
||||
}
|
||||
|
||||
List<BeanMetadataElement> noFilters = Collections.emptyList();
|
||||
filterChainMap.put(matcher, noFilters);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Needed to account for placeholders
|
||||
static String createPath(String path, boolean lowerCase) {
|
||||
return lowerCase ? path.toLowerCase() : path;
|
||||
|
@ -518,10 +495,6 @@ class HttpConfigurationBuilder {
|
|||
return requestCache;
|
||||
}
|
||||
|
||||
ManagedMap<BeanDefinition, List<BeanMetadataElement>> getFilterChainMap() {
|
||||
return filterChainMap;
|
||||
}
|
||||
|
||||
List<OrderDecorator> getFilters() {
|
||||
List<OrderDecorator> filters = new ArrayList<OrderDecorator>();
|
||||
|
||||
|
|
|
@ -51,6 +51,7 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
|
|||
static final String ATT_REQUIRES_CHANNEL = "requires-channel";
|
||||
|
||||
private static final String ATT_REF = "ref";
|
||||
private static final String ATT_SECURED = "secured";
|
||||
|
||||
static final String EXPRESSION_FIMDS_CLASS = "org.springframework.security.web.access.expression.ExpressionBasedFilterInvocationSecurityMetadataSource";
|
||||
static final String EXPRESSION_HANDLER_CLASS = "org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler";
|
||||
|
@ -72,11 +73,48 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
|
|||
CompositeComponentDefinition compositeDef =
|
||||
new CompositeComponentDefinition(element.getTagName(), pc.extractSource(element));
|
||||
pc.pushContainingComponent(compositeDef);
|
||||
final Object source = pc.extractSource(element);
|
||||
|
||||
final String portMapperName = createPortMapper(element, pc);
|
||||
|
||||
MatcherType matcherType = MatcherType.fromElement(element);
|
||||
ManagedMap<BeanDefinition, List<BeanMetadataElement>> filterChainMap = new ManagedMap<BeanDefinition, List<BeanMetadataElement>>();
|
||||
|
||||
String filterChainPattern = element.getAttribute(ATT_PATH_PATTERN);
|
||||
|
||||
BeanDefinition filterChainMatcher;
|
||||
|
||||
if (StringUtils.hasText(filterChainPattern)) {
|
||||
filterChainMatcher = matcherType.createMatcher(filterChainPattern, null);
|
||||
} else {
|
||||
filterChainMatcher = new RootBeanDefinition(AnyRequestMatcher.class);
|
||||
}
|
||||
|
||||
filterChainMap.put(filterChainMatcher, createFilterChain(element, pc, matcherType));
|
||||
|
||||
registerFilterChainProxy(pc, filterChainMap, pc.extractSource(element));
|
||||
|
||||
pc.popAndRegisterContainingComponent();
|
||||
return null;
|
||||
}
|
||||
|
||||
List<BeanMetadataElement> createFilterChain(Element element, ParserContext pc, MatcherType matcherType) {
|
||||
boolean unSecured = "false".equals(element.getAttribute(ATT_SECURED));
|
||||
|
||||
if (unSecured) {
|
||||
if (!StringUtils.hasText(element.getAttribute(ATT_PATH_PATTERN))) {
|
||||
pc.getReaderContext().error("The '" + ATT_SECURED + "' attribute must be used in combination with" +
|
||||
" the '" + ATT_PATH_PATTERN +"' attribute.", pc.extractSource(element));
|
||||
}
|
||||
|
||||
for (int n=0; n < element.getChildNodes().getLength(); n ++) {
|
||||
if (element.getChildNodes().item(n) instanceof Element) {
|
||||
pc.getReaderContext().error("If you are using <htt> to define an unsecured pattern, " +
|
||||
"it cannot contain child elements.", pc.extractSource(element));
|
||||
}
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final String portMapperName = createPortMapper(element, pc);
|
||||
|
||||
ManagedList<BeanReference> authenticationProviders = new ManagedList<BeanReference>();
|
||||
BeanReference authenticationManager = createAuthenticationManager(element, pc, authenticationProviders, null);
|
||||
|
@ -97,7 +135,7 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
|
|||
unorderedFilterChain.addAll(buildCustomFilterList(element, pc));
|
||||
|
||||
Collections.sort(unorderedFilterChain, new OrderComparator());
|
||||
checkFilterChainOrder(unorderedFilterChain, pc, source);
|
||||
checkFilterChainOrder(unorderedFilterChain, pc, pc.extractSource(element));
|
||||
|
||||
List<BeanMetadataElement> filterChain = new ManagedList<BeanMetadataElement>();
|
||||
|
||||
|
@ -105,27 +143,10 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
|
|||
filterChain.add(od.bean);
|
||||
}
|
||||
|
||||
// Get the map of empty filter chains (if any)
|
||||
ManagedMap<BeanDefinition, List<BeanMetadataElement>> filterChainMap = httpBldr.getFilterChainMap();
|
||||
|
||||
String filterChainPattern = element.getAttribute(ATT_PATH_PATTERN);
|
||||
|
||||
BeanDefinition filterChainMatcher;
|
||||
|
||||
if (StringUtils.hasText(filterChainPattern)) {
|
||||
filterChainMatcher = matcherType.createMatcher(filterChainPattern, null);
|
||||
} else {
|
||||
filterChainMatcher = new RootBeanDefinition(AnyRequestMatcher.class);
|
||||
}
|
||||
|
||||
filterChainMap.put(filterChainMatcher, filterChain);
|
||||
|
||||
registerFilterChainProxy(pc, filterChainMap, source);
|
||||
|
||||
pc.popAndRegisterContainingComponent();
|
||||
return null;
|
||||
return filterChain;
|
||||
}
|
||||
|
||||
|
||||
private String createPortMapper(Element elt, ParserContext pc) {
|
||||
// Register the portMapper. A default will always be created, even if no element exists.
|
||||
BeanDefinition portMapper = new PortMappingsBeanDefinitionParser().parse(
|
||||
|
|
|
@ -255,11 +255,15 @@ protect-pointcut.attlist &=
|
|||
|
||||
|
||||
http =
|
||||
## Container element for HTTP security configuration
|
||||
element http {http.attlist, (intercept-url+ & access-denied-handler? & form-login? & openid-login? & x509? & http-basic? & logout? & session-management & remember-me? & anonymous? & port-mappings & custom-filter* & request-cache?) }
|
||||
## Container element for HTTP security configuration. Multiple elements can now be defined, each with a specific pattern to which the enclosed security configuration applies. A pattern can also be configured to bypass Spring Security's filters completely by setting the "secured" attribute to "false".
|
||||
element http {http.attlist, (intercept-url* & access-denied-handler? & form-login? & openid-login? & x509? & http-basic? & logout? & session-management & remember-me? & anonymous? & port-mappings & custom-filter* & request-cache?) }
|
||||
http.attlist &=
|
||||
## The request URL pattern which will be mapped to the filter chain created by this <http> element. If omitted, the filter chain will match all requests.
|
||||
attribute pattern {xsd:token}?
|
||||
http.attlist &=
|
||||
## When set to 'false', requests matching the pattern attribute will be ignored by Spring Security. No security filters will be applied and no SecurityContext will be available. If set, the <http> element must be empty, with no children.
|
||||
attribute secured {boolean}?
|
||||
|
||||
http.attlist &=
|
||||
## Automatically registers a login form, BASIC authentication, anonymous authentication, logout services, remember-me and servlet-api-integration. If set to "true", all of these capabilities are added (although you can still customize the configuration of each by providing the respective element). If unspecified, defaults to "false".
|
||||
attribute auto-config {boolean}?
|
||||
|
|
|
@ -605,7 +605,7 @@
|
|||
</xs:attribute>
|
||||
</xs:attributeGroup>
|
||||
<xs:element name="http"><xs:annotation>
|
||||
<xs:documentation>Container element for HTTP security configuration</xs:documentation>
|
||||
<xs:documentation>Container element for HTTP security configuration. Multiple elements can now be defined, each with a specific pattern to which the enclosed security configuration applies. A pattern can also be configured to bypass Spring Security's filters completely by setting the "secured" attribute to "false".</xs:documentation>
|
||||
</xs:annotation><xs:complexType>
|
||||
<xs:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xs:element name="intercept-url"><xs:annotation>
|
||||
|
@ -692,6 +692,11 @@
|
|||
<xs:documentation>The request URL pattern which will be mapped to the filter chain created by this <http> element. If omitted, the filter chain will match all requests.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="secured" type="security:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>When set to 'false', requests matching the pattern attribute will be ignored by Spring Security. No security filters will be applied and no SecurityContext will be available. If set, the <http> element must be empty, with no children.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="auto-config" type="security:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Automatically registers a login form, BASIC authentication, anonymous authentication, logout services, remember-me and servlet-api-integration. If set to "true", all of these capabilities are added (although you can still customize the configuration of each by providing the respective element). If unspecified, defaults to "false".</xs:documentation>
|
||||
|
|
|
@ -35,11 +35,6 @@ abstract class AbstractHttpConfigTests extends AbstractXmlConfigTests {
|
|||
xml.'intercept-url'(pattern: path, method: httpMethod, access: authz)
|
||||
}
|
||||
|
||||
|
||||
def interceptUrlNoFilters(String path) {
|
||||
xml.'intercept-url'(pattern: path, filters: 'none')
|
||||
}
|
||||
|
||||
Filter getFilter(Class type) {
|
||||
List filters = getFilters("/any");
|
||||
|
||||
|
|
|
@ -83,9 +83,8 @@ class MiscHttpConfigTests extends AbstractHttpConfigTests {
|
|||
}
|
||||
|
||||
def filterListShouldBeEmptyForPatternWithNoFilters() {
|
||||
httpAutoConfig() {
|
||||
interceptUrlNoFilters('/unprotected')
|
||||
}
|
||||
xml.http(pattern: '/unprotected', secured: 'false')
|
||||
httpAutoConfig() {}
|
||||
createAppContext()
|
||||
|
||||
expect:
|
||||
|
@ -93,9 +92,8 @@ class MiscHttpConfigTests extends AbstractHttpConfigTests {
|
|||
}
|
||||
|
||||
def regexPathsWorkCorrectly() {
|
||||
httpAutoConfig('regex') {
|
||||
interceptUrlNoFilters('\\A\\/[a-z]+')
|
||||
}
|
||||
xml.http(pattern: '\\A\\/[a-z]+', secured: 'false', 'request-matcher': 'regex')
|
||||
httpAutoConfig() {}
|
||||
createAppContext()
|
||||
|
||||
expect:
|
||||
|
@ -105,9 +103,8 @@ class MiscHttpConfigTests extends AbstractHttpConfigTests {
|
|||
|
||||
def ciRegexPathsWorkCorrectly() {
|
||||
when:
|
||||
httpAutoConfig('ciRegex') {
|
||||
interceptUrlNoFilters('\\A\\/[a-z]+')
|
||||
}
|
||||
xml.http(pattern: '\\A\\/[a-z]+', secured: 'false', 'request-matcher': 'ciRegex')
|
||||
httpAutoConfig() {}
|
||||
createAppContext()
|
||||
|
||||
then:
|
||||
|
|
|
@ -16,16 +16,16 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic
|
|||
|
||||
class PlaceHolderAndELConfigTests extends AbstractHttpConfigTests {
|
||||
|
||||
void setup() {
|
||||
def setup() {
|
||||
// Add a PropertyPlaceholderConfigurer to the context for all the tests
|
||||
xml.'b:bean'('class': PropertyPlaceholderConfigurer.class.name)
|
||||
}
|
||||
|
||||
def filtersEqualsNoneSupportsPlaceholderForPattern() {
|
||||
def unsecuredPatternSupportsPlaceholderForPattern() {
|
||||
System.setProperty("pattern.nofilters", "/unprotected");
|
||||
|
||||
xml.http(pattern: '${pattern.nofilters}', secured: 'false')
|
||||
httpAutoConfig() {
|
||||
interceptUrlNoFilters('${pattern.nofilters}')
|
||||
interceptUrl('/**', 'ROLE_A')
|
||||
}
|
||||
createAppContext()
|
||||
|
@ -44,9 +44,9 @@ class PlaceHolderAndELConfigTests extends AbstractHttpConfigTests {
|
|||
System.setProperty("default.target", "/defaultTarget");
|
||||
System.setProperty("auth.failure", "/authFailure");
|
||||
|
||||
xml.http(pattern: '${login.page}', secured: 'false')
|
||||
xml.http {
|
||||
interceptUrl('${secure.Url}', '${secure.role}')
|
||||
interceptUrlNoFilters('${login.page}');
|
||||
'form-login'('login-page':'${login.page}', 'default-target-url': '${default.target}',
|
||||
'authentication-failure-url':'${auth.failure}');
|
||||
}
|
||||
|
|
|
@ -151,9 +151,8 @@ public class HttpSecurityBeanDefinitionParserTests {
|
|||
@Test
|
||||
public void filterListShouldBeEmptyForPatternWithNoFilters() throws Exception {
|
||||
setContext(
|
||||
" <http auto-config='true'>" +
|
||||
" <intercept-url pattern='/unprotected' filters='none' />" +
|
||||
" </http>" + AUTH_PROVIDER_XML);
|
||||
" <http pattern='/unprotected' secured='false' />" +
|
||||
" <http auto-config='true'/> " + AUTH_PROVIDER_XML);
|
||||
|
||||
List<Filter> filters = getFilters("/unprotected");
|
||||
|
||||
|
@ -165,8 +164,8 @@ public class HttpSecurityBeanDefinitionParserTests {
|
|||
System.setProperty("pattern.nofilters", "/unprotected");
|
||||
setContext(
|
||||
" <b:bean class='org.springframework.beans.factory.config.PropertyPlaceholderConfigurer'/>" +
|
||||
" <http pattern='${pattern.nofilters}' secured='false' />" +
|
||||
" <http auto-config='true'>" +
|
||||
" <intercept-url pattern='${pattern.nofilters}' filters='none' />" +
|
||||
" <intercept-url pattern='/**' access='ROLE_A' />" +
|
||||
" </http>" + AUTH_PROVIDER_XML);
|
||||
|
||||
|
@ -178,9 +177,9 @@ public class HttpSecurityBeanDefinitionParserTests {
|
|||
@Test
|
||||
public void regexPathsWorkCorrectly() throws Exception {
|
||||
setContext(
|
||||
" <http auto-config='true' request-matcher='regex'>" +
|
||||
" <intercept-url pattern='\\A\\/[a-z]+' filters='none' />" +
|
||||
" </http>" + AUTH_PROVIDER_XML);
|
||||
" <http pattern='\\A\\/[a-z]+' secured='false' request-matcher='regex'/>" +
|
||||
" <http auto-config='true' />"
|
||||
+ AUTH_PROVIDER_XML);
|
||||
assertEquals(0, getFilters("/imlowercase").size());
|
||||
List<Filter> allFilters = getFilters("/ImCaughtByTheAnyRequestMatcher");
|
||||
checkAutoConfigFilters(allFilters);
|
||||
|
@ -189,9 +188,9 @@ public class HttpSecurityBeanDefinitionParserTests {
|
|||
@Test
|
||||
public void ciRegexPathsWorkCorrectly() throws Exception {
|
||||
setContext(
|
||||
" <http auto-config='true' request-matcher='ciRegex'>" +
|
||||
" <intercept-url pattern='\\A\\/[a-z]+' filters='none' />" +
|
||||
" </http>" + AUTH_PROVIDER_XML);
|
||||
" <http pattern='\\A\\/[a-z]+' secured='false' request-matcher='ciRegex' />" +
|
||||
" <http auto-config='true' />"
|
||||
+ AUTH_PROVIDER_XML);
|
||||
assertEquals(0, getFilters("/imMixedCase").size());
|
||||
// This will be matched by the default pattern ".*"
|
||||
List<Filter> allFilters = getFilters("/Im_Caught_By_The_AnyRequestMatcher");
|
||||
|
@ -313,9 +312,9 @@ public class HttpSecurityBeanDefinitionParserTests {
|
|||
System.setProperty("auth.failure", "/authFailure");
|
||||
setContext(
|
||||
"<b:bean class='org.springframework.beans.factory.config.PropertyPlaceholderConfigurer'/>" +
|
||||
"<http pattern='${login.page}' secured='false' />" +
|
||||
"<http>" +
|
||||
" <intercept-url pattern='${secure.Url}' access='${secure.role}' />" +
|
||||
" <intercept-url pattern='${login.page}' filters='none' />" +
|
||||
" <form-login login-page='${login.page}' default-target-url='${default.target}' " +
|
||||
" authentication-failure-url='${auth.failure}' />" +
|
||||
"</http>" + AUTH_PROVIDER_XML);
|
||||
|
@ -883,8 +882,8 @@ public class HttpSecurityBeanDefinitionParserTests {
|
|||
closeAppContext();
|
||||
// No filters applied to login page
|
||||
setContext(
|
||||
" <http pattern='/login.jsp*' secured='false'/>" +
|
||||
" <http>" +
|
||||
" <intercept-url pattern='/login.jsp*' filters='none'/>" +
|
||||
" <intercept-url pattern='/**' access='ROLE_A'/>" +
|
||||
" <anonymous />" +
|
||||
" <form-login login-page='/login.jsp' default-target-url='/messageList.html'/>" +
|
||||
|
|
|
@ -4,10 +4,11 @@
|
|||
xmlns:beans="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
|
||||
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">
|
||||
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd">
|
||||
|
||||
<http pattern="/login.jsp" secured="false" />
|
||||
|
||||
<http entry-point-ref="aep">
|
||||
<intercept-url pattern="/login.jsp" filters="none" />
|
||||
<intercept-url pattern="/**" access="ROLE_DEVELOPER,ROLE_USER" />
|
||||
|
||||
<session-management session-authentication-strategy-ref="sas"/>
|
||||
|
|
|
@ -4,15 +4,15 @@
|
|||
xmlns:beans="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
|
||||
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">
|
||||
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd">
|
||||
|
||||
<!--
|
||||
Http App Context to test form login, remember-me and concurrent session control.
|
||||
Needs to be supplemented with authentication provider(s)
|
||||
-->
|
||||
<http pattern="/login.jsp" secured="false" />
|
||||
|
||||
<http use-expressions="true">
|
||||
<intercept-url pattern="/login.jsp*" filters="none" />
|
||||
<intercept-url pattern="/secure/**" access="hasAnyRole('ROLE_DEVELOPER','ROLE_USER')" />
|
||||
<intercept-url pattern="/**" access="hasAnyRole('ROLE_DEVELOPER','ROLE_USER')" />
|
||||
|
||||
|
|
Loading…
Reference in New Issue