Spring Security 6 and Spring Boot 3 with Simple Project.

In this article we will learn about basic authentication and role-based authentication and authorization.


Prerequisites

  1. Good Knowledge of Java.
  2. Basic Understanding of Spring Boot.
  3. Basic Understanding of MySQL workbench and IDE (In this project I use Intellij IDE).

Required Technologies and Apps

  1. Java (latest Jdk)
  2. Spring Boot project file.
  3. Intellij IDE
  4. Postman
  5. MySQL Workbench

Get source code from GitHub —  Download Source Code

Spring Security

Spring security is a farmwork that provides authentication, authorization and protection against common attacks like CSRF.

Authentication — It is the process of verifying the identity of a user or entity.

Authorization — It is the process of determine what actions or resourses a user or system is permitted to access after they have been successfully authenticated.

Generate Spring BootProject File from Spring Initializer Tool.

Spring Initializr



Click generate button and download the zip file. After, open the file from Intellij IDE.

Basic Authentication and Authorization.

Before moving to basic authentication, we need to see the default authentication mechanism of Spring Security. When you add a spring-security dependency by default, spring security provides a default security mechanism. Let’s look at it.

First, comment on your JPA and MySQL driver dependencies in your pom.xml file. Otherwise, you cannot start your application. We don’t need those dependencies now. We will use those dependencies later.

Add controllers for your EmployeeController class.

package com.example.SpringSecurityDemo.Controller;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("api/v1/employees")
public class EmployeeController {

@GetMapping
public String getAllEmployees(){
return "You Received All Employees List";
}

@PostMapping
public String saveEmployees(){
return "You saved a Employee";
}

@PutMapping
public String updateEmployees(){
return "You updated a Employee";
}

}

We didn’t add logic for these methods. We just need these methods to test our security.

Then run your application. (Click the run button in your main class.)

Start your browser and type the url — localhost:8080/api/v1/employees. Then you need to give username and password. Username is user and password can be found from your run tab in Intellij. Spring security has given us the default password.




It has logout feature.


Actually, we don’t need this default password and username. We need to create usernames and password for list of users. we can use basic authentication.

Here are some characteristics of basic authentication.

  1. It is the most basic option to secure the REST APIs.
  2. Basic Auth uses an HTTP header in order to provide the username and password when making a request to a server.
  3. Basic Auth uses Base 64 encoded username and password in the header.
  4. Basic Authentication DO NOT use cookies, hence there is no concept of a session or logging out a user, which means each request has to carry that header in order to be authenticated.

Let’s do basic authentication.

1 — Add SecurityConfig.java class in your Config package.

package com.example.SpringSecurityDemo.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
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
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {

@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception{

http.csrf().disable()
.authorizeHttpRequests((authorize)->{
authorize.requestMatchers("api/v1/**").permitAll();

authorize.anyRequest().authenticated();
}).httpBasic(Customizer.withDefaults());
return http.build();
}

@Bean
public UserDetailsService userDetailsService(){

UserDetails john = User.builder()
.username("john")
.password(passwordEncoder().encode("john"))
.roles("USER")
.build();

UserDetails sam = User.builder()
.username("sam")
.password(passwordEncoder().encode("sam"))
.roles("ADMIN")
.build();

return new InMemoryUserDetailsManager(john,sam);
}
}





PasswordEncoder interface is used to encode the password and store it securely. The BCryptPasswordEncoder class implementation uses the bcrypt algorithm to hash the passwords.

SecurityFilterChain is a class provided by Spring Security. It is responsible for handling the security aspect of receiving HTTP requests.

csrf (Cross-Site Request Forgery) is a security feature that prevents malicious websites from performing actions on behalf of authenticated users.

authorizeHttpRequests specify authorization rules.

here, we used http basic ( httpBasic()). (Also, you can use Form Based Authentication. Then you need to add formLogin( ) instead httpBasic( ))

UserDetailsService interface is used to retrieve user-related data during the authentication process.

InMemoryUserDetailsManager is an implementation of the userDetailsService interface. It is used when you want to store user details in memory, typically for testing or similar applications where a full database isn’t necessary.

2 — Then you need to add some annotations to the EmployeeController.java class.

package com.example.SpringSecurityDemo.Controller;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("api/v1/employees")
public class EmployeeController {

@GetMapping
@PreAuthorize("hasRole('ROLE_USER') or hasRole('ROLE_ADMIN')")
public String getAllEmployees(){
return "You Received All Employees List";
}

@PostMapping
@PreAuthorize("hasRole('ROLE_ADMIN')")
public String saveEmployees(){
return "You saved a Employee";
}

@PutMapping
@PreAuthorize("hasRole('ROLE_ADMIN')")
public String updateEmployees(){
return "You updated a Employee";
}

}

The @PreAuthorize annotation can be used to set roles or authorities. We can use it @PreAuthorize(“hasRole(‘ROLE_USER’)”) or @PreAuthorize(“hasAuthority(‘ROLE_USER’)”).

For example, the post method can only be accessed by ADMIN, whereas the get method is accessible to both USER and ADMIN.

To do this you need to add @EnableMethodSecurity annotaion to SecurityConfig class.

3 — Run your application. Test using Postman.

John is an user. So, he didn’t have access to post and put operations. Sam is an admin. So, he can access to all the endpoints.




Actually, these user-related data should come from the database. So, let’s make a database connection and receive user-related data from the database.

Spring security integration with the database

To do this, first you need to uncomment the Spring Data JPA and MySQL Driver dependencies in pom.xml file.

1 — Add properties to application.properties file.

spring.datasource.url = jdbc:mysql://localhost:3306/emp_db?createDatabaseIfNotExist=true
spring.datasource.username = root
spring.datasource.password = Online12@

# Hibernate properties
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect

spring.jpa.hibernate.ddl-auto = update

2 — Create Employee and Roles entities in your Entity package with many-to-many mapping.

Employee.java

package com.example.SpringSecurityDemo.Entity;

import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Set;

@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "employee")
public class Employee {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
@Column(nullable = false, unique = true)
private String username;
@Column(nullable = false, unique = true)
private String email;
@Column(nullable = false)
private String password;
@ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@JoinTable(name = "employee_roles",
joinColumns = @JoinColumn(name = "employee_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id")
)

private Set<Roles> roles;


}

Roles.java

package com.example.SpringSecurityDemo.Entity;


import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "roles")
public class Roles {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
}

3 — Create repositories for these entities in your Repository package.

//Employee Repo
package com.example.SpringSecurityDemo.Repository;
import com.example.SpringSecurityDemo.Entity.Employee;
import org.springframework.data.jpa.repository.JpaRepository;

public interface EmployeeRepository extends JpaRepository<Employee,Long> {
Employee findByUsername(String username);
}
//Roles Repo
package com.example.SpringSecurityDemo.Repository;
import com.example.SpringSecurityDemo.Entity.Roles;
import org.springframework.data.jpa.repository.JpaRepository;

public interface RolesRepository extends JpaRepository<Roles,Long> {

}

4 — You can use the previously made controller class.

5 — Change the SecurityConfig.java class in your Config package.

package com.example.SpringSecurityDemo.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.config.annotation.web.configuration.EnableWebSecurity;
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.web.SecurityFilterChain;


@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {

private UserDetailsService userDetailsService;

@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception{

http.csrf().disable()
.authorizeHttpRequests((authorize)->{
authorize.requestMatchers("api/v1/**").permitAll();
authorize.anyRequest().authenticated();
}).httpBasic(Customizer.withDefaults());
return http.build();
}

@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
return configuration.getAuthenticationManager();
}

}

You need to inject UserDetailsService interface and AuthenticationManager bean. Authentication Manager handles the actual authentication logic, such as verifying password, checking user account and loading user details.

6 — Create the EmployeeDetailsService.java class in your Config package.

package com.example.SpringSecurityDemo.Config;

import com.example.SpringSecurityDemo.Entity.Employee;
import com.example.SpringSecurityDemo.Repository.EmployeeRepository;
import lombok.Data;
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 java.util.Set;
import java.util.stream.Collectors;

@Service
@Data
public class EmployeeDetailsService implements UserDetailsService {

@Autowired
private EmployeeRepository employeeRepository;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Employee employee = employeeRepository.findByUsername(username);

Set<GrantedAuthority> authorities = employee.getRoles().stream()
.map((roles) -> new SimpleGrantedAuthority(roles.getName()))
.collect(Collectors.toSet());

return new org.springframework.security.core.userdetails.User(
username,
employee.getPassword(),
authorities
);

}
}

This class is a custom implementation of UserDetailsService interface. Its purpose is to load user related data for authentication and authorization from repositories and convert it into a format spring security understands. (UserDetails)

(1) Find username form EmployeeRepository.

(2) Set Authorities for Roles. (For Employees)

(3) Send parameates to the constructor of the org.springframework.security.core.userdetails.User class to create a UserDetails object.

7 — Run your application and add data to your database.

INSERT INTO employee VALUES
(1,'john@gmail.com','john','$2a$12$.DNBDk4w3VlG6Fn3sNmUOeCw6VXuVa7O3oDCfvRrfMucwMn5VthQK','john'),
(2,'sam@gmail.com','sam','$2a$12$FujPv5E0QKVRVt1zIIu3GuTMxkvLs9dEgTQOx0z8WYfyjYJAZF0Zm','sam');

INSERT INTO roles VALUES (1,'ROLE_USER'),(2,'ROLE_ADMIN');

INSERT INTO employee_roles VALUES (1,1),(2,2);

Here, I generated password from this site — Bcrypt-Generator.com — Generate, Check, Hash, Decode Bcrypt Strings


8 — Test APIs

Sam —> Admin(Employee) — Can access all APIs.

John —> User(Employee) — Only can access get API.






Note

This is the foundation of the spring security.

You can also integrate additional authentication mechanisms, such as OAuth2, JWT, LDAP, or custom authentication providers, as needed.

having a good awareness of additional security mechanisms you can greatly enhance the security posture of your application.

So, learn some of these mechanisms. I have written an artical about JWT. you can read it.

Jwt artical — https://medium.com/@Lakshitha_Fernando/jwt-spring-security-6-and-spring-boot-3-with-simple-project-819d84e09af2

In real world there are several authentication mechanisms.


JWT belongs to token based authentication.

Summary

In this article, we learned about basic authentication and role-based authentication and authorization with Spring Security.


Post a Comment

Previous Post Next Post