Disclaimer
As the Spring Security FAQ mentions, the first thing you should do is ask should I really do this? Security is complicated and the configuration should be tested extensively. Allowing the configuration to change dynamically only further complicates things making the application that much more vulnerable. If you really want to do this, the FAQ outlines a basic method to accomplish this. I have expanded upon the FAQ's answer below.
Implement Custom FilterInvocationSecurityMetadataSource
To obtain the security URL mappings dynamically you can implement your own FilterInvocationSecurityMetadataSource. An example implementation is given below.
NOTE: Keep in mind that getAttributes will be invoked for every request that Spring Security intercepts so you will most likely want some sort of caching.
public class JdbcFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
FilterInvocation fi = (FilterInvocation) object;
String url = fi.getRequestUrl();
HttpServletRequest request = fi.getHttpRequest();
// Instead of hard coding the roles lookup the roles from the database using the url and/or HttpServletRequest
// Do not forget to add caching of the lookup
String[] roles = new String[] { "ROLE_ADMIN", "ROLE_USER" };
return SecurityConfig.createList(roles);
}
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
}
Create a BeanPostProcessor
You cannot use the namespace to wire it up, so taking another tip from the FAQ you can use a BeanPostProcessor which might look like:
public class FilterInvocationSecurityMetadataSourcePostProcessor implements BeanPostProcessor, InitializingBean {
private FilterInvocationSecurityMetadataSource securityMetadataSource;
public Object postProcessAfterInitialization(Object bean, String name) {
if (bean instanceof FilterSecurityInterceptor) {
((FilterSecurityInterceptor)bean).setSecurityMetadataSource(securityMetadataSource);
}
return bean;
}
public Object postProcessBeforeInitialization(Object bean, String name) {
return bean;
}
public void setSecurityMetadataSource(FilterInvocationSecurityMetadataSource securityMetadataSource) {
this.securityMetadataSource = securityMetadataSource;
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(securityMetadataSource,"securityMetadataSource cannot be null");
}
}
XML Configuration
Then, assuming both of the above beans are in the package sample, you would add the following configuration
<bean class="sample.FilterInvocationSecurityMetadataSourcePostProcessor">
<property name="securityMetadataSource">
<bean class="sample.JdbcFilterInvocationSecurityMetadataSource"/>
</property>
</bean>
Possible Problems
If you end up getting a ClassCastException, you are likely running into SEC-1957 which was fixed in Spring Security 3.1.1+ Try updating to the latest version to resolve this.