In this tutorial, you will learn how to build login and registration REST APIs for a simple Blog application using Spring Boot, Spring Security, Hibernate, and a MySQL database. We will go through step-by-step instructions to set up the project, configure the necessary dependencies, and implement the login and registration functionality. By the end of this tutorial, you will have a fully functional REST API that allows users to register and login to the application.
Tools and Technologies Used
- Spring Boot 3
- JDK 17 or later
- Spring MVC
- Spring Security 6
- Hibernate
- Maven
- Spring Data JPA
- IDE (Eclipse, Spring Tool Suite, IntelliJ IDEA, etc.)
- MySQL
Step 1: Create a Spring Boot Application
Using Spring Initializr
- Navigate to Spring Initializr: Open Spring Initializr in your browser.
- Configure the Project:
- Project: Maven Project
- Language: Java
- Spring Boot: 3.2
- Group: com.rameshfadatare
- Artifact: springboot-blog-rest-api
- Name: springboot-blog-rest-api
- Description: Demo project for Spring Boot Blog Application
- Package name: com.rameshfadatare
- Packaging: Jar
- Java: 17
- Add Dependencies:
- Spring Web
- Lombok
- Spring Data JPA
- Spring Security
- Dev Tools
- MySQL Driver
- Generate the Project: Click on the Generate button to download the project as a zip file.
- Extract the Zip File: Extract the zip file to your preferred location.
- Import the Project: Import the extracted project as a Maven project in your preferred IDE.
Step 2: Add Maven Dependencies
Ensure your pom.xml file contains the necessary dependencies. It should look something like this:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</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-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Explanation:
- spring-boot-starter-data-jpa: Starter for using Spring Data JPA with Hibernate.
- spring-boot-starter-web: Starter for building web applications, including RESTful applications using Spring MVC.
- spring-boot-devtools: Provides fast application restarts, LiveReload, and configurations for enhanced development experience.
- mysql-connector-java: MySQL JDBC driver to connect with MySQL database.
- lombok: Java library that helps to reduce boilerplate code.
- spring-boot-starter-validation: Starter for using Java Bean Validation with Hibernate Validator.
- spring-boot-starter-security: Starter for using Spring Security.
- spring-boot-starter-test: Starter for testing Spring Boot applications.
Step 3: Configure MySQL Database
Create a Database
First, create a database in MySQL using the following command:
create database myblog;
Configure Database Connection
Open src/main/resources/application.properties and add the following properties to configure the database connection:
spring.datasource.url = jdbc:mysql://localhost:3306/myblog?useSSL=false&serverTimezone=UTC
spring.datasource.username = root
spring.datasource.password = root
# Hibernate properties
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQLDialect
# Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto = update
logging.level.org.springframework.security=DEBUG
Explanation:
- spring.datasource.url: JDBC URL of the MySQL database.
- spring.datasource.username: Username to connect to the database.
- spring.datasource.password: Password to connect to the database.
- spring.jpa.properties.hibernate.dialect: Dialect to be used by Hibernate for the MySQL database.
- spring.jpa.hibernate.ddl-auto: Hibernate property to automatically create/update database schema.
- logging.level.org.springframework.security: Logging level for Spring Security.
Make sure to replace spring.datasource.username and spring.datasource.password with your MySQL credentials.
Step 4: Model Layer – Create JPA Entities
We will create User and Role JPA entities and establish a many-to-many relationship between them.
User JPA Entity
package com.rameshfadatare.springboot.entity;
import lombok.Data;
import jakarta.persistence.*;
import java.util.Set;
@Data
@Entity
@Table(name = "users", uniqueConstraints = {
@UniqueConstraint(columnNames = {"username"}),
@UniqueConstraint(columnNames = {"email"})
})
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
private String username;
private String email;
private String password;
@ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@JoinTable(name = "user_roles",
joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id"))
private Set<Role> roles;
}
Explanation:
- @Entity: Specifies that the class is an entity and is mapped to a database table.
- @Table: Specifies the primary table for the annotated entity.
- @UniqueConstraint: Specifies that the columns in the table must be unique.
- @Id: Specifies the primary key of an entity.
- @GeneratedValue: Provides the specification of generation strategies for the values of primary keys.
- @ManyToMany: Defines a many-to-many relationship between User and Role entities.
- @JoinTable: Specifies the join table used in the many-to-many relationship.
Role JPA Entity
package com.rameshfadatare.springboot.entity;
import lombok.Getter;
import lombok.Setter;
import jakarta.persistence.*;
@Setter
@Getter
@Entity
@Table(name = "roles")
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@Column(length = 60)
private String name;
}
Explanation:
- @Column: Specifies the mapped column for a persistent property or field.
- @Setter, @Getter: Lombok annotations to generate getter and setter methods.
Step 5: Repository Layer
UserRepository
package com.rameshfadatare.springboot.repository;
import com.rameshfadatare.springboot.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
Optional<User> findByUsernameOrEmail(String username, String email);
Optional<User> findByUsername(String username);
Boolean existsByUsername(String username);
Boolean existsByEmail(String email);
}
Explanation:
- JpaRepository: Provides JPA related methods such as saving, deleting, and finding entities.
- Optional: A container object which may or may not contain a non-null value.
RoleRepository
package com.rameshfadatare.springboot.repository;
import com.rameshfadatare.springboot.entity.Role;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface RoleRepository extends JpaRepository<Role, Long> {
Optional<Role> findByName(String name);
}
Explanation:
- findByName: Custom method to find a role by its name.
Step 6: Service Layer – CustomUserDetailsService
The CustomUserDetailsService class implements the UserDetailsService interface and provides the implementation for the loadUserByUsername() method.
package com.rameshfadatare.springboot.service;
import com.rameshfadatare.springboot.entity.User;
import com.rameshfadatare.springboot.repository.UserRepository;
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 java.util.Set;
import java.util.stream.Collectors;
@Service
public class CustomUserDetailsService implements UserDetailsService {
private UserRepository userRepository;
public CustomUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String usernameOrEmail) throws UsernameNotFoundException {
User user = userRepository.findByUsernameOrEmail(usernameOrEmail, usernameOrEmail)
.orElseThrow(() -> new UsernameNotFoundException("User not found with username or email: " + usernameOrEmail));
Set<GrantedAuthority> authorities = user
.getRoles()
.stream()
.map((role) -> new SimpleGrantedAuthority(role.getName())).collect(Collectors.toSet());
return new org.springframework.security.core.userdetails.User(user.getEmail(),
user.getPassword(),
authorities);
}
}
Explanation:
- UserDetailsService: Interface which loads user-specific data.
- loadUserByUsername: Locates the user based on the username or email.
- GrantedAuthority: Represents an authority granted to an Authentication object.
- SimpleGrantedAuthority: A concrete implementation of the GrantedAuthority interface.
Step 7: Spring Security Configuration
SecurityConfig
package com.rameshfadatare.springboot.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableMethodSecurity
public class SecurityConfig {
private UserDetailsService userDetailsService;
public SecurityConfig(UserDetailsService userDetailsService){
this.userDetailsService = userDetailsService;
}
@Bean
public static PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(
AuthenticationConfiguration configuration) throws Exception {
return configuration.getAuthenticationManager();
}
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeHttpRequests((authorize) ->
authorize.requestMatchers(HttpMethod.GET, "/api/**").permitAll()
.requestMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
);
return http.build();
}
}
Explanation:
- PasswordEncoder: The
BCryptPasswordEncoderis used to encode passwords. - AuthenticationManager: This bean is required to authenticate users.
- SecurityFilterChain: Configures Spring Security to disable CSRF protection, allow public access to certain endpoints, and require authentication for other endpoints.
Step 8: DTO or Payload Classes
LoginDto
package com.rameshfadatare.springboot.payload;
import lombok.Data;
@Data
public class LoginDto {
private String usernameOrEmail;
private String password;
}
SignUpDto
package com.rameshfadatare.springboot.payload;
import lombok.Data;
@Data
public class SignUpDto {
private String name;
private String username;
private String email;
private String password;
}
Explanation:
- LoginDto: DTO class for login payload.
- SignUpDto: DTO class for registration payload.
- @Data: Lombok annotation to generate getter, setter, toString, equals, and hashcode methods.
Step 9: Controller Layer – Login/Sign-in and Register/SignUp REST APIs
AuthController
package com.rameshfadatare.springboot.controller;
import com.rameshfadatare.springboot.entity.Role;
import com.rameshfadatare.springboot.entity.User;
import com.rameshfadatare.springboot.payload.LoginDto;
import com.rameshfadatare.springboot.payload.SignUpDto;
import com.rameshfadatare.springboot.repository.RoleRepository;
import com.rameshfadatare.springboot.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Collections;
@RestController
@RequestMapping("/api/auth")
public class AuthController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserRepository userRepository;
@Autowired
private RoleRepository roleRepository;
@Autowired
private PasswordEncoder passwordEncoder;
@PostMapping("/signin")
public ResponseEntity<String> authenticateUser(@RequestBody LoginDto loginDto) {
Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(
loginDto.getUsernameOrEmail(), loginDto.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication);
return new ResponseEntity<>("User signed-in successfully!", HttpStatus.OK);
}
@PostMapping("/signup")
public ResponseEntity<?> registerUser(@RequestBody SignUpDto signUpDto) {
if (userRepository.existsByUsername(signUpDto.getUsername())) {
return new ResponseEntity<>("Username is already taken!", HttpStatus.BAD_REQUEST);
}
if (userRepository.existsByEmail(signUpDto.getEmail())) {
return new ResponseEntity<>("Email is already taken!", HttpStatus.BAD_REQUEST);
}
User user = new User();
user.setName(signUpDto.getName());
user.setUsername(signUpDto.getUsername());
user.setEmail(signUpDto.getEmail());
user.setPassword(passwordEncoder.encode(signUpDto.getPassword()));
Role roles = roleRepository.findByName("ROLE_ADMIN").get();
user.setRoles(Collections.singleton(roles));
userRepository.save(user);
return new ResponseEntity<>("User registered successfully", HttpStatus.OK);
}
}
Explanation:
- authenticateUser: Handles user login. Authenticates the user and sets the authentication context.
- registerUser: Handles user registration. Checks if the username or email already exists, encodes the password, assigns the role, and saves the user.
Step 10: Run Spring Boot Application
You can run the application in two ways:
- Using Maven: From the root directory of the application, run the following command:
$ mvn spring-boot:run - Using IDE: Run the
Application.main()method as a standalone Java application.
Step 11: Test using Postman
Refer to the following screenshots to test the Login and Registration REST APIs using Postman.
SignUp REST API
SignIn/Login REST API
GitHub Repository
You can find the complete source code for this tutorial in the following GitHub repository: GitHub Repository
Udemy Course
For more in-depth knowledge and practical implementations, check out this Udemy course: Building Real-Time REST APIs with Spring Boot and Deploy on AWS Cloud