diff --git a/core/src/main/java/org/springframework/security/config/BasicAuthenticationBeanDefinitionParser.java b/core/src/main/java/org/springframework/security/config/BasicAuthenticationBeanDefinitionParser.java new file mode 100644 index 0000000000..cbaf137476 --- /dev/null +++ b/core/src/main/java/org/springframework/security/config/BasicAuthenticationBeanDefinitionParser.java @@ -0,0 +1,43 @@ +package org.springframework.security.config; + +import org.springframework.beans.factory.config.BeanDefinition; +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.ui.basicauth.BasicProcessingFilter; +import org.springframework.security.ui.basicauth.BasicProcessingFilterEntryPoint; +import org.w3c.dom.Element; + +/** + * Creates a {@link BasicProcessingFilter} and {@link BasicProcessingFilterEntryPoint} and + * registers them in the application context. + * + * @author Luke Taylor + * @version $Id$ + */ +public class BasicAuthenticationBeanDefinitionParser implements BeanDefinitionParser { + public static final String DEFAULT_BASIC_AUTH_FILTER_ID = "_basicAuthenticationFilter"; + public static final String DEFAULT_BASIC_AUTH_ENTRY_POINT_ID = "_basicAuthenticationEntryPoint"; + + + public BeanDefinition parse(Element elt, ParserContext parserContext) { + BeanDefinitionBuilder filterBuilder = + BeanDefinitionBuilder.rootBeanDefinition(BasicProcessingFilter.class); + RootBeanDefinition entryPoint = new RootBeanDefinition(BasicProcessingFilterEntryPoint.class); + + String realm = elt.getAttribute("realm"); + + entryPoint.getPropertyValues().addPropertyValue("realmName", realm); + + filterBuilder.addPropertyValue("authenticationEntryPoint", entryPoint); + // Detect auth manager + filterBuilder.setAutowireMode(RootBeanDefinition.AUTOWIRE_BY_TYPE); + + parserContext.getRegistry().registerBeanDefinition(DEFAULT_BASIC_AUTH_FILTER_ID, + filterBuilder.getBeanDefinition()); + parserContext.getRegistry().registerBeanDefinition(DEFAULT_BASIC_AUTH_ENTRY_POINT_ID, entryPoint); + + return null; + } +} diff --git a/core/src/main/java/org/springframework/security/config/FormLoginBeanDefinitionParser.java b/core/src/main/java/org/springframework/security/config/FormLoginBeanDefinitionParser.java new file mode 100644 index 0000000000..e103a4ce7e --- /dev/null +++ b/core/src/main/java/org/springframework/security/config/FormLoginBeanDefinitionParser.java @@ -0,0 +1,96 @@ +package org.springframework.security.config; + +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.beans.factory.config.BeanDefinition; +import org.springframework.security.ui.webapp.AuthenticationProcessingFilter; +import org.springframework.security.ui.webapp.AuthenticationProcessingFilterEntryPoint; +import org.springframework.security.ui.webapp.DefaultLoginPageGeneratingFilter; +import org.springframework.util.StringUtils; +import org.w3c.dom.Element; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * @author Luke Taylor + * @version $Id$ + */ +public class FormLoginBeanDefinitionParser implements BeanDefinitionParser { + protected final Log logger = LogFactory.getLog(getClass()); + + public static final String DEFAULT_FORM_LOGIN_FILTER_ID = "_formLoginFilter"; + public static final String DEFAULT_FORM_LOGIN_ENTRY_POINT_ID = "_formLoginEntryPoint"; + + private static final String LOGIN_URL_ATTRIBUTE = "loginUrl"; + private static final String LOGIN_PAGE_ATTRIBUTE = "loginPage"; + + private static final String FORM_LOGIN_TARGET_URL_ATTRIBUTE = "defaultTargetUrl"; + private static final String DEFAULT_FORM_LOGIN_TARGET_URL = "/index"; + + private static final String FORM_LOGIN_AUTH_FAILURE_URL_ATTRIBUTE = "defaultTargetUrl"; + // TODO: Change AbstractProcessingFilter to not need a failure URL and just write a failure message + // to the response if one isn't set. + private static final String DEFAULT_FORM_LOGIN_AUTH_FAILURE_URL = "/loginError"; + + + public BeanDefinition parse(Element elt, ParserContext parserContext) { + BeanDefinition filterBean = createFilterBean(elt); + + BeanDefinitionBuilder entryPointBuilder = + BeanDefinitionBuilder.rootBeanDefinition(AuthenticationProcessingFilterEntryPoint.class); + + + String loginPage = elt.getAttribute(LOGIN_PAGE_ATTRIBUTE); + + // If no login page has been defined, add in the default page generator. + if (!StringUtils.hasText(loginPage)) { + logger.info("No login page configured in form-login element. The default internal one will be used. Use" + + "the 'loginPage' attribute to specify the URL of the login page."); + loginPage = DefaultLoginPageGeneratingFilter.DEFAULT_LOGIN_PAGE_URL; + RootBeanDefinition loginPageFilter = new RootBeanDefinition(DefaultLoginPageGeneratingFilter.class); + loginPageFilter.getConstructorArgumentValues().addGenericArgumentValue(filterBean); + parserContext.getRegistry().registerBeanDefinition("_springSecurityLoginPageFilter", loginPageFilter); + } + + entryPointBuilder.addPropertyValue("loginFormUrl", loginPage); + + parserContext.getRegistry().registerBeanDefinition(DEFAULT_FORM_LOGIN_FILTER_ID, filterBean); + parserContext.getRegistry().registerBeanDefinition(DEFAULT_FORM_LOGIN_ENTRY_POINT_ID, + entryPointBuilder.getBeanDefinition()); + + return null; + } + + private BeanDefinition createFilterBean(Element elt) { + BeanDefinitionBuilder filterBuilder = + BeanDefinitionBuilder.rootBeanDefinition(AuthenticationProcessingFilter.class); + + String loginUrl = elt.getAttribute(LOGIN_URL_ATTRIBUTE); + + if (StringUtils.hasText(loginUrl)) { + filterBuilder.addPropertyValue("filterProcessesUrl", loginUrl); + } + + String defaultTargetUrl = elt.getAttribute(FORM_LOGIN_TARGET_URL_ATTRIBUTE); + + if (!StringUtils.hasText(defaultTargetUrl)) { + defaultTargetUrl = DEFAULT_FORM_LOGIN_TARGET_URL; + } + + filterBuilder.addPropertyValue("defaultTargetUrl", defaultTargetUrl); + + String authenticationFailureUrl = elt.getAttribute(FORM_LOGIN_AUTH_FAILURE_URL_ATTRIBUTE); + + if (!StringUtils.hasText(authenticationFailureUrl)) { + authenticationFailureUrl = DEFAULT_FORM_LOGIN_AUTH_FAILURE_URL; + } + + filterBuilder.addPropertyValue("authenticationFailureUrl", authenticationFailureUrl); + // Set autowire to pick up the authentication manager. + filterBuilder.setAutowireMode(RootBeanDefinition.AUTOWIRE_BY_TYPE); + + return filterBuilder.getBeanDefinition(); + } +} diff --git a/core/src/main/java/org/springframework/security/config/HttpSecurityBeanDefinitionParser.java b/core/src/main/java/org/springframework/security/config/HttpSecurityBeanDefinitionParser.java index df1922e4cb..3c69d07403 100644 --- a/core/src/main/java/org/springframework/security/config/HttpSecurityBeanDefinitionParser.java +++ b/core/src/main/java/org/springframework/security/config/HttpSecurityBeanDefinitionParser.java @@ -1,34 +1,32 @@ package org.springframework.security.config; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.beans.factory.xml.BeanDefinitionParser; -import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.util.xml.DomUtils; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; -import org.springframework.security.util.FilterChainProxy; -import org.springframework.security.intercept.web.PathBasedFilterInvocationDefinitionMap; -import org.springframework.security.intercept.web.FilterSecurityInterceptor; -import org.springframework.security.intercept.web.FilterInvocationDefinitionMap; -import org.springframework.security.ConfigAttributeEditor; +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.ConfigAttributeDefinition; -import org.springframework.security.ui.ExceptionTranslationFilter; -import org.springframework.security.ui.webapp.AuthenticationProcessingFilter; -import org.springframework.security.ui.webapp.AuthenticationProcessingFilterEntryPoint; +import org.springframework.security.ConfigAttributeEditor; import org.springframework.security.context.HttpSessionContextIntegrationFilter; +import org.springframework.security.intercept.web.*; +import org.springframework.security.ui.ExceptionTranslationFilter; +import org.springframework.security.util.FilterChainProxy; +import org.springframework.security.util.RegexUrlPathMatcher; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.springframework.util.xml.DomUtils; import org.w3c.dom.Element; -import java.util.List; +import javax.servlet.Filter; import java.util.Iterator; +import java.util.List; /** * Sets up HTTP security: filter stack and protected URLs. * * - * @author luke + * @author Luke Taylor * @version $Id$ */ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { @@ -39,39 +37,23 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { public static final String DEFAULT_LOGOUT_FILTER_ID = "_logoutFilter"; public static final String DEFAULT_EXCEPTION_TRANSLATION_FILTER_ID = "_exceptionTranslationFilter"; public static final String DEFAULT_FILTER_SECURITY_INTERCEPTOR_ID = "_filterSecurityInterceptor"; - public static final String DEFAULT_FORM_LOGIN_FILTER_ID = "_formLoginFilter"; - public static final String DEFAULT_FORM_LOGIN_ENTRY_POINT_ID = "_formLoginEntryPoint"; public static final String LOGOUT_ELEMENT = "logout"; public static final String FORM_LOGIN_ELEMENT = "form-login"; + public static final String BASIC_AUTH_ELEMENT = "http-basic"; - private static final String PATH_ATTRIBUTE = "path"; - private static final String FILTERS_ATTRIBUTE = "filters"; - private static final String ACCESS_CONFIG_ATTRIBUTE = "access"; - - private static final String LOGIN_URL_ATTRIBUTE = "loginUrl"; + static final String PATH_PATTERN_ATTRIBUTE = "pattern"; + static final String PATTERN_TYPE_ATTRIBUTE = "pathType"; + static final String PATTERN_TYPE_REGEX = "regex"; - private static final String FORM_LOGIN_TARGET_URL_ATTRIBUTE = "defaultTargetUrl"; - private static final String DEFAULT_FORM_LOGIN_TARGET_URL = "/index"; + static final String FILTERS_ATTRIBUTE = "filters"; + static final String NO_FILTERS_VALUE = "none"; + static final Filter[] EMPTY_FILTER_CHAIN = new Filter[0]; - private static final String FORM_LOGIN_AUTH_FAILURE_URL_ATTRIBUTE = "defaultTargetUrl"; - // TODO: Change AbstractProcessingFilter to not need a failure URL and just write a failure message - // to the response if one isn't set. - private static final String DEFAULT_FORM_LOGIN_AUTH_FAILURE_URL = "/loginError"; + private static final String ACCESS_CONFIG_ATTRIBUTE = "access"; public BeanDefinition parse(Element element, ParserContext parserContext) { - // Create HttpSCIF, FilterInvocationInterceptor, ExceptionTranslationFilter - - // Find other filter beans. - - // Create appropriate bean list for config attributes to create FIDS - - // Add any secure URLs with specific filter chains to FIDS as separate ConfigAttributes - - // Add secure URLS with roles to objectDefinitionSource for FilterSecurityInterceptor - RootBeanDefinition filterChainProxy = new RootBeanDefinition(FilterChainProxy.class); - RootBeanDefinition httpSCIF = new RootBeanDefinition(HttpSessionContextIntegrationFilter.class); //TODO: Set session creation parameters based on session-creation attribute @@ -79,22 +61,28 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { BeanDefinitionBuilder filterSecurityInterceptorBuilder = BeanDefinitionBuilder.rootBeanDefinition(FilterSecurityInterceptor.class); - BeanDefinitionBuilder exceptionTranslationFilterBuilder = BeanDefinitionBuilder.rootBeanDefinition(ExceptionTranslationFilter.class); // Autowire for entry point (for now) + // TODO: Examine entry point beans in post processing and pick the correct one + // i.e. form login or cas if defined, then any other non-basic, non-digest, then basic or digest exceptionTranslationFilterBuilder.setAutowireMode(RootBeanDefinition.AUTOWIRE_BY_TYPE); // TODO: Get path type attribute and determine FilDefInvS class - PathBasedFilterInvocationDefinitionMap filterChainInvocationDefSource - = new PathBasedFilterInvocationDefinitionMap(); - filterChainProxy.getPropertyValues().addPropertyValue("filterInvocationDefinitionSource", - filterChainInvocationDefSource); + FilterChainMap filterChainMap = new FilterChainMap(); + + String patternType = element.getAttribute(PATTERN_TYPE_ATTRIBUTE); + + FilterInvocationDefinitionMap interceptorFilterInvDefSource = new PathBasedFilterInvocationDefinitionMap(); + + if (patternType.equals(PATTERN_TYPE_REGEX)) { + filterChainMap.setUrlPathMatcher(new RegexUrlPathMatcher()); + interceptorFilterInvDefSource = new RegExpBasedFilterInvocationDefinitionMap(); + } - PathBasedFilterInvocationDefinitionMap interceptorFilterInvDefSource - = new PathBasedFilterInvocationDefinitionMap(); + filterChainProxy.getPropertyValues().addPropertyValue("filterChainMap", filterChainMap); filterSecurityInterceptorBuilder.addPropertyValue("objectDefinitionSource", interceptorFilterInvDefSource); @@ -102,7 +90,7 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { filterSecurityInterceptorBuilder.setAutowireMode(RootBeanDefinition.AUTOWIRE_BY_TYPE); parseInterceptUrls(DomUtils.getChildElementsByTagName(element, "intercept-url"), - filterChainInvocationDefSource, interceptorFilterInvDefSource); + filterChainMap, interceptorFilterInvDefSource); // TODO: if empty, set a default set a default /**, omitting login url BeanDefinitionRegistry registry = parserContext.getRegistry(); @@ -110,51 +98,20 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { Element logoutElt = DomUtils.getChildElementByTagName(element, LOGOUT_ELEMENT); if (logoutElt != null) { - BeanDefinition logoutFilter = new LogoutBeanDefinitionParser().parse(logoutElt, parserContext); + new LogoutBeanDefinitionParser().parse(logoutElt, parserContext); } Element formLoginElt = DomUtils.getChildElementByTagName(element, FORM_LOGIN_ELEMENT); if (formLoginElt != null) { - BeanDefinitionBuilder formLoginFilterBuilder = - BeanDefinitionBuilder.rootBeanDefinition(AuthenticationProcessingFilter.class); - BeanDefinitionBuilder formLoginEntryPointBuilder = - BeanDefinitionBuilder.rootBeanDefinition(AuthenticationProcessingFilterEntryPoint.class); - - // Temporary login value - formLoginEntryPointBuilder.addPropertyValue("loginFormUrl", "/login"); - - - String loginUrl = formLoginElt.getAttribute(LOGIN_URL_ATTRIBUTE); - - if (StringUtils.hasText(loginUrl)) { - formLoginFilterBuilder.addPropertyValue("filterProcessesUrl", loginUrl); - } - - String defaultTargetUrl = formLoginElt.getAttribute(FORM_LOGIN_TARGET_URL_ATTRIBUTE); - - if (!StringUtils.hasText(defaultTargetUrl)) { - defaultTargetUrl = DEFAULT_FORM_LOGIN_TARGET_URL; - } - - formLoginFilterBuilder.addPropertyValue("defaultTargetUrl", defaultTargetUrl); - - String authenticationFailureUrl = formLoginElt.getAttribute(FORM_LOGIN_AUTH_FAILURE_URL_ATTRIBUTE); - - if (!StringUtils.hasText(authenticationFailureUrl)) { - authenticationFailureUrl = DEFAULT_FORM_LOGIN_AUTH_FAILURE_URL; - } - - formLoginFilterBuilder.addPropertyValue("authenticationFailureUrl", authenticationFailureUrl); - // Set autowire to pick up the authentication manager. - formLoginFilterBuilder.setAutowireMode(RootBeanDefinition.AUTOWIRE_BY_TYPE); + new FormLoginBeanDefinitionParser().parse(formLoginElt, parserContext); + } + Element basicAuthElt = DomUtils.getChildElementByTagName(element, BASIC_AUTH_ELEMENT); - registry.registerBeanDefinition(DEFAULT_FORM_LOGIN_FILTER_ID, - formLoginFilterBuilder.getBeanDefinition()); - registry.registerBeanDefinition(DEFAULT_FORM_LOGIN_ENTRY_POINT_ID, - formLoginEntryPointBuilder.getBeanDefinition()); - } + if (basicAuthElt != null) { + new BasicAuthenticationBeanDefinitionParser().parse(basicAuthElt, parserContext); + } registry.registerBeanDefinition(DEFAULT_FILTER_CHAIN_PROXY_ID, filterChainProxy); registry.registerBeanDefinition(DEFAULT_HTTP_SESSION_FILTER_ID, httpSCIF); @@ -168,15 +125,16 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { // app context has been created and all beans are available. registry.registerBeanDefinition("__httpConfigBeanFactoryPostProcessor", - new RootBeanDefinition(HttpSecurityConfigPostProcessor.class)); + new RootBeanDefinition(HttpSecurityConfigPostProcessor.class)); return null; } /** - * Parses the intercept-url elements and populates the FilterChainProxy's FilterInvocationDefinitionSource + * Parses the intercept-url elements and populates the FilterChainProxy's FilterChainMap and the + * FilterInvocationDefinitionSource used in FilterSecurityInterceptor. */ - private void parseInterceptUrls(List urlElts, FilterInvocationDefinitionMap filterChainInvocationDefSource, + private void parseInterceptUrls(List urlElts, FilterChainMap filterChainMap, FilterInvocationDefinitionMap interceptorFilterInvDefSource) { Iterator urlEltsIterator = urlElts.iterator(); @@ -186,7 +144,7 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { while (urlEltsIterator.hasNext()) { Element urlElt = (Element) urlEltsIterator.next(); - String path = urlElt.getAttribute(PATH_ATTRIBUTE); + String path = urlElt.getAttribute(PATH_PATTERN_ATTRIBUTE); Assert.hasText(path, "path attribute cannot be empty or null"); @@ -204,11 +162,12 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { String filters = urlElt.getAttribute(FILTERS_ATTRIBUTE); if (StringUtils.hasText(filters)) { - attributeEditor.setAsText(filters); - } - - + if (!filters.equals(NO_FILTERS_VALUE)) { + throw new IllegalStateException("Currently only 'none' is supported as the custom filters attribute"); + } + filterChainMap.addSecureUrl(path, EMPTY_FILTER_CHAIN); + } } } } diff --git a/core/src/main/java/org/springframework/security/config/HttpSecurityConfigPostProcessor.java b/core/src/main/java/org/springframework/security/config/HttpSecurityConfigPostProcessor.java index fe2a09dc07..01cee6494b 100644 --- a/core/src/main/java/org/springframework/security/config/HttpSecurityConfigPostProcessor.java +++ b/core/src/main/java/org/springframework/security/config/HttpSecurityConfigPostProcessor.java @@ -1,15 +1,22 @@ package org.springframework.security.config; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.BeansException; -import org.springframework.security.util.FilterChainProxy; -import org.springframework.security.context.HttpSessionContextIntegrationFilter; +import org.springframework.core.OrderComparator; +import org.springframework.core.Ordered; import org.springframework.security.AuthenticationManager; +import org.springframework.security.context.HttpSessionContextIntegrationFilter; +import org.springframework.security.intercept.web.FilterChainMap; +import org.springframework.security.ui.AuthenticationEntryPoint; +import org.springframework.security.util.FilterChainProxy; import org.springframework.util.Assert; import javax.servlet.Filter; -import java.util.Map; +import java.util.*; /** * Responsible for tying up the HTTP security configuration - building ordered filter stack and linking up @@ -18,36 +25,96 @@ import java.util.Map; * @author Luke Taylor * @version $Id$ */ -public class HttpSecurityConfigPostProcessor implements BeanFactoryPostProcessor { - public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { - FilterChainProxy filterChainProxy = - (FilterChainProxy) beanFactory.getBean(HttpSecurityBeanDefinitionParser.DEFAULT_FILTER_CHAIN_PROXY_ID); +public class HttpSecurityConfigPostProcessor implements BeanFactoryPostProcessor, Ordered { + private Log logger = LogFactory.getLog(getClass()); + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { HttpSessionContextIntegrationFilter httpSCIF = (HttpSessionContextIntegrationFilter) beanFactory.getBean(HttpSecurityBeanDefinitionParser.DEFAULT_HTTP_SESSION_FILTER_ID); - AuthenticationManager authManager = (AuthenticationManager) getBeanOfType(AuthenticationManager.class, beanFactory); + configureAuthenticationEntryPoint(beanFactory); - // - Map filters = beanFactory.getBeansOfType(Filter.class); + configureFilterChain(beanFactory); + } + /** + * Selects the entry point that should be used in ExceptionTranslationFilter. Strategy is + * + *