Popular Posts

Spring Security and Hibernate Integration

Introduction

Introduction

Spring security is the one of the best security framework for Java based web application.

This tutorial demonstrates integration of spring security framework using Hibernate Annotations.

Code snippets provided in this tutorial will helps you to quickly add to your application.

authn
This Tutorial doesn’t cover basics of spring & spring security in details.you can get those details from official sites.

Configurations

Spring-security.xml looks like below. Create appropriate pages.

Role and URL access configured as shown below

spring-security.xml
       <http auto-config="true" access-denied-page="/accessdenied.jsp">
              <intercept-url pattern="/tester/*" access="ROLE_TESTER" />
              <intercept-url pattern="/admin/*" access="ROLE_ADMIN" />
              <intercept-url pattern="/manager/*" access="ROLE_MANAGER" />
              <intercept-url pattern="/landing.htm"
                     access="ROLE_MANAGER,ROLE_ADMIN,ROLE_TESTER" />


              <form-login login-page="/login.htm" default-target-url="/landing.htm"
                     authentication-failure-url="/loginfailed.htm"
                     always-use-default-target="true" />
              <!-- <http-basic/> -->
              <logout invalidate-session="true" logout-success-url="/landing.htm" />
       </http>

Authentication providers user JDBC database dataSource service

authentication-provider.xml
<authentication-manager>
              <authentication-provider>
                     <jdbc-user-service data-source-ref="dataSource"
                           users-by-username-query=" select username,password,
              enabled from users where USERNAME=?"
                           authorities-by-username-query=" select
              u.username, ur.authority from users u, user_roles ur where u.user_id = ur.user_id
              and u.username =? " />
              </authentication-provider>
</authentication-manager>

This part we will change to works with hibernate

Change the authentication provider class as shown below with custom authentication provider.

configuration.xml
  <beans:bean id="authenticationProvider"
              class="com.vajjala.testng.adtf.service.EmployeeServiceImpl" /> (1)

 <authentication-manager>
              <authentication-provider user-service-ref="authenticationProvider"/> (2)
      </authentication-manager>

Here we have two bean definitions.

1 Custom authentication provider and
2 Authentication manager which is referring the custom user-service.

Below is complete source code for Custom EmployeeServiceImpl it must implement directly or indirectly the interface UserDetailsService.

EmployeeServiceImpl.java
package com.vajjala.testng.adtf.service;
import java.util.List;
import org.apache.log4j.Logger;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import com. vajjala.testng.adtf.hbm.Authority;
import com. vajjala.testng.adtf.hbm.Employee;
import com. vajjala.testng.adtf.hbm.Organization;

@Service("employeeService")
public class EmployeeServiceImpl implements EmployeeService {

                @Autowired
                private SessionFactory sessionFactory;
                Session session;
                Transaction transaction;
                public Logger logger = Logger.getRootLogger();

            @SuppressWarnings("all")
            public List<Employee> getAllUsers() {
                        session = sessionFactory.openSession();
                        Query query = session.createQuery("from Employee ");
                        return query.list();
            }
            public void saveAuthority(Authority authority) {
                        try {

                                    session = sessionFactory.openSession();
                                    transaction = session.beginTransaction();
                                    session.save(authority);
                                    transaction.commit();
                        } catch (Exception e) {
                                    transaction.rollback();
                        }

            }

            public void saveOrganization(Organization organization) {

                        try {
                                    session = sessionFactory.openSession();
                                    transaction = session.beginTransaction();
                                    session.save(organization);
                                    transaction.commit();
                        } catch (Exception e) {
                                    transaction.rollback();
                        }
            }



            public void saveEmployee(Employee employee) {

                        try {

                                    session = sessionFactory.openSession();
                                    transaction = session.beginTransaction();
                                    session.save(employee);
                                    transaction.commit();
                        } catch (Exception e) {
                                    transaction.rollback();
                        }

            }

                @Override
                public UserDetails loadUserByUsername(String username)
                                                throws UsernameNotFoundException {

                                session = sessionFactory.openSession();
                                username = (username == null) ? "" : username;
                                Query query =
 session.createQuery("from Employee where  username=:username");
                                query.setParameter("username", username);

                                List results = query.list();
                                if (results.size() < 1) {

                                 throw new UsernameNotFoundException(username + "not found");

                                }

                                return (UserDetails) results.get(0);

                }

EmployeeService which extending the org.springframework.security.core.userdetails.UserDetailsService which contains only one method public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

EmployeeService.java
package com.vajjala.testng.adtf.service;
import java.util.List;
import org.springframework.security.core.userdetails.UserDetailsService;
import com.adaequare.testng.adtf.hbm.Authority;
import com.adaequare.testng.adtf.hbm.Employee;
import com.adaequare.testng.adtf.hbm.Organization;

public interface EmployeeService extends UserDetailsService{

           //your custom method definitions comes in this service layer
           void saveEmployee(Employee employee);
           void saveAuthority(Authority authority);

           void saveOrganization(Organization organization);

           List<Employee> getAllUsers();

}

it is returning UserDetails object which is again provided by the spring security framework.

But query is having Employee Object which is again custom object.

query.java
Query query = session.createQuery("from Employee where username=:username");

Employee Object which implements UserDetails interface.

Employee object is having some of custom method some of them are implementation for UserDetails class implementation those are really very important for secure web application. Those methods are highlighter in bold.

Employee.java
package com.vajjala.testng.adtf.hbm;

import java.io.Serializable;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.OneToMany;
import javax.persistence.Table;

import org.hibernate.annotations.Type;
import org.springframework.security.core.userdetails.UserDetails;

@Entity
@Table(name = "USERS")
public class Employee implements UserDetails, Serializable {

                private static final long serialVersionUID = 8825646974241476909L;

                @Id
                @GeneratedValue(strategy = GenerationType.AUTO)
                @Column(name = "USER_ID")
                private Long userId;

                @Column(name = "USERNAME", nullable = false, length = 30, unique = true)
                private String username;

                @Column(name = "PASSWORD", nullable = false, length = 30)
                private String password;

                @Column(name = "DESIGNATION", length = 30)
                private String designation;

                @Column(name = "FIRSTNAME", nullable = false, length = 30)
                private String firstname;

                @Column(name = "LASTNAME", nullable = true, length = 30)
                private String lastname;

                @Column(name = "EMAIL", nullable = true, length = 30, unique = true)
                private String email;

                @Column(name = "PHONE", nullable = true, length = 30)
                private String phone;

                public Long getUserId() {
                                return userId;
                }

                public void setUserId(Long userId) {
                                this.userId = userId;
                }

                @Override
                public String getUsername() {
                                return username;
                }

                @Override
                public String getPassword() {
                                return password;
                }

                public void setUsername(String username) {
                                this.username = username;
                }


                public void setPassword(String password) {
                                this.password = password;
                }

                public String getDesignation() {
                                return designation;
                }

                public void setDesignation(String designation) {
                                this.designation = designation;
                }

                @OneToMany(cascade = CascadeType.ALL)
                @JoinTable(name = "USER_ROLES", joinColumns = { @JoinColumn(name = "USER_ID") }, inverseJoinColumns = { @JoinColumn(name = "AUTHORITY") })
                private Set<Authority> authorities;

                public void setAuthorities(Set<Authority> authorities) {
                                this.authorities = authorities;
                }

                @Override
                public Set<Authority> getAuthorities() {
                                return authorities;
                }

                @OneToMany(cascade = CascadeType.ALL)
                @JoinTable(name = "USER_ORGS", joinColumns = { @JoinColumn(name = "USER_ID") }, inverseJoinColumns = { @JoinColumn(name = "ORG_ID") })
                private Set<Organization> organizations;

                public Set<Organization> getOrganizations() {
                                return organizations;
                }

                public void setOrganizations(Set<Organization> organizations) {
                                this.organizations = organizations;
                }

                public String getFirstname() {
                                return firstname;
                }

                public void setFirstname(String firstname) {
                                this.firstname = firstname;
                }

                public String getLastname() {
                                return lastname;
                }

                public void setLastname(String lastname) {
                                this.lastname = lastname;
                }

                public String getEmail() {
                                return email;
                }

                public void setEmail(String email) {
                                this.email = email;
                }

                public String getPhone() {
                                return phone;
                }

                public void setPhone(String phone) {
                                this.phone = phone;
                }

                @Column(name = "accountNonExpired")
                @Type(type = "yes_no")
                private boolean accountNonExpired;

                @Column(name = "credentialsNonExpired")
                @Type(type = "yes_no")
                private boolean credentialsNonExpired;

                @Column(name = "enabled")
                @Type(type = "yes_no")
                private boolean enabled;

                @Column(name = "accountNonLocked")
                @Type(type = "yes_no")
                private boolean accountNonLocked;

                public void setAccountNonLocked(boolean accountNonLocked) {
                                this.accountNonLocked = accountNonLocked;
                }

                public void setAccountNonExpired(boolean accountNonExpired) {
                                this.accountNonExpired = accountNonExpired;
                }

                public void setCredentialsNonExpired(boolean credentialsNonExpired) {
                                this.credentialsNonExpired = credentialsNonExpired;
                }

                public void setEnabled(boolean enabled) {
                                this.enabled = enabled;
                }

                @Override
                public boolean isAccountNonExpired() {
                                return accountNonExpired;
                }

                @Override
                public boolean isAccountNonLocked() {

                                return accountNonLocked;
                }

                @Override
                public boolean isCredentialsNonExpired() {

                                return credentialsNonExpired;
                }

                @Override
                public boolean isEnabled() {

                                return enabled;
                }

}

There is one more method which we need to discuss is the

        @Override
        public Set<Authority> getAuthorities() {
             return authorities;
        }

Here Authority class which again must implement the interface called GrantedAuthority

All the methods must follow java bean standard implementation. and there is only one method from GrantedAuthority interface is and must have one default constructor .

        @Override
        public String getAuthority() {
           return authority;
        }
Authority.java
package com.vajjala.testng.adtf.hbm;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import org.springframework.security.core.GrantedAuthority;

@Entity
@Table(name = "ROLES")
public class Authority implements Serializable, GrantedAuthority {

                private static final long serialVersionUID = 418847605346388857L;


                public Authority(){
                                // must have one default constructor
                }

                @Id
                @Column(name = "AUTHORITY")
                private String authority;

                @Column(name = "ROLE_NAME",nullable=false,unique=true)
                private String roleName;

                public String getRoleName() {
                                return roleName;
                }

                public void setRoleName(String roleName) {
                                this.roleName = roleName;
                }

                @Override
                public String getAuthority() {
                                return authority;
                }

                public void setAuthority(String authority) {
                                this.authority = authority;
                }

                public Authority(String authority,String roleName){
                                this.authority=authority;
                                this.roleName=roleName;
                }

}

Finally all you need to declare in spring-database.xml file as shown below.

applicationContext.xml
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:p="http://www.springframework.org/schema/p"xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/tx
              http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">

       <context:property-placeholder location="classpath:jdbc.properties" />
       <tx:annotation-driven transaction-manager="hibernateTransactionManager" />
       <bean id="hibernateTransactionManager"
              class="org.springframework.orm.hibernate3.HibernateTransactionManager">
              <property name="sessionFactory" ref="sessionFactory" />
       </bean>
       <bean id="dataSource"
              class="org.springframework.jdbc.datasource.DriverManagerDataSource">
              <property name="driverClassName" value="${database.driver}" />
              <property name="url" value="${database.url}" />
              <property name="username" value="${database.user}" />
              <property name="password" value="${database.password}" />
       </bean>

       <bean id="sessionFactory"
              class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
              <property name="dataSource" ref="dataSource" />
              <property name="annotatedClasses">
                     <list>
                           <value>com.vajjala.testng.adtf.hbm.Employee</value>
                           <value>com. vajjala .testng.adtf.hbm.Authority</value>
                           <value>com. vajjala .testng.adtf.hbm.Organization</value>
                     </list>
              </property>
              <property name="hibernateProperties">
                     <props>
                           <prop key="hibernate.dialect">${hibernate.dialect}</prop>
                           <prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
                            <prop key="hibernate.hbm2ddl.auto">update</prop>

                     </props>
              </property>
       </bean>
</beans>

Jdbc.properties file can have Database specific settings (here I done for mysql)

jdbc.properties
database.driver=com.mysql.jdbc.Driver
database.url=jdbc:mysql://localhost:3306/spring
database.user=root
database.password=secrete
hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
hibernate.show_sql=false

now create form with spring security action and properties as shown below

login.html
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib prefix="auth" uri="spring.security.taglib.tld"%>
<form name='loginform' action="<c:url value='j_spring_security_check' />"method='POST'        autocomplete="off">
<table class="loginform">
<tr>
<td colspan="3" class="username" height="20px"><c:if   test="${not empty error}">
<div class="error">
       ${sessionScope["SPRING_SECURITY_LAST_EXCEPTION"].message}</div>
</c:if></td>
</tr>
<tr>

<td class="username">Username:<input type='text' name='j_username' value='' >
</td>


<td class="username">Password: <input type='password'                       name='j_password'  /></td>

<td width="30px"><input type="submit" name="login" value="Sign In"  />
</td>

</tr>

</table>
</form>
SecurityContextHolder will return an object of type Employee which contains all your custom method to use for your application
Employee employee = (Employee) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
request.getSession().setAttribute("FIRSTNAME",eployee.getFirstname());

Create one bean processor to create one user automatically when application starts that can be achieved through below code.

ApplicationProcessor.java
package com.vajjala.testng.adtf.processor;
import java.util.HashSet;
import org.apache.log4j.Logger;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import com. vajjala.testng.adtf.hbm.Authority;
import com. vajjala.testng.adtf.hbm.Employee;
import com. vajjala.testng.adtf.hbm.Organization;
import com. vajjala.testng.adtf.service.EmployeeService;

public class ApplicationProcessor implements ApplicationContextAware {

        public Logger logger = Logger.getRootLogger();

        EmployeeService employeeService;

        @Override
        public void setApplicationContext(ApplicationContext context)
                                                        throws BeansException {

                    employeeService = (EmployeeService) context.getBean("employeeService");

                    if (employeeService.getAllUsers().isEmpty()) {
                                            initData(context);
                    }
                    System.out.println("Application Ready to Use");
        }

        private void initData(ApplicationContext context) {

                try {
                    System.out.println("######  INITILIZING DEFAULT DATA ##########");

                    Authority authority = new Authority("ROLE_ADMIN", "ADMINSTRATOR");
                    employeeService.saveAuthority(authority);

                    employeeService.saveAuthority(new Authority("ROLE_TESTER",
                                                                    "TEST ENGINEER"));
                    logger.info("ROLES CREATED");

                    Organization organization = new Organization("TRVAJJALA");
                    employeeService.saveOrganization(organization);

                    logger.info("ORGANIZATIONS CREATED");

                    Employee details = new Employee();
                    details.setUsername("admin");
                    details.setPassword("1234");
                    details.setAccountNonExpired(true);
                    details.setAccountNonLocked(true);
                    details.setCredentialsNonExpired(true);

                    details.setEnabled(true);
                    details.setFirstname("xxxxxxxx");
                    details.setLastname("xxxxxxx");
                    details.setPhone("00000000");
                    details.setEmail("trvajjala@adaequare.com");
                    details.setDesignation("Sr.Software Engineer");

                    details.setAuthorities(new HashSet<Authority>());
                    details.getAuthorities().add(authority);

                    details.setOrganizations(new HashSet<Organization>());
                    details.getOrganizations().add(organization);

                    employeeService.saveEmployee(details);

                } catch (Exception e) {

                }

        }

}

Customizing Spring Security Messages can done through below Custom Adapter

HibernateAuthenticationProvider.java
package com.vajjala.testng.adtf.provider;
import org.springframework.dao.DataAccessException;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.authentication.dao.SaltSource;
import org.springframework.security.authentication.encoding.PasswordEncoder;
import org.springframework.security.authentication.encoding.PlaintextPasswordEncoder;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.util.Assert;

public class HibernateAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {


                private PasswordEncoder passwordEncoder = new PlaintextPasswordEncoder();

                private SaltSource saltSource;

                private UserDetailsService userDetailsService;

                private boolean includeDetailsObject = true;

                protected void additionalAuthenticationChecks(UserDetails userDetails,
                                                UsernamePasswordAuthenticationToken authentication)
                                                throws AuthenticationException {
                                Object salt = null;

                                if (this.saltSource != null) {
                                                salt = this.saltSource.getSalt(userDetails);
                                }

                                if (authentication.getCredentials() == null) {
                                                throw new BadCredentialsException("Enter Username and Password ");
                                }

                                String presentedPassword = authentication.getCredentials().toString();

                                if (!passwordEncoder.isPasswordValid(userDetails.getPassword(),
                                                                presentedPassword, salt)) {
                                                throw new BadCredentialsException(" Invalid Username or Password ");
                                }
                }

                protected void doAfterPropertiesSet() throws Exception {
                                Assert.notNull(this.userDetailsService,
                                                                "A UserDetailsService must be set");
                }

                protected  UserDetails retrieveUser(String username,
                                                UsernamePasswordAuthenticationToken authentication)
                                                throws AuthenticationException {
                                UserDetails loadedUser;

                                if((username==null)||(username.trim().length()<1)){
                                                throw new AuthenticationServiceException(" Please Enter Valid Username ");
                                }

                                try {
                                                loadedUser = getUserDetailsService().loadUserByUsername(
                                                                                username);
                                } catch (DataAccessException repositoryProblem) {
                                                throw new AuthenticationServiceException("Enter Valid Username or Password");
                                }

                                if (loadedUser == null) {
                                                throw new AuthenticationServiceException(
                                                                                "Invalid Username or Password ");
                                }
                                return loadedUser;
                }
                public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
                                this.passwordEncoder = passwordEncoder;
                }

                protected PasswordEncoder getPasswordEncoder() {
                                return passwordEncoder;
                }


                public void setSaltSource(SaltSource saltSource) {
                                this.saltSource = saltSource;
                }

                protected SaltSource getSaltSource() {
                                return saltSource;
                }

                public void setUserDetailsService(UserDetailsService userDetailsService) {
                                this.userDetailsService = userDetailsService;
                }

                protected UserDetailsService getUserDetailsService() {
                                return userDetailsService;
                }

                protected boolean isIncludeDetailsObject() {
                                return includeDetailsObject;
                }

                public void setIncludeDetailsObject(boolean includeDetailsObject) {
                                this.includeDetailsObject = includeDetailsObject;
                }

}

Spring Security configuration

spring-security.xml
 <beans:bean
        id="userdetailsService"
        class="com.adaequare.testng.adtf.service.EmployeeServiceImpl">
    </beans:bean>

    <beans:bean
        id="authenticationProvider"
        class="com.adaequare.testng.adtf.provider.HibernateAuthenticationProvider">

        <beans:property
            name="userDetailsService"
            ref="userdetailsService" />
    </beans:bean>

    <authentication-manager>
        <authentication-provider ref="authenticationProvider"/>
    </authentication-manager>

7 comments:

  1. You should have ManyToMany relationship between Authority and Employee because other way it won't work properly. I've spend two days to fix the problem of adding multiple users. when I try to add another user with the same authority it goes with exception that I'm trying to add a record with primary key that already exist. Cheers ;)

    ReplyDelete
  2. But this tutorial is very good it helped me a lot) thanks

    ReplyDelete
  3. This tutorial is very good and neatly written, after long R & D I could able to set up complete application.
    is it possible to share data for the all the tables

    ReplyDelete
  4. Excellent..Really need What I want...Thanks for such good explanation.

    ReplyDelete
  5. This tutorial is excellent.... Thanks For working code and explaination
    -- Nilesh

    ReplyDelete
  6. thank you for sharing this informative blog.. this blog really helpful for everyone.. explanation are clear so easy to understand...

    java training online | java j2ee training online | java j2ee training in chennai | java j2ee training and placement

    ReplyDelete