Date: February 4, 2025
Security is a critical aspect of modern web applications. In this part, we’ll secure your Spring Boot REST API by integrating Spring Security 6, adding authentication and authorization mechanisms to protect your endpoints.
What You’ll Learn in This Part
- Configuring Spring Security in Spring Boot 3
- Adding user authentication with in-memory and database-backed users
- Protecting REST endpoints with role-based access control (RBAC)
- Implementing JWT (JSON Web Token) based stateless authentication
- Securing password storage with BCrypt hashing
Step 1: Add Spring Security Dependency
Add this to your pom.xml
or build.gradle
:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
Step 2: Basic Security Configuration
Create a security configuration class:
package com.example.demoapp.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.web.SecurityFilterChain;
@Configuration
@EnableMethodSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable()) // For API use, CSRF disabled
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/users/**").authenticated()
.anyRequest().permitAll()
)
.httpBasic(Customizer.withDefaults()); // Use HTTP Basic auth for simplicity
return http.build();
}
}
Here, all
/api/users/**
endpoints require authentication; others are public.
Step 3: In-Memory User Details for Testing
Add an in-memory user with roles for testing:
import org.springframework.context.annotation.Bean;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Bean
public UserDetailsService userDetailsService() {
UserDetails user = User.withUsername("user")
.password(passwordEncoder().encode("password"))
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
Step 4: Securing Controller Methods with Roles
Add role-based security on controller methods using annotations:
import org.springframework.security.access.prepost.PreAuthorize;
@RestController
@RequestMapping("/api/users")
public class UserController {
// Only users with ROLE_USER can access this
@GetMapping("/{id}")
@PreAuthorize("hasRole('USER')")
public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {
// ...
}
// Only authenticated users can create users
@PostMapping
@PreAuthorize("isAuthenticated()")
public ResponseEntity<UserDTO> createUser(@RequestBody UserDTO userDto) {
// ...
}
// Additional secured methods here
}
Step 5: Moving to Database-backed Users (Optional)
Later, you may want to load users from the database. This requires implementing UserDetailsService
and querying your user table.
Example:
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByEmail(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
return org.springframework.security.core.userdetails.User.withUsername(user.getEmail())
.password(user.getPassword()) // password hashed in DB
.roles("USER") // or dynamic roles
.build();
}
}
Update the security config to use this service.
Step 6: Implementing JWT Authentication (Advanced)
For stateless API authentication, JWT tokens are preferred. This involves:
- Creating JWT utility classes to generate and validate tokens
- Creating authentication endpoints (
/login
) - Adding JWT filter to Spring Security filter chain to validate tokens per request
Due to scope, we’ll cover JWT integration in a future tutorial. For now, you can use HTTP Basic or form login for simple security.
Step 7: Password Hashing Best Practices
Always store passwords hashed with a strong algorithm like BCrypt. Spring Security’s PasswordEncoder
interface provides this:
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
When creating new users, encode passwords before saving:
user.setPassword(passwordEncoder.encode(rawPassword));
Testing Your Secured API
Try accessing /api/users
endpoints without credentials — you’ll get 401 Unauthorized. With basic auth (user:password
), you can access the protected endpoints.
Use tools like Postman or curl:
curl -u user:password http://localhost:8080/api/users
Summary
- Added Spring Security to protect REST API endpoints
- Configured basic HTTP authentication with in-memory users
- Applied method-level security with role-based access control
- Discussed moving to DB-backed users and JWT tokens for production-ready security
- Emphasized safe password storage using BCrypt
Next Up
In Part 5, we will focus on Testing and Deploying the full application to production with best practices.