Spring Security
Basic Configuration
Spring Security is a framework for securing Spring applications. By default, all endpoints are protected by a login form.
Add the dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
Once added, restart the application. All endpoints now require a login.
Default credentials in the console:
Username: user
Password: <random generated in console>
Default User/Password
Configure a default user in application.properties:
# Default user
spring.security.user.name=admin
spring.security.user.password=admin123
spring.security.user.roles=ADMIN
HTTP Basic Authentication
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.httpBasic(basic -> {});
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
SecurityFilterChain
SecurityFilterChain defines the security rules for URLs:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers(HttpMethod.GET, "/api/products/**").permitAll()
.requestMatchers(HttpMethod.POST, "/api/products/**").hasRole("ADMIN")
.requestMatchers("/api/orders/**").authenticated()
.anyRequest().denyAll()
);
return http.build();
}
}
UserDetailsService
A custom implementation for loading users from the database:
@Service
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
public CustomUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
User user = userRepository.findByEmail(email)
.orElseThrow(() -> new UsernameNotFoundException(
"User not found: " + email));
return org.springframework.security.core.userdetails.User.builder()
.username(user.getEmail())
.password(user.getPassword())
.roles(user.getRole())
.build();
}
}
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String email;
private String password;
private String role; // ROLE_USER, ROLE_ADMIN
// getters, setters
}
Form Login
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/login", "/register").permitAll()
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.loginProcessingUrl("/perform-login")
.defaultSuccessUrl("/dashboard", true)
.failureUrl("/login?error=true")
.permitAll()
)
.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/login?logout=true")
.permitAll()
);
return http.build();
}
Role-Based Access Control
The @PreAuthorize Annotation
@RestController
@RequestMapping("/api/products")
public class ProductController {
@GetMapping
public ResponseEntity<List<Product>> getAllProducts() {
return ResponseEntity.ok(productService.getAll());
}
@PostMapping
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<Product> createProduct(@RequestBody Product product) {
return ResponseEntity.ok(productService.create(product));
}
@PutMapping("/{id}")
@PreAuthorize("hasRole('ADMIN') or @productSecurity.isOwner(#id, authentication)")
public ResponseEntity<Product> updateProduct(@PathVariable Long id,
@RequestBody Product product) {
return ResponseEntity.ok(productService.update(id, product));
}
@DeleteMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<Void> deleteProduct(@PathVariable Long id) {
productService.delete(id);
return ResponseEntity.noContent().build();
}
}
Custom Security Method
@Component("productSecurity")
public class ProductSecurity {
private final ProductRepository productRepository;
public ProductSecurity(ProductRepository productRepository) {
this.productRepository = productRepository;
}
public boolean isOwner(Long productId, Authentication authentication) {
String email = authentication.getName();
Product product = productRepository.findById(productId)
.orElseThrow(() -> new RuntimeException("Product not found"));
return product.getOwnerEmail().equals(email);
}
}
Enable method security:
@Configuration
@EnableMethodSecurity
public class MethodSecurityConfig {
}
In-Memory User
For testing or development:
@Bean
public UserDetailsService userDetailsService() {
UserDetails user = User.builder()
.username("user")
.password(passwordEncoder().encode("password"))
.roles("USER")
.build();
UserDetails admin = User.builder()
.username("admin")
.password(passwordEncoder().encode("admin"))
.roles("USER", "ADMIN")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
Complete Configuration for a REST API
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll()
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults());
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
With Spring Security, your application is protected from unauthorized access. In the next chapter, we'll learn about API Documentation with Swagger/OpenAPI.