Browse Source

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.

Luke Taylor 17 years ago
parent
commit
815f04b6c3

+ 30 - 23
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;
+	}
 }

+ 104 - 15
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)) {

+ 95 - 28
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 "<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() {

+ 6 - 1
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+}

+ 11 - 1
core/src/main/resources/org/springframework/security/config/spring-security-2.0.xsd

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