Sunday, January 3, 2010

Spring Security – Part 2 – Custom securityMetadataSource

Now let us look into filterSecurityInterceptor. This filter authorizes web requests based on URL patterns. Instead of having the URLs configured in application context, we will have URLs in the properties file. We can even have these URLs configured in database or LDAP server. Here let us have properties file having URLs and the corresponding roles. The properties file looks like –

/admin/**=ROLE_ADMIN,ROLE_MANAGER
/admin/userPreference/**=ROLE_MANAGER
/admin/userPreference/updatePreference.action=ROLE_USER,ROLE_ASSOCIATE
/admin/userPreference/deletePreference.action=ROLE_USER

Here ROLE_ADMIN, ROLE_MANAGER are given complete access to all admin URLs, ROLE_MANAGER is given access to all URLs having /admin/userPreference/ and ROLE_USER, ROLE_ASSOCIATE are given page / action level access.

Now let us define filterSecurityInterceptor.

<bean id="filterSecurityInterceptor"
class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="accessDecisionManager"/>
<property name="securityMetadataSource" ref="mySecureResourceFilter"/>
</bean>

We have already configured authenticationManager in the previous part. Let us now define accessDecisionManager.

<bean id="accessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased">
<property name="decisionVoters">
<list>
<bean class="org.springframework.security.access.vote.RoleVoter" />
<bean class="org.springframework.security.access.vote.AuthenticatedVoter" />
</list>
</property>
</bean>

We will look into it in more detail in next part.

Now let us write custom securityMetadataSource – mySecureResourceFilter

Let us define the bean first -


<bean id="mySecureResourceFilter" class="com.test.common.MySecureResourceFilter">
<property name="urlProperties">
<util:properties location="classpath:urls.properties" />
</property>
</bean>

Given the requested URL, we have to find all the roles authorized to access it. Like if requested URL is /admin/changePreference/deletePreference.action, we will get the roles from the properties file which can access the following URLs –

/admin/changePreference/deletePreference.action
/admin/changePreference/**
/admin/**

All this is implemented in getAttributes (..) method defined below which returns the name of the Authorities (or Roles) that are allowed to access requested URL.

import java.util.Collection;
import java.util.Properties;

import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;

import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;

public class MySecureResourceFilter implements
FilterInvocationSecurityMetadataSource {

private Properties urlProperties;

public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}

public Collection<ConfigAttribute> getAttributes(Object filter)
throws IllegalArgumentException {
FilterInvocation filterInvocation = (FilterInvocation) filter;
String url = filterInvocation.getRequestUrl();

//get the roles for requested page from the property file
String urlPropsValue = urlProperties.getProperty(url);
StringBuilder rolesStringBuilder = new StringBuilder();
if(urlPropsValue != null) {
rolesStringBuilder.append(urlPropsValue).append(",");
}

if(!url.endsWith("/")) {
int lastSlashIndex = url.lastIndexOf("/");
url = url.substring(0, lastSlashIndex + 1);
}


String [] urlParts = url.split("/");

StringBuilder urlBuilder = new StringBuilder();
for (String urlPart : urlParts) {
if(urlPart.trim().length() == 0) {
continue;
}
urlBuilder.append("/").append(urlPart);
urlPropsValue = urlProperties.getProperty(urlBuilder.toString() + "/**");

if(urlPropsValue != null) {
rolesStringBuilder.append(urlPropsValue).append(",");
}
}

if(rolesStringBuilder.toString().endsWith(",")) {
rolesStringBuilder.deleteCharAt(rolesStringBuilder.length()-1);
}


if(rolesStringBuilder.length() == 0) {
return null;
}

return SecurityConfig.createListFromCommaDelimitedString(rolesStringBuilder.toString());
}

public boolean supports(Class<?> arg0) {
return true;
}

public void setUrlProperties(Properties urlProperties) {
this.urlProperties = urlProperties;
}

public Properties getUrlProperties() {
return urlProperties;
}

}

That’s it. In next part we will look into role based Method Invocation.

5 comments:

empty said...

I did the same thing, instead modified the code to get the roles from database.

Earlier I had the


After I implemented the FilterInvocationSecurityMetadataSource , i removed it from my xml ,as this will be driven by the filter class. But now I am not able to logout. Even with out loging in I am able to go to rest of pages.

yen said...

Can you give examples or idea on how to transform below kind of intercept-url from spring security xml configuration file into a separate properties file?

<intercept-url pattern="/index.html" access="permitAll" filters="none" />
<intercept-url pattern="/login.html" access="permitAll" filters="none" />
<intercept-url pattern="/**" access="isAuthenticated()"/>
<intercept-url pattern="/admin/**" access="hasRole('ROLE_SUPERVISOR')"/>

Sanjay Dalal said...

Hi Pranav,

Thanks for the post. It was useful. You might want to attach your source code as well. I wish there was documentation on the Spring Security website about such an extension.

Unknown said...
This comment has been removed by the author.
Unknown said...

Thank you for the comment.
Once I have this configuration how do I use the security:authorize tag in my jsp with url attribute. The following tag in jsp is currently not working for me.
sec:authorize url='/secure/index.jsp'

Also, I had one more question. How does unprotected url work? I tried ROLE_ANONYMOUS but it is not working for me.