The example that comes with acegi security, and the one that comes with appfuse define secure URL patterns, and the roles that can access them in the XML configuration file, which is not a very flexible solution. It is a good approach if you have predefined roles and access rights , that are never going to change (or the changes are very rare). In case you have a security module, where you define which role can access which URL patterns, this method is the worst thing you could do.
The solution to this problem is to define your own data source, which loads the secure URL patterns and the corresponding roles that can access it from somewhere (database for example). A simple way to do that is by extending PathBasedFilterInvocationDefinitionMap and loading the secure URL patterns and the roles that can access them in an initialization method or in the constructor. Here's an example:
public class UrlPatternRolesDefinitionSource extends PathBasedFilterInvocationDefinitionMap { public void init(){ Session session = sessionFactory.openSession(); try{ List for (UrlPattern pattern : urlPatternsList) { ConfigAttributeDefinition configDefinition = new ConfigAttributeDefinition(); for(Role role: pattern.getRoles()){ ConfigAttribute config = new SecurityConfig(role.getAuthority()); configDefinition.addConfigAttribute(config); } addSecureUrl(pattern.getUrlPattern(), configDefinition); } } catch (FindException e) { // Handle exception } finally { session.close(); } } } |
In this example I used a Hibernate DAO I created (urlPatternDao) to retrieve the secure URL patterns which is defined and initialized with the sessionFactory somewhere else in the code (using spring's dependency injection). You can implement it anyway you like (JDBC, with any other ORM framework, or any other method you like).
In this example, each URL pattern can has a set roles that can access it, so I loop over this set to create the ConfigAttributeDefinition. I prefer if you have your own implementation of the ConfigAttributeDefinition, but this is going to be discussed in part 2 of this article. Of course the Role class used in this code implements the GrantedAuthority interface.
As for the XML configuration of this entry, it's shown below:
<bean id="filterInvocationInterceptor" class="org.acegisecurity.intercept.web. FilterSecurityInterceptor"> <property name="authenticationManager" ref="authenticationManager"/> <property name="accessDecisionManager" ref="accessDecisionManager"/> <property name="objectDefinitionSource" ref="objectDefinitionSource"/> </bean> <bean id="objectDefinitionSource" class= "net.jnassef.security.UrlPatternRolesDefinitionSource" init-method="init"> <property name="convertUrlToLowercaseBeforeComparison"> <value type="boolean">false</value> </property> <property name="urlPatternDao" ref="urlPatternDao"/> <property name="sessionFactory" ref="sessionFactory"/> </bean> |
As for the first bean in the previous snippet, this definition is the default that comes with the sample acegi application and with appfuse, except to the reference to the objectDefinitionSource bean which was added by us, to allow dynamic role management. The second bean maps to the class we created, and defines the initialization method, that will load the secured URL patterns, and the roles associated with them. This definition also sets the DAO and the hibernate session factory used by the class. Of course you don't need those if you want to have any other implementation.
The previous implementation seems good as a solution for dynamic role management, but if we look more closely, it's not a very good solution (or to be more accurate, it's not a complete solution). The problem that is going to face us here is that secure URL patterns are loaded only ONCE at the application start up, and any change done to the role access rights will not effective until the application is started, and this is not dynamic at all. In fact, there is no difference between this solution, and having the access rights in the XML configuration file, except that you will not need to redeploy your application, but you will need to restart it. There are several solutions to this problem, and this will be the main focus of the next part of this article.