First version of web.xml to acegi translator

This commit is contained in:
Luke Taylor 2005-06-26 17:30:36 +00:00
parent f58cdb7c49
commit 25fa471779
4 changed files with 479 additions and 0 deletions

View File

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

View File

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

View File

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

View File

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