Introduction :
This is a step-by-step tutorial that helps you build a Spring security-Hibernate application easily in a clear and concise way.This article is all about implementing Spring Security with custom login in your Spring MVC web application to secure a URL access with database authentication using hibernate.
In my previous article,I explained Spring Security by placing user credentials in XML configuration. How ever it is not safe to keep user details in XML file.So let us use database to secure & maintain authentication and authorization details.
Technologies used :
java 1.7
tomcat 8
spring 4.1.6.RELEASE
spring-security 4.0.1.RELEASE
Hibernate 4.3.11.Final
Mysql DB (driver version 5.1.30)
Maven 3
Step 1: Maven dependencies
<properties> <spring-version>4.1.6.RELEASE</spring-version> <spring-orm-version>4.1.1.RELEASE</spring-orm-version> <spring-security-version>4.0.1.RELEASE</spring-security-version> <hibernate.version>4.3.11.Final</hibernate.version> <mysql.driver.version>5.1.30</mysql.driver.version> </properties> <dependencies> <!-- spring dependencies --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>${spring-orm-version}</version> </dependency> <!-- spring security dependencies --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-core</artifactId> <version>${spring-security-version}</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>${spring-security-version}</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>${spring-security-version}</version> </dependency> <!-- hibernate dependencies --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-entitymanager</artifactId> <version>${hibernate.version}</version> </dependency> <!-- mysql driver --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.driver.version}</version> </dependency> </dependencies> <build> <finalName>spring-security-custom-login-using-hibernate</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.3</version> <configuration> <source>1.7</source> <target>1.7</target> </configuration> </plugin> </plugins> </build>
Step 2: Login Database Diagram & Script's :
At first we use two tables USER & ROLE.As single user can have multiple roles and each role can be part of multiple users, so there is MANY to MANY association between USER and ROLE.And database diagram looks like
Database scripts for above diagram
create table USER ( USER_ID varchar(255) not null, ACTIVE integer not null, NAME varchar(32) not null, PASSWORD varchar(64) not null, primary key (USER_ID)) create table ROLE ( ROLE_ID integer not null auto_increment, NAME varchar(32) not null, primary key (ROLE_ID)) create table USER_ROLE ( USER_ID varchar(255) not null, ROLE_ID integer not null, primary key (USER_ID, ROLE_ID)) alter table USER_ROLE add constraint FK_oqmdk7xj0ainhxpvi79fkaq3y foreign key (ROLE_ID) references ROLE (ROLE_ID) alter table USER_ROLE add constraint FK_j2j8kpywaghe8i36swcxv8784 foreign key (USER_ID) references USER (USER_ID)
Step 3: Defining Hibernate Entity Model's :
For the above database table, Hibernate entity models look's like
User.java :
@Entity @Table(name = "USER") public class User implements Serializable{ @Id @Column(name = "USER_ID", unique = true, nullable = false) private String userId; @Column(name = "NAME", nullable = false, length = 32) private String name; @Column(name = "PASSWORD", nullable = false, length = 64) private String password; @Column(name = "ACTIVE", nullable = false, length = 1) private Integer active; @ManyToMany(fetch = FetchType.EAGER,cascade = CascadeType.ALL) @JoinTable( name = "USER_ROLE", joinColumns = @JoinColumn(name = "USER_ID"), inverseJoinColumns = @JoinColumn(name = "ROLE_ID") ) private Set<Role> roles = new HashSet<>(); //setters and getters }
Role.java :
@Entity @Table(name = "ROLE") public class Role implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "ROLE_ID", unique = true, nullable = false) private Integer roleId; @Column(name = "NAME", nullable = false, length = 32) private String name; @ManyToMany(mappedBy = "roles") private Set<User> users = new HashSet<>(); //setters and getters }
Step 4: Configure Custom Login & intercept URL's :
If you want to use a custom login page for your application, then you can configure spring-security to use your custom login page instead. You can use the <security:form-login> tag to define your custom login form page within the <security:http> … </security:http> tag. Below is the configuration example.
<security:http auto-config="true" use-expressions="true"> <!-- Interceptor urls --> <security:intercept-url pattern="/" access="permitAll" /> <security:intercept-url pattern="/userpage" access="hasRole('USER')" /> <security:intercept-url pattern="/admin**" access="hasRole('ADMIN')" /> <security:form-login login-page="/login" default-target-url="/userpage" login-processing-url="/j_spring_security_check" authentication-failure-url="/accessdenied" username-parameter="username" password-parameter="password" /> <!-- Logout --> <security:logout logout-success-url="/logout" /> <!-- enable csrf protection --> <security:csrf/> </security:http>
default-target-url : url where the user should navigate after successful login.
authentication-failure-url: url where the user should navigate after a login failure.
username-parameter and password-parameter : username-parameter and password-parameter - These two are optional. By default spring-security accepts the parameter names “j_username” and “j_password” as username and password in the login form. If you want to given any other name to the username and password input fields in the login form, then you can specify the custom parameter names in these two attributes in
In “logout-success-url” you can specify the page url path which should execute when user uses spring-security logout process.
Step 5: Configuring LoginService in Spring Security XML:
Spring security have AuthenticationManager and it delegate to a collection of AuthenticationProvider instances.An AuthenticationProvider will call an object that implements the UserDetailsService interface. A UserDetailsService looks up the user data and returns a UserDetails object fully populated. If the UserDetailsService cannot find this user then a UsernameNotFoundException is thrown.UserDetailsService interface has just one method loadUserByUsername(String username) and returns a UserDetails object, then the AuthenticationProvider will check that the password matches the password the user entered. If it does not match, then the AuthenticationProvider will throw an AuthenticationException.
<security:authentication-manager> <security:authentication-provider user-service-ref="loginService"> </security:authentication-provider> </security:authentication-manager>
Below LoginService implemented from UserDetailsService and overriding method loadUserByUsername(String username).And referring loginService to authentication-provider in the XML configuration.And LoginService looks as
@Service("loginService") public class LoginService implements UserDetailsService { @Autowired private HibernateTemplate hibernateTemplate; @Override @Transactional public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException { User user = hibernateTemplate.get(User.class, username); if (user == null) { return null; } List<SimpleGrantedAuthority> simpleGrantedAuthorities = buildSimpleGrantedAuthorities(user); UserDetails userDetails = new org.springframework.security.core.userdetails.User(user.getName(), user.getPassword(), user.getActive() == 1 ? true : false, true , true, true, simpleGrantedAuthorities); return userDetails; } private List<SimpleGrantedAuthority> buildSimpleGrantedAuthorities(final User user) { List<SimpleGrantedAuthority> simpleGrantedAuthorities = new ArrayList<>(); if (user.getRoles() != null) { for (Role role : user.getRoles()) { simpleGrantedAuthorities.add(new SimpleGrantedAuthority(role.getName())); } } return simpleGrantedAuthorities; } }
Step 6: Database Configuration
In the above Login service we autowired hibernate template to intract to database table.Below is the configuration to build Hibernate template
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://localhost:3306/LoginDB"></property> <property name="username" value="root"></property> <property name="password" value="admin"></property> <property name="connectionProperties"> <props> <prop key="hibernate.show_sql"> true </prop> <prop key="hibernate.enable_lazy_load_no_trans"> true </prop> </props> </property> </bean> <bean id="loginSessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="packagesToScan" value="com.spring.security" /> </bean> <bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager"> <property name="sessionFactory" ref="loginSessionFactory"/> </bean> <bean id="hibernateTemplate" class="org.springframework.orm.hibernate4.HibernateTemplate"> <property name="sessionFactory" ref="loginSessionFactory"/> </bean>
Step 7: Adding Controller
@Controller public class HelloWorldController { @RequestMapping(value = {"/"}, method = RequestMethod.GET) public ModelAndView homePage() { ModelAndView model = new ModelAndView(); model.addObject("greeting", "Hi, Welcome to hello world app. "); model.setViewName("welcome"); return model; } @RequestMapping(value = {"/login"}, method = RequestMethod.GET) public ModelAndView loginPage() { ModelAndView model = new ModelAndView(); model.addObject("greeting", "Hi, Welcome to login page"); model.setViewName("login"); return model; } @RequestMapping(value = {"/accessdenied"}, method = RequestMethod.GET) public ModelAndView accessDeniedPage() { ModelAndView model = new ModelAndView(); model.addObject("message", "Either username or password is incorrect."); model.setViewName("accessdenied"); return model; } @RequestMapping(value = {"/userpage"}, method = RequestMethod.GET) public ModelAndView userPage() { Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); if (principal instanceof UserDetails) { Collection<? extends GrantedAuthority> authorities = ((UserDetails) principal).getAuthorities(); if (authorities.size() == 1) { final Iterator<? extends GrantedAuthority> iterator = authorities.iterator(); GrantedAuthority grantedAuthority = iterator.next(); if (grantedAuthority.getAuthority().equals("ADMIN")) { return adminPage(); } } } ModelAndView model = new ModelAndView(); model.addObject("title", "Spring Security Hello World"); model.addObject("user", getUser()); model.setViewName("user"); return model; } @RequestMapping(value = "/adminpage", method = RequestMethod.GET) public ModelAndView adminPage() { ModelAndView model = new ModelAndView(); model.addObject("title", "Spring Security Hello World"); model.addObject("user", getUser()); model.setViewName("admin"); return model; } @RequestMapping(value = "/logout", method = RequestMethod.GET) public ModelAndView logoutPage(HttpServletRequest request, HttpServletResponse response) { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); if (auth != null) { new SecurityContextLogoutHandler().logout(request, response, auth); } return homePage(); } private String getUser() { String userName = null; Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); if (principal instanceof UserDetails) { userName = ((UserDetails) principal).getUsername(); } else { userName = principal.toString(); } return userName; } }
Step 8: Adding Views
1) welcome.jsp :
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>HelloWorld page</title> </head> <body> ${title}<br/><br/> Dear ${user}, you are successfully logged into this application. <br/> <a href="<c:url value="/logout" />">Logout</a> </body> </html>
2) login.jsp :
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>Login</title> </head> <body> <h1>Spring Security Custom Login Form</h1> <div id="logindiv"> <form name='loginForm' action="<c:url value='j_spring_security_check' />" method='POST'> <table> <tr> <td>User:</td> <td><input type='text' name='username' value=''></td> </tr> <tr> <td>Password:</td> <td><input type='password' name='password' /></td> </tr> <tr> <td colspan='2'><input name="submit" type="submit" value="submit" /></td> </tr> </table> <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" /> </form> </div> </body> </html>
3) user.jsp :
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>HelloWorld page</title> </head> <body> ${title}<br/><br/> Dear ${user}, you are successfully logged into this application. <br/> <a href="<c:url value="/logout" />">Logout</a> </body> </html>
4) admin.jsp :
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>HelloWorld page</title> </head> <body> ${title}<br/><br/> Dear ${user}, you are successfully logged into this application as admin. <br/> <a href="<c:url value="/logout" />">Logout</a> </body> </html>
5) AccessDenied.jsp :
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>Access Denied Page</title> </head> <body> <h2> Access Denied </h2> <br/>${message}<br/> Click here for<a href="<c:url value="/login" />"> Login</a> </body> </html>
Step 9 : Screens
After deploying above application(spring-security-custom-login-using-hibernate.war) in web server(tomcat) and you can find below screens
1) welcome page :
2) Login page :
3) User Page :
4) Admin Page :
5) AccessDenied Page :
Download Source :
Please download source from GitHub.
Conclusion :
After reading this article you will get an idea how Spring Security works and how you can integrate spring security with database using hibernate with custom login form.