In this tutorial, you will learn how to build login or sign-in and registration or signup REST API using Spring boot, Spring Security, Hibernate, and MySQL database.
We will create login and registration REST APIs for a simple Blog application.
Tools and Technologies Used
- Spring Boot 3
- JDK - 17 or later
- Spring MVC
- Spring Security 6
- Hibernate
- Maven
- Spring Data JPA
- IDE - Eclipse or Spring Tool Suite (STS) or IntelliJ IDEA // Any IDE works
- MYSQL
1. Create a Spring boot application
Spring Boot provides a web tool called Spring Initializer to quickly bootstrap an application. To use it, go to https://start.spring.io/ and generate a new Spring Boot project.
Use the below details in the Spring boot creation:
Project Name: springboot-blog-rest-api
Project Type: Maven
Choose dependencies: Spring Web, Lombok, Spring Data JPA, Spring Security, Dev Tools and MySQL Driver
Package name: net.javaguides.springboot
Packaging: Jar
Download the Spring Boot project as a zip file, unzip it and import it into your favourite IDE.
2. Maven Dependencies
Here is the pom.xml file for your reference:
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.springboot.blog</groupId>
<artifactId>springboot-blog-rest-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-blog-rest-api</name>
<description>Spring boot blog application rest api's</description>
<properties>
<java.version>17</java.version>
</properties>
<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>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>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
3. Configure MySQL Database
create database myblog
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
4. Model Layer - Create JPA Entities
User JPA Entity
package com.springboot.blog.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;
}
Role JPA Entity
package com.springboot.blog.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;
}
5. Repository Layer
UserRepository
package com.springboot.blog.repository;
import com.springboot.blog.entity.User;
import org.springframework.data.domain.Example;
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);
}
RoleRepository
package com.springboot.blog.repository;
import com.springboot.blog.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);
}
6. Service Layer - CustomUserDetailsService
Let's write a logic to load user details by name or email from the database.import com.springboot.blog.entity.User;
import com.springboot.blog.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);
}
}
7. Spring Security Configuration
package com.springboot.blog.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.anyRequest().authenticated()
authorize.requestMatchers(HttpMethod.GET, "/api/**").permitAll()
.requestMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
);
return http.build();
}
}
authorize.requestMatchers(HttpMethod.GET, "/api/**").permitAll()
8. DTO or Payload Classes
LoginDto
package com.springboot.blog.payload;
import lombok.Data;
@Data
public class LoginDto {
private String usernameOrEmail;
private String password;
}
SignUpDto
package com.springboot.blog.payload;
import lombok.Data;
@Data
public class SignUpDto {
private String name;
private String username;
private String email;
private String password;
}
9. Controller Layer - Login/Sign-in and Register/SignUp REST APIs
package com.springboot.blog.controller;
import com.springboot.blog.entity.Role;
import com.springboot.blog.entity.User;
import com.springboot.blog.payload.LoginDto;
import com.springboot.blog.payload.SignUpDto;
import com.springboot.blog.repository.RoleRepository;
import com.springboot.blog.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){
// add check for username exists in a DB
if(userRepository.existsByUsername(signUpDto.getUsername())){
return new ResponseEntity<>("Username is already taken!", HttpStatus.BAD_REQUEST);
}
// add check for email exists in DB
if(userRepository.existsByEmail(signUpDto.getEmail())){
return new ResponseEntity<>("Email is already taken!", HttpStatus.BAD_REQUEST);
}
// create user object
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);
}
}
@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){
// add check for username exists in a DB
if(userRepository.existsByUsername(signUpDto.getUsername())){
return new ResponseEntity<>("Username is already taken!", HttpStatus.BAD_REQUEST);
}
// add check for email exists in DB
if(userRepository.existsByEmail(signUpDto.getEmail())){
return new ResponseEntity<>("Email is already taken!", HttpStatus.BAD_REQUEST);
}
// create user object
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);
}
10. Run Spring Boot Application
$ mvn spring-boot:run
Important
INSERT INTO roles VALUES('ROLE_ADMIN');
Comments
Post a Comment
Leave Comment