Skip to main content

Overview

The Library Management API implements JWT (JSON Web Token) based authentication with Spring Security. This guide covers security configuration, JWT setup, and authorization rules.

Security Architecture

The security system consists of three main components:
  1. SecurityConfig - Defines security filter chain and authentication providers
  2. JwtTokenValidator - Custom filter that validates JWT tokens
  3. JwtUtils - Utility for creating and validating JWT tokens

JWT Configuration Properties

Required Properties

JWT properties must be configured via environment variables. Never commit these values to version control.
PropertyEnvironment VariableDescriptionExample
security.jwt.user.generatoruser_jwtJWT issuer identifierlibrary-api
security.jwt.key.privatekey_jwtSecret key for signing tokensyour-256-bit-secret-key

Configuration in application.properties

security.jwt.user.generator=${user_jwt}
security.jwt.key.private=${key_jwt}
Defined in: /workspace/source/src/main/resources/application.properties:17-18

Setting Environment Variables

export user_jwt="library-management-api"
export key_jwt="your-secure-256-bit-secret-key-here-change-in-production"
The JWT secret key should be at least 256 bits (32 characters) for HMAC256 algorithm security.

Security Filter Chain

The SecurityConfig class defines the security filter chain that processes all HTTP requests. Defined in: /workspace/source/src/main/java/com/raven/training/config/security/SecurityConfig.java

Filter Chain Configuration

The security filter chain implements the following rules:
1

Disable CSRF Protection

Cross-Site Request Forgery protection is disabled for stateless JWT authentication:
.csrf(csrf -> csrf.disable())
2

Enable HTTP Basic Authentication

HTTP Basic authentication is enabled as a fallback mechanism:
.httpBasic(Customizer.withDefaults())
3

Configure Stateless Sessions

Session management is set to STATELESS for JWT-based authentication:
.sessionManagement(session -> 
  session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
4

Define Authorization Rules

Public and protected endpoints are configured (see Authorization Rules below)
5

Add JWT Validation Filter

Custom JWT token validator is added before basic authentication:
.addFilterBefore(new JwtTokenValidator(jwtUtils), 
  BasicAuthenticationFilter.class)

Authorization Rules

Public Endpoints (No Authentication Required)

The following endpoints are accessible without authentication:

Swagger UI Documentation

  • /swagger-ui/**
  • /swagger-ui.html
  • /v3/api-docs/**
  • /swagger-resources/**
  • /webjars/**

Authentication Endpoints

  • POST /api/v1/auth/**
Includes login, registration, and token refresh endpoints.
Configured in: SecurityConfig.java:64-73

Protected Endpoints

All other endpoints require authentication:
http.anyRequest().authenticated();
Requests to protected endpoints must include a valid JWT token in the Authorization header.

JWT Token Management

Token Structure

JWT tokens created by the API include the following claims:
ClaimDescriptionExample
iss (Issuer)Token issuer from security.jwt.user.generatorlibrary-management-api
sub (Subject)Username of the authenticated userjohn.doe
authoritiesUser roles/authorities (comma-separated)ROLE_USER,ROLE_ADMIN
iat (Issued At)Token creation timestamp1709740800
exp (Expiration)Token expiration timestamp1709742600
jti (JWT ID)Unique token identifier (UUID)550e8400-e29b-41d4-a716-446655440000
nbf (Not Before)Timestamp before which token is invalid1709740800

Token Expiration

Tokens are valid for 30 minutes (1,800,000 milliseconds) from creation:
.withExpiresAt(new Date(System.currentTimeMillis() + 1800000))
Defined in: JwtUtils.java:59
To customize token expiration, modify the hardcoded value in JwtUtils.createToken() or extract it to a configuration property.

Token Validation Process

The JwtTokenValidator filter validates tokens on every request:
1

Extract Token from Header

Extracts the JWT from the Authorization header:
Authorization: Bearer <token>
2

Validate Token Signature

Verifies the token using HMAC256 algorithm with the configured secret key.
3

Verify Claims

Checks:
  • Token issuer matches security.jwt.user.generator
  • Token has not expired
  • Current time is after nbf (Not Before) claim
4

Set Security Context

Extracts username and creates authentication object in Spring Security context.
Implementation: JwtTokenValidator.java:57-76

Authentication Components

Authentication Manager

Provides the core authentication functionality:
@Bean
public AuthenticationManager authenticationManager(
    AuthenticationConfiguration authenticationConfiguration
) throws Exception {
    return authenticationConfiguration.getAuthenticationManager();
}
Defined in: SecurityConfig.java:90-93

Authentication Provider

Configures DAO-based authentication with custom user details service:
@Bean
public AuthenticationProvider authenticationProvider(
    UserDetailServiceImpl userDetailService
) {
    DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
    provider.setUserDetailsService(userDetailService);
    provider.setPasswordEncoder(passwordEncoder());
    return provider;
}
Defined in: SecurityConfig.java:103-109

Password Encoder

Uses BCrypt for password hashing:
@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}
Defined in: SecurityConfig.java:117-120
BCrypt automatically handles salt generation and is resistant to rainbow table attacks.

Using JWT Tokens

Obtaining a Token

Authenticate via the login endpoint:
curl -X POST http://localhost:8081/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "username": "john.doe",
    "password": "password123"
  }'
Response:
{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expiresIn": 1800000
}

Making Authenticated Requests

Include the token in the Authorization header:
curl -X GET http://localhost:8081/api/v1/books \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

Security Best Practices

Strong Secret Keys

  • Use at least 256-bit random keys
  • Rotate keys periodically
  • Store in secure secret management systems

HTTPS Only

  • Always use HTTPS in production
  • JWT tokens in HTTP headers are visible to attackers
  • Configure SSL/TLS termination

Token Expiration

  • Keep token lifetimes short (current: 30 minutes)
  • Implement refresh token mechanism
  • Invalidate tokens on logout

Secure Storage

  • Never store tokens in localStorage
  • Use httpOnly cookies when possible
  • Clear tokens on client-side logout

Production Configuration

Environment-Specific Settings

# Use a simple key for development
security.jwt.user.generator=${user_jwt:dev-library-api}
security.jwt.key.private=${key_jwt:dev-secret-key-change-me}

Additional Security Headers

Consider adding security headers configuration:
.headers(headers -> headers
    .frameOptions(frame -> frame.deny())
    .xssProtection(xss -> xss.enable())
    .contentTypeOptions(Customizer.withDefaults())
)

Troubleshooting

Causes:
  • Token has expired (30-minute lifetime)
  • Invalid signature (wrong secret key)
  • Token issuer doesn’t match configuration
Solutions:
  • Verify security.jwt.key.private matches between environments
  • Check token expiration timestamp
  • Obtain a new token via login endpoint
Causes:
  • Valid token but insufficient permissions
  • Endpoint requires specific roles
Solutions:
  • Check user authorities in token claims
  • Verify endpoint authorization requirements
Error: Request rejected without tokenSolutions:
  • Ensure header format: Authorization: Bearer <token>
  • Check for typos in header name
  • Verify token is included in request
Error: CORS policy blocking requestsSolutions:
  • Add CORS configuration to SecurityConfig
  • Allow Authorization header in CORS settings
  • Configure allowed origins properly

Build docs developers (and LLMs) love