In the previous tutorial, we looked into Spring Security In-Memory Authentication Example. This tutorial demonstrates:
- How to create a custom login form in Spring MVC application with Spring Security.
- How to integrate the Hibernate with Spring security framework to load the user’s authentication.
- How to use the UserDetailsService interface to load the user’s authentication information from a database.
Let's use Spring boot to quickly create and bootstrap a spring-based application. We configure Spring Security to use database authentication in this spring boot application.
Tools and Technologies Used
- Spring Boot - 2.1.0 RELEASE
- Spring Framework - 5.1.2 RELEASE
- Spring Security - 5.1.1 RELEASE
- Hibernate - 5.04.Final
- Maven 3.5
- Eclipse IDE
- MySQL
- Servlet
- JSP
Development Steps
Let's use below development steps to create this example:
- Creating a Spring Boot Application
- Project Structure
- Maven Dependencies - Pom.xml
- JPA Entity - User.java
- Spring Data JPA Repository - UserRepository.java
- Spring Controller - WelcomeController.java
- Spring Security Configuration
- UserDetailsService Implementation - UserDetailsServiceImpl.java
- application.propertis
- View Layer
- Running the Application
- Demo
- Conclusion
1. Creating a Spring Boot Application
There are many ways to create a Spring Boot application. You can refer below articles to create a Spring Boot application.
>> Create Spring Boot Project With Spring Initializer
>> Create Spring Boot Project in Spring Tool Suite [STS]
>> Create Spring Boot Project in Spring Tool Suite [STS]
2. Project Structure
Following is the package or project structure for your reference -
3. Maven Dependencies - Pom.xml
Add the following jars dependencies in your 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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>net.javaguides.springsecurity</groupId>
<artifactId>spring-security-database-authentication-example</artifactId>
<version>0.0.1-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.0.RELEASE</version>
<relativePath />
<!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>3.3.6</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap-datepicker</artifactId>
<version>1.0.1</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>1.9.1</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
4. JPA Entity - User.java
Let's create a User JPA entity which will map to database table users to store username and password:
package net.javaguides.springsecurity.model;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "users")
public class User {
private Long id;
private String username;
private String password;
public User() {
}
public User(String username, String password) {
super();
this.username = username;
this.password = password;
}
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
5. Spring Data JPA Repository - UserRepository.java
package net.javaguides.springsecurity.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import net.javaguides.springsecurity.model.User;
public interface UserRepository extends JpaRepository<User, Long> {
User findByUsername(String username);
}
6. Spring Controller - HomeController.java
Let's create HomeController which will map HTTP requests to views:
package net.javaguides.springsecurity.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class HomeController {
@RequestMapping(value = "/login", method = RequestMethod.GET)
public String login(Model model, String error, String logout) {
if (error != null)
model.addAttribute("error", "Your username and password is invalid.");
if (logout != null)
model.addAttribute("message", "You have been logged out successfully.");
return "login";
}
@RequestMapping(value = {
"/",
"/welcome"
}, method = RequestMethod.GET)
public String welcome(Model model) {
return "welcome";
}
}
7. Spring Security Configuration
The WebSecurityConfig class is annotated with @EnableWebSecurity to enable Spring Security’s web security support and provide the Spring MVC integration.
WebSecurityConfig also extends WebSecurityConfigurerAdapter and overrides a couple of its methods to set some specifics of the web security configuration.
package net.javaguides.springsecurity.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, proxyTargetClass = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/resources/**", "/WEB-INF/jsp/*").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/welcome")
.failureUrl("/login?error")
.permitAll()
.and()
.logout()
.permitAll();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
}
}
8. UserDetailsService Implementation - UserDetailsServiceImpl.java
To create a custom user service, we need to implement the UserDetailsService interface and override the loadUserByUsername() method.
package net.javaguides.springsecurity.config;
import java.util.HashSet;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import net.javaguides.springsecurity.model.User;
import net.javaguides.springsecurity.repository.UserRepository;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
@Transactional(readOnly = true)
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username);
Set < GrantedAuthority > grantedAuthorities = new HashSet < > ();
grantedAuthorities.add(new SimpleGrantedAuthority("USER"));
grantedAuthorities.add(new SimpleGrantedAuthority("ADMIN"));
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(),
grantedAuthorities);
}
}
Note that we are adding hardcoded roles - USER and ADMIN but you can create roles table and map with the users table, for this check out my tutorial at https://www.javaguides.net/2019/09/user-account-registration-and-login.html.
9. application.properties
We are using the h2 database so no need to configure database details because spring boot automatically includes these details whenever it finds an h2 database dependency on the classpath. Let's configure view resolver:
## Spring view resolver set up
spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp
spring.jpa.hibernate.ddl-auto=create
spring.jpa.show-sql=true
10. View Layer
login.jsp
Below login JSP page simply presents a form that captures username and password and posts them to "/login". As configured, Spring Security provides a filter that intercepts that request and authenticates the user. If the user fails to authenticate, the page is redirected to "/login?error" and our page displays the appropriate error message.<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<c:set var="contextPath" value="${pageContext.request.contextPath}"/>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
<meta name="description" content="">
<meta name="author" content="">
<title>Log in with your account</title>
<link href="${contextPath}/resources/css/bootstrap.min.css" rel="stylesheet">
<link href="${contextPath}/resources/css/common.css" rel="stylesheet">
</head>
<body>
<div class="container">
<form method="POST" action="${contextPath}/login" class="form-signin">
<h2 class="form-heading">Log in</h2>
<div class="form-group ${error != null ? 'has-error' : ''}">
<span>${message}</span>
<input name="username" type="text" class="form-control" placeholder="Username"
autofocus="true"/>
<input name="password" type="password" class="form-control" placeholder="Password"/>
<span>${error}</span>
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
<button class="btn btn-lg btn-primary btn-block" type="submit">Log In</button>
</div>
</form>
</div>
<!-- /container -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
<script src="${contextPath}/resources/js/bootstrap.min.js"></script>
</body>
</html>
welcome.jsp
After login success, the welcome page will display. Upon successfully signing out, our application is sent to "/login?logout" and our page displays the appropriate success message
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:set var="contextPath" value="${pageContext.request.contextPath}"/>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
<meta name="description" content="">
<meta name="author" content="">
<title>Create an account</title>
<link href="${contextPath}/resources/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
<c:if test="${pageContext.request.userPrincipal.name != null}">
<form id="logoutForm" method="POST" action="${contextPath}/logout">
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
</form>
<h2>Welcome ${pageContext.request.userPrincipal.name} | <a onclick="document.forms['logoutForm'].submit()">Logout</a></h2>
</c:if>
</div>
<!-- /container -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
<script src="${contextPath}/resources/js/bootstrap.min.js"></script>
</body>
</html>
11. Running the Application
Notice that we are inserting single record in database using CommandLineRunner interface so that we can validate username and password:
package net.javaguides.springsecurity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import net.javaguides.springsecurity.model.User;
import net.javaguides.springsecurity.repository.UserRepository;
@SpringBootApplication
public class Application implements CommandLineRunner {
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
@Autowired
private UserRepository userRepository;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Override
public void run(String...args) throws Exception {
userRepository.save(new User("ramesh", bCryptPasswordEncoder.encode("ramesh")));
}
}
Notice that we are encoding a password before storing it in the database.
12. Demo
Hit this link in browser - http://localhost:8080.
Below is the custom login page, enter username, password and submit the form:
After login success, below screen will display:
Click on the logout link to logout from the web application.
13. Conclusion
In this tutorial, we have seen how to create a custom login form in the Spring MVC application with Spring Security. We have seen how to integrate the Hibernate with the Spring security framework to load the user’s authentication. Finally, we have also seen how to use the UserDetailsService interface to load the user’s authentication information from a database.
Download source code from my Github repository at https://github.com/RameshMF/spring-security-tutorial.
Related Tutorials
- User Registration Module + Spring Boot 2 + Spring Security + Hibernate 5 + Thymeleaf + MySQL // Popular
Comments
Post a Comment
Leave Comment