Saturday, January 2, 2010

Spring Security – Part 1 – Necessary Configuration and Custom UserDetailsService

Here we will look into –

1. The necessary configurations required
2. Write custom userDetailsService reading user name, password and roles from properties file

We will not use default security namespace configuration instead we will define beans and configure our own FilterChainProxy.

Add the schema declaration to the application context file –
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
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
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
xmlns:sec="http://www.springframework.org/schema/security"
xmlns:util="http://www.springframework.org/schema/util">

. . . . . .

</beans>

web.xml configuration
The first thing we need to do is add the following filter declaration to web.xml file:
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>
org.springframework.web.filter.DelegatingFilterProxy
</filter-class>
</filter>

<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

Now let us define and configure springSecurityFilterChain. It delegates request to a chain of Spring-managed filters. Since we would be customizing the Spring Security’s behavior we would be manually configuring the filters instead of using the default configuration.

For a web application we need to configure the following filters in the mentioned order –

1. HttpSessionContextIntegrationFilter – It queries HTTPSession to retrieve SecurityContext and populates SecurityContextHolder for the duration of web request. At the end of the web request, any updates made to the SecurityContextHolder will be persisted back to the HttpSession by this filter.

2. LogoutFilter – It clears SecurityContextHolder when logout is requested.

3. AuthenticationProcessingFilter – It puts Authentication into the SecurityContext on login request.

4. ExceptionTranslationFilter – It converts SpringSecurity exceptions into HTTP response or HTTP redirect.

5. FilterSecurityInterceptor – It authorizes web requests based on URL patterns.

Now let us define springSecurityFilterChain in application context file.
<bean id="springSecurityFilterChain" class="org.springframework.security.web.FilterChainProxy">
<sec:filter-chain-map path-type="ant">
<sec:filter-chain pattern="/**" filters=" httpSessionContextFilter,
logoutFilter,
authenticationProcessingFilter,
exceptionTranslationFilter,
filterSecurityInterceptor" />
</sec:filter-chain-map>
</bean>


Let us now configure the filters –

1. httpSessionContextFilter
Let us use the default configuration for httpSessionContextFilter.

<bean id="httpSessionContextFilter" class="org.springframework.security.web.context.HttpSessionContextIntegrationFilter"/>

2. exceptionTranslationFilter
It handles any AccessDeniedException and AuthenticationException thrown within the filter chain.
If an AuthenticationException is detected, the filter will launch the authenticationEntryPoint. If an AccessDeniedException is detected, the filter will determine whether or not the user is an anonymous user. If they are an anonymous user, the authenticationEntryPoint will be launched. If they are not an anonymous user, the filter will delegate to the AccessDeniedHandler.
Let us define the beans now. Login form url is set as “/login.jsp” for authenticationEntryPoint and error page is set as “/error/jsp” for access denied handler.

<bean id="exceptionTranslationFilter" class="org.springframework.security.web.access.ExceptionTranslationFilter">
<property name="authenticationEntryPoint" ref="authenticationEntryPoint"/>
<property name="accessDeniedHandler" ref="accessDeniedHandler" />
</bean>


<bean id="authenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint" >
<property name="loginFormUrl" value="/login.jsp" />
</bean>

<bean id="accessDeniedHandler" class="org.springframework.security.web.access.AccessDeniedHandlerImpl">
<property name="errorPage" value="/error401.jsp"/>
</bean>


3. authenticationProcessingFilter –
User information can be stored in database, in LDAP server or in properties file. Let us have user name, password and roles defined in a properties file (user.properties). We will write custom implementation of Spring Security's UserDetailsService - MyUserDetailService

user.properties has the following entries –
pranav=123123,ROLE_ADMIN
pranjal=321321,ROLE_MANAGER
sudheer=abcabc,ROLE_USER

Now let us write MyUserDetailService that implements UserDetailsService implementing method loadUserByUsername. This would load the user information from the user.properties creating UserDetails.

import java.util.Properties;
import org.springframework.dao.DataAccessException;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.core.userdetails.memory.UserAttribute;
import org.springframework.security.core.userdetails.memory.UserAttributeEditor;

public class MyUserDetailService implements UserDetailsService {
private Properties userProperties;

public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException {

String userPropsValue = userProperties.getProperty(username);
if (userPropsValue == null) {
throw new UsernameNotFoundException(username
+ "User does not exist");
}

UserAttributeEditor configAttribEd = new UserAttributeEditor();
configAttribEd.setAsText(userPropsValue);

UserAttribute userAttributes = (UserAttribute) configAttribEd
.getValue();

return new User(username, userAttributes.getPassword(), userAttributes
.isEnabled(), true, true, true, userAttributes.getAuthorities());
}

public void setUserProperties(Properties userProperties) {
this.userProperties = userProperties;
}

public Properties getUserProperties() {
return userProperties;
}

}

Now let us define the beans. Post successful authentication user would be shown the requested URL or the default target url “/home.action” set to authenticationSuccessHandler.
<bean id="authenticationProcessingFilter" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
<property name="authenticationManager" ref="authenticationManager" />
<property name="authenticationSuccessHandler" ref="authenticationSuccessHandler"/>
</bean>

<bean id="authenticationManager" class="org.springframework.security.authentication.ProviderManager">
<property name="providers">
<list>
<ref bean="authenticationProvider"/>
</list>
</property>
</bean>

<bean id="authenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<property name="userDetailsService" ref="userDetailService"/>
</bean>

<bean id="userDetailService" class="com.test.common.MyUserDetailService">
<property name="userProperties">
<util:properties location="classpath:users.properties" />
</property>
</bean>

<bean id="authenticationSuccessHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler">
<property name="defaultTargetUrl" value="/home.action"/>
</bean>


We will look into filterSecurityInterceptor and logoutFilter in next part.




2 comments:

Cat said...

AuthenticationProcessingFilter and HttpSessionContextIntegrationFilter are deprecated in Spring Security 3. You should update your configuration to use the renamed versions.

Metta said...

Hi, i tried with your posted example on existing spring source tutorial with suggested changes. But filter the "authenticationProcessingFilter" is not at invoking and not creating any HttpSession object.

Can you please post the solid example?