In my previous tutorials, I have shown in-memory authentications Spring Security Form based Authentication – XML Configuration , Spring Security Form based Authentication – Annotations but in this tutorial I will show you how to authenticate user using Spring JDBC and Spring MVC web application to secure pages. I will create spring mvc based web application and I will configure Spring Security to protect a page from outside access.
Spring Security allows to you to integrate security features with JEE web application easily, it takes care about all incoming HTTP requests via servlet filter, and implements “user defined” security checking.
In this tutorial, I will show you how to integrate Spring Security 4.2.1 with Spring MVC4 web application to secure URL access.
For this tutorial I will create maven based web project in Eclipse.
Prerequisites
The following configurations are required in order to run the application
Eclipse Kepler
JDK 1.8
Tomcat 8
Have maven 3 installed and configured
Spring mvc 4 dependencies in pom.xml
Spring security 4 dependencies in pom.xml
Now we will see the below steps how to create a maven based project in Eclipse
Step 1. Create a maven based web project in Eclipse
Go to File -> New -> Other
. On popup window under Maven
select Maven Project
. Then click on Next. Select the workspace location – either default or browse the location. Click on Next. Now in next window select the row as highlighted from the below list of archtypes
and click on Next button.
maven-arctype-webapp
Now enter the required fields (Group Id, Artifact Id) as shown below
Group Id : com.roytuts
Artifact Id : spring-security
Step 2. Modify the pom.xml
file as shown below.
The minimal dependency artifacts required for Spring Security are spring-security-web
and spring-security-config
.
<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>com.roytuts</groupId> <artifactId>spring-security</artifactId> <packaging>war</packaging> <version>0.0.1-SNAPSHOT</version> <name>spring-security Maven Webapp</name> <url>http://maven.apache.org</url> <properties> <java.version>1.8</java.version> <spring.version>4.3.4.RELEASE</spring.version> <mysqlconnector.version>5.1.36</mysqlconnector.version> <spring.security.version>4.2.1.RELEASE</spring.security.version> </properties> <dependencies> <!-- The spring-webmvc module contains Spring’s model-view-controller (MVC) and REST Web Services implementation for web applications --> <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.version}</version> </dependency> <!-- Spring Security --> <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> <!-- Servlet --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.3.1</version> <scope>provided</scope> </dependency> <!-- jstl --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!-- mysql java connector --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysqlconnector.version}</version> </dependency> </dependencies> <build> <finalName>spring-security-annotations</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>${java.version}</source> <target>${java.version}</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.6</version> <configuration> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> </plugins> </build> </project>
In the above pom.xml file you notice additional plugin for failOnMissingWebXml
configuration is required because we are going to write Java code purely based on annotations, so we will delete the web.xml
file from WEB-INF directory.
Step 3. If you see JRE System Library[J2SE-1.5]
then change the version by below process
Do right-click on the project and go to Build -> Configure build path
, under Libraries tab click on JRE System Library[J2SE-1.5]
, click on Edit button and select the appropriate jdk 1.8 from the next window. Click on Finish then Ok.
Step 4. Now when the build process finished then delete the web.xml
file from WEB-INF
directory. Instead we will create below two classes which are equivalent to whatever were there in web.xml file
package com.roytuts.spring.mvc.config; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; import com.roytuts.spring.mvc.config.WebMvcConfig; import com.roytuts.spring.security.config.WebSecurityConfig; public class MvcWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return new Class[] { WebSecurityConfig.class, WebMvcConfig.class }; } @Override protected Class<?>[] getServletConfigClasses() { return null; } @Override protected String[] getServletMappings() { return new String[] { "/" }; } }
The above class declared Spring MVC DispatcherServlet
, that acts as a front controller to handle incoming request and response for the URL pattern "/"
. This is equivalent to declaring DispatcherServlet in web.xml file in my tutorial Spring Security Form based Authentication – XML Configuration
I have also loaded config classes WebSecurityConfig.class and WebMvcConfic.class, that are equivalent to security.xml and controllers.xml configurations in my tutorial Spring Security Form based Authentication – XML Configuration
package com.roytuts.spring.security.config; import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer; public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer { }
The above class is equivalent to add the filter declaration DelegatingFilterProxy
to your web.xml file.
This provides a hook into the Spring Security web infrastructure. DelegatingFilterProxy
is a Spring Framework class which delegates to a filter implementation which is defined as a Spring bean in your application context. In this case, the bean is named springSecurityFilterChain
, which is an internal infrastructure bean created by the namespace to handle web security. Note that you should not use this bean name yourself.
Step 5. Create below WebSecurityConfig.java
that is equivalent to security.xml
file under src/main/resources
directory in my tutorial Spring Security Form based Authentication – XML Configuration
package com.roytuts.spring.security.config; import javax.sql.DataSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.encoding.ShaPasswordEncoder; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private DataSource dataSource; @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { final String sqlUserName = "select u.user_name, u.user_pass, u.enable from user u where u.user_name = ?"; final String sqlAuthorities = "select ur.user_name, ur.user_role from user_role ur where ur.user_name = ?"; auth.jdbcAuthentication().dataSource(dataSource).usersByUsernameQuery(sqlUserName) .authoritiesByUsernameQuery(sqlAuthorities).passwordEncoder(new ShaPasswordEncoder()); } @Override protected void configure(HttpSecurity http) throws Exception { http// , .authorizeRequests()// , .antMatchers("/admin")// Ensures that request with "/admin" to // our application requires the user to // be authenticated .access("hasRole('ADMIN')")// Any URL that starts with // "/admin" will // be restricted to users who have the // role "ROLE_ADMIN", .and()// , .formLogin()// Allows users to authenticate with form based // login, .loginPage("/login")// specifies the location of the log in // page, .loginProcessingUrl("/j_spring_security_check")// login // processing // URL, .defaultSuccessUrl("/admin")// default-target-url, .failureUrl("/login?error")// authentication-failure-url, .usernameParameter("username")// overrides Spring's default // j_username with // username-parameter, .passwordParameter("password");// overrides Spring's default // j_password with // password-parameter } }
Step 6. Create below WebMvcConfig.java
that is equivalent to controllers.xml
file under src/main/resources
directory in my tutorial Spring Security Form based Authentication – XML Configuration
package com.roytuts.spring.mvc.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.MessageSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.context.support.ResourceBundleMessageSource; import org.springframework.core.env.Environment; import org.springframework.jdbc.datasource.DriverManagerDataSource; import org.springframework.web.servlet.ViewResolver; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.servlet.view.InternalResourceViewResolver; @EnableWebMvc @Configuration @PropertySource(value = { "classpath:messages.properties", "classpath:jdbc.properties" }) @ComponentScan(basePackages = /* "com.roytuts.spring.security.controllers" */"com.roytuts.spring.*") public class WebMvcConfig extends WebMvcConfigurerAdapter { @Autowired private Environment environment; @Bean public ViewResolver getViewResolver() { InternalResourceViewResolver viewResolver = new InternalResourceViewResolver("/views/", ".jsp"); return viewResolver; } @Override public void addResourceHandlers(final ResourceHandlerRegistry registry) { registry.addResourceHandler("/static/**").addResourceLocations("/static/"); } @Bean("messageSource") public MessageSource messageSource() { ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); messageSource.setBasename("messages"); messageSource.setDefaultEncoding("UTF-8"); return messageSource; } @Bean(name = "dataSource") public DriverManagerDataSource dataSource() { DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName(environment.getRequiredProperty("jdbc.driverClassName")); dataSource.setUrl(environment.getRequiredProperty("jdbc.url")); dataSource.setUsername(environment.getRequiredProperty("jdbc.username")); dataSource.setPassword(environment.getRequiredProperty("jdbc.password")); return dataSource; } }
The annotation @EnableWebMvc
is equivalent to <mvc:annotation-driven />
to work with annotations in Spring MVC.
The addResourceHandlers()
acts in the similar way as <mvc:resources location="/static/" mapping="/static/**" />
to load static resources from static directory.
The annotation @ComponentScan
is equivalent to <context:component-scan/>
to load all annotation-driven controllers from the given base package.
I have also declared view resolver bean and message resource for i18n
supports.
Step 7. Create below messages.properties
file with below content and put it under src/main/resources
folder
page.title=Spring Security JDBC Authentication page.home.heading=Home Page page.login.heading=Login Here page.admin.heading=Administrator Control Panel page.admin.message=This page demonstrates how to use Spring security. page.goto.admin=Go to Administrator page login.failure.reason=Invalid credentials welcome.msg=Welcome logout.text=Logout logout.msg.success=You have been successfully logged out.
Create below below jdbc.properties
file with below content and put it under src/main/resources
folder
jdbc.driverClassName=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost/roytuts jdbc.username=root jdbc.password=
Step 8. Create below controller with below source
package com.roytuts.spring.security.controllers; import java.util.Locale; import javax.servlet.http.HttpServletRequest; import org.springframework.context.MessageSource; import org.springframework.context.MessageSourceAware; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @Controller @RequestMapping("/") public class SpringSecurityController implements MessageSourceAware { private MessageSource messageSource; @Override public void setMessageSource(MessageSource messageSource) { this.messageSource = messageSource; } @RequestMapping("/") public String defaultPage(Model model) { model.addAttribute("msg", "Welcome to Spring Security"); return "index"; } @RequestMapping("/login") public String loginPage(Model model, @RequestParam(value = "error", required = false) String error, @RequestParam(value = "logout", required = false) String logout) { if (error != null) { model.addAttribute("error", messageSource.getMessage("login.failure.reason", null, Locale.US)); } if (logout != null) { model.addAttribute("msg", messageSource.getMessage("logout.msg.success", null, Locale.US)); } return "login"; } @RequestMapping("/logout") public String logoutPage(Model model, HttpServletRequest request) { request.getSession().invalidate(); return "redirect:/login?logout"; } @RequestMapping("/admin") public String adminPage(Model model) { model.addAttribute("title", messageSource.getMessage("page.admin.heading", null, Locale.US)); model.addAttribute("message", messageSource.getMessage("page.admin.message", null, Locale.US)); return "admin"; } }
Step 9. We need some style, so create the below style.css
file and put it under webapp/static/css
directory
.error { padding: 15px; margin-bottom: 20px; border: 1px solid transparent; border-radius: 4px; color: #a94442; background-color: #f2dede; border-color: #ebccd1; } .msg { padding: 15px; margin-bottom: 20px; border: 1px solid transparent; border-radius: 4px; color: #31708f; background-color: #d9edf7; border-color: #bce8f1; } #login-box { width: 500px; padding: 20px; margin: 50px auto; background: #fff; -webkit-border-radius: 2px; -moz-border-radius: 2px; border: 1px solid #000; }
Step 10. Below is the index.jsp
file and put it under webapp/views
directory and see how keys are used to fetch corresponding value from messages.properties
file. This index.jsp
file is not secured and is accessible directly.
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title><fmt:bundle basename="messages"> <fmt:message key="page.title" /> </fmt:bundle></title> </head> <body> <div align="center"> <h1> <fmt:bundle basename="messages"> <fmt:message key="page.home.heading" /> </fmt:bundle> </h1> <a href="${pageContext.request.contextPath}/admin"><fmt:bundle basename="messages"> <fmt:message key="page.goto.admin" /> </fmt:bundle></a> </div> </body> </html>
Step 11. Below admin.jsp
file in webapp/views
directory is secured and user must login before viewing the content of this file. When you try to access the admin.jsp
file then you will automatically be redirected to the login.jsp
file.
<%@ page language="java" session="true" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <%@ taglib prefix="fmt" uri="http://java.sun.com/jstl/fmt"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title><fmt:bundle basename="messages"> <fmt:message key="page.title" /> </fmt:bundle></title> </head> <body> <div align="center"> <h1>${title}</h1> <h2>${message}</h2> <c:if test="${pageContext.request.userPrincipal.name != null}"> <h2> <fmt:bundle basename="messages"> <fmt:message key="welcome.msg" /> </fmt:bundle> : ${pageContext.request.userPrincipal.name} | <a href="<c:url value='logout'/>"><fmt:bundle basename="messages"> <fmt:message key="logout.text" /> </fmt:bundle></a> </h2> </c:if> </div> </body> </html>
Step 12. The content of the login.jsp
file under webapp/views
directory.
<%@ page language="java" session="true" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <%@ taglib prefix="fmt" uri="http://java.sun.com/jstl/fmt"%> <html> <head> <title><fmt:bundle basename="messages"> <fmt:message key="page.title" /> </fmt:bundle></title> <link rel="stylesheet" type="text/css" href="<c:url value="/static/css/style.css"/>" /> </head> <body> <div id="login-box"> <h2> <fmt:bundle basename="messages"> <fmt:message key="page.login.heading" /> </fmt:bundle> </h2> <c:if test="${not empty error}"> <div class="error">${error}</div> </c:if> <c:if test="${not empty msg}"> <div class="msg">${msg}</div> </c:if> <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>
Step 13. Create below tables in MySQL database. The password is encrypted using sha-1 algorithm. The decrypted value of the encrypted password is roy
.
USE `roytuts`; /*Table structure for table `user` */ DROP TABLE IF EXISTS `user`; CREATE TABLE `user` ( `user_name` varchar(30) COLLATE utf8_unicode_ci NOT NULL, `user_pass` varchar(255) COLLATE utf8_unicode_ci NOT NULL, `enable` tinyint(1) NOT NULL DEFAULT '1', PRIMARY KEY (`user_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; /*Data for the table `user` */ insert into `user`(`user_name`,`user_pass`,`enable`) values ('roy','ae685575101ee7165c90a8f2c30c6e60cdd9e482',1);
/*Table structure for table `user_role` */ DROP TABLE IF EXISTS `user_role`; CREATE TABLE `user_role` ( `user_name` varchar(30) COLLATE utf8_unicode_ci NOT NULL, `user_role` varchar(15) COLLATE utf8_unicode_ci NOT NULL, KEY `fk_user` (`user_name`), CONSTRAINT `fk_user` FOREIGN KEY (`user_name`) REFERENCES `user` (`user_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; /*Data for the table `user_role` */ insert into `user_role`(`user_name`,`user_role`) values ('roy','ROLE_ADMIN');
Step 14. When you deploy the application and run the application you will see different output in the browser.
When you hit the URL http://localhost:8080/spring-security/
The page title you should see as Spring Security JDBC Authentication
instead of Spring Security Basic (XML)
When you click on link Go to Administrator page
When you click on Submit
button without giving any credentials or wrong credentials
When you give username/password as roy/roy
When you click on Logout
link
Thanks for reading.