Tuesday, 5 March 2013

How to use Custom DAO class in Spring Security for authentication and authorization


Objective 1 : Use Custom DAO classes in Spring Security Spring Security provides mechanism by which we can specify database queries in spring security xml file , but sometimes we want to use our own custom dao classes which are already built.
Objective 2 : Forward the request to different home pages based on the authorized role



First , to use your custom dao class , we have to create a bean which implements org.springframework.security.userdetails.UserDetailsService interface. Override the loadUserByUserName method of this interface. We have to create org.springframework.security.userdetails.User from our custom dao object.
package com.security;

import org.springframework.security.GrantedAuthority;
import org.springframework.security.GrantedAuthorityImpl;
import org.springframework.security.userdetails.User;
import org.springframework.security.userdetails.UserDetails;
import org.springframework.security.userdetails.UserDetailsService;
import org.springframework.security.userdetails.UsernameNotFoundException;
import com.dal.interfaces.UserDAO;
import com.exceptions.DAOException;

/**
 * //this class is used by spring controller to authenticate and authorize user
 * modified this class to user our Database and defined user roles
 * 
 * @author abhishek.somani
 * 
 */
public class UserDetailServiceImpl implements UserDetailsService {
 private UserDAO userdao;

 public void setUserdao(UserDAO userdao) {
  this.userdao = userdao;
 }

 // this class is used by spring controller to authenticate and authorize
 // user
 @Override
 public UserDetails loadUserByUsername(String userId)
   throws UsernameNotFoundException {
  com.model.User u;
  try {
   u = userdao.get(userId);
   if (u == null)
    throw new UsernameNotFoundException("user name not found");

  } catch (DAOException e) {
   throw new UsernameNotFoundException("database error ");
  }
  return buildUserFromUserEntity(u);

 }

 private User buildUserFromUserEntity(com.model.User userEntity) {
  // convert model user to spring security user
  String username = userEntity.getUserId();
  String password = userEntity.getPassword();
  boolean enabled = true;
  boolean accountNonExpired = true;
  boolean credentialsNonExpired = true;
  boolean accountNonLocked = true;
  GrantedAuthority[] authorities = new GrantedAuthorityImpl[1];
  authorities[0] = new GrantedAuthorityImpl(userEntity.getRole());

  User springUser = new User(username, password, enabled,
    accountNonExpired, credentialsNonExpired, accountNonLocked,
    authorities);
  return springUser;
 }

}

In Spring-security.xml we have to give reference of this bean in user-service-ref in authentication-provider tag.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:security="http://www.springframework.org/schema/security"
 xmlns:context="http://www.springframework.org/schema/context"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.springframework.org/schema/beans
                         http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
                        http://www.springframework.org/schema/security
                         http://www.springframework.org/schema/security/spring-security-2.0.1.xsd
                          http://www.springframework.org/schema/context/spring-context-2.5.xsd">

<!-- this is security configuration file which maps urls according to user roles authorization -->

 <security:http auto-config="true">
 <security:logout logout-success-url="/login" invalidate-session="true" logout-url="/logout"/>
  <security:intercept-url pattern="/login.jsp" filters="none" />
  <security:intercept-url pattern="/login" filters="none" />
  <security:intercept-url pattern="/logout" filters="none" />
  <security:intercept-url pattern="/Test"  access="ROLE_ADMIN" />
  <security:intercept-url pattern="/home" access="ROLE_ADMIN,ROLE_USER"/>
  <security:intercept-url pattern="/user/*" access="ROLE_USER" />
  <security:intercept-url pattern="/admin/*" access="ROLE_ADMIN" />
  <security:intercept-url pattern="/*" filters="none"/>
  <security:form-login login-page="/login"
   default-target-url="/home" authentication-failure-url="/login?error=1"
   always-use-default-target="true"/>
  
   
 </security:http>
 <security:authentication-provider
  user-service-ref="userDetailsService" />

 <bean id='userDetailsService' class='com.security.UserDetailServiceImpl'>
  <property name='userdao' ref='userDao' />
 </bean>

</beans>
Now we want to redirect user after authentication and authorization by user. If a user has User role , it should go to home page of user or if a user has admin role then it should go to home page of admin .

For this , create a simple controller .After successfull authorization and authentication request.getUserPrinicipal will have the Prinicipal object containing userName which is set in UserDetailServiceImpl and we can check the roles by request.isUserInRole method.

In spring-security.xml , set target url to this controller and set always-use-default-target attribute to true in form-login tag , because we always want this controller to execute after user successfully authenticate the user. Sometimes if the request contains referer header , spring security redirect it to that previous link.That is why we set always-use-default-target attribute to true.
package com.controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;
import com.constants.Constants;
import com.model.UserRoles;
import com.util.CustomLogger;

/**
 * home controller redirects the user based on the roles 
 * @author abhishek.somani
 *
 */
public class HomeController extends AbstractController
{

 @Override
 protected ModelAndView handleRequestInternal(HttpServletRequest request,HttpServletResponse arg1) throws Exception
 {
  //this is the home controller to redirect user to their home pages based on role name 
  // for Admin it should be /admin/home
  //for user it should be /user/home
    
  if (request.isUserInRole("ROLE_USER"))
  {
   request.setAttribute("appendURL", "user");
   return new ModelAndView("user/home", "welcome ", null);
  }
  if (request.isUserInRole("ROLE_ADMIN"))
  {
   request.setAttribute("appendURL", "admin");
   return new ModelAndView("admin/home", "welcome ", null);
  }
  
  throw new Exception("No roles Detected");
 }

}
Do Comment if you face any difficulty.