Skip to main content

Overview

The Library API uses Spring Security to protect endpoints and manage authentication. This guide covers the security architecture, configuration, and best practices.

Security Architecture

The application implements the following security features:

HTTP Basic Authentication

Stateless authentication using HTTP Basic Auth headers

Password Encryption

BCrypt password hashing with strength factor 12

Session Management

Stateless sessions for REST API design

Endpoint Protection

Role-based access control for API endpoints

Spring Security Configuration

The security configuration is defined in SecurityConfig.java:
SecurityConfig.java
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests((requests) -> requests
                .requestMatchers("/", "/auth/login", "/home").permitAll()
                .requestMatchers("/auth/register").permitAll()
                .anyRequest().authenticated())
            .httpBasic(Customizer.withDefaults())
            .sessionManagement(session -> 
                session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .logout((logout) -> logout.permitAll());

        return http.build();
    }

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

Configuration Breakdown

1

Enable Web Security

The @EnableWebSecurity annotation activates Spring Security:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
    // Configuration beans
}
2

Configure HTTP Security

Define which endpoints require authentication:
.authorizeHttpRequests((requests) -> requests
    .requestMatchers("/", "/auth/login", "/home").permitAll()
    .requestMatchers("/auth/register").permitAll()
    .anyRequest().authenticated())
  • Public endpoints: /, /auth/login, /home, /auth/register
  • Protected endpoints: All other endpoints require authentication
3

Enable HTTP Basic Authentication

Configure stateless HTTP Basic authentication:
.httpBasic(Customizer.withDefaults())
.sessionManagement(session -> 
    session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
Stateless sessions mean no session cookies are created. Each request must include authentication credentials.

Password Encryption

The application uses BCrypt for password hashing with a strength factor of 12.

BCrypt Configuration

SecurityConfig.java
@Bean
public BCryptPasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder(12);
}

Understanding BCrypt Strength

strength
integer
default:"12"
The BCrypt work factor determines computational cost:
  • 4-10: Fast, suitable for testing (not recommended for production)
  • 12: Balanced security and performance (recommended)
  • 13-15: Higher security, slower performance
  • 16+: Very secure, but significantly slower
Higher strength values exponentially increase computation time. Strength 12 provides strong security with reasonable performance.

Encrypting Passwords

When creating or updating users, always encrypt passwords:
UserService.java
@Autowired
private BCryptPasswordEncoder passwordEncoder;

public UserEntity createUser(UserDto userDto) {
    UserEntity user = new UserEntity();
    user.setEmail(userDto.getEmail());
    user.setFirstname(userDto.getFirstname());
    user.setLastname(userDto.getLastname());
    
    // Encrypt password before saving
    String encryptedPassword = passwordEncoder.encode(userDto.getPassword());
    user.setPassword(encryptedPassword);
    
    return userRepository.save(user);
}

Validating Passwords

To verify a password during authentication:
AuthService.java
public boolean validatePassword(String rawPassword, String encodedPassword) {
    return passwordEncoder.matches(rawPassword, encodedPassword);
}

Authentication Setup

Authentication Provider

The application uses a DaoAuthenticationProvider for database-backed authentication:
SecurityConfig.java
@Bean
public AuthenticationProvider authenticationProvider() {
    DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
    daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
    daoAuthenticationProvider.setUserDetailsService(userDetailsService());
    return daoAuthenticationProvider;
}

Authentication Manager

The authentication manager coordinates authentication providers:
SecurityConfig.java
@Bean
public AuthenticationManager authManager(HttpSecurity http) throws Exception {
    return http.getSharedObject(AuthenticationManagerBuilder.class)
            .authenticationProvider(authenticationProvider())
            .build();
}

UserDetailsService

The application uses an in-memory user store (can be replaced with database-backed implementation):
SecurityConfig.java
private final Map<String, UserDetails> users = new HashMap<>();

@Bean
public UserDetailsService userDetailsService() {
    return new InMemoryUserDetailsManager(users.values());
}
For production, implement a custom UserDetailsService that loads users from the database.

Implementing Database-Backed Authentication

Replace the in-memory user store with database authentication:
1

Create UserDetailsService implementation

Create a custom service that loads users from the database:
CustomUserDetailsService.java
@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String email) 
            throws UsernameNotFoundException {
        UserEntity user = userRepository.findByEmail(email)
            .orElseThrow(() -> new UsernameNotFoundException(
                "User not found with email: " + email));

        return User.builder()
            .username(user.getEmail())
            .password(user.getPassword())
            .roles("USER")
            .build();
    }
}
2

Update SecurityConfig

Inject and use the custom service:
SecurityConfig.java
@Autowired
private CustomUserDetailsService customUserDetailsService;

@Bean
public AuthenticationProvider authenticationProvider() {
    DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
    provider.setPasswordEncoder(passwordEncoder());
    provider.setUserDetailsService(customUserDetailsService);
    return provider;
}
3

Create UserRepository method

Add a method to find users by email:
UserRepository.java
public interface UserRepository extends JpaRepository<UserEntity, Long> {
    Optional<UserEntity> findByEmail(String email);
}

Protecting Endpoints

Public vs Protected Endpoints

Configure which endpoints are public and which require authentication:
SecurityConfig.java
.authorizeHttpRequests((requests) -> requests
    // Public endpoints
    .requestMatchers("/", "/auth/login", "/home").permitAll()
    .requestMatchers("/auth/register").permitAll()
    .requestMatchers("/api/public/**").permitAll()
    
    // Admin-only endpoints
    .requestMatchers("/api/admin/**").hasRole("ADMIN")
    
    // Authenticated endpoints
    .requestMatchers("/api/books/**").authenticated()
    .requestMatchers("/api/rentals/**").authenticated()
    .requestMatchers("/api/users/**").authenticated()
    
    // All other requests require authentication
    .anyRequest().authenticated())

Role-Based Access Control

Implement role-based authorization:
SecurityConfig.java
.authorizeHttpRequests((requests) -> requests
    // Public access
    .requestMatchers("/auth/**").permitAll()
    
    // User role required
    .requestMatchers("/api/books").hasAnyRole("USER", "ADMIN")
    .requestMatchers("/api/rentals").hasAnyRole("USER", "ADMIN")
    
    // Admin role required
    .requestMatchers("/api/users/all").hasRole("ADMIN")
    .requestMatchers("/api/books/create").hasRole("ADMIN")
    .requestMatchers("/api/books/delete/**").hasRole("ADMIN")
    
    .anyRequest().authenticated())

Method-Level Security

Protect individual methods with annotations:
1

Enable method security

Add @EnableMethodSecurity to your configuration:
SecurityConfig.java
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
    // Configuration
}
2

Use security annotations

Protect methods with annotations:
BookController.java
@RestController
@RequestMapping("/api/books")
public class BookController {

    @GetMapping
    @PreAuthorize("hasAnyRole('USER', 'ADMIN')")
    public List<BookDto> getAllBooks() {
        // Method implementation
    }

    @PostMapping
    @PreAuthorize("hasRole('ADMIN')")
    public BookDto createBook(@RequestBody BookDto bookDto) {
        // Method implementation
    }

    @DeleteMapping("/{isbn}")
    @PreAuthorize("hasRole('ADMIN')")
    public void deleteBook(@PathVariable String isbn) {
        // Method implementation
    }
}

Making Authenticated Requests

Using cURL

# Basic authentication with username and password
curl -u username:password http://localhost:8080/api/books

# With explicit Basic Auth header
curl -H "Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=" \
     http://localhost:8080/api/books

Using JavaScript/Fetch

const username = '[email protected]';
const password = 'password123';
const credentials = btoa(`${username}:${password}`);

fetch('http://localhost:8080/api/books', {
  method: 'GET',
  headers: {
    'Authorization': `Basic ${credentials}`,
    'Content-Type': 'application/json'
  }
})
.then(response => response.json())
.then(data => console.log(data));

Using Postman

1

Open Authorization tab

In Postman, select the request and go to the “Authorization” tab.
2

Select Basic Auth

Choose “Basic Auth” from the Type dropdown.
3

Enter credentials

Enter your username and password. Postman will automatically encode them.
4

Send request

Send the request. The Authorization header will be automatically added.

CSRF Protection

The current implementation does not explicitly disable CSRF protection. For stateless REST APIs, you may want to disable it.
For stateless REST APIs, CSRF protection can be disabled by adding this configuration:
SecurityConfig.java
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .csrf(csrf -> csrf.disable()) // Disable CSRF for REST APIs
        .authorizeHttpRequests((requests) -> requests
            // Authorization rules
        )
        .httpBasic(Customizer.withDefaults());
    
    return http.build();
}
Only disable CSRF if you’re building a stateless REST API. If your application uses session-based authentication or serves HTML forms, keep CSRF protection enabled.

CORS Configuration

CORS is not configured in the current implementation. If your API needs to be accessed from web browsers on different domains, add this configuration.
To enable CORS for browser-based clients:
CorsConfig.java
@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
            .allowedOrigins("http://localhost:3000", "https://yourdomain.com")
            .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH")
            .allowedHeaders("*")
            .allowCredentials(true)
            .maxAge(3600);
    }
}

Security Best Practices

Use HTTPS

Always use HTTPS in production to encrypt credentials in transit. HTTP Basic Auth sends credentials in base64 encoding, which is easily decoded.

Strong Passwords

Enforce password policies: minimum length, complexity requirements, and password history.

Rate Limiting

Implement rate limiting to prevent brute force attacks on authentication endpoints.

Audit Logging

Log authentication attempts, especially failures, for security monitoring.

Token Expiration

Consider implementing JWT tokens with expiration for better security than persistent Basic Auth.

Input Validation

Validate and sanitize all user inputs to prevent injection attacks.

Troubleshooting

Error: All requests return 401 UnauthorizedSolutions:
  • Verify you’re sending the Authorization header
  • Check credentials are correct
  • Ensure user exists in the database/user store
  • Verify password is properly encrypted when stored
  • Check the Basic Auth header is properly formatted: Basic base64(username:password)
Error: Authenticated but access denied (403)Solutions:
  • Verify user has the required role for the endpoint
  • Check the endpoint’s security configuration
  • Ensure roles are properly assigned to the user
Error: CORS policy blocking requests from browserSolutions:
  • Configure CORS to allow your frontend origin
  • Ensure allowCredentials(true) is set if sending credentials
  • Check allowed methods include the HTTP method you’re using
Error: Correct password not workingSolutions:
  • Verify password was encrypted before saving: passwordEncoder.encode(password)
  • Check BCrypt encoder is being used for validation
  • Ensure the same BCrypt strength is used for encoding and validation
  • Verify the stored password hash starts with $2a$ or $2b$ (BCrypt format)
Error: Session-related errors in stateless APISolutions:
  • Ensure SessionCreationPolicy.STATELESS is configured
  • Don’t rely on session storage in your application code
  • Send authentication credentials with every request

Advanced Security Topics

JWT Authentication

Consider implementing JWT tokens for better scalability and stateless authentication.

OAuth 2.0

Integrate OAuth 2.0 for third-party authentication (Google, GitHub, etc.).

Two-Factor Auth

Add 2FA for enhanced security on sensitive operations.

API Key Authentication

Implement API keys for service-to-service authentication.

Next Steps

API Reference

Explore the protected API endpoints

User Management

Learn about user management endpoints

Build docs developers (and LLMs) love