SEC-1094: Simplified WebXml attribute mapping. Removed generic jaxen-based implementation on which it was based in favour of simple DOM model traversal. Updated sample.

This commit is contained in:
Luke Taylor 2009-06-08 15:23:41 +00:00
parent aa511bb1f4
commit 5808da12ff
9 changed files with 167 additions and 183 deletions

View File

@ -13,7 +13,7 @@
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
</dependency>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
@ -81,12 +81,6 @@
<artifactId>hsqldb</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.1</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>annotations-api</artifactId>
@ -98,7 +92,7 @@
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>

View File

@ -1,103 +0,0 @@
package org.springframework.security.core.authority.mapping;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Collection;
import java.util.Set;
import org.springframework.security.core.authority.mapping.XmlMappableAttributesRetriever;
import junit.framework.TestCase;
/**
*
* @author TSARDD
* @since 18-okt-2007
*/
@SuppressWarnings("unchecked")
public class XmlMappableRolesRetrieverTests extends TestCase {
private static final String DEFAULT_XML = "<roles><role>Role1</role><role>Role2</role></roles>";
private static final String DEFAULT_XPATH = "/roles/role/text()";
private static final String[] DEFAULT_EXPECTED_ROLES = new String[] { "Role1", "Role2" };
public final void testAfterPropertiesSetException() {
TestXmlMappableAttributesRetriever t = new TestXmlMappableAttributesRetriever();
try {
t.afterPropertiesSet();
fail("AfterPropertiesSet didn't throw expected exception");
} catch (IllegalArgumentException expected) {
} catch (Exception unexpected) {
fail("AfterPropertiesSet throws unexpected exception");
}
}
public void testGetMappableRoles() {
XmlMappableAttributesRetriever r = getXmlMappableRolesRetriever(true, getDefaultInputStream(), DEFAULT_XPATH);
Set<String> resultRoles = r.getMappableAttributes();
assertNotNull("Result roles should not be null", resultRoles);
assertEquals("Number of result roles doesn't match expected number of roles", DEFAULT_EXPECTED_ROLES.length, resultRoles.size());
Collection expectedRolesColl = Arrays.asList(DEFAULT_EXPECTED_ROLES);
assertTrue("Role collections do not match", expectedRolesColl.containsAll(resultRoles)
&& resultRoles.containsAll(expectedRolesColl));
}
public void testCloseInputStream() {
testCloseInputStream(true);
}
public void testDontCloseInputStream() {
testCloseInputStream(false);
}
private void testCloseInputStream(boolean closeAfterRead) {
CloseableByteArrayInputStream is = getDefaultInputStream();
XmlMappableAttributesRetriever r = getXmlMappableRolesRetriever(closeAfterRead, is, DEFAULT_XPATH);
r.getMappableAttributes();
assertEquals(is.isClosed(), closeAfterRead);
}
private XmlMappableAttributesRetriever getXmlMappableRolesRetriever(boolean closeInputStream, InputStream is, String xpath) {
XmlMappableAttributesRetriever result = new TestXmlMappableAttributesRetriever();
result.setCloseInputStream(closeInputStream);
result.setXmlInputStream(is);
result.setXpathExpression(xpath);
try {
result.afterPropertiesSet();
} catch (Exception e) {
fail("Unexpected exception" + e.toString());
}
return result;
}
private CloseableByteArrayInputStream getDefaultInputStream() {
return getInputStream(DEFAULT_XML);
}
private CloseableByteArrayInputStream getInputStream(String data) {
return new CloseableByteArrayInputStream(data.getBytes());
}
private static final class TestXmlMappableAttributesRetriever extends XmlMappableAttributesRetriever {
}
private static final class CloseableByteArrayInputStream extends ByteArrayInputStream {
private boolean closed = false;
public CloseableByteArrayInputStream(byte[] buf) {
super(buf);
}
public void close() throws IOException {
super.close();
closed = true;
}
public boolean isClosed() {
return closed;
}
}
}

View File

@ -23,10 +23,7 @@ Import-Template:
org.springframework.transaction.*;version="[3.0.0, 3.1.0)";resolution:=optional,
org.springframework.util;version="[3.0.0, 3.1.0)",
net.sf.ehcache.*;version="[1.4.1, 2.0.0)";resolution:=optional,
org.w3c.dom;version="0";resolution:=optional,
org.xml.sax;version="0";resolution:=optional,
javax.annotation.security.*;version="0";resolution:=optional,
javax.crypto.*;version="0";resolution:=optional,
javax.xml.*;version="0";resolution:=optional,
javax.security.auth.*;version="0";resolution:=optional

View File

@ -187,6 +187,11 @@ class="org.springframework.security.web.authentication.preauth.PreAuthenticatedA
<literal>userPrincipal</literal> property of the <interfacename>HttpServletRequest</interfacename>. use of this
filter would usually be combined with the use of J2EE roles as described above in <xref linkend="j2ee-preauth-details"/>.
</para>
<para>
There is a sample application in the codebase which uses this approach, so get hold of the code from subversion and
have a look at the application context file if you are interested. The code is in the <filename>samples/preauth</filename>
directory.
</para>
</section>
</section>

View File

@ -9,8 +9,8 @@
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:sec="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.0.1.xsd">
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd">
<bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy">
<sec:filter-chain-map path-type="ant">
@ -32,7 +32,18 @@
<bean id="j2eePreAuthFilter" class="org.springframework.security.web.authentication.preauth.j2ee.J2eePreAuthenticatedProcessingFilter">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="authenticationDetailsSource" ref="authenticationDetailsSource"/>
<property name="authenticationDetailsSource">
<bean class="org.springframework.security.web.authentication.preauth.j2ee.J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource">
<property name="mappableRolesRetriever">
<bean class="org.springframework.security.web.authentication.preauth.j2ee.WebXmlMappableAttributesRetriever" />
</property>
<property name="userRoles2GrantedAuthoritiesMapper">
<bean class="org.springframework.security.core.authority.mapping.SimpleAttributes2GrantedAuthoritiesMapper">
<property name="convertAttributeToUpperCase" value="true"/>
</bean>
</property>
</bean>
</property>
</bean>
<bean id="preAuthenticatedProcessingFilterEntryPoint"
@ -47,26 +58,6 @@
</constructor-arg>
</bean>
<bean id="authenticationDetailsSource" class="org.springframework.security.web.authentication.preauth.j2ee.J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource">
<property name="mappableRolesRetriever" ref="j2eeMappableRolesRetriever"/>
<property name="userRoles2GrantedAuthoritiesMapper" ref="j2eeUserRoles2GrantedAuthoritiesMapper"/>
</bean>
<bean id="j2eeUserRoles2GrantedAuthoritiesMapper" class="org.springframework.security.core.authority.mapping.SimpleAttributes2GrantedAuthoritiesMapper">
<property name="convertAttributeToUpperCase" value="true"/>
</bean>
<bean id="j2eeMappableRolesRetriever" class="org.springframework.security.web.authentication.preauth.j2ee.WebXmlMappableAttributesRetriever">
<property name="webXmlInputStream"><bean factory-bean="webXmlResource" factory-method="getInputStream"/>
</property>
</bean>
<bean id="webXmlResource" class="org.springframework.web.context.support.ServletContextResource">
<constructor-arg ref="servletContext"/>
<constructor-arg value="/WEB-INF/web.xml"/>
</bean>
<bean id="servletContext" class="org.springframework.web.context.support.ServletContextFactoryBean"/>
<bean id="etf" class="org.springframework.security.web.access.ExceptionTranslationFilter">

View File

@ -13,6 +13,7 @@ import org.springframework.security.web.authentication.preauth.AbstractPreAuthen
* @since 2.0
*/
public class J2eePreAuthenticatedProcessingFilter extends AbstractPreAuthenticatedProcessingFilter {
/**
* Return the J2EE user name.
*/

View File

@ -1,50 +1,123 @@
package org.springframework.security.web.authentication.preauth.j2ee;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.springframework.security.core.authority.mapping.XmlMappableAttributesRetriever;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.security.core.authority.mapping.MappableAttributesRetriever;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* <p>
* This MappableAttributesRetriever implementation reads the list of defined J2EE
* roles from a web.xml file. It's functionality is based on the
* XmlMappableAttributesRetriever base class.
* <p>
* Example on how to configure this MappableAttributesRetriever in the Spring
* configuration file:
*
* <pre>
*
*
* &lt;bean id=&quot;j2eeMappableRolesRetriever&quot; class=&quot;org.springframework.security.ui.preauth.j2ee.WebXmlMappableAttributesRetriever&quot;&gt;
* &lt;property name=&quot;webXmlInputStream&quot;&gt;&lt;bean factory-bean=&quot;webXmlResource&quot; factory-method=&quot;getInputStream&quot;/&gt;&lt;/property&gt;
* &lt;/bean&gt;
* &lt;bean id=&quot;webXmlResource&quot; class=&quot;org.springframework.web.context.support.ServletContextResource&quot;&gt;
* &lt;constructor-arg&gt;&lt;ref local=&quot;servletContext&quot;/&gt;&lt;/constructor-arg&gt;
* &lt;constructor-arg&gt;&lt;value&gt;/WEB-INF/web.xml&lt;/value&gt;&lt;/constructor-arg&gt;
* &lt;/bean&gt;
* &lt;bean id=&quot;servletContext&quot; class=&quot;org.springframework.web.context.support.ServletContextFactoryBean&quot;/&gt;
*
* </pre>
* This <tt>MappableAttributesRetriever</tt> implementation reads the list of defined J2EE
* roles from a <tt>web.xml</tt> file and returns these from {{@link #getMappableAttributes()}.
*
* @author Ruud Senden
* @author Luke Taylor
* @since 2.0
*/
public class WebXmlMappableAttributesRetriever extends XmlMappableAttributesRetriever {
private static final String XPATH_EXPR = "/web-app/security-role/role-name/text()";
public class WebXmlMappableAttributesRetriever implements ResourceLoaderAware, MappableAttributesRetriever, InitializingBean {
protected final Log logger = LogFactory.getLog(getClass());
/**
* Constructor setting the XPath expression to use
*/
public WebXmlMappableAttributesRetriever() {
super.setXpathExpression(XPATH_EXPR);
private ResourceLoader resourceLoader;
private Set<String> mappableAttributes;
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
public Set<String> getMappableAttributes() {
return mappableAttributes;
}
/**
* @param anInputStream
* The InputStream to read the XML data from
* Loads the web.xml file using the configured <tt>ResourceLoader</tt> and
* parses the role-name elements from it, using these as the set of <tt>mappableAttributes</tt>.
*/
public void setWebXmlInputStream(InputStream anInputStream) {
super.setXmlInputStream(anInputStream);
public void afterPropertiesSet() throws Exception {
Resource webXml = resourceLoader.getResource("/WEB-INF/web.xml");
Document doc = getDocument(webXml.getInputStream());
NodeList webApp = doc.getElementsByTagName("web-app");
if (webApp.getLength() != 1) {
throw new IllegalArgumentException("Failed to find 'web-app' element in resource" + webXml);
}
NodeList securityRoles = ((Element)webApp.item(0)).getElementsByTagName("security-role");
ArrayList<String> roleNames = new ArrayList<String>();
for (int i=0; i < securityRoles.getLength(); i++) {
Element secRoleElt = (Element) securityRoles.item(i);
NodeList roles = secRoleElt.getElementsByTagName("role-name");
if (roles.getLength() > 0) {
String roleName = ((Element)roles.item(0)).getTextContent().trim();
roleNames.add(roleName);
logger.info("Retrieved role-name '" + roleName + "' from web.xml");
} else {
logger.info("No security-role elements found in " + webXml);
}
}
mappableAttributes = Collections.unmodifiableSet(new HashSet<String>(roleNames));
}
/**
* @return Document for the specified InputStream
*/
private Document getDocument(InputStream aStream) {
Document doc;
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(false);
DocumentBuilder db = factory.newDocumentBuilder();
db.setEntityResolver(new MyEntityResolver());
doc = db.parse(aStream);
return doc;
} catch (FactoryConfigurationError e) {
throw new RuntimeException("Unable to parse document object", e);
} catch (ParserConfigurationException e) {
throw new RuntimeException("Unable to parse document object", e);
} catch (SAXException e) {
throw new RuntimeException("Unable to parse document object", e);
} catch (IOException e) {
throw new RuntimeException("Unable to parse document object", e);
} finally {
try {
aStream.close();
} catch (IOException e) {
logger.warn("Failed to close input stream for web.xml", e);
}
}
}
/**
* We do not need to resolve external entities, so just return an empty
* String.
*/
private static final class MyEntityResolver implements EntityResolver {
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
return new InputSource(new StringReader(""));
}
}
}

View File

@ -1,21 +1,34 @@
package org.springframework.security.web.authentication.preauth.j2ee;
import java.io.InputStream;
import static org.junit.Assert.*;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import org.springframework.security.web.authentication.preauth.j2ee.WebXmlMappableAttributesRetriever;
import org.junit.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import junit.framework.TestCase;
public class WebXmlJ2eeDefinedRolesRetrieverTests {
public class WebXmlJ2eeDefinedRolesRetrieverTests extends TestCase {
public final void testRole1To4Roles() throws Exception {
final List<String> ROLE1TO4_EXPECTED_ROLES = Arrays.asList(new String[] { "Role1", "Role2", "Role3", "Role4" });
InputStream role1to4InputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("webxml/Role1-4.web.xml");
@Test
public void testRole1To4Roles() throws Exception {
List<String> ROLE1TO4_EXPECTED_ROLES = Arrays.asList(new String[] { "Role1", "Role2", "Role3", "Role4" });
final Resource webXml = new ClassPathResource("webxml/Role1-4.web.xml");
WebXmlMappableAttributesRetriever rolesRetriever = new WebXmlMappableAttributesRetriever();
rolesRetriever.setWebXmlInputStream(role1to4InputStream);
rolesRetriever.setResourceLoader(new ResourceLoader() {
public ClassLoader getClassLoader() {
return Thread.currentThread().getContextClassLoader();
}
public Resource getResource(String location) {
return webXml;
}
});
rolesRetriever.afterPropertiesSet();
Set<String> j2eeRoles = rolesRetriever.getMappableAttributes();
assertNotNull(j2eeRoles);
@ -25,10 +38,19 @@ public class WebXmlJ2eeDefinedRolesRetrieverTests extends TestCase {
j2eeRoles.containsAll(ROLE1TO4_EXPECTED_ROLES));
}
public final void testGetZeroJ2eeRoles() throws Exception {
InputStream noRolesInputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("webxml/NoRoles.web.xml");
@Test
public void testGetZeroJ2eeRoles() throws Exception {
final Resource webXml = new ClassPathResource("webxml/NoRoles.web.xml");
WebXmlMappableAttributesRetriever rolesRetriever = new WebXmlMappableAttributesRetriever();
rolesRetriever.setWebXmlInputStream(noRolesInputStream);
rolesRetriever.setResourceLoader(new ResourceLoader() {
public ClassLoader getClassLoader() {
return Thread.currentThread().getContextClassLoader();
}
public Resource getResource(String location) {
return webXml;
}
});
rolesRetriever.afterPropertiesSet();
Set<String> j2eeRoles = rolesRetriever.getMappableAttributes();
assertEquals("J2eeRoles expected size: 0, actual size: " + j2eeRoles.size(), 0, j2eeRoles.size());

View File

@ -24,6 +24,7 @@ Import-Template:
org.springframework.beans.*;version="[3.0.0, 3.1.0)",
org.springframework.context.*;version="[3.0.0, 3.1.0)",
org.springframework.core;version="[3.0.0, 3.1.0)",
org.springframework.core.io;version="[3.0.0, 3.1.0)",
org.springframework.dao;version="[3.0.0, 3.1.0)";resolution:=optional,
org.springframework.expression;version="[3.0.0, 3.1.0)";resolution:=optional,
org.springframework.expression.spel.*;version="[3.0.0, 3.1.0)";resolution:=optional,
@ -31,4 +32,7 @@ Import-Template:
org.springframework.mock.web;version="[3.0.0, 3.1.0)";resolution:=optional,
org.springframework.web.context.*;version="[3.0.0, 3.1.0)";resolution:=optional,
org.springframework.util;version="[3.0.0, 3.1.0)";resolution:=optional,
javax.servlet.*;version="0"
org.w3c.dom;version="0";resolution:=optional,
org.xml.sax;version="0";resolution:=optional,
javax.servlet.*;version="0",
javax.xml.parsers.*;version="0";resolution:=optional