Spring Security 3.1 Group-Based Access Control with Custom Database and JSF 2.0

In all our previous tutorials, access was given to users directly, which is hardly implemented in real world applications.
Most of the security or permissioning system uses “Group-Based Access Control” system including Microsoft Active Directory, LDAP etc. “Group-Based Access Control” means access is given to a group and members of a given group will inherit the rights(permission of that group). Individual users who don’t belong to any group won’t have any access rights.

Implementing such a system can really be a painful task, considering the implications generated out of a fragile security framework. Thankfully Spring Security has provided out-of-the-box solution for implementing “Group-Based Access Control” system in our application.

With some background behind us, let’s get our hand dirty.

First we need to tell customjdbcUserService to use groups instead of role, to accomplish this we have to update jsfspring-sec-bean-config.xml.

jsfspring-sec-bean-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:beans="http://www.springframework.org/schema/beans"
	xmlns:sec="http://www.springframework.org/schema/security"
	xsi:schemaLocation="
	   http://www.springframework.org/schema/beans
	   http://www.springframework.org/schema/beans/spring-beans-3.1.xsd 
       http://www.springframework.org/schema/security
	   http://www.springframework.org/schema/security/spring-security-3.1.xsd"
	   >
       
	<beans:bean id="navigator" name="navigator" class="com.mumz.jsfspringsec.beans.Navigator" scope="session">
	</beans:bean>
	
	<beans:bean id="loginBean" name="loginBean" class="com.mumz.jsfspringsec.beans.LoginBean" scope="prototype">
		<beans:property name="authenticationManager" ref="authenticationManager"></beans:property>
		<beans:property name="rememberMeServices" ref="rememberMeServices"></beans:property>
		<beans:property name="userDetailsService" ref="customjdbcUserService"></beans:property>
	</beans:bean>
	
	<beans:bean id="dataSource"
		class="org.springframework.jdbc.datasource.DriverManagerDataSource">
	 	<beans:property name="driverClassName" value="com.mysql.jdbc.Driver" />
		<beans:property name="url" value="jdbc:mysql://localhost:3306/jsf-spring-security" />
		<beans:property name="username" value="root" />
		<beans:property name="password" value="root" />
   </beans:bean>
   
   <beans:bean id="customjdbcUserService" class="com.mumz.jsfspringsec.dao.CustomJDBCDaoImpl">
   		<beans:property name="dataSource" ref="dataSource"/>
		<beans:property name="enableAuthorities" value="false"/>
		<beans:property name="enableGroups" value="true"></beans:property>
		<beans:property name="usersByUsernameQuery">
			<beans:value>SELECT JSF_SPRING_SEC_USERS_USERNAME, JSF_SPRING_SEC_USERS_PASSWORD, JSF_SPRING_SEC_USERS_ENABLED FROM JSF_SPRING_SEC_USERS WHERE JSF_SPRING_SEC_USERS_USERNAME = ?</beans:value>
		</beans:property>
		<beans:property name="authoritiesByUsernameQuery">
			<beans:value>
				 SELECT JSF_SPRING_SEC_ROLES_USERNAME,JSF_SPRING_SEC_ROLES_ROLE_NAME from JSF_SPRING_SEC_ROLES where JSF_SPRING_SEC_ROLES_USERNAME = ?
			</beans:value>
		</beans:property>
		<beans:property name="groupAuthoritiesByUsernameQuery">
			<beans:value>
				SELECT 
						GROUPDTLS.JSF_SPRING_SEC_GROUPS_GROUP_ID, 
						GROUPDTLS.JSF_SPRING_SEC_GROUPS_GROUP_NAME, 
						GROUPPERMISSION.JSF_SPRING_SEC_GROUP_AUTHORITIES_AUTHORITY 
				FROM 
						JSF_SPRING_SEC_GROUPS GROUPDTLS, 
						JSF_SPRING_SEC_GROUP_AUTHORITIES GROUPPERMISSION, 
						JSF_SPRING_SEC_GROUP_MEMBERS GROUPMEMBERS, 
						JSF_SPRING_SEC_USERS USERS 
				WHERE 
						USERS.JSF_SPRING_SEC_USERS_USERNAME = ? AND 
						GROUPMEMBERS.JSF_SPRING_SEC_GROUP_MEMBERS_USER_ID = USERS.PK_JSF_SPRING_SEC_USERS AND 
						GROUPMEMBERS.JSF_SPRING_SEC_GROUP_MEMBERS_GROUP_ID = GROUPDTLS.JSF_SPRING_SEC_GROUPS_GROUP_ID AND 
						GROUPPERMISSION.JSF_SPRING_SEC_GROUP_AUTHORITIES_GROUP_ID = GROUPDTLS.JSF_SPRING_SEC_GROUPS_GROUP_ID 
			</beans:value>
		</beans:property>
   </beans:bean>
   
	<beans:bean id="rememberMeServices"
		class="org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices">
		<beans:property name="key" value="jsfspring-sec" /> 
		<beans:property	name="userDetailsService" ref="customjdbcUserService" /> 
		<beans:property	name="alwaysRemember" value="true" /> 
		<beans:property	name="tokenValiditySeconds" value="60" /> 
	</beans:bean>
	
	<beans:bean id="rememberMeAuthenticationProvider" 
		class="org.springframework.security.authentication.RememberMeAuthenticationProvider">
  		<beans:property name="key" value="jsfspring-sec"/>
	</beans:bean>
	
	<beans:bean id="rememberMeFilter" 
		class="org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter">
  		<beans:property name="rememberMeServices" ref="rememberMeServices"/>
  		<beans:property name="authenticationManager" ref="authenticationManager" />
	</beans:bean>
	
	<beans:bean class="org.springframework.security.authentication.encoding.ShaPasswordEncoder" id="passwordEncoder">
	</beans:bean>
	
	<beans:bean id="databasePasswordEncrypter" class="com.mumz.jsfspringsec.dao.security.DBPasswordEncrypterBean" init-method="encryptDBPassword" depends-on="dataSource">
		<beans:property name="passwordEncoder" ref="passwordEncoder"></beans:property>
		<beans:property name="dataSource" ref="dataSource"></beans:property>
		<beans:property name="userDetailsService" ref="customjdbcUserService"></beans:property>
		<beans:property name="saltSource" ref="saltSource"></beans:property>
		<beans:property name="selectQuery">
			<beans:value>SELECT JSF_SPRING_SEC_USERS_USERNAME, JSF_SPRING_SEC_USERS_PASSWORD, JSF_SPRING_SEC_USERS_ENCRYPTED FROM JSF_SPRING_SEC_USERS WHERE (JSF_SPRING_SEC_USERS_ENCRYPTED='' || JSF_SPRING_SEC_USERS_ENCRYPTED IS NULL)</beans:value>
		</beans:property>
		<beans:property name="updateQuery">
			<beans:value>UPDATE JSF_SPRING_SEC_USERS SET JSF_SPRING_SEC_USERS_PASSWORD = ?, JSF_SPRING_SEC_USERS_ENCRYPTED='YES' WHERE JSF_SPRING_SEC_USERS_USERNAME = ? </beans:value>
		</beans:property>
	</beans:bean>
	
	<beans:bean class="org.springframework.security.authentication.dao.ReflectionSaltSource" id="saltSource">
		<beans:property name="userPropertyToUse" value="username"/>
	</beans:bean>
</beans:beans>

JdbcDaoImpl of Spring Security needs three queries in order to fetch the required information correctly, namely usersByUsernameQuery, authoritiesByUsernameQuery and groupAuthoritiesByUsernameQuery. In our previous examples we were not using Group-Based access control hence we didn’t define groupAuthoritiesByUsernameQuery. Updated Schema is below, note we have not deleted JSF_SPRING_SEC_ROLES table but in this version of application we ain’t using it.


Database Schema:

SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0;
SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;
SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='TRADITIONAL';

DROP SCHEMA IF EXISTS `JSF-SPRING-SECURITY` ;
CREATE SCHEMA IF NOT EXISTS `JSF-SPRING-SECURITY` DEFAULT CHARACTER SET UTF8 ;
USE `JSF-SPRING-SECURITY` ;

-- -----------------------------------------------------
-- TABLE `JSF-SPRING-SECURITY`.`JSF_SPRING_SEC_GROUPS`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `JSF-SPRING-SECURITY`.`JSF_SPRING_SEC_GROUPS` ;

CREATE  TABLE IF NOT EXISTS `JSF-SPRING-SECURITY`.`JSF_SPRING_SEC_GROUPS` (
  `JSF_SPRING_SEC_GROUPS_GROUP_ID` INT(11) NOT NULL AUTO_INCREMENT ,
  `JSF_SPRING_SEC_GROUPS_GROUP_NAME` VARCHAR(45) NOT NULL ,
  PRIMARY KEY (`JSF_SPRING_SEC_GROUPS_GROUP_ID`) )
ENGINE = INNODB
AUTO_INCREMENT = 1
DEFAULT CHARACTER SET = UTF8;


-- -----------------------------------------------------
-- TABLE `JSF-SPRING-SECURITY`.`JSF_SPRING_SEC_GROUP_AUTHORITIES`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `JSF-SPRING-SECURITY`.`JSF_SPRING_SEC_GROUP_AUTHORITIES` ;

CREATE  TABLE IF NOT EXISTS `JSF-SPRING-SECURITY`.`JSF_SPRING_SEC_GROUP_AUTHORITIES` (
  `JSF_SPRING_SEC_GROUP_AUTHORITIES_AUTHORITY_ID` INT(11) NOT NULL AUTO_INCREMENT ,
  `JSF_SPRING_SEC_GROUP_AUTHORITIES_GROUP_ID` INT(11) NOT NULL ,
  `JSF_SPRING_SEC_GROUP_AUTHORITIES_AUTHORITY` VARCHAR(45) NOT NULL ,
  PRIMARY KEY (`JSF_SPRING_SEC_GROUP_AUTHORITIES_AUTHORITY_ID`) ,
  INDEX `FK_JSF_SPRING_SEC_GROUP_AUTHORITIES_GROUP_ID` (`JSF_SPRING_SEC_GROUP_AUTHORITIES_GROUP_ID` ASC) ,
  CONSTRAINT `FK_JSF_SPRING_SEC_GROUP_AUTHORITIES_GROUP_ID`
    FOREIGN KEY (`JSF_SPRING_SEC_GROUP_AUTHORITIES_GROUP_ID` )
    REFERENCES `JSF-SPRING-SECURITY`.`JSF_SPRING_SEC_GROUPS` (`JSF_SPRING_SEC_GROUPS_GROUP_ID` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = INNODB
AUTO_INCREMENT = 1
DEFAULT CHARACTER SET = UTF8;


-- -----------------------------------------------------
-- TABLE `JSF-SPRING-SECURITY`.`JSF_SPRING_SEC_USERS`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `JSF-SPRING-SECURITY`.`JSF_SPRING_SEC_USERS` ;

CREATE  TABLE IF NOT EXISTS `JSF-SPRING-SECURITY`.`JSF_SPRING_SEC_USERS` (
  `PK_JSF_SPRING_SEC_USERS` INT(11) NOT NULL AUTO_INCREMENT ,
  `JSF_SPRING_SEC_USERS_USERNAME` VARCHAR(45) NOT NULL ,
  `JSF_SPRING_SEC_USERS_PASSWORD` VARCHAR(255) NOT NULL ,
  `JSF_SPRING_SEC_USERS_ENABLED` VARCHAR(5) NOT NULL ,
  `JSF_SPRING_SEC_USERS_CREATED_DT` VARCHAR(45) NOT NULL ,
  `JSF_SPRING_SEC_USERS_MODIFIED_DT` VARCHAR(45) NULL DEFAULT NULL ,
  `JSF_SPRING_SEC_USERS_ENCRYPTED` VARCHAR(45) NULL DEFAULT NULL ,
  PRIMARY KEY (`PK_JSF_SPRING_SEC_USERS`) ,
  UNIQUE INDEX `JSF_SPRING_SEC_USERS_USERNAME_UNIQUE` (`JSF_SPRING_SEC_USERS_USERNAME` ASC) )
ENGINE = INNODB
AUTO_INCREMENT = 1
DEFAULT CHARACTER SET = UTF8;


-- -----------------------------------------------------
-- TABLE `JSF-SPRING-SECURITY`.`JSF_SPRING_SEC_GROUP_MEMBERS`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `JSF-SPRING-SECURITY`.`JSF_SPRING_SEC_GROUP_MEMBERS` ;

CREATE  TABLE IF NOT EXISTS `JSF-SPRING-SECURITY`.`JSF_SPRING_SEC_GROUP_MEMBERS` (
  `JSF_SPRING_SEC_GROUP_MEMBERS_ID` INT(11) NOT NULL ,
  `JSF_SPRING_SEC_GROUP_MEMBERS_GROUP_ID` INT(11) NOT NULL ,
  `JSF_SPRING_SEC_GROUP_MEMBERS_USER_ID` INT(11) NOT NULL ,
  PRIMARY KEY (`JSF_SPRING_SEC_GROUP_MEMBERS_ID`) ,
  INDEX `FK_JSF_SPRING_SEC_GROUP_MEMBERS_GROUP_ID` (`JSF_SPRING_SEC_GROUP_MEMBERS_GROUP_ID` ASC) ,
  INDEX `FK_JSF_SPRING_SEC_GROUP_MEMBERS_USER_ID` (`JSF_SPRING_SEC_GROUP_MEMBERS_USER_ID` ASC) ,
  CONSTRAINT `FK_JSF_SPRING_SEC_GROUP_MEMBERS_GROUP_ID`
    FOREIGN KEY (`JSF_SPRING_SEC_GROUP_MEMBERS_GROUP_ID` )
    REFERENCES `JSF-SPRING-SECURITY`.`JSF_SPRING_SEC_GROUPS` (`JSF_SPRING_SEC_GROUPS_GROUP_ID` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION,
  CONSTRAINT `FK_JSF_SPRING_SEC_GROUP_MEMBERS_USER_ID`
    FOREIGN KEY (`JSF_SPRING_SEC_GROUP_MEMBERS_USER_ID` )
    REFERENCES `JSF-SPRING-SECURITY`.`JSF_SPRING_SEC_USERS` (`PK_JSF_SPRING_SEC_USERS` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = INNODB
DEFAULT CHARACTER SET = UTF8;


-- -----------------------------------------------------
-- TABLE `JSF-SPRING-SECURITY`.`JSF_SPRING_SEC_ROLES`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `JSF-SPRING-SECURITY`.`JSF_SPRING_SEC_ROLES` ;

CREATE  TABLE IF NOT EXISTS `JSF-SPRING-SECURITY`.`JSF_SPRING_SEC_ROLES` (
  `PK_JSF_SPRING_SEC_ROLES` INT(11) NOT NULL AUTO_INCREMENT ,
  `JSF_SPRING_SEC_ROLES_USERNAME` VARCHAR(45) NOT NULL ,
  `JSF_SPRING_SEC_ROLES_ROLE_NAME` VARCHAR(45) NOT NULL ,
  `JSF_SPRING_SEC_ROLES_CREATED_DT` VARCHAR(45) NOT NULL ,
  `JSF_SPRING_SEC_ROLES_MODIFIED_DT` VARCHAR(45) NULL DEFAULT NULL ,
  PRIMARY KEY (`PK_JSF_SPRING_SEC_ROLES`) ,
  INDEX `FK_USERNAME_JSF_SPRING_SEC_ROLES` (`JSF_SPRING_SEC_ROLES_USERNAME` ASC) ,
  CONSTRAINT `FK_USERNAME_JSF_SPRING_SEC_ROLES`
    FOREIGN KEY (`JSF_SPRING_SEC_ROLES_USERNAME` )
    REFERENCES `JSF-SPRING-SECURITY`.`JSF_SPRING_SEC_USERS` (`JSF_SPRING_SEC_USERS_USERNAME` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = INNODB
AUTO_INCREMENT = 1
DEFAULT CHARACTER SET = UTF8;



SET SQL_MODE=@OLD_SQL_MODE;
SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;

Data Dump

INSERT INTO `jsf_spring_sec_users` (`PK_JSF_SPRING_SEC_USERS`, `JSF_SPRING_SEC_USERS_USERNAME`, `JSF_SPRING_SEC_USERS_PASSWORD`, `JSF_SPRING_SEC_USERS_ENABLED`, `JSF_SPRING_SEC_USERS_CREATED_DT`, `JSF_SPRING_SEC_USERS_MODIFIED_DT`, `JSF_SPRING_SEC_USERS_ENCRYPTED`) VALUES (1,'admin','a40546cc4fd6a12572828bb803380888ad1bfdab','true','2012-07-17 10:07:41','2012-07-17 10:07:41','YES'),(2,'guest','225f8735e27294f150ed51ad0a9dbc586cdeedc3','true','2012-07-17 16:59:13','2012-07-17 16:59:13','YES');

INSERT INTO `jsf_spring_groups` (`JSF_SPRING_SEC_GROUPS_GROUP_ID`, `JSF_SPRING_SEC_GROUPS_GROUP_NAME`) VALUES (1,'ADMIN'),(2,'GUEST');

INSERT INTO `jsf_spring_sec_group_authorities` (`JSF_SPRING_SEC_GROUP_AUTHORITIES_AUTHORITY_ID`, `JSF_SPRING_SEC_GROUP_AUTHORITIES_GROUP_ID`, `JSF_SPRING_SEC_GROUP_AUTHORITIES_AUTHORITY`) VALUES (1,1,'ROLE_USER'),(2,2,'ROLE_GUEST'),(3,1,'ROLE_ADMIN');

INSERT INTO `jsf_spring_sec_group_members` (`JSF_SPRING_SEC_GROUP_MEMBERS_ID`, `JSF_SPRING_SEC_GROUP_MEMBERS_GROUP_ID`, `JSF_SPRING_SEC_GROUP_MEMBERS_USER_ID`) VALUES (1,1,1),(2,2,2);

INSERT INTO `jsf_spring_sec_roles` (`PK_JSF_SPRING_SEC_ROLES`, `JSF_SPRING_SEC_ROLES_USERNAME`, `JSF_SPRING_SEC_ROLES_ROLE_NAME`, `JSF_SPRING_SEC_ROLES_CREATED_DT`, `JSF_SPRING_SEC_ROLES_MODIFIED_DT`) VALUES (1,'admin','ROLE_USER','2012-07-17 16:57:49','2012-07-17 16:57:49'),(2,'admin','ROLE_ADMIN','2012-07-17 16:58:08','2012-07-17 16:58:08'),(3,'guest','ROLE_USER','2012-07-17 16:59:17','2012-07-17 16:59:17');

That’s all the change that we need to do to move from user based access control to Group-Based Access control.

There is one comment

  1. Nick

    I think you have a minor error…

    INSERT INTO `jsf_spring_groups` (`JSF_SPRING_SEC_GROUPS_GROUP_ID`, `JSF_SPRING_SEC_GROUPS_GROUP_NAME`) VALUES (1,’ADMIN’),(2,’GUEST’);

    jsf_spring_groups
    should be
    jsf_spring_sec_groups

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s