소스 검색

First version of web.xml to acegi translator

Luke Taylor 20 년 전
부모
커밋
25fa471779

+ 108 - 0
core/src/main/java/org/acegisecurity/util/WebXmlSecurityToSpringBeansTranslator.java

@@ -0,0 +1,108 @@
+package net.sf.acegisecurity.util;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.DOMImplementation;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.support.DefaultListableBeanFactory;
+import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
+import org.xml.sax.SAXException;
+
+import javax.xml.transform.stream.StreamSource;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.Source;
+import javax.xml.transform.Result;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.dom.DOMResult;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.ParserConfigurationException;
+import java.io.IOException;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+
+
+/**
+ * A utility to translate a web.xml file into a set of
+ * acegi security spring beans.
+ *
+ * <p>
+ * This class wraps the XSL transform which actually does
+ * most of the work. It also tests the result by
+ * loading it into a Spring bean factory.
+ * </p>
+ *
+ * @author Luke Taylor
+ * @version $Id$
+ */
+public class WebXmlSecurityToSpringBeansTranslator {
+    private String webToSpringXsltFile = "web-to-spring.xsl";
+    private String outputFileName = "applicationContext-acegi-security.xml";
+    private Transformer transformer, identityTransformer;
+    private DefaultListableBeanFactory beanFactory;
+    DocumentBuilderFactory dbf;
+
+    public WebXmlSecurityToSpringBeansTranslator() throws Exception {
+        ClassPathResource resource = new ClassPathResource(webToSpringXsltFile);
+        Source xsltSource = new StreamSource(resource.getInputStream());
+        dbf = DocumentBuilderFactory.newInstance();
+        dbf.setNamespaceAware(true);
+        TransformerFactory tf = TransformerFactory.newInstance();
+        transformer = tf.newTransformer(xsltSource);
+        identityTransformer = tf.newTransformer();
+        identityTransformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, "-//SPRING//DTD BEAN//EN");
+        identityTransformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, "http://www.springframework.org/dtd/spring-beans.dtd");
+    }
+
+    public void translate(InputStream in) throws TransformerException, IOException, ParserConfigurationException, SAXException {
+        DocumentBuilder db = dbf.newDocumentBuilder();
+        Document d = db.parse(in);
+        translate(d);
+    }
+
+    /**
+     * Converts the web.xml supplied as a DOM Node
+     *
+     * @param webXml the web application xml
+     */
+    public void translate(Node webXml) throws IOException, TransformerException, ParserConfigurationException {
+        Source xmlSource = new DOMSource(webXml);
+        DOMResult domResult = new DOMResult();
+
+        transformer.transform(xmlSource, domResult);
+
+        // Obtain DOM for additional manipulation here.
+        Node document = domResult.getNode();
+
+        // Tranform DOM with identity transform to get the output file
+        Result streamResult = new StreamResult(new FileOutputStream(outputFileName));
+        xmlSource = new DOMSource(document);
+        identityTransformer.transform(xmlSource, streamResult);
+        beanFactory = new DefaultListableBeanFactory();
+        XmlBeanDefinitionReader beanReader = new XmlBeanDefinitionReader(beanFactory);
+        beanReader.loadBeanDefinitions(new FileSystemResource(outputFileName));
+    }
+
+    public String getOutputFileName() {
+        return outputFileName;
+    }
+
+    public void setOutputFileName(String outputFileName) {
+        this.outputFileName = outputFileName;
+    }
+
+    /**
+     * Mainly intended for testing
+     * @return the bean factory built from the created acegi security application context file
+     *
+     */
+    public BeanFactory getBeanFactory() {
+        return beanFactory;
+    }
+}

+ 258 - 0
core/src/main/resources/web-to-spring.xsl

@@ -0,0 +1,258 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- 
+ | XSL Sheet used by the web.xml to acegi-security beans converter
+ | $Id$
+ -->
+
+
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+
+<xsl:output doctype-public="-//SPRING//DTD BEAN//EN"
+            doctype-system="http://www.springframework.org/dtd/spring-beans.dtd"
+            indent="yes"/>
+
+<xsl:variable name="lowercase" select="'abcdefghijklmnopqrstuvwxyz'"/>
+<xsl:variable name="uppercase" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"/>    
+    
+<xsl:variable name="welcome-files" select="web-app/welcome-file-list/welcome-file"/>
+<!-- convert the auth-method content to upper case -->
+<xsl:variable name="auth-method" select="translate(string(web-app/login-config/auth-method), $lowercase, $uppercase)"/>
+
+<xsl:variable name="all-roles">
+    <xsl:for-each select="web-app/security-role/role-name">
+        <xsl:text>ROLE_</xsl:text>
+        <xsl:value-of select="translate(string(), $lowercase, $uppercase)"/>           
+        <xsl:if test="position() != last()">,</xsl:if>
+    </xsl:for-each>    
+</xsl:variable>
+
+<!-- The list of filters for use in filterToBeanProxy -->
+<xsl:variable name="filter-list">
+<xsl:text>/**=httpSessionContextIntegrationFilter</xsl:text>
+<xsl:choose>
+    <xsl:when test="$auth-method = 'FORM'">
+        <xsl:text>,authenticationProcessingFilter</xsl:text>
+    </xsl:when>
+    <xsl:when test="$auth-method = 'BASIC'">
+        <xsl:text>,basicProcessingFilter</xsl:text>
+    </xsl:when>
+    <xsl:otherwise>
+        <xsl:message terminate="yes">Unsupported auth-method in web.xml, must be FORM or BASIC</xsl:message>
+    </xsl:otherwise>
+</xsl:choose>
+<xsl:text>,rememberMeProcessingFilter,anonymousProcessingFilter,securityEnforcementFilter</xsl:text>
+</xsl:variable>
+    
+    
+    
+<xsl:template match = "web-app">
+
+<beans>
+    <xsl:call-template name="filter-to-bean-proxy"/>
+    <xsl:call-template name="authentication-beans"/>
+    
+    <xsl:apply-templates select="./login-config"/>
+    <xsl:call-template name="filter-invocation-interceptor"/>
+</beans>
+</xsl:template>
+
+<xsl:template name="authentication-beans">
+    <xsl:comment>======================== AUTHENTICATION =======================</xsl:comment>
+    
+    <bean id="authenticationManager" class="net.sf.acegisecurity.providers.ProviderManager">
+      <property name="providers">
+         <list>
+            <ref local="daoAuthenticationProvider"/>
+            <ref local="anonymousAuthenticationProvider"/>
+             <ref local="rememberMeAuthenticationProvider"/>
+         </list>
+      </property>
+    </bean>
+   
+    <bean id="daoAuthenticationProvider" class="net.sf.acegisecurity.providers.dao.DaoAuthenticationProvider">
+      <property name="authenticationDao"><ref local="inMemoryDaoImpl"/></property>
+      <!-- property name="userCache"><ref local="userCache"/></property-->
+    </bean>
+
+    <bean id="inMemoryDaoImpl" class="net.sf.acegisecurity.providers.dao.memory.InMemoryDaoImpl">
+        <property name="userMap">
+            <value>    
+              superuser=password,<xsl:value-of select="$all-roles"/>
+              <xsl:text>&#xA;</xsl:text>
+            </value>
+        </property>
+    </bean>
+    
+    <bean id="anonymousProcessingFilter" class="net.sf.acegisecurity.providers.anonymous.AnonymousProcessingFilter">
+      <property name="key"><value>foobar</value></property>
+      <property name="userAttribute"><value>anonymousUser,ROLE_ANONYMOUS</value></property>
+    </bean>
+    
+    <bean id="anonymousAuthenticationProvider" class="net.sf.acegisecurity.providers.anonymous.AnonymousAuthenticationProvider">
+      <property name="key"><value>foobar</value></property>
+    </bean>
+    
+    <bean id="httpSessionContextIntegrationFilter" class="net.sf.acegisecurity.context.HttpSessionContextIntegrationFilter">
+    </bean>
+    
+    <bean id="rememberMeProcessingFilter" class="net.sf.acegisecurity.ui.rememberme.RememberMeProcessingFilter">
+      <property name="rememberMeServices"><ref local="rememberMeServices"/></property>
+    </bean>
+    
+    <bean id="rememberMeServices" class="net.sf.acegisecurity.ui.rememberme.TokenBasedRememberMeServices">
+      <property name="authenticationDao"><ref local="inMemoryDaoImpl"/></property>
+      <property name="key"><value>springRocks</value></property>
+    </bean>
+    
+    <bean id="rememberMeAuthenticationProvider" class="net.sf.acegisecurity.providers.rememberme.RememberMeAuthenticationProvider">
+      <property name="key"><value>springRocks</value></property>
+    </bean>    
+</xsl:template>
+
+<!-- login configuration -->
+<xsl:template match="login-config">
+    <xsl:call-template name="security-enforcement-filter"/>
+    <xsl:choose>
+        <xsl:when test="$auth-method = 'FORM'">
+            <xsl:call-template name="form-login"/>
+        </xsl:when>
+        <xsl:when test="$auth-method = 'BASIC'">
+   <bean id="basicProcessingFilter" class="net.sf.acegisecurity.ui.basicauth.BasicProcessingFilter">
+      <property name="authenticationManager"><ref local="authenticationManager"/></property>
+      <property name="authenticationEntryPoint"><ref local="basicProcessingFilterEntryPoint"/></property>
+   </bean>
+
+   <bean id="basicProcessingFilterEntryPoint" class="net.sf.acegisecurity.ui.basicauth.BasicProcessingFilterEntryPoint">
+      <property name="realmName"><value>Your Realm</value></property>
+   </bean>                
+        </xsl:when>
+    </xsl:choose>
+            
+</xsl:template>
+
+<!-- 
+ | Inserts the security enforcement filter bean with the appropriate entry point 
+ | (depending on whether FORM or BASIC authentication is selected in web.xml). 
+ -->    
+<xsl:template name="security-enforcement-filter">
+   <bean id="securityEnforcementFilter" class="net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter">
+      <property name="filterSecurityInterceptor"><ref local="filterInvocationInterceptor"/></property>
+      <property name="authenticationEntryPoint">
+    <xsl:choose>
+        <xsl:when test="$auth-method = 'FORM'">
+      <ref local="authenticationProcessingFilterEntryPoint"/>
+        </xsl:when>
+        <xsl:when test="$auth-method = 'BASIC'">
+      <ref local="basicProcessingFilterEntryPoint"/>              
+        </xsl:when>
+    </xsl:choose>
+      </property>
+   </bean>
+</xsl:template>
+    
+<!--
+ | Outputs a standard filterToBeanProxy bean.
+ -->
+<xsl:template name="filter-to-bean-proxy">
+    <xsl:comment>======================== FILTER CHAIN =======================</xsl:comment>
+
+	<xsl:comment>if you wish to use channel security, add "channelProcessingFilter," in front
+	      of "httpSessionContextIntegrationFilter" in the list below</xsl:comment>
+	<bean id="filterChainProxy" class="net.sf.acegisecurity.util.FilterChainProxy">
+      <property name="filterInvocationDefinitionSource">
+         <value>
+            CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
+            PATTERN_TYPE_APACHE_ANT
+            <xsl:value-of select="$filter-list"/>
+         </value>
+      </property>
+	</bean>
+
+</xsl:template>
+    
+<!-- 
+    Converts a form login configuration to an Acegi AuthenticationProcessingFilter and its entry point.
+    The content of the form-login-page element is used for the loginFormUrl property of the entry point 
+    and the form-error-page is used for the authenticationFailureUrl property of the filter.
+    
+    The user must manually change the form Url to "j_acegi_security_check"
+ -->
+    <xsl:template name="form-login">
+        <xsl:message>Processing form login configuration</xsl:message>
+        <xsl:message>Remember to switch your login form action from "j_security_check" to "j_acegi_security_check"</xsl:message>       
+        
+   <bean id="authenticationProcessingFilter" class="net.sf.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
+      <property name="authenticationManager"><ref bean="authenticationManager"/></property>
+      <property name="authenticationFailureUrl"><value><xsl:value-of select="form-login-config/form-error-page"/></value></property>
+      <property name="defaultTargetUrl"><value></value></property>
+      <property name="filterProcessesUrl"><value>/j_acegi_security_check</value></property>
+      <property name="rememberMeServices"><ref local="rememberMeServices"/></property>
+   </bean>
+
+   <bean id="authenticationProcessingFilterEntryPoint" class="net.sf.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
+      <property name="loginFormUrl"><value><xsl:value-of select="form-login-config/form-login-page"/></value></property>
+      <property name="forceHttps"><value>false</value></property>
+   </bean> 
+        
+    </xsl:template>
+    
+    <xsl:template name="filter-invocation-interceptor">
+        <bean id="httpRequestAccessDecisionManager" class="net.sf.acegisecurity.vote.AffirmativeBased">
+            <property name="allowIfAllAbstainDecisions"><value>false</value></property>
+            <property name="decisionVoters">
+                <list>
+                    <ref bean="roleVoter"/>
+                </list>
+            </property>
+        </bean>
+        
+       <!-- An access decision voter that reads ROLE_* configuration settings -->
+        <bean id="roleVoter" class="net.sf.acegisecurity.vote.RoleVoter"/>        
+        
+        <xsl:text>&#xA;</xsl:text>
+       <xsl:comment> 
+       Note the order that entries are placed against the objectDefinitionSource is critical.
+       The FilterSecurityInterceptor will work from the top of the list down to the FIRST pattern that matches the request URL.
+       Accordingly, you should place MOST SPECIFIC (ie a/b/c/d.*) expressions first, with LEAST SPECIFIC (ie a/.*) expressions last
+       </xsl:comment>
+        <bean id="filterInvocationInterceptor" class="net.sf.acegisecurity.intercept.web.FilterSecurityInterceptor">
+          <property name="authenticationManager"><ref bean="authenticationManager"/></property>
+          <property name="accessDecisionManager"><ref local="httpRequestAccessDecisionManager"/></property>
+          <property name="objectDefinitionSource">
+             <value>
+                <xsl:text>&#xA;CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON&#xA;</xsl:text>                 
+                <xsl:text>PATTERN_TYPE_APACHE_ANT&#xA;</xsl:text>
+                <xsl:apply-templates select="security-constraint"/>
+             </value>
+          </property>
+        </bean>        
+    </xsl:template>
+    
+    <xsl:template match="security-constraint">
+        <xsl:value-of select="web-resource-collection/url-pattern"/>
+        <xsl:text>=</xsl:text>
+        <xsl:for-each select="./auth-constraint/role-name">
+            <xsl:choose>
+                <xsl:when test="string() = '*'">
+                    <xsl:value-of select="$all-roles"/>
+                </xsl:when>
+                <xsl:otherwise>
+                    <xsl:text>ROLE_</xsl:text>
+                    <xsl:value-of select="translate(string(), $lowercase, $uppercase)"/>
+                </xsl:otherwise>
+            </xsl:choose>
+            <xsl:if test="position() != last()">,</xsl:if>
+        </xsl:for-each>
+        <xsl:text>&#xA;</xsl:text>
+    </xsl:template>
+
+    <xsl:template name="list-roles">
+        <xsl:for-each select="security-role/role-name">
+            <xsl:text>ROLE_</xsl:text>
+            <xsl:value-of select="translate(string(), $lowercase, $uppercase)"/>           
+            <xsl:if test="position() != last()">,</xsl:if>
+        </xsl:for-each>
+    </xsl:template>    
+    
+</xsl:stylesheet>

+ 59 - 0
core/src/test/java/org/acegisecurity/util/WebXmlSecurityToSpringBeansTranslatorTests.java

@@ -0,0 +1,59 @@
+package net.sf.acegisecurity.util;
+
+import junit.framework.TestCase;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+import org.springframework.beans.factory.BeanFactory;
+
+import net.sf.acegisecurity.providers.ProviderManager;
+import net.sf.acegisecurity.providers.dao.DaoAuthenticationProvider;
+import net.sf.acegisecurity.providers.dao.memory.InMemoryDaoImpl;
+import net.sf.acegisecurity.UserDetails;
+import net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter;
+
+import net.sf.acegisecurity.intercept.web.FilterSecurityInterceptor;
+
+/**
+ * Tests the WebXmlSecurityToSpringBeansTranslator by applying it
+ * to a test sample web.xml file.
+ *
+ * @author Luke Taylor
+ * @version $Id$
+ */
+public class WebXmlSecurityToSpringBeansTranslatorTests extends TestCase {
+
+    public void testFileTranslation() throws Exception {
+        WebXmlSecurityToSpringBeansTranslator t = new WebXmlSecurityToSpringBeansTranslator();
+
+        Resource r = new ClassPathResource("test-web.xml");
+        t.translate(r.getInputStream());
+
+        BeanFactory bf = t.getBeanFactory();
+        assertNotNull(bf.getBean("filterChainProxy"));
+
+        ProviderManager pm = (ProviderManager) bf.getBean("authenticationManager");
+        assertNotNull(pm);
+        assertEquals(3, pm.getProviders().size());
+
+        DaoAuthenticationProvider dap =
+                (DaoAuthenticationProvider) bf.getBean("daoAuthenticationProvider");
+        assertNotNull(dap);
+
+        InMemoryDaoImpl dao = (InMemoryDaoImpl) dap.getAuthenticationDao();
+        UserDetails user = dao.loadUserByUsername("superuser");
+        assertEquals("password",user.getPassword());
+        assertEquals(2, user.getAuthorities().length);
+        assertNotNull(bf.getBean("anonymousProcessingFilter"));
+        assertNotNull(bf.getBean("anonymousAuthenticationProvider"));
+        assertNotNull(bf.getBean("httpSessionContextIntegrationFilter"));
+        assertNotNull(bf.getBean("rememberMeProcessingFilter"));
+        assertNotNull(bf.getBean("rememberMeAuthenticationProvider"));
+
+        SecurityEnforcementFilter sef =
+                (SecurityEnforcementFilter) bf.getBean("securityEnforcementFilter");
+        assertNotNull(sef);
+        assertNotNull(sef.getAuthenticationEntryPoint());
+        FilterSecurityInterceptor fsi = sef.getFilterSecurityInterceptor();
+
+    }
+}

+ 54 - 0
core/src/test/resources/test-web.xml

@@ -0,0 +1,54 @@
+<web-app>
+  <display-name>login-xml</display-name>
+
+  <welcome-file-list>
+    <welcome-file>index.jsp</welcome-file>
+    <welcome-file>index.html</welcome-file>
+  </welcome-file-list>
+
+  <security-constraint>
+    <web-resource-collection>
+      <url-pattern>/home.jsp</url-pattern>
+    </web-resource-collection>
+    <auth-constraint>
+      <role-name>*</role-name>
+    </auth-constraint>
+  </security-constraint>
+
+  <security-constraint>
+    <web-resource-collection>
+      <url-pattern>/admin/*</url-pattern>
+    </web-resource-collection>
+    <auth-constraint>
+      <role-name>admin</role-name>
+    </auth-constraint>
+  </security-constraint>
+
+  <security-constraint>
+    <web-resource-collection>
+      <url-pattern>/user/*</url-pattern>
+    </web-resource-collection>
+    <auth-constraint>
+      <role-name>user</role-name>
+      <role-name>admin</role-name>
+    </auth-constraint>
+  </security-constraint>
+
+  <login-config>
+    <auth-method>form</auth-method>
+    <form-login-config>
+      <form-login-page>/login.jsp</form-login-page>
+      <form-error-page>/login.jsp?login_error=1</form-error-page>
+    </form-login-config>
+
+  </login-config>
+
+  <security-role>
+    <role-name>user</role-name>
+  </security-role>
+
+  <security-role>
+    <role-name>admin</role-name>
+  </security-role>
+
+</web-app>