Spring Security and Hibernate Integration
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.
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
<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-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.
<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
.
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;
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 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.
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;
}
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.
<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)
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
<%@ 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.
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
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
<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>
Comments
Post a Comment