- Introduction to Spring Boot for Beginners
- Introduction to REST API
- Introduction to Spring Security
- Creating a Spring Boot Project
- Creating CRUD REST APIs with MySQL Database
- Securing the REST API using Basic Authentication
- Securing the REST API using In-Memory Authentication
- Securing the REST API using Database Authentication
Introduction to Spring Boot
Spring Boot is an open-source Java-based framework for creating stand-alone, production-grade Spring applications. It simplifies the development process by providing defaults for code and annotation configuration, enabling you to start coding quickly without worrying about setup details.
Key Features of Spring Boot
- Auto-Configuration: Automatically configures your Spring application based on the dependencies you have added to the project.
- Standalone: Creates stand-alone Spring applications with embedded servers.
- Production-ready Features: Includes production-ready features such as metrics, health checks, and externalized configuration.
- Convention over Configuration: Reduces the need for explicit configuration by following conventions.
- Spring Boot Starters: Provides a set of pre-configured dependencies for various functionalities, making it easy to get started.
Introduction to REST API
A REST API (Representational State Transfer Application Programming Interface) is an architectural style for building web services that interact over HTTP. REST APIs allow different software systems to communicate and exchange data efficiently. The key principles of REST include statelessness, resource-based interactions, and standardized HTTP methods like GET, POST, PUT, and DELETE.
Key Principles of REST
- Stateless: Each request from the client to the server must contain all the information needed to understand and process the request.
- Resource-Based: REST treats any content as a resource, such as users, posts, or items.
- HTTP Methods: REST uses standard HTTP methods for CRUD operations (Create, Read, Update, Delete).
Introduction to Spring Security
Spring Security is a powerful and highly customizable authentication and access-control framework for Java applications. It is the de facto standard for securing Spring-based applications and provides comprehensive security services for Java EE-based enterprise software applications.
Key Features of Spring Security
- Authentication: Verifying the identity of a user.
- Authorization: Determining whether an authenticated user has access to a specific resource.
- Protection Against Attacks: Such as session fixation, clickjacking, cross-site request forgery, etc.
Step 1: Creating a Spring Boot Project
Using Spring Initializr
-
Go to Spring Initializr.
-
Configure the project:
- Project: Maven Project
- Language: Java
- Spring Boot: 3.3.0
- Packaging: Jar
- Java: 21
- Dependencies: Spring Web, Spring Data JPA, MySQL Driver, Lombok, Spring Security
-
Click on "Generate" to download the project.
-
Unzip the downloaded project and open it in your favorite IDE.
Example Project Structure
spring-boot-security-rest-api/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/example/demo/
│ │ │ └── DemoApplication.java
│ │ │ └── controller/
│ │ │ └── ProductController.java
│ │ │ └── OrderController.java
│ │ │ └── model/
│ │ │ └── Product.java
│ │ │ └── Order.java
│ │ │ └── User.java
│ │ │ └── Role.java
│ │ │ └── repository/
│ │ │ └── ProductRepository.java
│ │ │ └── OrderRepository.java
│ │ │ └── UserRepository.java
│ │ │ └── service/
│ │ │ └── ProductService.java
│ │ │ └── OrderService.java
│ │ │ └── UserService.java
│ │ │ └── config/
│ │ │ └── SecurityConfig.java
│ │ └── resources/
│ │ ├── application.properties
│ └── test/
│ └── java/
│ └── com/example/demo/
│ └── DemoApplicationTests.java
├── mvnw
├── mvnw.cmd
├── pom.xml
└── .mvn/
└── wrapper/
└── maven-wrapper.properties
Step 2: Creating CRUD REST APIs with MySQL Database
Setting Up the Database
- Create a MySQL database named
spring_boot_db
.
Configure Database Connection
-
Update the
application.properties
file to configure the MySQL database connection.spring.datasource.url=jdbc:mysql://localhost:3306/spring_boot_db spring.datasource.username=root spring.datasource.password=root spring.jpa.hibernate.ddl-auto=update spring.jpa.show-sql=true
Create Entity Classes
-
Create a new package named
model
and add theProduct
andOrder
entity classes.package com.example.demo.model; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; @Entity public class Product { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private double price; public Product() {} public Product(String name, double price) { this.name = name; this.price = price; } // Getters and setters public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } }
package com.example.demo.model; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; @Entity public class Order { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private Long productId; private int quantity; public Order() {} public Order(Long productId, int quantity) { this.productId = productId; this.quantity = quantity; } // Getters and setters public Long getId() { return id; } public void setId(Long id) { this.id = id; } public Long getProductId() { return productId; } public void setProductId(Long productId) { this.productId = productId; } public int getQuantity() { return quantity; } public void setQuantity(int quantity) { this.quantity = quantity; } }
Explanation
@Entity
: Specifies that the class is an entity and is mapped to a database table.@Id
: Specifies the primary key of an entity.@GeneratedValue(strategy = GenerationType.IDENTITY)
: Provides the specification of generation strategies for the primary key values.
Create Repository Interfaces
-
Create a new package named
repository
and add theProductRepository
andOrderRepository
interfaces.package com.example.demo.repository; import com.example.demo.model.Product; import org.springframework.data.jpa.repository.JpaRepository; public interface ProductRepository extends JpaRepository<Product, Long> { }
package com.example.demo.repository; import com.example.demo.model.Order; import org.springframework.data.jpa.repository.JpaRepository; public interface OrderRepository extends JpaRepository<Order, Long> { }
Explanation
extends JpaRepository<Product, Long>
: Indicates that theProductRepository
interface extendsJpaRepository
, providing CRUD operations for theProduct
entity.extends JpaRepository<Order, Long>
: Indicates that theOrderRepository
interface extendsJpaRepository
, providing CRUD operations for theOrder
entity.
Create Service Classes
-
Create a new package named
service
and add theProductService
andOrderService
classes.package com.example.demo.service; import com.example.demo.model.Product; import com.example.demo.repository.ProductRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; import java.util.Optional; @Service public class ProductService { private final ProductRepository productRepository; @Autowired public ProductService(ProductRepository productRepository) { this.productRepository = productRepository; } public List<Product> getAllProducts() { return productRepository.findAll(); } public Optional<Product> getProductById(Long id) { return productRepository.findById(id); } public Product createProduct(Product product) { return productRepository.save(product); } public Optional<Product> updateProduct(Long id, Product productDetails) { return productRepository.findById(id).map(product -> { product.setName(productDetails.getName()); product.setPrice(productDetails.getPrice()); return productRepository.save(product); }); } public void deleteProduct(Long id) { productRepository.deleteById(id); } }
package com.example.demo.service; import com.example.demo.model.Order; import com.example.demo.repository.OrderRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; import java.util.Optional; @Service public class OrderService { private final OrderRepository orderRepository; @Autowired public OrderService(OrderRepository orderRepository) { this.orderRepository = orderRepository; } public List<Order> getAllOrders() { return orderRepository.findAll(); } public Optional<Order> getOrderById(Long id) { return orderRepository.findById(id); } public Order createOrder(Order order) { return orderRepository.save(order); } public Optional<Order> updateOrder(Long id, Order orderDetails) { return orderRepository.findById(id).map(order -> { order.setProductId(orderDetails.getProductId()); order.setQuantity(orderDetails.getQuantity()); return orderRepository.save(order); }); } public void deleteOrder(Long id) { orderRepository.deleteById(id); } }
Explanation
@Service
: Indicates that the class is a service component in the Spring context.public ProductService(ProductRepository productRepository)
: Uses constructor-based dependency injection to inject theProductRepository
bean.public OrderService(OrderRepository orderRepository)
: Uses constructor-based dependency injection to inject theOrderRepository
bean.
Create Controller Classes
-
Create a new package named
controller
and add theProductController
andOrderController
classes.package com.example.demo.controller; import com.example.demo.model.Product; import com.example.demo.service.ProductService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; import java.util.Optional; @RestController @RequestMapping("/api/products") public class ProductController { private final ProductService productService; @Autowired public ProductController(ProductService productService) { this.productService = productService; } @GetMapping public List<Product> getAllProducts() { return productService.getAllProducts(); } @GetMapping("/{id}") public Optional<Product> getProductById(@PathVariable Long id) { return productService.getProductById(id); } @PostMapping public Product createProduct(@RequestBody Product product) { return productService.createProduct(product); } @PutMapping("/{id}") public Optional<Product> updateProduct(@PathVariable Long id, @RequestBody Product productDetails) { return productService.updateProduct(id, productDetails); } @DeleteMapping("/{id}") public void deleteProduct(@PathVariable Long id) { productService.deleteProduct(id); } }
package com.example.demo.controller; import com.example.demo.model.Order; import com.example.demo.service.OrderService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; import java.util.Optional; @RestController @RequestMapping("/api/orders") public class OrderController { private final OrderService orderService; @Autowired public OrderController(OrderService orderService) { this.orderService = orderService; } @GetMapping public List<Order> getAllOrders() { return orderService.getAllOrders(); } @GetMapping("/{id}") public Optional<Order> getOrderById(@PathVariable Long id) { return orderService.getOrderById(id); } @PostMapping public Order createOrder(@RequestBody Order order) { return orderService.createOrder(order); } @PutMapping("/{id}") public Optional<Order> updateOrder(@PathVariable Long id, @RequestBody Order orderDetails) { return orderService.updateOrder(id, orderDetails); } @DeleteMapping("/{id}") public void deleteOrder(@PathVariable Long id) { orderService.deleteOrder(id); } }
Explanation
@RestController
: Indicates that the class is a REST controller.@RequestMapping("/api/products")
: Maps HTTP requests to the/api/products
URL.@RequestMapping("/api/orders")
: Maps HTTP requests to the/api/orders
URL.- CRUD Methods: Implements CRUD operations for the
Product
andOrder
entities.
Step 3: Securing the REST API
Securing the REST API using Basic In-memory Authentication
-
Add the
SecurityConfig
class in theconfig
package.package com.example.demo.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; 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.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) .authorizeHttpRequests(auth -> auth .requestMatchers("/api/orders/**").hasAnyRole("USER", "ADMIN") .requestMatchers("/api/products/**").hasRole("ADMIN") .anyRequest().authenticated() ) .httpBasic(withDefaults()); return http.build(); } @Bean public UserDetailsService userDetailsService() { UserDetails user = User.withUsername("user") .password("{noop}password") .roles("USER") .build(); UserDetails admin = User.withUsername("admin") .password("{noop}admin") .roles("ADMIN") .build(); return new InMemoryUserDetailsManager(user, admin); } }
Explanation
@Configuration
: Indicates that the class has@Bean
definition methods.@EnableWebSecurity
: Enables Spring Security's web security support.securityFilterChain(HttpSecurity http)
: Configures the security filter chain.http.csrf(csrf -> csrf.disable())
: Disables CSRF protection.http.authorizeHttpRequests(auth -> auth.requestMatchers("/api/orders/**").hasAnyRole("USER", "ADMIN").requestMatchers("/api/products/**").hasRole("ADMIN").anyRequest().authenticated())
: Configures authorization rules.http.httpBasic(withDefaults())
: Configures basic HTTP authentication.userDetailsService()
: Creates an in-memory user details manager with two users: one with the roleUSER
and the other with the roleADMIN
.
Securing the REST API using Database Authentication
-
Create
User
andRole
entities.package com.example.demo.model; import jakarta.persistence.*; import java.util.Set; @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String username; private String password; @ManyToMany(fetch = FetchType.EAGER) @JoinTable( name = "users_roles", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id") ) private Set<Role> roles; // Getters and setters }
package com.example.demo.model; import jakarta.persistence.*; @Entity public class Role { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; // Getters and setters }
-
Create
UserRepository
andRoleRepository
.package com.example.demo.repository; import com.example.demo.model.User; import org.springframework.data.jpa.repository.JpaRepository; public interface UserRepository extends JpaRepository<User, Long> { User findByUsername(String username); }
package com.example.demo.repository; import com.example.demo.model.Role; import org.springframework.data.jpa.repository.JpaRepository; public interface RoleRepository extends JpaRepository<Role, Long> { }
-
Create
UserService
andUserDetailsServiceImpl
.package com.example.demo.service; import com.example.demo.model.Role; import com.example.demo.model.User; import com.example.demo.repository.UserRepository; 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 public class UserDetailsServiceImpl implements UserDetailsService { @Autowired private UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUsername(username); if (user == null) { throw new UsernameNotFoundException("User not found"); } Set<GrantedAuthority> authorities = user.getRoles().stream() .map(role -> new SimpleGrantedAuthority(role.getName())) .collect(Collectors.toSet()); return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), authorities); } }
-
Update
SecurityConfig
to use database authentication.package com.example.demo.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.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.SecurityFilterChain; @Configuration @EnableWebSecurity public class SecurityConfig { @Autowired private UserDetailsService userDetailsService; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) .authorizeHttpRequests(auth -> auth .requestMatchers("/api/orders/**").hasAnyRole("USER", "ADMIN") .requestMatchers("/api/products/**").hasRole("ADMIN") .anyRequest().authenticated() ) .userDetailsService(userDetailsService) .httpBasic(withDefaults()); return http.build(); } }
Explanation
@ManyToMany
: Indicates a many-to-many relationship betweenUser
andRole
.UserRepository
: ExtendsJpaRepository
, providing CRUD operations for theUser
entity.RoleRepository
: ExtendsJpaRepository
, providing CRUD operations for theRole
entity.UserDetailsServiceImpl
: Implements theUserDetailsService
interface to load user-specific data.securityFilterChain(HttpSecurity http)
: Configures the security filter chain to use theUserDetailsService
for authentication.
By following these steps, you will have a secured REST API where:
- Viewing and placing orders is accessible to users with the roles
USER
andADMIN
. - Managing products is accessible only to users with the role
ADMIN
.
Testing the REST APIs Using Postman
-
Get All Products
- Request:
GET /api/products
- Response:
[ { "id": 1, "name": "Product 1", "price": 100.0 }, { "id": 2, "name": "Product 2", "price": 200.0 } ]
- Request:
-
Get Product by ID
- Request:
GET /api/products/{id}
- Response:
{ "id": 1, "name": "Product 1", "price": 100.0 }
- Request:
-
Create Product
- Request:
POST /api/products
{ "name": "New Product", "price": 150.0 }
- Response:
{ "id": 3, "name": "New Product", "price": 150.0 }
- Request:
-
Update Product
- Request:
PUT /api/products/{id}
{ "name": "Updated Product", "price": 180.0 }
- Response:
{ "id": 1, "name": "Updated
- Request:
Product", "price": 180.0 } ```
-
Delete Product
- Request:
DELETE /api/products/{id}
- Response:
204 No Content
- Request:
-
Get All Orders
- Request:
GET /api/orders
- Response:
[ { "id": 1, "productId": 1, "quantity": 2 }, { "id": 2, "productId": 2, "quantity": 1 } ]
- Request:
-
Get Order by ID
- Request:
GET /api/orders/{id}
- Response:
{ "id": 1, "productId": 1, "quantity": 2 }
- Request:
-
Create Order
- Request:
POST /api/orders
{ "productId": 1, "quantity": 3 }
- Response:
{ "id": 3, "productId": 1, "quantity": 3 }
- Request:
-
Update Order
- Request:
PUT /api/orders/{id}
{ "productId": 1, "quantity": 5 }
- Response:
{ "id": 1, "productId": 1, "quantity": 5 }
- Request:
-
Delete Order
- Request:
DELETE /api/orders/{id}
- Response:
204 No Content
- Request:
This completes the setup and testing of securing REST APIs in Spring Boot using basic, in-memory, and database authentication.
Comments
Post a Comment
Leave Comment