diff --git a/core/src/main/java/org/springframework/security/config/FormLoginBeanDefinitionParser.java b/core/src/main/java/org/springframework/security/config/FormLoginBeanDefinitionParser.java index f44746b007..9053a68dd8 100644 --- a/core/src/main/java/org/springframework/security/config/FormLoginBeanDefinitionParser.java +++ b/core/src/main/java/org/springframework/security/config/FormLoginBeanDefinitionParser.java @@ -24,7 +24,6 @@ public class FormLoginBeanDefinitionParser implements BeanDefinitionParser { protected final Log logger = LogFactory.getLog(getClass()); static final String ATT_LOGIN_URL = "login-processing-url"; - static final String DEF_LOGIN_URL = "/j_spring_security_check"; static final String ATT_LOGIN_PAGE = "login-page"; static final String DEF_LOGIN_PAGE = DefaultLoginPageGeneratingFilter.DEFAULT_LOGIN_PAGE_URL; @@ -35,11 +34,23 @@ public class FormLoginBeanDefinitionParser implements BeanDefinitionParser { static final String ATT_FORM_LOGIN_AUTHENTICATION_FAILURE_URL = "authentication-failure-url"; static final String DEF_FORM_LOGIN_AUTHENTICATION_FAILURE_URL = DefaultLoginPageGeneratingFilter.DEFAULT_LOGIN_PAGE_URL + "?" + DefaultLoginPageGeneratingFilter.ERROR_PARAMETER_NAME; + String defaultLoginProcessingUrl; + String filterClassName; + + RootBeanDefinition filterBean; + RootBeanDefinition entryPointBean; + String loginPage; + + FormLoginBeanDefinitionParser(String defaultLoginProcessingUrl, String filterClassName) { + this.defaultLoginProcessingUrl = defaultLoginProcessingUrl; + this.filterClassName = filterClassName; + } + public BeanDefinition parse(Element elt, ParserContext parserContext) { String loginUrl = null; String defaultTargetUrl = null; String authenticationFailureUrl = null; - String loginPage = null; + Object source = null; if (elt != null) { @@ -52,7 +63,7 @@ public class FormLoginBeanDefinitionParser implements BeanDefinitionParser { ConfigUtils.registerProviderManagerIfNecessary(parserContext); - RootBeanDefinition filterBean = createFilterBean(loginUrl, defaultTargetUrl, loginPage, authenticationFailureUrl); + filterBean = createFilterBean(loginUrl, defaultTargetUrl, loginPage, authenticationFailureUrl); filterBean.setSource(source); filterBean.getPropertyValues().addPropertyValue("authenticationManager", @@ -62,34 +73,18 @@ public class FormLoginBeanDefinitionParser implements BeanDefinitionParser { BeanDefinitionBuilder.rootBeanDefinition(AuthenticationProcessingFilterEntryPoint.class); entryPointBuilder.setSource(source); + entryPointBuilder.addPropertyValue("loginFormUrl", StringUtils.hasText(loginPage) ? loginPage : DEF_LOGIN_PAGE); - // 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 = DEF_LOGIN_PAGE; - RootBeanDefinition loginPageFilter = new RootBeanDefinition(DefaultLoginPageGeneratingFilter.class); - loginPageFilter.getConstructorArgumentValues().addGenericArgumentValue( - new RuntimeBeanReference(BeanIds.FORM_LOGIN_FILTER)); - parserContext.getRegistry().registerBeanDefinition(BeanIds.DEFAULT_LOGIN_PAGE_GENERATING_FILTER, loginPageFilter); - } - - entryPointBuilder.addPropertyValue("loginFormUrl", loginPage); - - parserContext.getRegistry().registerBeanDefinition(BeanIds.FORM_LOGIN_FILTER, filterBean); - parserContext.getRegistry().registerBeanDefinition(BeanIds.FORM_LOGIN_ENTRY_POINT, - entryPointBuilder.getBeanDefinition()); + entryPointBean = (RootBeanDefinition) entryPointBuilder.getBeanDefinition(); return null; } private RootBeanDefinition createFilterBean(String loginUrl, String defaultTargetUrl, String loginPage, String authenticationFailureUrl) { - BeanDefinitionBuilder filterBuilder = - BeanDefinitionBuilder.rootBeanDefinition(AuthenticationProcessingFilter.class); - + BeanDefinitionBuilder filterBuilder = BeanDefinitionBuilder.rootBeanDefinition(filterClassName); if (!StringUtils.hasText(loginUrl)) { - loginUrl = DEF_LOGIN_URL; + loginUrl = defaultLoginProcessingUrl; } filterBuilder.addPropertyValue("filterProcessesUrl", loginUrl); @@ -114,4 +109,16 @@ public class FormLoginBeanDefinitionParser implements BeanDefinitionParser { return (RootBeanDefinition) filterBuilder.getBeanDefinition(); } + + RootBeanDefinition getFilterBean() { + return filterBean; + } + + RootBeanDefinition getEntryPointBean() { + return entryPointBean; + } + + String getLoginPage() { + return loginPage; + } } 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 7ff70ad094..a578e21e09 100644 --- a/core/src/main/java/org/springframework/security/config/HttpSecurityBeanDefinitionParser.java +++ b/core/src/main/java/org/springframework/security/config/HttpSecurityBeanDefinitionParser.java @@ -6,6 +6,8 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.support.BeanDefinitionBuilder; @@ -28,6 +30,7 @@ import org.springframework.security.securechannel.SecureChannelProcessor; import org.springframework.security.securechannel.RetryWithHttpEntryPoint; import org.springframework.security.securechannel.RetryWithHttpsEntryPoint; import org.springframework.security.ui.ExceptionTranslationFilter; +import org.springframework.security.ui.webapp.DefaultLoginPageGeneratingFilter; import org.springframework.security.util.FilterChainProxy; import org.springframework.security.util.RegexUrlPathMatcher; import org.springframework.security.util.AntUrlPathMatcher; @@ -44,6 +47,7 @@ import org.w3c.dom.Element; * @version $Id$ */ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { + protected final Log logger = LogFactory.getLog(getClass()); static final String ATT_REALM = "realm"; static final String DEF_REALM = "Spring Security Application"; @@ -190,11 +194,6 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { registry.registerBeanDefinition(BeanIds.CHANNEL_DECISION_MANAGER, channelDecisionManager); } - String realm = element.getAttribute(ATT_REALM); - if (!StringUtils.hasText(realm)) { - realm = DEF_REALM; - } - Element sessionControlElt = DomUtils.getChildElementByTagName(element, Elements.CONCURRENT_SESSIONS); if (sessionControlElt != null) { new ConcurrentSessionsBeanDefinitionParser().parse(sessionControlElt, parserContext); @@ -220,16 +219,8 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { if (logoutElt != null || autoConfig) { new LogoutBeanDefinitionParser().parse(logoutElt, parserContext); } - - Element formLoginElt = DomUtils.getChildElementByTagName(element, Elements.FORM_LOGIN); - if (formLoginElt != null || autoConfig) { - new FormLoginBeanDefinitionParser().parse(formLoginElt, parserContext); - } - - Element basicAuthElt = DomUtils.getChildElementByTagName(element, Elements.BASIC_AUTH); - if (basicAuthElt != null || autoConfig) { - new BasicAuthenticationBeanDefinitionParser(realm).parse(basicAuthElt, parserContext); - } + + parseBasicFormLoginAndOpenID(element, parserContext, autoConfig); Element x509Elt = DomUtils.getChildElementByTagName(element, Elements.X509); if (x509Elt != null) { @@ -248,6 +239,104 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { return null; } + private void parseBasicFormLoginAndOpenID(Element element, ParserContext parserContext, boolean autoConfig) { + RootBeanDefinition formLoginFilter = null; + RootBeanDefinition formLoginEntryPoint = null; + String formLoginPage = null; + RootBeanDefinition openIDFilter = null; + RootBeanDefinition openIDEntryPoint = null; + String openIDLoginPage = null; + + String realm = element.getAttribute(ATT_REALM); + if (!StringUtils.hasText(realm)) { + realm = DEF_REALM; + } + + Element basicAuthElt = DomUtils.getChildElementByTagName(element, Elements.BASIC_AUTH); + if (basicAuthElt != null || autoConfig) { + new BasicAuthenticationBeanDefinitionParser(realm).parse(basicAuthElt, parserContext); + } + + Element formLoginElt = DomUtils.getChildElementByTagName(element, Elements.FORM_LOGIN); + + if (formLoginElt != null || autoConfig) { + FormLoginBeanDefinitionParser parser = new FormLoginBeanDefinitionParser("/j_spring_security_check", + "org.springframework.security.ui.webapp.AuthenticationProcessingFilter"); + + parser.parse(formLoginElt, parserContext); + formLoginFilter = parser.getFilterBean(); + formLoginEntryPoint = parser.getEntryPointBean(); + formLoginPage = parser.getLoginPage(); + } + + Element openIDLoginElt = DomUtils.getChildElementByTagName(element, Elements.OPENID_LOGIN); + + if (openIDLoginElt != null) { + FormLoginBeanDefinitionParser parser = new FormLoginBeanDefinitionParser("/j_spring_openid_security_check", + "org.springframework.security.ui.openid.OpenIDAuthenticationProcessingFilter"); + + parser.parse(openIDLoginElt, parserContext); + openIDFilter = parser.getFilterBean(); + openIDEntryPoint = parser.getEntryPointBean(); + openIDLoginPage = parser.getLoginPage(); + } + + if (formLoginFilter == null && openIDFilter == null) { + return; + } + + if (formLoginFilter != null) { + parserContext.getRegistry().registerBeanDefinition(BeanIds.FORM_LOGIN_FILTER, formLoginFilter); + parserContext.getRegistry().registerBeanDefinition(BeanIds.FORM_LOGIN_ENTRY_POINT, formLoginEntryPoint); + } + + if (openIDFilter != null) { + parserContext.getRegistry().registerBeanDefinition(BeanIds.OPEN_ID_FILTER, openIDFilter); + parserContext.getRegistry().registerBeanDefinition(BeanIds.OPEN_ID_ENTRY_POINT, openIDEntryPoint); + } + + // If no login page has been defined, add in the default page generator. + if (formLoginPage == null && openIDLoginPage == null) { + logger.info("No login page configured. The default internal one will be used. Use the '" + + FormLoginBeanDefinitionParser.ATT_LOGIN_PAGE + "' attribute to set the URL of the login page."); + BeanDefinitionBuilder loginPageFilter = + BeanDefinitionBuilder.rootBeanDefinition(DefaultLoginPageGeneratingFilter.class); + + if (formLoginFilter != null) { + loginPageFilter.addConstructorArg(new RuntimeBeanReference(BeanIds.FORM_LOGIN_FILTER)); + } + + if (openIDFilter != null) { + loginPageFilter.addConstructorArg(new RuntimeBeanReference(BeanIds.OPEN_ID_FILTER)); + } + + parserContext.getRegistry().registerBeanDefinition(BeanIds.DEFAULT_LOGIN_PAGE_GENERATING_FILTER, + loginPageFilter.getBeanDefinition()); + } + + // We need to establish the main entry point. + // Basic takes precedence if explicit element is used and no others are configured + if (basicAuthElt != null && formLoginElt == null && openIDLoginElt == null) { + parserContext.getRegistry().registerAlias(BeanIds.BASIC_AUTHENTICATION_ENTRY_POINT, BeanIds.MAIN_ENTRY_POINT); + return; + } + + // If formLogin has been enabled either through an element or auto-config, then it is used if no openID login page + // has been set + if (formLoginFilter != null && openIDLoginPage == null) { + parserContext.getRegistry().registerAlias(BeanIds.FORM_LOGIN_ENTRY_POINT, BeanIds.MAIN_ENTRY_POINT); + return; + } + + // Otherwise use OpenID + if (openIDFilter != null && formLoginFilter == null) { + parserContext.getRegistry().registerAlias(BeanIds.OPEN_ID_ENTRY_POINT, BeanIds.MAIN_ENTRY_POINT); + return; + } + + throw new IllegalStateException("Couldn't set entry point"); + } + static UrlMatcher createUrlMatcher(Element element) { String patternType = element.getAttribute(ATT_PATH_TYPE); if (!StringUtils.hasText(patternType)) { diff --git a/core/src/main/java/org/springframework/security/ui/webapp/DefaultLoginPageGeneratingFilter.java b/core/src/main/java/org/springframework/security/ui/webapp/DefaultLoginPageGeneratingFilter.java index ffa9eb1270..d3af5d9973 100644 --- a/core/src/main/java/org/springframework/security/ui/webapp/DefaultLoginPageGeneratingFilter.java +++ b/core/src/main/java/org/springframework/security/ui/webapp/DefaultLoginPageGeneratingFilter.java @@ -8,6 +8,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; +import org.springframework.beans.BeanWrapperImpl; import org.springframework.security.AuthenticationException; import org.springframework.security.ui.AbstractProcessingFilter; import org.springframework.security.ui.FilterChainOrder; @@ -26,21 +27,52 @@ import org.springframework.security.ui.rememberme.AbstractRememberMeServices; public class DefaultLoginPageGeneratingFilter extends SpringSecurityFilter { public static final String DEFAULT_LOGIN_PAGE_URL = "/spring_security_login"; public static final String ERROR_PARAMETER_NAME = "login_error"; + boolean formLoginEnabled; + boolean openIdEnabled; 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 AbstractRememberMeServices) { - rememberMeParameter = ((AbstractRememberMeServices)authFilter.getRememberMeServices()).getParameter(); - } + private String openIDauthenticationUrl; + private String openIDusernameParameter; + private String openIDrememberMeParameter; + + public DefaultLoginPageGeneratingFilter(AbstractProcessingFilter filter) { + if (filter instanceof AuthenticationProcessingFilter) { + init((AuthenticationProcessingFilter)filter, null); + } else { + init(null, filter); + } } + + public DefaultLoginPageGeneratingFilter(AuthenticationProcessingFilter authFilter, AbstractProcessingFilter openIDFilter) { + init(authFilter, openIDFilter); + } + + private void init(AuthenticationProcessingFilter authFilter, AbstractProcessingFilter openIDFilter) { + if (authFilter != null) { + formLoginEnabled = true; + authenticationUrl = authFilter.getDefaultFilterProcessesUrl(); + usernameParameter = authFilter.getUsernameParameter(); + passwordParameter = authFilter.getPasswordParameter(); + + if (authFilter.getRememberMeServices() instanceof AbstractRememberMeServices) { + rememberMeParameter = ((AbstractRememberMeServices)authFilter.getRememberMeServices()).getParameter(); + } + } + + if (openIDFilter != null) { + openIdEnabled = true; + openIDauthenticationUrl = openIDFilter.getAuthenticationFailureUrl(); + openIDusernameParameter = (String) (new BeanWrapperImpl(openIDFilter)).getPropertyValue("claimedIdentityFieldName"); + if (openIDFilter.getRememberMeServices() instanceof AbstractRememberMeServices) { + openIDrememberMeParameter = ((AbstractRememberMeServices)openIDFilter.getRememberMeServices()).getParameter(); + } + } + } + + protected void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { if (isLoginUrlRequest(request)) { response.getOutputStream().print(generateLoginPageHtml(request)); @@ -68,24 +100,59 @@ public class DefaultLoginPageGeneratingFilter extends SpringSecurityFilter { } } } - - return "Login Page\n" + - (loginError ? ("Your login attempt was not successful, try again.

Reason: " + - errorMsg + "
") : "") + - "
\n" + - " \n" + - " \n" + - " \n" + - - (rememberMeParameter == null ? "" : - " \n" - ) + - " \n" + - " \n" + - "
User:
Password:
Remember me on this computer.
\n" + - "
"; + + StringBuffer sb = new StringBuffer(); + + sb.append("Login Page"); + + if (formLoginEnabled) { + sb.append("\n"); + } + + if (loginError) { + sb.append("

Your login attempt was not successful, try again.

Reason: "); + sb.append(errorMsg); + sb.append("

"); + } + + if (formLoginEnabled) { + sb.append("

Login with Username and Password

"); + sb.append("
\n"); + sb.append(" \n"); + sb.append(" \n"); + sb.append(" \n"); + + if (rememberMeParameter != null) { + sb.append(" \n"); + } + + sb.append(" \n"); + sb.append(" \n"); + sb.append("
User:
Password:
Remember me on this computer.
\n"); + sb.append("
"); + } + + if(openIdEnabled) { + sb.append("

Login with OpenID Identity

"); + sb.append("
\n"); + sb.append(" \n"); + sb.append(" \n"); + + if (rememberMeParameter != null) { + sb.append(" \n"); + } + + sb.append(" \n"); + sb.append(" \n"); + sb.append("
Identity:
Remember me on this computer.
\n"); + sb.append("
"); + } + + sb.append(""); + + return sb.toString(); } public int getOrder() { diff --git a/core/src/main/resources/org/springframework/security/config/spring-security-2.0.rnc b/core/src/main/resources/org/springframework/security/config/spring-security-2.0.rnc index 08b7e00f90..715e37e4fa 100644 --- a/core/src/main/resources/org/springframework/security/config/spring-security-2.0.rnc +++ b/core/src/main/resources/org/springframework/security/config/spring-security-2.0.rnc @@ -223,7 +223,7 @@ logout.attlist &= attribute invalidate-session {"true" | "false"}? form-login = - ## Sets up a form login configuration + ## Sets up a form login configuration for authentication with a username and password element form-login {form-login.attlist, empty} form-login.attlist &= ## The URL that the login form is posted to. If unspecified, it defaults to /j_spring_security_check. @@ -238,6 +238,11 @@ form-login.attlist &= ## The URL for the login failure page. If no login failure URL is specified, Spring Security will automatically create a failure login URL at /spring_security_login?login_error and a corresponding filter to render that login failure URL when requested. attribute authentication-failure-url {xsd:string}? +openid-login = + ## Sets up form login for authentication with an Open ID identity + element openid-login {form-login.attlist, empty} + + filter-chain-map = ## Used to explicitly configure a FilterChainProxy instance with a FilterChainMap element filter-chain-map {filter-chain-map.attlist, filter-chain+} diff --git a/core/src/main/resources/org/springframework/security/config/spring-security-2.0.xsd b/core/src/main/resources/org/springframework/security/config/spring-security-2.0.xsd index adaf6f15a4..c837a78912 100644 --- a/core/src/main/resources/org/springframework/security/config/spring-security-2.0.xsd +++ b/core/src/main/resources/org/springframework/security/config/spring-security-2.0.xsd @@ -4,6 +4,7 @@ targetNamespace="http://www.springframework.org/schema/security"> + Defines the hashing algorithm used on user passwords. We recommend @@ -474,7 +475,8 @@ - Sets up a form login configuration + Sets up a form login configuration for authentication with + a username and password @@ -743,6 +745,14 @@ + + + Sets up form login for authentication with an Open ID identity + + + + + Used to explicitly configure a FilterChainProxy instance with a