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:
Luke Taylor 2007-10-20 23:17:01 +00:00
parent f0d8db5ce6
commit cffd3131f0
7 changed files with 446 additions and 121 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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