In this section, we will develop an application using Spring MVC, Hibernate, and Spring Security. Here, we have a custom login page, logout page, employee page (to list employees), and add employee page (to add employees), which is secured by the Spring Framework. A user can log into the application using the custom login page and view the secured page based on the authentication and authorization. A user will be redirected to the custom login page on any authentication failure along with the error message, which describes the reason for failure. User will be logged out from the application on clicking on the logout link and redirected to the logout page.
The overall project structure is as follows:
In the pom.xml
file, you'll find the following code:
A list of all required dependencies are listed here in pom.xml
. To get Spring Security features, you need to add spring-security-core
, spring-security-web
, and spring-security-config
to the pom.xml
file:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.packt.Spring.chapter8.springsecurity</groupId> <artifactId>ehrPayrollWithSecurity</artifactId> <packaging>war</packaging> <version>0.0.1-SNAPSHOT</version> <name>ehrPayrollWithSecurity Maven Webapp</name> <url>http://maven.apache.org</url>
Here, the properties specify the versions used:
<properties> <spring.version>4.1.3.RELEASE</spring.version> <security.version>4.0.0.CI-SNAPSHOT</security.version> <hibernate.version>4.2.11.Final</hibernate.version> <org.aspectj-version>1.7.4</org.aspectj-version> </properties>
Here are the dependencies for all the JARs:
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <!-- Spring --> <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> <!-- Spring transaction --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${spring.version}</version> </dependency> <!-- Spring Security --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-core</artifactId> <version>${security.version}</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>${security.version}</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>${security.version}</version> </dependency> <!-- Spring ORM --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>${spring.version}</version> </dependency> <!-- AspectJ --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>${org.aspectj-version}</version> </dependency> <!-- Hibernate ORM framework dependencies --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>${hibernate.version}</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-entitymanager</artifactId> <version>${hibernate.version}</version> </dependency> <!-- Java Servlet and JSP dependencies (for compilation only) --> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>3.0.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.1</version> <scope>provided</scope> </dependency> <!-- JSTL dependency --> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!-- Apache Commons DBCP dependency (for database connection pooling) --> <dependency> <groupId>commons-dbcp</groupId> <artifactId>commons-dbcp</artifactId> <version>1.4</version> </dependency> <!-- postgresql Connector Java dependency (JDBC driver for postgresql) --> <dependency> <groupId>postgresql</groupId> <artifactId>postgresql</artifactId> <version>9.0-801.jdbc4</version> </dependency> <!-- logging --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.4.2</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.14</version> </dependency> </dependencies> <build> <finalName>ehrPayrollWithSecurity</finalName> </build> </project>
Add filters to web.xml
, where all incoming requests will be handled by Spring Security. The Spring Security JAR contains DelegatingFilterProxy
, which delegates control to a filter chaining in the Spring Security internals. The bean name should be springSecurityFilterChain
.
In the web.xml
file, you'll find the following code:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0"> <display-name>Archetype Created Web Application</display-name> <servlet> <servlet-name>SpringDispatcher</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>SpringDispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <context-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/SpringDispatcher-servlet.xml, /WEB-INF/spring-security.xml </param-value> </context-param> <!-- Spring Security --> <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
To resolve the view, view resolver has been added to the SpringDispatcher-servlet.xml
configuration file. Also, dataSource
, sessionFactory
, and transactionManager
have been defined here:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd"> <context:component-scan base-package="org.packt.Spring.chapter8.springsecurity" /> <context:property-placeholder location="/WEB-INF/hibernate.properties" /> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="${jdbc.driverClassName}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> </bean> <bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="annotatedClasses" value="org.packt.Spring.chapter8.springsecurity.model.Employee" /> <property name="hibernateProperties"> <props> <prop key="hibernate.dialect">${hibernate.dialect}</prop> <prop key="hibernate.show_sql">${hibernate.show_sql}</prop> </props> </property> </bean> <bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean> <tx:annotation-driven transaction-manager="transactionManager" /> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix"> <value>/WEB-INF/views/</value> </property> <property name="suffix"> <value>.jsp</value> </property> </bean> </beans>
We have defined a role called ROLE_ADMIN
. We have defined credentials for this role. Also, we have mapped URLs with roles that will be handled by Spring Security. To provide custom login form, add <form:login>
in this file. When the user tries to access a secured resource, a custom login page will be served.
In the security-config.xml
file, you'll find the following code:
<?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/security" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd"> <http auto-config="true"> <intercept-url pattern="/employee/*" access="ROLE_ADMIN" /> <form-login login-processing-url="/login" login-page="/loginPage" username-parameter="username" password-parameter="password" default-target-url="/employee/listemployee" authentication-failure-url="/loginPage?auth=fail" /> <logout logout-url="/logout" logout-success-url="/logoutPage" /> </http> <authentication-manager> <authentication-provider> <user-service> <user name="ravi" password="ravi@123" authorities="ROLE_ADMIN" /> </user-service> </authentication-provider> </authentication-manager> </beans:beans>
The LoginController
class contains two methods, namely logoutPage
and loginPage
, with request mapping. The /loginPage
redirects the user to the login page and the /logoutpage
redirects the user to the logout page:
package org.packt.Spring.chapter8.springsecurity.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller public class LoginController { @RequestMapping(value = "/logoutPage", method = RequestMethod.GET) public String logoutPage() { return "logout"; } @RequestMapping(value = "/loginPage", method = RequestMethod.GET) public String loginPage() { return "login"; } }
This controller class has the listEmployee()
, addEmployee()
, and deleteEmployee()
methods. In the EmployeeController.java
file, you'll find the following code:
package org.packt.Spring.chapter8.springsecurity.controller; import org.packt.Spring.chapter8.springsecurity.model.Employee; import org.packt.Spring.chapter8.springsecurity.service.EmployeeService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.servlet.ModelAndView; @Controller @RequestMapping("/employee") public class EmployeeController { @Autowired private EmployeeService employeeService; @RequestMapping(value = "/listemployee", method = RequestMethod.GET) public String listEmployees(ModelMap model) { model.addAttribute("employeesList", employeeService.listEmployee()); return "employee"; } @RequestMapping(value = "/addemployee", method = RequestMethod.GET) public ModelAndView addEmployee(ModelMap model) { return new ModelAndView("addemployee", "command", new Employee()); } @RequestMapping(value = "/updatemployee", method = RequestMethod.POST) public String updateEmployee( @ModelAttribute("employeeForm") Employee employee, ModelMap model) { this.employeeService.insertEmployee(employee); model.addAttribute("employeesList", employeeService.listEmployee()); return "employee"; } @RequestMapping(value = "/delete/{empId}", method = RequestMethod.GET) public String deleteEmployee(@PathVariable("empId") Integer empId, ModelMap model) { this.employeeService.deleteEmployee(empId); model.addAttribute("employeesList", employeeService.listEmployee()); return "employee"; } }
This login page provides an input box to accept credentials from the user. In the login.jsp
file, you'll find the following code:
<%@ taglib uri='http://java.sun.com/jsp/jstl/core' prefix='c'%> <html> <head> <title>Login Page</title> </head> <body> <h2 style="color: orange">Login to eHR Payroll</h2> <c:if test="${'fail' eq param.auth}"> <div style="color:red"> Login Failed!!!<br /> Reason : ${sessionScope["SPRING_SECURITY_LAST_EXCEPTION"].message} </div> </c:if> <form action="${pageContext.request.contextPath}/login" method="post"> <table frame="box" cellpadding="0" cellspacing="6"> <tr> <td>Username:</td> <td><input type='text' name='username' /></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> </form> </body> </html>
This logout page reflects that the user has been logged out from the application. In the logout.jsp
file, you'll find the following code:
<html> <title>Logout Page</title> <body> <h2>You have been successfully logged out.</h2> <a href="${pageContext.request.contextPath}/employee/listemployee">Login to eHR Payroll</a> </body> </html>
Once you deploy the web application after starting the server, open the URL http://localhost:8080/ehrPayrollWithSecurity/loginPage
a custom login page will appear:
If you enter the wrong credentials, the following error will appear:
If you enter the correct credentials, you will be navigated to the listEmployee
page:
Clicking on Logout will navigate you to the logout page, as shown here: