Added building of filter chain in post-processing, support for basic authentication and automatic generation of login page, if no loginUrl supplied.
This commit is contained in:
parent
f0d8db5ce6
commit
cffd3131f0
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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.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.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.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.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.context.HttpSessionContextIntegrationFilter;
|
||||
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";
|
||||
|
||||
static final String PATH_PATTERN_ATTRIBUTE = "pattern";
|
||||
static final String PATTERN_TYPE_ATTRIBUTE = "pathType";
|
||||
static final String PATTERN_TYPE_REGEX = "regex";
|
||||
|
||||
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 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";
|
||||
|
||||
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 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();
|
||||
|
||||
PathBasedFilterInvocationDefinitionMap interceptorFilterInvDefSource
|
||||
= new PathBasedFilterInvocationDefinitionMap();
|
||||
String patternType = element.getAttribute(PATTERN_TYPE_ATTRIBUTE);
|
||||
|
||||
FilterInvocationDefinitionMap interceptorFilterInvDefSource = new PathBasedFilterInvocationDefinitionMap();
|
||||
|
||||
if (patternType.equals(PATTERN_TYPE_REGEX)) {
|
||||
filterChainMap.setUrlPathMatcher(new RegexUrlPathMatcher());
|
||||
interceptorFilterInvDefSource = new RegExpBasedFilterInvocationDefinitionMap();
|
||||
}
|
||||
|
||||
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,52 +98,21 @@ 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);
|
||||
|
||||
|
||||
registry.registerBeanDefinition(DEFAULT_FORM_LOGIN_FILTER_ID,
|
||||
formLoginFilterBuilder.getBeanDefinition());
|
||||
registry.registerBeanDefinition(DEFAULT_FORM_LOGIN_ENTRY_POINT_ID,
|
||||
formLoginEntryPointBuilder.getBeanDefinition());
|
||||
new FormLoginBeanDefinitionParser().parse(formLoginElt, parserContext);
|
||||
}
|
||||
|
||||
Element basicAuthElt = DomUtils.getChildElementByTagName(element, BASIC_AUTH_ELEMENT);
|
||||
|
||||
if (basicAuthElt != null) {
|
||||
new BasicAuthenticationBeanDefinitionParser().parse(basicAuthElt, parserContext);
|
||||
}
|
||||
|
||||
registry.registerBeanDefinition(DEFAULT_FILTER_CHAIN_PROXY_ID, filterChainProxy);
|
||||
registry.registerBeanDefinition(DEFAULT_HTTP_SESSION_FILTER_ID, httpSCIF);
|
||||
registry.registerBeanDefinition(DEFAULT_EXCEPTION_TRANSLATION_FILTER_ID,
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
*
|
||||
* <ol>
|
||||
* <li>If only one use that.</li>
|
||||
* <li>If more than one, check the default interactive login Ids in order of preference</li>
|
||||
* <li>throw an exception (for now). TODO: Examine additional beans and types and make decision</li>
|
||||
* </ol>
|
||||
*
|
||||
*
|
||||
* @param beanFactory
|
||||
*/
|
||||
private void configureAuthenticationEntryPoint(ConfigurableListableBeanFactory beanFactory) {
|
||||
logger.info("Selecting AuthenticationEntryPoint for use in ExceptionTranslationFilter");
|
||||
|
||||
BeanDefinition etf =
|
||||
beanFactory.getBeanDefinition(HttpSecurityBeanDefinitionParser.DEFAULT_EXCEPTION_TRANSLATION_FILTER_ID);
|
||||
Map entryPointMap = beanFactory.getBeansOfType(AuthenticationEntryPoint.class);
|
||||
List entryPoints = new ArrayList(entryPointMap.values());
|
||||
|
||||
Assert.isTrue(entryPoints.size() > 0, "No AuthenticationEntryPoint instances defined");
|
||||
|
||||
AuthenticationEntryPoint mainEntryPoint = (AuthenticationEntryPoint)
|
||||
entryPointMap.get(FormLoginBeanDefinitionParser.DEFAULT_FORM_LOGIN_ENTRY_POINT_ID);
|
||||
|
||||
if (mainEntryPoint == null) {
|
||||
throw new SecurityConfigurationException("Failed to resolve authentication entry point");
|
||||
}
|
||||
|
||||
logger.info("Main AuthenticationEntryPoint set to " + mainEntryPoint);
|
||||
|
||||
etf.getPropertyValues().addPropertyValue("authenticationEntryPoint", mainEntryPoint);
|
||||
}
|
||||
|
||||
private void configureFilterChain(ConfigurableListableBeanFactory beanFactory) {
|
||||
FilterChainProxy filterChainProxy =
|
||||
(FilterChainProxy) beanFactory.getBean(HttpSecurityBeanDefinitionParser.DEFAULT_FILTER_CHAIN_PROXY_ID);
|
||||
// Set the default match
|
||||
List defaultFilterChain = orderFilters(beanFactory);
|
||||
|
||||
FilterChainMap filterMap = filterChainProxy.getFilterChainMap();
|
||||
|
||||
String allUrlsMatch = filterMap.getMatcher().getUniversalMatchPattern();
|
||||
|
||||
filterMap.addSecureUrl(allUrlsMatch, (Filter[]) defaultFilterChain.toArray(new Filter[0]));
|
||||
}
|
||||
|
||||
private List orderFilters(ConfigurableListableBeanFactory beanFactory) {
|
||||
Map filters = beanFactory.getBeansOfType(Filter.class);
|
||||
|
||||
Assert.notEmpty(filters, "No filters found in app context!");
|
||||
|
||||
Iterator ids = filters.keySet().iterator();
|
||||
|
||||
List orderedFilters = new ArrayList();
|
||||
|
||||
while (ids.hasNext()) {
|
||||
String id = (String) ids.next();
|
||||
Filter filter = (Filter) filters.get(id);
|
||||
|
||||
if (filter instanceof FilterChainProxy) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!(filter instanceof Ordered)) {
|
||||
// TODO: Possibly log this as a warning and skip this filter.
|
||||
throw new IllegalArgumentException("Filter " + id + " must implement the Ordered interface");
|
||||
}
|
||||
|
||||
orderedFilters.add(filter);
|
||||
}
|
||||
|
||||
Collections.sort(orderedFilters, new OrderComparator());
|
||||
|
||||
return orderedFilters;
|
||||
}
|
||||
|
||||
private Object getBeanOfType(Class clazz, ConfigurableListableBeanFactory beanFactory) {
|
||||
Map beans = beanFactory.getBeansOfType(clazz);
|
||||
|
@ -56,4 +123,8 @@ public class HttpSecurityConfigPostProcessor implements BeanFactoryPostProcessor
|
|||
|
||||
return beans.values().toArray()[0];
|
||||
}
|
||||
|
||||
public int getOrder() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
package org.springframework.security.ui.webapp;
|
||||
|
||||
import org.springframework.security.AuthenticationException;
|
||||
import org.springframework.security.ui.AbstractProcessingFilter;
|
||||
import org.springframework.security.ui.FilterChainOrderUtils;
|
||||
import org.springframework.security.ui.SpringSecurityFilter;
|
||||
import org.springframework.security.ui.rememberme.TokenBasedRememberMeServices;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* For internal use with namespace configuration in the case where a user doesn't configure a login page.
|
||||
* The configuration code will insert this filter in the chain instead.
|
||||
*
|
||||
* Will only work if a redirect is used to the login page.
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
*/
|
||||
public class DefaultLoginPageGeneratingFilter extends SpringSecurityFilter {
|
||||
public static final String DEFAULT_LOGIN_PAGE_URL = "/login";
|
||||
private String authenticationUrl;
|
||||
private String usernameParameter;
|
||||
private String passwordParameter;
|
||||
private String rememberMeParameter;
|
||||
|
||||
public DefaultLoginPageGeneratingFilter(AuthenticationProcessingFilter authFilter) {
|
||||
authenticationUrl = authFilter.getDefaultFilterProcessesUrl();
|
||||
usernameParameter = authFilter.getUsernameParameter();
|
||||
passwordParameter = authFilter.getPasswordParameter();
|
||||
|
||||
if (authFilter.getRememberMeServices() instanceof TokenBasedRememberMeServices) {
|
||||
rememberMeParameter = ((TokenBasedRememberMeServices)authFilter.getRememberMeServices()).getParameter();
|
||||
}
|
||||
}
|
||||
|
||||
protected void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
|
||||
if (isLoginUrlRequest(request)) {
|
||||
response.getOutputStream().print(generateLoginPageHtml(request));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
|
||||
private String generateLoginPageHtml(HttpServletRequest request) {
|
||||
boolean loginError = StringUtils.hasText(request.getParameter("login_error"));
|
||||
String errorMsg = "none";
|
||||
String lastUser = "";
|
||||
|
||||
if (loginError) {
|
||||
HttpSession session = request.getSession(false);
|
||||
|
||||
if(session != null) {
|
||||
errorMsg = ((AuthenticationException)
|
||||
session.getAttribute(AbstractProcessingFilter.SPRING_SECURITY_LAST_EXCEPTION_KEY)).getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
return "<html><head><title>Login Page</title></head><body>\n" +
|
||||
(loginError ? ("<font color='red'>Your login attempt was not successful, try again.<br/><br/>Reason: " +
|
||||
errorMsg + "</font>") : "") +
|
||||
" <form action='" + request.getContextPath() + authenticationUrl + "' method='POST'>\n" +
|
||||
" <table>\n" +
|
||||
" <tr><td>User:</td><td><input type='text' name='" + usernameParameter + "' value='" + lastUser +
|
||||
"'></td></tr>\n" +
|
||||
" <tr><td>Password:</td><td><input type='password' name='"+ passwordParameter +"'></td></tr>\n" +
|
||||
|
||||
(rememberMeParameter == null ? "" :
|
||||
" <tr><td><input type='checkbox' name='"+ rememberMeParameter +
|
||||
"'></td><td>Remember me on this computer.</td></tr>\n"
|
||||
) +
|
||||
" <tr><td colspan='2'><input name=\"submit\" type=\"submit\"></td></tr>\n" +
|
||||
" <tr><td colspan='2'><input name=\"reset\" type=\"reset\"></td></tr>\n" +
|
||||
" </table>\n" +
|
||||
" </form></body></html>";
|
||||
}
|
||||
|
||||
public int getOrder() {
|
||||
return FilterChainOrderUtils.LOGIN_PAGE_FILTER_ORDER;
|
||||
}
|
||||
|
||||
private boolean isLoginUrlRequest(HttpServletRequest request) {
|
||||
String uri = request.getRequestURI();
|
||||
int pathParamIndex = uri.indexOf(';');
|
||||
|
||||
if (pathParamIndex > 0) {
|
||||
// strip everything after the first semi-colon
|
||||
uri = uri.substring(0, pathParamIndex);
|
||||
}
|
||||
|
||||
if ("".equals(request.getContextPath())) {
|
||||
return uri.endsWith(DEFAULT_LOGIN_PAGE_URL);
|
||||
}
|
||||
|
||||
return uri.endsWith(request.getContextPath() + DEFAULT_LOGIN_PAGE_URL);
|
||||
}
|
||||
}
|
|
@ -1,8 +1,21 @@
|
|||
package org.springframework.security.config;
|
||||
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
import org.junit.AfterClass;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
import org.springframework.security.context.HttpSessionContextIntegrationFilter;
|
||||
import org.springframework.security.intercept.web.FilterChainMap;
|
||||
import org.springframework.security.intercept.web.FilterSecurityInterceptor;
|
||||
import org.springframework.security.ui.ExceptionTranslationFilter;
|
||||
import org.springframework.security.ui.basicauth.BasicProcessingFilter;
|
||||
import org.springframework.security.ui.logout.LogoutFilter;
|
||||
import org.springframework.security.ui.webapp.AuthenticationProcessingFilter;
|
||||
import org.springframework.security.ui.webapp.DefaultLoginPageGeneratingFilter;
|
||||
import org.springframework.security.util.FilterChainProxy;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
|
||||
/**
|
||||
* @author Luke Taylor
|
||||
|
@ -16,8 +29,44 @@ public class HttpSecurityBeanDefinitionParserTests {
|
|||
appContext = new ClassPathXmlApplicationContext("org/springframework/security/config/http-security.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContextContainsExpectedBeansAndData() {
|
||||
@AfterClass
|
||||
public static void closeAppContext() {
|
||||
if (appContext != null) {
|
||||
appContext.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void filterChainProxyShouldReturnEmptyFilterListForUnprotectedUrl() {
|
||||
FilterChainProxy filterChainProxy =
|
||||
(FilterChainProxy) appContext.getBean(HttpSecurityBeanDefinitionParser.DEFAULT_FILTER_CHAIN_PROXY_ID);
|
||||
|
||||
FilterChainMap filterChainMap = filterChainProxy.getFilterChainMap();
|
||||
|
||||
Filter[] filters = filterChainMap.getFilters("/unprotected");
|
||||
|
||||
assertTrue(filters.length == 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void filterChainProxyShouldReturnCorrectFilterListForProtectedUrl() {
|
||||
FilterChainProxy filterChainProxy =
|
||||
(FilterChainProxy) appContext.getBean(HttpSecurityBeanDefinitionParser.DEFAULT_FILTER_CHAIN_PROXY_ID);
|
||||
|
||||
FilterChainMap filterChainMap = filterChainProxy.getFilterChainMap();
|
||||
|
||||
Filter[] filters = filterChainMap.getFilters("/someurl");
|
||||
|
||||
|
||||
|
||||
assertTrue("Expected 7 filters in chain", filters.length == 7);
|
||||
|
||||
assertTrue(filters[0] instanceof HttpSessionContextIntegrationFilter);
|
||||
assertTrue(filters[1] instanceof LogoutFilter);
|
||||
assertTrue(filters[2] instanceof AuthenticationProcessingFilter);
|
||||
assertTrue(filters[3] instanceof DefaultLoginPageGeneratingFilter);
|
||||
assertTrue(filters[4] instanceof BasicProcessingFilter);
|
||||
assertTrue(filters[5] instanceof ExceptionTranslationFilter);
|
||||
assertTrue(filters[6] instanceof FilterSecurityInterceptor);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,20 +6,22 @@
|
|||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
|
||||
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.0.xsd">
|
||||
|
||||
<security:autoconfig />
|
||||
<security:autoconfig />
|
||||
|
||||
<security:http createSession="ifRequired" pathType="ant" lowerCaseComparisons="true">
|
||||
<security:intercept-url path="/unprotected" filters="none"/>
|
||||
<security:intercept-url path="/somepath" filters="filter1,filter2,filter3" access="ROLE_SPECIAL,ROLE_USER" />
|
||||
<security:intercept-url path="/**" access="ROLE_USER" />
|
||||
<security:intercept-url pattern="/unprotected" filters="none"/>
|
||||
<security:intercept-url pattern="/somepath" access="ROLE_SPECIAL,ROLE_USER" />
|
||||
<security:intercept-url pattern="/**" access="ROLE_USER" />
|
||||
|
||||
<!-- Default logout configuration -->
|
||||
<security:logout logoutUrl="/j_spring_security_logout" logoutSuccessUrl="/" invalidateSession="true" />
|
||||
|
||||
<!-- Default form login configuration. Will create filter and entry point -->
|
||||
<security:form-login loginUrl="/j_spring_security_check" />
|
||||
|
||||
<!-- Default logout configuration -->
|
||||
<security:logout logoutUrl="/j_spring_security_logout" logoutSuccessUrl="/" invalidateSession="true" />
|
||||
|
||||
</security:http>
|
||||
<!-- Default basic auth configuration. Will create filter and entry point -->
|
||||
<security:http-basic realm="NamespaceTestRealm" />
|
||||
</security:http>
|
||||
|
||||
|
||||
<security:authentication-provider>
|
||||
|
@ -27,6 +29,6 @@ http://www.springframework.org/schema/security http://www.springframework.org/sc
|
|||
<security:user name="bob" password="bobspassword" authorities="ROLE_A,ROLE_B" />
|
||||
<security:user name="bill" password="billspassword" authorities="ROLE_A,ROLE_B,AUTH_OTHER" />
|
||||
</security:user-service>
|
||||
</security:authentication-provider>
|
||||
</security:authentication-provider>
|
||||
|
||||
</beans>
|
Loading…
Reference in New Issue