SpringSource has recently announced that they renamed Acegi Security as Spring Security, and are preparing for a major release which will be called as 2.0. Actually its first milestone release is already available for download. According to Ben Alex, there are various enhancements to bean configurations and new features introduced such as hierarchical roles etc.
After latest news from Spring Security side, let’s return back to my current issue. Spring Security, which is highly dependent on Servlet Filters, uses FilterSecurityInterceptor to protect web resources. We simply provide url pattern – role mappings during bean configuration. For example;
<bean id="filterSecurityInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor"> <property name="authenticationManager"> <ref bean="authenticationManager" /> </property> <property name="accessDecisionManager"> <ref bean="accessDecisionManager" /> </property> <property name="alwaysReauthenticate"> <value>${security.auth.alwaysReauthenticate}</value> </property> <property name="objectDefinitionSource"> <value> A.*index.*Z=ROLE_READER,ROLE_WRITER A.*reader.faces.*Z=ROLE_READER A.*writer.faces.*Z=ROLE_WRITER A.*Z=ROLE_ANONYMOUS,ROLE_READER,ROLE_WRITER </value> </property> </bean>
FilterSecurityInterceptor has an objectDefinitionSource property to which we provide those url pattern – role mappings. Type of objectDefinitionSource property is FilterInvocationDefinitionSource, and Acegi Security provides a default property editor (FilterInvocationDefinitionSourceEditor), which is located in the same package with FilterInvocationDefinitionSource class. Spring invokes property editors during applicaition context startup, and obtains target bean instances from some textual input in bean definition.
One of the limitations of above approach is that, it is difficult to package application context configurations with security enabled into separate deployment units and reuse them in several applications. One way to overcome this limitation would be to provide a default bean configuration of filterSecurityInterceptor and override it in your application’s bean configuration and provide these mappings in that overriding bean definition.
One another limitation is that, by defining url pattern – role mappings in xml files, you won’t have any chance of adding new or changing existing mappings in FilterInvocationDefinitionSource bean. You have to restart your application context each time when you make a change to those mappings, so that your changes to objectDefinitionSource property value can be read and new FilterInvocationDefinitionSource bean is constructed again.
It would be very nice if we could externalize those mappings by moving them out of xml files. For example, we can create a filterInvocationDefinitions.properties file and put those mappings in it. By that way, we can place those bean configurations in a separate jar, and reuse them in other web applications. We also won’t need to override filterSecurityInterceptor bean to define application specific mappings, but only make changes to the properties file. Moving objectDefinitionSource values out of xml alone won’t enable us to reload mappings during runtime, but it definetely opens up such a possibility. However, I will focus on externalization process at the moment. Reloading url – role mappings during runtime is another day’s issue.
In order to externalize mappings, we need a way to process that filterInvocationDefinitions.properties file and create a FlterInvocationDefinitionSource instance to inject into filterSecurityInterceptor bean. Yes, the answer is very simple: We can implement a FactoryBean which reads up properties file and creates a FilterInvocationDefinitionSource bean. For example;
<bean id="filterInvocationDefinitionSource" class="org.ems4j.security.intercept.web.ResourceFilterInvocationDefinitionSourceFactoryBean"> <property name="filterInvocationDefinitions" value="classpath:/resources/filterInvocationDefinitions.properties" /> </bean>
ResourceFilterInvocationDefinitionSourceFactoryBean gets filterInvocationDefinitions.properties in the form of Spring’s Resource type, process contents and creates a FilterInvocationDefinitionSource bean. After such a FactoryBean configuration, our filterSecurityInterceptor bean’s objectDefinitionSource configuration needs to be changed slightly;
<property name="objectDefinitionSource"> <ref local="filterInvocationDefinitionSource"/> </property>
Let’s look at inside of ResourceFilterInvocationDefinitionSourceFactoryBean closely. Acegi supports two different types of FilterInvocationDefinitionSource, namely PathBasedFilterInvocationDefinitionMap (for ant style patterns in urls) and RegExpBasedFilterInvocationDefinitionMap (for regular expression enabled urls) which is by default. Previously we could tell property editor to instantiate PathBased version with PATTERN_TYPE_APACHE_ANT directive on top of our mappings. Another directive is CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON to tell Acegi that we want to lowercase all urls before doing any comparison during authorization of web resources. We can represent those two options as boolean in our FactoryBean.
public class ResourceFilterInvocationDefinitionSourceFactoryBean extends AbstractFactoryBean { private boolean patternTypeApacheAnt = false; private boolean convertUrlToLowercaseBeforeComparison = false;
In createInstance() method of FactoryBean, we instantiate one of those types mentioned above and wrap it with FilterInvocationDefinitionDecorator which is also subtype of FilterInvocationDefinitionSource, and is used for lowercase url conversion.
protected Object createInstance() throws Exception { BufferedReader reader = null; try { FilterInvocationDefinitionDecorator decorator = new FilterInvocationDefinitionDecorator( patternTypeApacheAnt ? new PathBasedFilterInvocationDefinitionMap() : new RegExpBasedFilterInvocationDefinitionMap()); decorator.setConvertUrlToLowercaseBeforeComparison(this.convertUrlToLowercaseBeforeComparison); List mappings = new ArrayList(); reader = new BufferedReader(new InputStreamReader( filterInvocationDefinitions.getInputStream(), "utf-8")); String line = reader.readLine(); while (line != null) { String name = StringSplitUtils.substringBeforeLast(line, "="); String value = StringSplitUtils.substringAfterLast(line, "=");
We then read contents of filterInvocationDefinitions.properties line by line and create FilterInvocationDefinitionSourceMapping for each url pattern-role list mapping. Roles are added into those mapping objects as ConfigAttribute elements.
FilterInvocationDefinitionSourceMapping mapping = new FilterInvocationDefinitionSourceMapping(); mapping.setUrl(name); String[] tokens = StringUtils.commaDelimitedListToStringArray(value); for (int i = 0; i < tokens.length; i++) { mapping.addConfigAttribute(tokens[i].trim()); } mappings.add(mapping); line = reader.readLine(); } decorator.setMappings(mappings); return decorator.getDecorated(); } finally { try { if (reader != null) reader.close(); } catch (Exception ex) { // do nothing... } } } }
It is important to read properties file line by line because Acegi compares current url against url patterns in the order of definition. Comparison stops at first match.
In our case we moved mappings into a file. It is equally possible to move them into database as well. You can also provide a user interface to manage your protected web resources and their accessibilities.