SEC-1171: Implement parsing of empty filter chain patters via http 'secured' attribute and remove filters='none' support.

This commit is contained in:
Luke Taylor 2010-05-05 21:18:52 +01:00
parent 05c7abe191
commit 34401416b0
10 changed files with 91 additions and 96 deletions

View File

@ -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>();

View File

@ -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(

View File

@ -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}?

View File

@ -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 &lt;http&gt; 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 &lt;http&gt; 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>

View File

@ -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");

View File

@ -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:

View File

@ -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}');
}

View File

@ -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'/>" +

View File

@ -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"/>

View File

@ -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')" />