Răsfoiți Sursa

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 ani în urmă
părinte
comite
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