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

View File

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

View File

@ -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 "<html><head><title>Login Page</title></head><body onload='document.f.j_username.focus();'>\n" +
(loginError ? ("<font color='red'>Your login attempt was not successful, try again.<br/><br/>Reason: " +
errorMsg + "</font>") : "") +
" <form name='f' 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>";
StringBuffer sb = new StringBuffer();
sb.append("<html><head><title>Login Page</title></head>");
if (formLoginEnabled) {
sb.append("<body onload='document.f.").append(usernameParameter).append(".focus();'>\n");
}
if (loginError) {
sb.append("<p><font color='red'>Your login attempt was not successful, try again.<br/><br/>Reason: ");
sb.append(errorMsg);
sb.append("</font></p>");
}
if (formLoginEnabled) {
sb.append("<h3>Login with Username and Password</h3>");
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() {

View File

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

View File

@ -4,6 +4,7 @@
targetNamespace="http://www.springframework.org/schema/security">
<xs:attributeGroup name="hash">
<xs:attribute name="hash" use="required">
<xs:annotation>
<xs:documentation>Defines the hashing algorithm used on user passwords. We recommend
@ -474,7 +475,8 @@
</xs:element>
<xs:element name="form-login">
<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:complexType>
<xs:attributeGroup ref="security:form-login.attlist"/>
@ -743,6 +745,14 @@
</xs:annotation>
</xs:attribute>
</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:annotation>
<xs:documentation>Used to explicitly configure a FilterChainProxy instance with a