SEC-722: Add Open ID Namespace Support

http://jira.springframework.org/browse/SEC-722. Added element to namespace and modified form login parser to handle open id element. Also added openID support to login page generator.
This commit is contained in:
Luke Taylor 2008-03-20 20:05:11 +00:00
parent b62ad5b097
commit 815f04b6c3
5 changed files with 245 additions and 67 deletions

View File

@ -24,7 +24,6 @@ public class FormLoginBeanDefinitionParser implements BeanDefinitionParser {
protected final Log logger = LogFactory.getLog(getClass()); protected final Log logger = LogFactory.getLog(getClass());
static final String ATT_LOGIN_URL = "login-processing-url"; 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 ATT_LOGIN_PAGE = "login-page";
static final String DEF_LOGIN_PAGE = DefaultLoginPageGeneratingFilter.DEFAULT_LOGIN_PAGE_URL; 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 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; 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) { public BeanDefinition parse(Element elt, ParserContext parserContext) {
String loginUrl = null; String loginUrl = null;
String defaultTargetUrl = null; String defaultTargetUrl = null;
String authenticationFailureUrl = null; String authenticationFailureUrl = null;
String loginPage = null;
Object source = null; Object source = null;
if (elt != null) { if (elt != null) {
@ -52,7 +63,7 @@ public class FormLoginBeanDefinitionParser implements BeanDefinitionParser {
ConfigUtils.registerProviderManagerIfNecessary(parserContext); ConfigUtils.registerProviderManagerIfNecessary(parserContext);
RootBeanDefinition filterBean = createFilterBean(loginUrl, defaultTargetUrl, loginPage, authenticationFailureUrl); filterBean = createFilterBean(loginUrl, defaultTargetUrl, loginPage, authenticationFailureUrl);
filterBean.setSource(source); filterBean.setSource(source);
filterBean.getPropertyValues().addPropertyValue("authenticationManager", filterBean.getPropertyValues().addPropertyValue("authenticationManager",
@ -62,34 +73,18 @@ public class FormLoginBeanDefinitionParser implements BeanDefinitionParser {
BeanDefinitionBuilder.rootBeanDefinition(AuthenticationProcessingFilterEntryPoint.class); BeanDefinitionBuilder.rootBeanDefinition(AuthenticationProcessingFilterEntryPoint.class);
entryPointBuilder.setSource(source); 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. entryPointBean = (RootBeanDefinition) entryPointBuilder.getBeanDefinition();
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());
return null; return null;
} }
private RootBeanDefinition createFilterBean(String loginUrl, String defaultTargetUrl, String loginPage, String authenticationFailureUrl) { private RootBeanDefinition createFilterBean(String loginUrl, String defaultTargetUrl, String loginPage, String authenticationFailureUrl) {
BeanDefinitionBuilder filterBuilder = BeanDefinitionBuilder filterBuilder = BeanDefinitionBuilder.rootBeanDefinition(filterClassName);
BeanDefinitionBuilder.rootBeanDefinition(AuthenticationProcessingFilter.class);
if (!StringUtils.hasText(loginUrl)) { if (!StringUtils.hasText(loginUrl)) {
loginUrl = DEF_LOGIN_URL; loginUrl = defaultLoginProcessingUrl;
} }
filterBuilder.addPropertyValue("filterProcessesUrl", loginUrl); filterBuilder.addPropertyValue("filterProcessesUrl", loginUrl);
@ -114,4 +109,16 @@ public class FormLoginBeanDefinitionParser implements BeanDefinitionParser {
return (RootBeanDefinition) filterBuilder.getBeanDefinition(); return (RootBeanDefinition) filterBuilder.getBeanDefinition();
} }
RootBeanDefinition getFilterBean() {
return filterBean;
}
RootBeanDefinition getEntryPointBean() {
return entryPointBean;
}
String getLoginPage() {
return loginPage;
}
} }

View File

@ -6,6 +6,8 @@ import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; 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.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionBuilder; 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.RetryWithHttpEntryPoint;
import org.springframework.security.securechannel.RetryWithHttpsEntryPoint; import org.springframework.security.securechannel.RetryWithHttpsEntryPoint;
import org.springframework.security.ui.ExceptionTranslationFilter; import org.springframework.security.ui.ExceptionTranslationFilter;
import org.springframework.security.ui.webapp.DefaultLoginPageGeneratingFilter;
import org.springframework.security.util.FilterChainProxy; import org.springframework.security.util.FilterChainProxy;
import org.springframework.security.util.RegexUrlPathMatcher; import org.springframework.security.util.RegexUrlPathMatcher;
import org.springframework.security.util.AntUrlPathMatcher; import org.springframework.security.util.AntUrlPathMatcher;
@ -44,6 +47,7 @@ import org.w3c.dom.Element;
* @version $Id$ * @version $Id$
*/ */
public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
protected final Log logger = LogFactory.getLog(getClass());
static final String ATT_REALM = "realm"; static final String ATT_REALM = "realm";
static final String DEF_REALM = "Spring Security Application"; static final String DEF_REALM = "Spring Security Application";
@ -190,11 +194,6 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
registry.registerBeanDefinition(BeanIds.CHANNEL_DECISION_MANAGER, channelDecisionManager); 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); Element sessionControlElt = DomUtils.getChildElementByTagName(element, Elements.CONCURRENT_SESSIONS);
if (sessionControlElt != null) { if (sessionControlElt != null) {
new ConcurrentSessionsBeanDefinitionParser().parse(sessionControlElt, parserContext); new ConcurrentSessionsBeanDefinitionParser().parse(sessionControlElt, parserContext);
@ -220,16 +219,8 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
if (logoutElt != null || autoConfig) { if (logoutElt != null || autoConfig) {
new LogoutBeanDefinitionParser().parse(logoutElt, parserContext); new LogoutBeanDefinitionParser().parse(logoutElt, parserContext);
} }
Element formLoginElt = DomUtils.getChildElementByTagName(element, Elements.FORM_LOGIN); parseBasicFormLoginAndOpenID(element, parserContext, autoConfig);
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);
}
Element x509Elt = DomUtils.getChildElementByTagName(element, Elements.X509); Element x509Elt = DomUtils.getChildElementByTagName(element, Elements.X509);
if (x509Elt != null) { if (x509Elt != null) {
@ -248,6 +239,104 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
return null; 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) { static UrlMatcher createUrlMatcher(Element element) {
String patternType = element.getAttribute(ATT_PATH_TYPE); String patternType = element.getAttribute(ATT_PATH_TYPE);
if (!StringUtils.hasText(patternType)) { if (!StringUtils.hasText(patternType)) {

View File

@ -8,6 +8,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSession;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.security.AuthenticationException; import org.springframework.security.AuthenticationException;
import org.springframework.security.ui.AbstractProcessingFilter; import org.springframework.security.ui.AbstractProcessingFilter;
import org.springframework.security.ui.FilterChainOrder; import org.springframework.security.ui.FilterChainOrder;
@ -26,21 +27,52 @@ import org.springframework.security.ui.rememberme.AbstractRememberMeServices;
public class DefaultLoginPageGeneratingFilter extends SpringSecurityFilter { public class DefaultLoginPageGeneratingFilter extends SpringSecurityFilter {
public static final String DEFAULT_LOGIN_PAGE_URL = "/spring_security_login"; public static final String DEFAULT_LOGIN_PAGE_URL = "/spring_security_login";
public static final String ERROR_PARAMETER_NAME = "login_error"; public static final String ERROR_PARAMETER_NAME = "login_error";
boolean formLoginEnabled;
boolean openIdEnabled;
private String authenticationUrl; private String authenticationUrl;
private String usernameParameter; private String usernameParameter;
private String passwordParameter; private String passwordParameter;
private String rememberMeParameter; private String rememberMeParameter;
private String openIDauthenticationUrl;
public DefaultLoginPageGeneratingFilter(AuthenticationProcessingFilter authFilter) { private String openIDusernameParameter;
authenticationUrl = authFilter.getDefaultFilterProcessesUrl(); private String openIDrememberMeParameter;
usernameParameter = authFilter.getUsernameParameter();
passwordParameter = authFilter.getPasswordParameter(); public DefaultLoginPageGeneratingFilter(AbstractProcessingFilter filter) {
if (filter instanceof AuthenticationProcessingFilter) {
if (authFilter.getRememberMeServices() instanceof AbstractRememberMeServices) { init((AuthenticationProcessingFilter)filter, null);
rememberMeParameter = ((AbstractRememberMeServices)authFilter.getRememberMeServices()).getParameter(); } 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 { protected void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
if (isLoginUrlRequest(request)) { if (isLoginUrlRequest(request)) {
response.getOutputStream().print(generateLoginPageHtml(request)); response.getOutputStream().print(generateLoginPageHtml(request));
@ -68,24 +100,59 @@ public class DefaultLoginPageGeneratingFilter extends SpringSecurityFilter {
} }
} }
} }
return "<html><head><title>Login Page</title></head><body onload='document.f.j_username.focus();'>\n" + StringBuffer sb = new StringBuffer();
(loginError ? ("<font color='red'>Your login attempt was not successful, try again.<br/><br/>Reason: " +
errorMsg + "</font>") : "") + sb.append("<html><head><title>Login Page</title></head>");
" <form name='f' action='" + request.getContextPath() + authenticationUrl + "' method='POST'>\n" +
" <table>\n" + if (formLoginEnabled) {
" <tr><td>User:</td><td><input type='text' name='" + usernameParameter + "' value='" + lastUser + sb.append("<body onload='document.f.").append(usernameParameter).append(".focus();'>\n");
"'></td></tr>\n" + }
" <tr><td>Password:</td><td><input type='password' name='"+ passwordParameter +"'></td></tr>\n" +
if (loginError) {
(rememberMeParameter == null ? "" : sb.append("<p><font color='red'>Your login attempt was not successful, try again.<br/><br/>Reason: ");
" <tr><td><input type='checkbox' name='"+ rememberMeParameter + sb.append(errorMsg);
"'></td><td>Remember me on this computer.</td></tr>\n" sb.append("</font></p>");
) + }
" <tr><td colspan='2'><input name=\"submit\" type=\"submit\"></td></tr>\n" +
" <tr><td colspan='2'><input name=\"reset\" type=\"reset\"></td></tr>\n" + if (formLoginEnabled) {
" </table>\n" + sb.append("<h3>Login with Username and Password</h3>");
" </form></body></html>"; sb.append("<form name='f' action='").append(request.getContextPath()).append(authenticationUrl).append("' method='POST'>\n");
sb.append(" <table>\n");
sb.append(" <tr><td>User:</td><td><input type='text' name='");
sb.append(usernameParameter).append("' value='").append(lastUser).append("'></td></tr>\n");
sb.append(" <tr><td>Password:</td><td><input type='password' name='").append(passwordParameter).append("'/></td></tr>\n");
if (rememberMeParameter != null) {
sb.append(" <tr><td><input type='checkbox' name='").append(rememberMeParameter).append("'/></td><td>Remember me on this computer.</td></tr>\n");
}
sb.append(" <tr><td colspan='2'><input name=\"submit\" type=\"submit\"/></td></tr>\n");
sb.append(" <tr><td colspan='2'><input name=\"reset\" type=\"reset\"/></td></tr>\n");
sb.append(" </table>\n");
sb.append("</form>");
}
if(openIdEnabled) {
sb.append("<h3>Login with OpenID Identity</h3>");
sb.append("<form name='oidf' action='").append(request.getContextPath()).append(openIDauthenticationUrl).append("' method='POST'>\n");
sb.append(" <table>\n");
sb.append(" <tr><td>Identity:</td><td><input type='text' name='");
sb.append(openIDusernameParameter).append("'/></td></tr>\n");
if (rememberMeParameter != null) {
sb.append(" <tr><td><input type='checkbox' name='").append(openIDrememberMeParameter).append("'></td><td>Remember me on this computer.</td></tr>\n");
}
sb.append(" <tr><td colspan='2'><input name=\"submit\" type=\"submit\"/></td></tr>\n");
sb.append(" <tr><td colspan='2'><input name=\"reset\" type=\"reset\"/></td></tr>\n");
sb.append(" </table>\n");
sb.append("</form>");
}
sb.append("</body></html>");
return sb.toString();
} }
public int getOrder() { public int getOrder() {

View File

@ -223,7 +223,7 @@ logout.attlist &=
attribute invalidate-session {"true" | "false"}? attribute invalidate-session {"true" | "false"}?
form-login = 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} element form-login {form-login.attlist, empty}
form-login.attlist &= form-login.attlist &=
## The URL that the login form is posted to. If unspecified, it defaults to /j_spring_security_check. ## 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. ## 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}? 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 = filter-chain-map =
## Used to explicitly configure a FilterChainProxy instance with a FilterChainMap ## Used to explicitly configure a FilterChainProxy instance with a FilterChainMap
element filter-chain-map {filter-chain-map.attlist, filter-chain+} element filter-chain-map {filter-chain-map.attlist, filter-chain+}

View File

@ -4,6 +4,7 @@
targetNamespace="http://www.springframework.org/schema/security"> targetNamespace="http://www.springframework.org/schema/security">
<xs:attributeGroup name="hash"> <xs:attributeGroup name="hash">
<xs:attribute name="hash" use="required"> <xs:attribute name="hash" use="required">
<xs:annotation> <xs:annotation>
<xs:documentation>Defines the hashing algorithm used on user passwords. We recommend <xs:documentation>Defines the hashing algorithm used on user passwords. We recommend
@ -474,7 +475,8 @@
</xs:element> </xs:element>
<xs:element name="form-login"> <xs:element name="form-login">
<xs:annotation> <xs:annotation>
<xs:documentation>Sets up a form login configuration</xs:documentation> <xs:documentation>Sets up a form login configuration for authentication with
a username and password</xs:documentation>
</xs:annotation> </xs:annotation>
<xs:complexType> <xs:complexType>
<xs:attributeGroup ref="security:form-login.attlist"/> <xs:attributeGroup ref="security:form-login.attlist"/>
@ -743,6 +745,14 @@
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
</xs:attributeGroup> </xs:attributeGroup>
<xs:element name="openid-login">
<xs:annotation>
<xs:documentation>Sets up form login for authentication with an Open ID identity</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attributeGroup ref="security:form-login.attlist"/>
</xs:complexType>
</xs:element>
<xs:element name="filter-chain-map"> <xs:element name="filter-chain-map">
<xs:annotation> <xs:annotation>
<xs:documentation>Used to explicitly configure a FilterChainProxy instance with a <xs:documentation>Used to explicitly configure a FilterChainProxy instance with a