Enterprise-grade Spring Boot REST API demonstrating JWT authentication with custom security filter chain, role-based authorization, and permission-based access control.
- Features
- Technology Stack
- Architecture
- Prerequisites
- Installation
- Configuration
- Running the Application
- API Endpoints
- Authentication
- Security Filters
- Sample Users
- Testing
- JWT-based stateless authentication (HS512 algorithm)
- Two-level authentication system (Level 1: username/password, Level 2: MFA with encrypted token)
- Six custom security filters executing in a defined order
- Role-based access control (RBAC)
- Permission-based authorization
- Rate limiting by IP address
- IP blacklisting
- Country code validation
- Custom JWT claims support
- BCrypt password encryption
- AES token encryption for MFA
- Comprehensive error handling
- MySQL database with JPA/Hibernate
- RESTful API design
- Java: 8+
- Spring Boot: 2.7.18
- Spring Security: Custom filter chain
- Spring Data JPA: Database operations
- Hibernate: ORM
- MySQL: 8.0+ database
- JWT (JJWT): 0.11.5 with HS512 algorithm
- BCrypt: Password encryption
- Maven: Build tool
- Lombok: Reducing boilerplate code
The application implements 6 custom security filters that execute in this exact order:
- IpShunFilter: Blocks blacklisted IP addresses
- TokenVerifierFilter: Validates JWT tokens and populates security context
- RateLimitFilter: Implements rate limiting per IP address
- AuthorizationFilter: Enforces role-based access control
- PermissionsFilter: Enforces permission-based access control
- CountryCodeValidatorFilter: Validates X-Country-Code header
- users: User accounts with authentication details
- roles: Role definitions (USER, ADMIN)
- permissions: Permission definitions (READ_USER, WRITE_ADMIN, etc.)
- auth_tokens: Issued JWT tokens for tracking
- user_roles: Many-to-many relationship between users and roles
- role_permissions: Many-to-many relationship between roles and permissions
com.tts.security
├── config # Security and application configuration
├── controller # REST API controllers
├── dto # Data Transfer Objects
├── entity # JPA entities
├── exception # Exception handlers
├── filter # Custom security filters
├── repository # JPA repositories
├── service # Business logic
└── util # Utility classes (JWT, Encryption)
- Java JDK 8 or higher
- Maven 3.6+
- MySQL 8.0+
- Postman or curl (for testing)
cd tts-security-apiCreate a new MySQL database:
CREATE DATABASE tts_security_db;Edit src/main/resources/application.yml or set environment variables:
spring:
datasource:
url: jdbc:mysql://localhost:3306/tts_security_db?useSSL=false&serverTimezone=UTC
username: root
password: your_passwordOr use environment variables:
export DB_HOST=localhost
export DB_PORT=3306
export DB_NAME=tts_security_db
export DB_USER=root
export DB_PASSWORD=your_passwordmvn clean packageKey configuration properties in application.yml:
server:
port: 8080
jwt:
secret: MyVerySecretKeyForJWTTokenGenerationThatIsAtLeast256BitsLong12345
expiration: 86400000 # 24 hours in milliseconds
security:
blacklisted-ips: 192.168.1.100,10.0.0.50
rate-limit:
max-requests: 100
window-seconds: 60
allowed-countries: US,IN,GB,CA,AUmvn spring-boot:runjava -jar target/tts-security-api-1.0.0.jarThe application will start on http://localhost:8080
POST /api/token/issue
Issues a JWT token after validating credentials.
Request Body:
{
"userid": "john",
"password": "password123",
"encryptedToken": "optional-for-level-2",
"customClaims": {
"sessionType": "web",
"deviceId": "abc123"
}
}Response (200 OK):
{
"token": "eyJhbGciOiJIUzUxMiJ9...",
"type": "Bearer",
"userId": 1,
"username": "john",
"roles": ["USER"],
"permissions": ["READ_USER"],
"expiresIn": 86400000
}POST /api/token/introspect
Validates and introspects a JWT token.
Request Body:
{
"token": "eyJhbGciOiJIUzUxMiJ9..."
}Response (200 OK):
{
"active": true,
"username": "john",
"userId": 1,
"issuedAt": "2026-02-14T10:30:00",
"expiresAt": "2026-02-15T10:30:00",
"message": "Token is valid"
}All protected endpoints require:
Authorization: Bearer <token>headerX-Country-Codeheader (e.g.,US,IN,GB,CA,AU)
GET /api/userinfo
Returns authenticated user information.
Required: USER role, READ_USER permission
Response (200 OK):
{
"userId": 1,
"username": "john",
"roles": ["USER"],
"permissions": ["READ_USER"]
}GET /api/users
Returns all users in the system.
Required: USER role
Response (200 OK):
[
{
"id": 1,
"username": "john",
"authLevel": 1,
"enabled": true,
"roles": ["USER"]
}
]GET /api/users/{id}
Returns a specific user by ID.
Required: USER role
Response (200 OK):
{
"id": 1,
"username": "john",
"authLevel": 1,
"enabled": true,
"roles": ["USER"]
}POST /api/users
Creates a new user.
Required: ADMIN role, WRITE_ADMIN permission
Request Body:
{
"username": "newuser",
"password": "password123",
"authLevel": 1,
"enabled": true,
"roles": ["USER"],
"customClaims": {
"department": "Engineering"
}
}Response (201 Created):
{
"id": 4,
"username": "newuser",
"authLevel": 1,
"enabled": true,
"roles": ["USER"]
}PUT /api/users/{id}
Updates an existing user.
Required: ADMIN role, WRITE_ADMIN permission
Request Body:
{
"username": "updateduser",
"password": "newpassword",
"authLevel": 1,
"enabled": true,
"roles": ["USER", "ADMIN"]
}Response (200 OK):
{
"id": 4,
"username": "updateduser",
"authLevel": 1,
"enabled": true,
"roles": ["USER", "ADMIN"]
}DELETE /api/users/{id}
Deletes a user.
Required: ADMIN role, WRITE_ADMIN permission
Response (204 No Content)
Standard authentication for regular users.
Example:
{
"userid": "john",
"password": "password123"
}Enhanced security with MFA for sensitive accounts.
Example:
{
"userid": "jane",
"password": "password123",
"encryptedToken": "<encrypted-token-from-startup-logs>"
}The JWT token includes:
userId: User IDusername: Usernameroles: Array of role namespermissions: Array of permission namessub: Subject (username)iat: Issued at timestampexp: Expiration timestamp- Custom claims (optional)
- IpShunFilter: Blocks requests from blacklisted IPs (403 Forbidden)
- TokenVerifierFilter: Validates JWT and sets security context (401 Unauthorized)
- RateLimitFilter: Enforces rate limits (429 Too Many Requests)
- AuthorizationFilter: Checks role requirements (403 Forbidden)
- PermissionsFilter: Checks permission requirements (403 Forbidden)
- CountryCodeValidatorFilter: Validates country code (400/403)
Note: Public endpoints (/api/token/issue and /api/token/introspect) bypass all filters except IpShunFilter.
The application loads sample data on startup:
| Username | Password | Auth Level | Roles | Permissions |
|---|---|---|---|---|
| john | password123 | 1 | USER | READ_USER |
| admin | admin123 | 1 | USER, ADMIN | All permissions |
| jane | password123 | 2 (MFA) | USER | READ_USER |
Note: Jane's encrypted token is printed in the console logs on application startup.
Import the provided TTS-Security-API.postman_collection.json file into Postman.
1. Issue Token:
curl -X POST http://localhost:8080/api/token/issue \
-H "Content-Type: application/json" \
-d '{
"userid": "john",
"password": "password123"
}'2. Get User Info:
curl -X GET http://localhost:8080/api/userinfo \
-H "Authorization: Bearer <your-token>" \
-H "X-Country-Code: US"3. Get All Users:
curl -X GET http://localhost:8080/api/users \
-H "Authorization: Bearer <your-token>" \
-H "X-Country-Code: US"4. Create User (Admin only):
curl -X POST http://localhost:8080/api/users \
-H "Authorization: Bearer <admin-token>" \
-H "X-Country-Code: US" \
-H "Content-Type: application/json" \
-d '{
"username": "newuser",
"password": "password123",
"authLevel": 1,
"enabled": true,
"roles": ["USER"]
}'All errors follow this format:
{
"status": 401,
"error": "Unauthorized",
"message": "Invalid or expired JWT token",
"timestamp": "2026-02-14T10:30:00"
}- 200 OK: Successful GET/PUT request
- 201 Created: Successful POST request
- 204 No Content: Successful DELETE request
- 400 Bad Request: Invalid input or missing required header
- 401 Unauthorized: Authentication required or invalid token
- 403 Forbidden: Insufficient permissions or blocked
- 429 Too Many Requests: Rate limit exceeded
- 500 Internal Server Error: Server error
You can add custom claims to JWT tokens in two ways:
- Persistent Claims: Store in user's
customClaimsfield in the database - Request-Time Claims: Pass in the
customClaimsfield during token issuance
See CUSTOM-CLAIMS.md for detailed guide.
This project is provided as-is for educational and demonstration purposes.
For issues or questions, please refer to the documentation files:
QUICKSTART.md- Quick setup guideUSER-MANAGEMENT-API.md- User management endpointsCUSTOM-CLAIMS.md- Custom JWT claims guidedatabase-schema.sql- Database schema reference